Skip to content

GitHub App: open beta #12217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions docs/user/reference/git-integration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,136 @@ depending on where the project you are trying to access has permissions from.
.. seealso:: GitHub doc on `requesting access to your organization OAuth`_ for step-by-step instructions.

.. _requesting access to your organization OAuth: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/managing-your-membership-in-organizations/requesting-organization-approval-for-oauth-apps

GitHub App
----------

.. warning::

Our GitHub App is currently in beta, see our `blog post <https://about.readthedocs.com/blog/2025/06/welcome-to-our-beta-github-app/>`__ for more information.

We are in the process of migrating our GitHub OAuth application to a `GitHub App <https://docs.github.com/en/apps/overview>`__.
We have two GitHub Apps, one for each of our platforms:

- `Read the Docs Community <https://github.com/apps/read-the-docs-community>`__
- `Read the Docs Business <https://github.com/apps/read-the-docs-business>`__

Features
~~~~~~~~

When using GitHub, Read the Docs uses a GitHub App to interact with your repositories.
This has the following benefits over using an OAuth application:

- More control over which repositories Read the Docs can access.
You don't need to grant access to all your repositories in order to create an account or import a single repository.
- No need to create webhooks on your repositories.
The GitHub App subscribes to all required events when you install it.
- No need to create a deploy key on your repository (|com_brand| only).
The GitHub App can clone your private repositories using a temporal token.
- If the original user who connected the repository to Read the Docs loses access to the project or repository,
the GitHub App will still have access to the repository.
- You can revoke access to the GitHub App at any time from your GitHub settings.
- Never out of sync with changes on your repository.
The GitHub App subscribes to all required events and will always keep your project up to date with your repository.

Creating a project from a repository
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To create a project from a repository,
you need to install the Read the Docs GitHub App and grant access to that repository.

- `Read the Docs Community <https://github.com/apps/read-the-docs-community/installations/new/>`__
- `Read the Docs Business <https://github.com/apps/read-the-docs-business/installations/new/>`__

Once you have installed the GitHub App, click on the :guilabel:`Projects` tab, and click on :guilabel:`Add project`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels pretty clunky :/ I definitely know we're going to get support on this, and we should make sure we're including this information in the dashboard as well:

Don't see your project listed here? Make sure it's included in the <a>GitHub application</a>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have this message, wasn't sure where to put that other message related to the GH app...

Screenshot 2025-05-29 at 12-11-03 Add project - Read the Docs Community

Maybe having the two is fine?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really clear to me what we're trying to solve here, or at least what the aim is on UX.

Is the user expected to have to update their GHA permissions every time they create a project from a new repository?

If so, and this is going to be consistently the case, then we should probably have some better UI for GitHub App users specifically. Basically a more prominent or mandatory block than the "Can't find the repository" block at the bottom, and something explicit and closer to what Eric mentioned above.

If this isn't a standard case, the button at the botton is okay but I'd replace the "Referesh your repositories" button instead of having two buttons for GHA users. Having two buttons is going to result in users clicking both repeatedly trying to figure out which one and which order helps.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buttons solve different issues, one suggest the user to install the app into the repository they want to install, this action needs to be done from GH, the other button resyncs our DB with the repositories the use has access to. When the GH app users shouldn't need to refresh, but it could still be needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the user expected to have to update their GHA permissions every time they create a project from a new repository?

This depends on how the user grants access to repositories to the app, if they select individual projects, then it's needed, if they marked "select all", then there is no need, as GH will install the app on every repository when created.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends on how the user grants access to repositories to the app,

But not future repositories, correct? We might be somewhere in the middle then, it's going to be common enough that users will get tripped up here but not common enough to make this mandatory, visually.

The buttons solve different issues

I get they have different functions, but my concern is probably more that GHA users shouldn't need to refresh repositories. They definitely shouldn't have to click refresh after clicking to update our GHA permissions. But I thought refresh would be less common with GHA as we have repository create/update events through the webhook now. It's okay if we're not there yet though.

Can follow up more in readthedocs/ext-theme#606 I don't think the docs will change with that PR.

then search for the repository you want to import, and follow the instructions from there.

Connect a repository to an existing project
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To connect a repository to an existing Read the Docs project,
you need to install the Read the Docs GitHub App and grant access to the repository you want to connect.

- `Read the Docs Community <https://github.com/apps/read-the-docs-community/installations/new/>`__
- `Read the Docs Business <https://github.com/apps/read-the-docs-business/installations/new/>`__

Once you have installed the GitHub App, go the :guilabel:`Settings` page of the project,
and select the repository you want to connect from the :guilabel:`Connected repository` dropdown.

Manually migrating a project
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

We recommend using the migration page to migrate your projects from the old OAuth application to the new GitHub App.

