feat: add private repo support via GitHub App#23
Merged
KerwinTsaiii merged 12 commits intodevelopfrom Feb 27, 2026
Merged
Conversation
…t token - Support user-provided PAT for cloning private repos via access token input - GitHub App integration: OAuth token auto-used for private repo access when githubAppName is configured, with repo picker dropdown for installed apps - Default access token via values.yaml (Helm auto-creates K8s Secret) - Token priority: user PAT > OAuth token > defaultAccessToken - Multi-strategy repo validation: GitHub REST API > dulwich with token > dulwich bare - git-clone.sh uses dynamic insteadOf URL rewriting for credential injection - Unified isGitHubUser utility in shared package for consistent auth checks - Full dark mode support for repo picker, token input, and app install prompt - GitHub App install prompt only shown to GitHub OAuth users
- Remove access token input field from spawn UI (security concern) - Token sources now: OAuth token (GitHub App) > defaultAccessToken only - Users needing other private repos can use git directly in notebook - Handle GitHub App post-install redirect (setup_action=install without state) by redirecting to spawn page instead of 400 error - Simplify GitHub App install prompt with GitHub logo
- Rewrite OAuth setup docs to use GitHub App instead of legacy OAuth App - Add migration guide from OAuth App to GitHub App with comparison table - Add image placeholders for GitHub App setup screenshots - Reorder gitClone config in values.yaml (private repo settings first) - Add comprehensive inline documentation for gitClone options
Add gitClone, accelerators, metadata, quota, localAccounts sections. Update OAuth config to GitHub App style with scope: []. Remove obsolete extraConfig template code.
GitHub App user-to-server tokens expire after 8 hours. Add proactive token refresh that automatically renews tokens before expiry, so users don't need to re-login. - Store expires_at timestamp in auth_state during authentication - Add refresh_user() to CustomGitHubOAuthenticator with proactive refresh when token is within 10 minutes of expiry - Add refresh_user() delegation in CustomMultiAuthenticator to route refresh calls to the correct sub-authenticator - Set auth_refresh_age=3600 to check token freshness every hour - Compatible with GitHub Apps that have token expiration disabled (no refresh_token → skip refresh entirely)
Block local accounts from using usernames containing ':' to prevent prefix collision with OAuth authenticators. Two layers of defense: - MultiAuthenticator.validate_username rejects unknown prefixes via API - FirstUseAuthenticator.authenticate rejects all ':' in local logins
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.
Summary
Changes
Private Repo Support
setup_actionredirect/hub/api/github/reposAPI to list accessible repos from all GitHub App installationscustom.gitClone.defaultTokenfor org-level default tokenssecret-git-token.yamlHelm template for token storageOAuth Token Refresh
expires_attimestamp inauth_stateduring authenticationrefresh_user()toCustomGitHubOAuthenticatorwith proactive refresh (10min before expiry)refresh_user()delegation inCustomMultiAuthenticatorto route refresh calls to the correct sub-authenticatorauth_refresh_age=3600to check token freshness every hourrefresh_token→ skip entirely)Username Prefix Spoofing Prevention
validate_username()toCustomMultiAuthenticatorto reject unknown prefixes via Admin API:in local account usernames inFirstUseAuthenticatorto prevent prefix collisionUI Improvements
Documentation
Files Changed
Backend
runtime/hub/core/authenticators/github_oauth.py— OAuth callback handler, token refreshruntime/hub/core/authenticators/multi.py—refresh_userdelegation,validate_usernameruntime/hub/core/authenticators/firstuse.py— block:in local usernamesruntime/hub/core/handlers.py— GitHub repos API endpointruntime/hub/core/spawner/kubernetes.py— inject access token into git-cloneruntime/hub/core/config.py—defaultTokenconfig fieldruntime/hub/core/setup.py—auth_refresh_agesettingruntime/hub/core/scripts/git-clone.sh— token-based auth for private reposFrontend
runtime/hub/frontend/apps/spawn/— repo picker UIruntime/hub/frontend/packages/shared/src/api/git.ts— GitHub repos API clientruntime/hub/frontend/packages/shared/src/utils/user.ts— user helper utilitiesConfiguration
runtime/values.yaml— GitHub App and token settingsruntime/values-multi-nodes.yaml.example— updated example configruntime/chart/templates/hub/secret-git-token.yaml— new Helm templateruntime/chart/values.schema.yaml— schema updatesDocumentation
docs/jupyterhub/How_to_Setup_GitHub_OAuth.md— GitHub App setup guidedocs/jupyterhub/README.md— updated configuration referenceTesting
Checklist