Move GitHub integration from the OAuth App to a GitHub App - #533
Merged
Merged
Conversation
Replace the broad-scope OAuth App integration with a fine-grained GitHub App that uses user access tokens for login and installation access tokens for repo operations. Legacy OAuth users see a blocking banner encouraging them to upgrade, and plugin submission is gated until they do. - Add github_installations table and github_auth_type column on users - Register github-app Socialite driver alongside legacy github driver - Add GitHubAppService for JWT generation and installation token management - Add webhook controller for installation lifecycle events - Update token resolution with 3-tier priority: installation > user > platform - Add migration banner to integrations, plugins index, and plugin create pages - Add GitHubAppStatus Livewire component showing installation coverage - Block legacy OAuth users from plugin submission - Add 27 new tests covering auth, webhooks, token resolution, and UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved 6 conflicts: - User.php: combined imports from both branches - routes/web.php: kept dashboard/ prefix from main + new github setup route - 4 modify/delete: accepted main's deletion of old blade views and CustomerPluginController, re-applied migration banner and blocking logic to new Livewire components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Conflicts were all cases where both sides added something next to each other, so both were kept: - User.php: our GitHubAuthType import and cast alongside main's MustVerifyEmail import and early adopter role cast - GitHubIntegrationController: Cache and Http imports - .env.example: GitHub App vars alongside the new Turnstile vars - integrations view: main's new GitHub Account card and disconnect modal, followed by our GitHub App status panel Main also added GitHubUserService::webhookExists(), which read the user's token directly. Switched it to resolveTokenForRepo() so GitHub App users get the installation token like the other repo calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signing in with the GitHub App switched legacy users over without ever asking them to install the app, which quietly broke syncing for their plugin repos. Login now redirects anyone with uncovered plugin repos to the install page, the banner lists repos the app can't reach, and the create page prompts for an install instead of showing an empty repo list. Also clears the cached repo list when an installation is recorded or its repos change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Verify the installation_id on the setup redirect against the user's own installations, and fetch the repo list during setup - Add github:sync-installations (daily) to catch missed webhooks - Store refresh tokens and renew expired GitHub App user tokens - Revoke the legacy OAuth grant when someone moves to the app - Handle push and release events through the app webhook, so covered plugins no longer need a per-repo webhook - Add github:send-app-migration-notice (with --preview) and the email - Add GITHUB_LEGACY_OAUTH_CUTOFF_DATE: legacy tokens are ignored after it, and github:retire-legacy-oauth clears them - Banner: urgent for plugin authors, a soft note for everyone else - GitHub connection column and filters in the Filament users table Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CI copies .env.example, which has an empty GITHUB_APP_PRIVATE_KEY_PATH.
That reached file_get_contents('') and threw a ValueError that the
surrounding catch blocks don't handle. Treat an empty value as unset and
throw a clear exception when the key file is missing.
Also fixes import style in four files Pint flagged, and moves
GitHubAppServiceTest into tests/Feature, since phpunit.xml only runs the
Feature suite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Replace GITHUB_APP_PRIVATE_KEY_PATH with an inline GITHUB_APP_PRIVATE_KEY. It can be pasted across multiple lines or kept on one line with \n. - Set the app slug to nativephp-plugin-marketplace in config, since it's public and fixed, and drop GITHUB_APP_SLUG. - Build the Integrations panel's install links with installationUrl() like everywhere else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simonhamp
marked this pull request as ready for review
September 26, 2026 11:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Our GitHub OAuth App asks for the
reposcope, which gives us read and write access to every private repo a user owns. This moves nativephp.com to a GitHub App, so we only see the repos people choose to share with us. The old OAuth App keeps working while people move over.How it works
github_installationstable. They're kept up to date by:/webhooks/github-app/auth/github/setupgithub:sync-installationsjob that catches anything the webhook missedinstallation_idagainst the user's own installations before saving it, since that value comes from the query string.GITHUB_APP_*env vars aren't set, everything falls back to the old OAuth App, so this can be deployed before the app is configured.The platform
GITHUB_TOKENis unchanged and still handles invites tonativephp/mobileandnativephp/claude-code.Moving people over
users.github_auth_typerecords which flow someone is on. A migration marks everyone with an existing token asoauth.Rollout
GITHUB_APP_ID,GITHUB_APP_CLIENT_ID,GITHUB_APP_CLIENT_SECRET,GITHUB_APP_PRIVATE_KEYandGITHUB_APP_WEBHOOK_SECRET. The private key goes in.envinline, in double quotes, either across multiple lines or on one line with\n. The app's slug is fixed asnativephp-plugin-marketplaceinconfig/services.php.GITHUB_LEGACY_OAUTH_CUTOFF_DATE. It appears in the banner and the email.php artisan github:send-app-migration-notice --preview=you@example.com, then send it withphp artisan github:send-app-migration-notice. Run it with--dry-runfirst to see who gets it. Each person is only emailed once, and it won't send without a cutoff date.php artisan github:retire-legacy-oauth. It clears the stored legacy tokens, keeps GitHub IDs and usernames, and lists plugin authors who never moved over.GitHub App settings
https://nativephp.com/auth/github/callback(shared with the old OAuth App, which is fine)https://nativephp.com/auth/github/setup, with "Redirect on update" ticked so we pick up repo changes straight awayhttps://nativephp.com/webhooks/github-app, subscribed to Installation, Installation repositories, Push and ReleaseKnown gaps
Testing
The full suite passes (1806 tests, 1 skipped). New tests cover sign-in and linking, token refresh and revocation, setup verification, installation syncing, the app webhook, token resolution, the banners and create page prompt, the email and both commands, and the Filament filter.
🤖 Generated with Claude Code