- `Read the Docs Community <https://app.readthedocs.com/accounts/migrate-to-github-app/>`__
- `Read the Docs Business <https://app.readthedocs.com/accounts/migrate-to-github-app/>`__

But in case you need to manually migrate a project,
you can follow these steps:

- Go to the :guilabel:`Settings` page of your Read the Docs project,
and click on :guilabel:`Integrations`, and delete all the integrations that are listed there.
- Go to the settings page of your GitHub repository,
click on :guilabel:`Webhooks`, and delete all the webhooks with URLs that start with:

- ``https://readthedocs.org/api/v2/webhook/<your-project-slug>`` or ``https://app.readthedocs.org/api/v2/webhook/<your-project-slug>`` for Read the Docs Community.
- ``https://readthedocs.com/api/v2/webhook/<your-project-slug>`` or ``https://app.readthedocs.com/api/v2/webhook/<your-project-slug>`` for Read the Docs Business.

- For projects using Read the Docs Business,
go to the settings page of your GitHub repository,
click on :guilabel:`Deploy keys`, and delete the deploy with a title matching the format ``support@readthedocs.com (<your-project-slug>)``.
- :ref:`Connect the project to the repository <reference/git-integration:Connect a repository to an existing project>`.

Revoking access
~~~~~~~~~~~~~~~

.. warning::

If you revoke access to the GitHub App with any of the methods below,
all projects linked to that repository will stop working,
but the projects and its documentation will still be available.
If you grant access to the repository again,
you will need to :ref:`manually connect your project to the repository <reference/git-integration:Connect a repository to an existing project>`.

You can revoke access to the Read the Docs GitHub App at any time from your GitHub settings.

- `Read the Docs Community <https://github.com/apps/read-the-docs-community/installations/new/>`__
- `Read the Docs Business <https://github.com/apps/read-the-docs-business/installations/new/>`__

There are three ways to revoke access to the Read the Docs GitHub App:

Revoke access to one or more repositories:
Remove the repositories from the list of repositories that the GitHub App has access to.
Suspend the GitHub App:
This will suspend the GitHub App and revoke access to all repositories.
The installation and configuration will still be available,
and you can re-enable the GitHub App at any time.
Uninstall the GitHub App:
This will uninstall the GitHub App and revoke access to all repositories.
The installation and configuration will be removed,
and you will need to re-install the GitHub App and reconfigure it to use it again.

Troubleshooting
~~~~~~~~~~~~~~~

**Repository not in the list to import**

Make sure you have installed the corresponding GitHub App in your GitHub account or organization,
and have granted access to the repository you want to import.

- `Read the Docs Community <https://github.com/apps/read-the-docs-community/installations/new/>`__
- `Read the Docs Business <https://github.com/apps/read-the-docs-business/installations/new/>`__

If you still can't see the repository in the list,
you may need to wait a couple of minutes and refresh the page,
or click on the "Refresh your repositories" button in the import page.

**Repository is in the list, but can't be imported**

Make sure you have admin access to the repository you are trying to import.
If you are using |org_brand|, make sure your project is public,
or use |com_brand| to import private repositories.

If you still can't import the repository,
you may need to wait a couple of minutes and refresh the page,
or click on the "Refresh your repositories" button in the import page.
1 change: 1 addition & 0 deletions readthedocs/allauth/providers/githubapp/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class GitHubAppProvider(GitHubProvider):
"""

id = "githubapp"
name = "GitHub App"
oauth2_adapter_class = GitHubAppOAuth2Adapter


Expand Down
3 changes: 2 additions & 1 deletion readthedocs/core/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.providers.github.provider import GitHubProvider
from django.conf import settings
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.urls import reverse
Expand Down Expand Up @@ -134,7 +135,7 @@ def _can_use_github_app(self, user):

Only staff users can use the GitHub App for now.
"""
return user.is_staff
return settings.RTD_ALLOW_GITHUB_APP or user.is_staff

def _block_use_of_old_github_oauth_app(self, request, sociallogin):
"""
Expand Down
18 changes: 17 additions & 1 deletion readthedocs/core/tests/test_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from allauth.socialaccount.models import SocialAccount, SocialLogin
from allauth.socialaccount.providers.github.provider import GitHubProvider
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase
from django.test import TestCase, override_settings
from django_dynamic_fixture import get

from readthedocs.allauth.providers.githubapp.provider import GitHubAppProvider
Expand All @@ -16,6 +16,7 @@ def setUp(self):
self.user = get(User, username="test")
self.adapter = get_social_account_adapter()

@override_settings(RTD_ALLOW_GITHUB_APP=False)
def test_dont_allow_using_githubapp_for_non_staff_users(self):
assert not SocialAccount.objects.filter(provider=GitHubAppProvider.id).exists()

Expand Down Expand Up @@ -45,6 +46,21 @@ def test_dont_allow_using_githubapp_for_non_staff_users(self):
provider=GitHubAppProvider.id
).exists()

@override_settings(RTD_ALLOW_GITHUB_APP=True)
def test_allow_using_githubapp_for_all_users(self):
assert not self.user.is_staff

request = mock.MagicMock(user=self.user)
sociallogin = SocialLogin(
user=User(email="me@example.com"),
account=SocialAccount(provider=GitHubAppProvider.id),
)
self.adapter.pre_social_login(request, sociallogin)
# No exception raised, but the account is not created, as that is done in another step by allauth.
assert not self.user.socialaccount_set.filter(
provider=GitHubAppProvider.id
).exists()

def test_allow_using_githubapp_for_staff_users(self):
self.user.is_staff = True
self.user.save()
Expand Down
14 changes: 14 additions & 0 deletions readthedocs/oauth/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.utils.translation import gettext_lazy as _

from readthedocs.notifications.constants import ERROR
from readthedocs.notifications.constants import INFO
from readthedocs.notifications.constants import WARNING
from readthedocs.notifications.messages import Message
from readthedocs.notifications.messages import registry
Expand All @@ -17,6 +18,7 @@
MESSAGE_OAUTH_DEPLOY_KEY_ATTACHED_FAILED = "oauth:deploy-key:attached-failed"
MESSAGE_OAUTH_WEBHOOK_NOT_REMOVED = "oauth:migration:webhook-not-removed"
MESSAGE_OAUTH_DEPLOY_KEY_NOT_REMOVED = "oauth:migration:ssh-key-not-removed"
MESSAGE_PROJECTS_TO_MIGRATE_TO_GITHUB_APP = "oauth:migration:projects-to-migrate-to-github-app"

messages = [
Message(
Expand Down Expand Up @@ -112,5 +114,17 @@
),
type=WARNING,
),
Message(
id=MESSAGE_PROJECTS_TO_MIGRATE_TO_GITHUB_APP,
header=_("You have projects that need to be migrated to the new GitHub App"),
body=_(
textwrap.dedent(
"""
Migrate your projects from the <a href="{% url "migrate_to_github_app" %}">migration page</a>.
"""
).strip(),
),
type=INFO,
),
]
registry.add(messages)
18 changes: 18 additions & 0 deletions readthedocs/oauth/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
from django.db.models.signals import post_save
from django.dispatch import receiver

from readthedocs.allauth.providers.githubapp.provider import GitHubAppProvider
from readthedocs.notifications.models import Notification
from readthedocs.oauth.migrate import has_projects_pending_migration
from readthedocs.oauth.models import RemoteRepository
from readthedocs.oauth.notifications import MESSAGE_PROJECTS_TO_MIGRATE_TO_GITHUB_APP
from readthedocs.oauth.tasks import sync_remote_repositories
from readthedocs.projects.models import Feature

Expand Down Expand Up @@ -46,3 +50,17 @@ def update_project_clone_url(sender, instance, created, *args, **kwargs):
instance.projects.exclude(feature__feature_id=Feature.DONT_SYNC_WITH_REMOTE_REPO).update(
repo=instance.clone_url
)


@receiver(social_account_added, sender=SocialLogin)
def notify_user_about_migration_on_github_app_connection(sender, request, sociallogin, **kwargs):
provider = sociallogin.account.get_provider()
if provider.id != GitHubAppProvider.id:
return

if has_projects_pending_migration(sociallogin.user):
Notification.objects.add(
message_id=MESSAGE_PROJECTS_TO_MIGRATE_TO_GITHUB_APP,
attached_to=sociallogin.user,
dismissable=True,
)
7 changes: 7 additions & 0 deletions readthedocs/profiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ def post(self, request, *args, **kwargs):
else:
projects = get_valid_projects_missing_migration(request.user)

count = 0
for project in projects:
result = migrate_project_to_github_app(project=project, user=request.user)
if not result.webhook_removed:
Expand All @@ -421,5 +422,11 @@ def post(self, request, *args, **kwargs):
"project_slug": project.slug,
},
)
count += 1

if count > 0:
messages.success(request, _(f"Successfully migrated {count} project(s)."))
else:
messages.info(request, _("No projects were migrated."))

return HttpResponseRedirect(reverse("migrate_to_github_app") + "?step=migrate")
1 change: 1 addition & 0 deletions readthedocs/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ def SOCIALACCOUNT_PROVIDERS(self):
GITHUB_APP_NAME = "readthedocs"
GITHUB_APP_PRIVATE_KEY = ""
GITHUB_APP_WEBHOOK_SECRET = ""
RTD_ALLOW_GITHUB_APP = True

@property
def GITHUB_APP_CLIENT_ID(self):
Expand Down