Support for Authentication Schemes - Decouple user auth from GitHub API credentials#245
Support for Authentication Schemes - Decouple user auth from GitHub API credentials#245
Conversation
|
@karpikpl 👋 This repository doesn't have Copilot instructions. With Copilot instructions, I can understand the repository better, work faster and produce higher quality PRs. I can generate a .github/copilot-instructions.md file for you automatically. Click here to open a pre-filled issue and assign it to me. I'll write the instructions, and then tag you for review. |
Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com>
…iple OAuth providers Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com>
Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com>
There was a problem hiding this comment.
Pull Request Overview
This PR implements a new authentication architecture that decouples user authentication from GitHub API credentials, providing enhanced security and flexibility. The changes introduce GitHub App authentication as the preferred method while maintaining backward compatibility with existing authentication schemes.
Key Changes
- New GitHub App authentication module with JWT generation and token caching
- Support for multiple OAuth providers (Google, Microsoft) beyond GitHub
- Optional user authorization system with configurable access control
- Enhanced authentication priority system with three tiers of authentication methods
Reviewed Changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
server/modules/github-app-auth.ts |
Core GitHub App authentication module with JWT generation and token management |
server/modules/authorization.ts |
User authorization logic for access control |
server/modules/authentication.ts |
Enhanced authentication flow with priority-based scheme selection |
server/routes/auth/github.get.ts |
Updated GitHub OAuth handler with authorization checks |
server/routes/auth/google.get.ts |
New Google OAuth authentication handler |
server/routes/auth/microsoft.get.ts |
New Microsoft OAuth authentication handler |
nuxt.config.ts |
Configuration updates for new authentication schemes |
package.json |
Added JWT library dependencies |
README.md |
Comprehensive documentation for new authentication features |
.env |
Environment variable templates for new authentication options |
tests/github-app-auth.spec.ts |
Basic test placeholder for GitHub App authentication |
tests/authorization.spec.ts |
Basic test placeholder for authorization module |
|
@copilot can you merge main into this branch? |
# Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
…hentication features Co-authored-by: karpikpl <3539908+karpikpl@users.noreply.github.com>
✅ Completed all requested items:
Test Results: All 140 tests now pass (improved from 87), with comprehensive coverage of the new authentication features including edge cases, error scenarios, and multi-provider OAuth support. Commit: Warning Firewall rules blocked me from connecting to one or more addressesI tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Based on PR #245 but extended for this repo's full stack: - PostgreSQL 16 service for historical mode / storage tests - npm ci with Node LTS and cache - Build step (required for preview server in Playwright) - Unit tests (vitest, mocked data, no GitHub token needed) - Playwright tests in mock mode (excluding @seed/@storage DB tests) Agents working on DB features can use DATABASE_URL and ENABLE_HISTORICAL_MODE to run the full storage pipeline locally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: multi-provider OAuth support (Google, Microsoft, Auth0, Keycloak) - Add OAuth handlers for Google, Microsoft, Auth0, and Keycloak - Add shared authorization utility with checkAuthorization() pure function and isUserAuthorized() Nuxt runtime config wrapper - Authorization is checked BEFORE setUserSession() — unauthorized users never receive a session cookie - Update GitHub OAuth handler with authorization check and login field - Add NUXT_PUBLIC_REQUIRE_AUTH and NUXT_PUBLIC_AUTH_PROVIDERS env vars - Add NUXT_AUTHORIZED_USERS and NUXT_AUTHORIZED_EMAIL_DOMAINS for optional application-level allowlisting (any provider) - Keep NUXT_PUBLIC_USING_GITHUB_AUTH for backwards compatibility - Add dynamic provider login buttons in MainComponent.vue - Add PAT-mode shield icon button that opens dialog listing OAuth options - Update nuxt.config.ts with runtimeConfig for all five providers - Add 18 unit tests for authorization logic - Update DEPLOYMENT.md: comprehensive Authentication section for all five providers (PAT, GitHub, Google, Microsoft, Auth0, Keycloak) with setup steps, env var reference table, and authorization docs - Update README.md: deprecate NUXT_PUBLIC_USING_GITHUB_AUTH, document all new auth env vars with provider table Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add GitHub App installation token support for PAT-less data access - Add server/modules/github-app-auth.ts: signs RS256 JWTs with Node.js built-in Web Crypto (no external deps), exchanges for installation token, caches with 5-min refresh buffer before expiry - Update authentication.ts: GitHub App is now priority 2 in the credential chain (mock → App token → PAT → user OAuth session token) - Add githubAppId/githubAppPrivateKey/githubAppInstallationId to nuxt.config.ts runtimeConfig - Document new vars in .env, README.md, and DEPLOYMENT.md - DEPLOYMENT.md: new 'GitHub App Installation Token' section with step-by-step App creation, private key export, and env var setup This enables Google/Microsoft/Auth0/Keycloak users to access the dashboard without any user-owned PAT — the installation token is machine-issued and scoped to exactly the required org permissions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: auto-discover GitHub App installation ID + org picker - github-app-auth.ts: replace single global cache with per-installation Map; add paginated listAppInstallations() with 5-min TTL + thundering-herd dedup; getGitHubAppToken() resolves org from githubOrg/githubEnt query param → config → single-install auto-select; no installation ID config needed - server/api/installations.get.ts: new auth-protected endpoint returning accessible orgs (Flow 2: App JWT; Flow 1: session.organizations) - server/routes/auth/github.get.ts: redirect to /select-org when multiple installations found (was always redirecting to first org) - app/pages/select-org.vue: blocking org picker page; single org auto- navigates, multiple shows Vuetify v-select + Continue button - app/router.options.ts: add /select-org route - nuxt.config.ts: remove githubAppInstallationId (no longer needed) - .env, README.md, DEPLOYMENT.md: update docs to reflect auto-discovery Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(org-picker): filter installations to user's own orgs for GitHub OAuth When a GitHub OAuth user logs in, call GET /user/installations with their token so the org picker only shows orgs they have access to — not all 48 marketplace installations. Priority order in /api/installations: 1. GitHub OAuth session token → /user/installations (user-filtered) 2. session.organizations (pre-populated at GitHub login) 3. App JWT → list ALL (fallback for non-GitHub OAuth / unauthenticated) Also updated github.get.ts to always pre-populate session.organizations (removed isPublicApp guard) so the picker page doesn't need a second API call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(org-picker): only filter installations per-user for public/marketplace apps For private/internal apps (NUXT_PUBLIC_IS_PUBLIC_APP not set), listing all installations via App JWT is correct — there are only a few known orgs. Only marketplace/public apps need user-filtered results, since the App JWT would otherwise return every org that ever installed the app from the marketplace. When NUXT_PUBLIC_IS_PUBLIC_APP=true: 1. GitHub OAuth token in session → /user/installations (live, user-scoped) 2. session.organizations (pre-populated at GitHub login, used as fallback) When NUXT_PUBLIC_IS_PUBLIC_APP=false (default): - App JWT lists all installations (private app, small known set) - Falls back to session.organizations for GitHub OAuth without App key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(org-picker): show text input for public apps, dropdown for private apps Public/marketplace apps (NUXT_PUBLIC_IS_PUBLIC_APP=true): - App JWT lists ALL marketplace installs — useless for individual users - Exception: GitHub OAuth login → /user/installations returns user-filtered list - For all other login methods (Google, Microsoft, etc.): return empty list and show a manual text input so the user can type their org slug Private/internal apps (default): - App JWT lists a small, known set of installed orgs → show dropdown This inverts the previous logic: the dropdown is for private apps where the install list is meaningful; the text input is the fallback for public apps where listing all installs would be misleading. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * security: enforce per-org authorization for public/marketplace apps Without this check, any authenticated user (including Google/Microsoft OAuth) could access Copilot metrics for ANY org that installed the marketplace app by simply passing ?githubOrg=target-org. Fix: when NUXT_PUBLIC_IS_PUBLIC_APP=true and no default org is configured, the middleware verifies that the requested org is present in session.organizations — a list populated at GitHub OAuth login via GET /user/installations, which GitHub scopes to the user's own orgs. Consequences: - GitHub OAuth users: can only access orgs from their /user/installations - Non-GitHub OAuth (Google, Microsoft, etc.): session.organizations is empty → 403 for all org-specific API requests. Users must sign in with GitHub to prove org membership, or an admin must set NUXT_PUBLIC_GITHUB_ORG to pin to a specific org. The check is a no-op when NUXT_PUBLIC_IS_PUBLIC_APP is not set (private internal apps) or when a default org is configured via env vars. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: block private key + public app misconfiguration, simplify installations auth When NUXT_PUBLIC_IS_PUBLIC_APP=true and NUXT_GITHUB_APP_PRIVATE_KEY is set, throw a 500 error immediately. The two modes are fundamentally incompatible: a private key grants app-level access to ALL marketplace installations, while public apps should use the user's own GitHub OAuth token (naturally user-scoped). Changes: - installations.get.ts: throw 500 misconfiguration error instead of silently using the JWT path; remove try/catch that was swallowing errors - middleware/github.ts: remove session.organizations authorization check — no longer needed since the incompatible config combo is blocked at the source - github.get.ts: remove session.organizations storage — not needed for security, kept inline installations fetch only for redirect logic (single org → direct, multiple → /select-org) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: guard select-org redirect behind auth check to prevent 401 When requireAuth/usingGithubAuth/isPublicApp is set and no session exists, index.vue was unconditionally redirecting to /select-org before the user logged in. /api/installations then returned 401. Fix: only redirect to /select-org when auth is not required OR the user is already logged in. When auth is required and there is no session, let MainComponent render — it shows the login overlay, and after OAuth the callback redirects to /select-org or directly to the org as before. Also add a safety net in select-org.vue: if a 401 is returned (e.g. session expired, direct URL navigation), redirect back to / so the login overlay is displayed rather than showing a raw error message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: graceful fallback when App JWT fails, fix inflight cache bug - installations.get.ts: catch JWT/network errors and return [] so the UI shows a manual text input instead of an error page - github-app-auth.ts: move installationsInflight = null into finally block so a failed promise is never cached (previously caused permanent failure until server restart) - github-app-auth.ts: add diagnostic log in buildAppJwt showing whether a numeric App ID or Client ID is used as JWT issuer, with a clearer error message when PEM signing fails Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: declare router in select-org.vue useRouter() is auto-imported by Nuxt but must be called to get the instance. Without this, all router.push/replace calls silently failed, making the Continue button appear to do nothing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: GitHub OAuth scope and post-login redirect for non-member users - Default GitHub OAuth scope to ['read:user'] so the authorize URL is never built with an empty scope= param (GitHub returns 404) - Remove /user/installations call from github.get.ts onSuccess handler; redirect all users to /select-org instead, which already uses the App JWT (private app) or user token (public app) correctly via /api/installations — fixes 404 for GitHub users not in the org - Use config.oauth?.github?.clientId as JWT issuer fallback so NUXT_GITHUB_APP_ID is not required when NUXT_OAUTH_GITHUB_CLIENT_ID is already set Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: isPublicApp triggers auth requirement and GitHub login overlay NUXT_PUBLIC_IS_PUBLIC_APP=true now implies authentication is required, consistent with NUXT_PUBLIC_USING_GITHUB_AUTH. Previously the server middleware and MainComponent login overlay both missed isPublicApp in their requireAuth checks, causing unauthenticated requests to fall through and throw a generic 500 instead of redirecting to GitHub OAuth. Changes: - server/middleware/github.ts: add isPublicApp to requireAuth - app/components/MainComponent.vue: add isPublicApp to isAuthRequired and to the activeProviders fallback (defaults to GitHub provider) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: NUXT_PUBLIC_AUTH_PROVIDERS being set implies auth required Previously, setting NUXT_PUBLIC_AUTH_PROVIDERS=github alone was not enough — you also needed NUXT_PUBLIC_REQUIRE_AUTH=true or the legacy NUXT_PUBLIC_USING_GITHUB_AUTH=true to enforce authentication. Now any non-empty AUTH_PROVIDERS value implies requireAuth, so the minimum OAuth setup is: NUXT_PUBLIC_AUTH_PROVIDERS=github NUXT_OAUTH_GITHUB_CLIENT_ID=... NUXT_OAUTH_GITHUB_CLIENT_SECRET=... NUXT_PUBLIC_USING_GITHUB_AUTH remains supported for backwards compat but is no longer needed for new deployments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: simplify auth flags and public app org picker - NUXT_PUBLIC_AUTH_PROVIDERS being set now implies authentication required; NUXT_PUBLIC_REQUIRE_AUTH is no longer needed and hidden from docs - NUXT_PUBLIC_USING_GITHUB_AUTH removed from docs (kept in code for compat) - NUXT_PUBLIC_IS_PUBLIC_APP: org picker always shows text input (no server- side /user/installations enumeration); user types org slug directly - .env.example: cleaned up auth section, one comment explains AUTH_PROVIDERS - README + DEPLOYMENT: removed REQUIRE_AUTH and USING_GITHUB_AUTH entries, stripped REQUIRE_AUTH=true from all provider config examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add GitHub App installation callback welcome page Adds app/pages/callback.vue — a simple informational page for the GitHub App post-installation Setup URL. GitHub redirects here after install/update with ?setup_action=install|update (code and installation_id are ignored since there's no server-side storage). The page: - Shows 'Thanks for Installing!' or 'App Updated' based on setup_action - Informs users about the self-hosted PostgreSQL mode for historical metrics and per-user analytics - Links to DEPLOYMENT.md for setup instructions - Provides a 'Go to Dashboard' button back to the app Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename callback page to install (/callback → /install) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Register /install route in router.options.ts The router.options.ts manually overrides all routes, so new pages must be explicitly added here. Also moves install.vue to install/index.vue directory structure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add Copilot metrics access permission note to install page Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refactor public app org detection and add transparency messaging - Move /user/installations call into onSuccess in github.get.ts to match main branch pattern; stores organizations in session; redirects to org directly for single-org users or /select-org for multiple/zero - Simplify select-org.vue to read organizations from session (no API call on mount); preserve multi-org picker vs main's blind [0] pick - Simplify installations.get.ts to return session.organizations for isPublicApp (no redundant GitHub API call) - Remove old flat install.vue; add install/index.vue with privacy banner - Add transparency alert to select-org.vue explaining app uses user's own credentials and does not store/process Copilot data - Update MARKETPLACE.md: clarify listed org permissions are for self-hosted only; hosted app requests no org-level permissions - Update TRANSPARENCY_DISCLOSURES.md: split Data Handling and Permissions sections into hosted vs self-hosted modes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix docs: hosted app still requires Copilot permissions on GitHub App GitHub App user-to-server (ghu_) tokens are constrained by the App's own permission settings — removing Copilot Metrics/Seat Management from the App causes 403s even when the user has access. Document this clearly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add 'how it works' section to post-install page Explains the GitHub App permission intersection model: - App permissions are a hard ceiling - User's own GitHub permissions still apply - Neither party can escalate privileges unilaterally Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Replace raw red error div with v-alert and add 403 message - Replace .error-message div with Vuetify v-alert (tonal, closable) - Add 403 case with human-readable explanation about Copilot metrics access permissions instead of exposing raw GitHub API URLs - Keep default fallback for unhandled status codes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add copilot-setup-steps.yml for Copilot coding agent Based on PR #245 but extended for this repo's full stack: - PostgreSQL 16 service for historical mode / storage tests - npm ci with Node LTS and cache - Build step (required for preview server in Playwright) - Unit tests (vitest, mocked data, no GitHub token needed) - Playwright tests in mock mode (excluding @seed/@storage DB tests) Agents working on DB features can use DATABASE_URL and ENABLE_HISTORICAL_MODE to run the full storage pipeline locally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CodeQL alerts: remove sensitive data from logs, add JWT signing suppression, add missing semicolon - Remove appId from console.log in buildAppJwt (clear-text logging of OAuth data) - Add codeql[js/insufficient-password-hash] suppression on sign.update(): this is RS256 JWT signing for GitHub App auth, not password storage - Add missing semicolon in authentication.ts (code quality) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CodeQL: require NUXT_GITHUB_APP_ID explicitly, remove OAuth clientId fallback CodeQL was flagging config.oauth.github.clientId as a sensitive OAuth taint source flowing through the JWT signing path (sign.update). The clientId fallback was a convenience shortcut but is semantically wrong — the GitHub App ID (numeric) and OAuth Client ID are different identifiers. Requiring NUXT_GITHUB_APP_ID to be set explicitly breaks the taint chain and is cleaner: users setting up App auth should set this env var regardless. - Remove config.oauth?.github?.clientId fallback from authentication.ts - Remove same fallback from github-app-auth.ts getGitHubAppToken - Remove now-unnecessary codeql suppression comment - Update error messages to reference NUXT_GITHUB_APP_ID only Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger CodeQL re-evaluation after alert fixes Previous CodeQL check fired before the fix commit's SARIF was uploaded. Branch now has zero open code scanning alerts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove OAuth clientId fallback from installations endpoint The config.oauth?.github?.clientId fallback in installations.get.ts created a CodeQL taint path: oauth.clientId → appId → listAppInstallations → buildAppJwt → signJWT → sign.update() → flagged as insufficient password hash Users who need to list App installations must set NUXT_GITHUB_APP_ID explicitly. This matches the requirement in authentication.ts and github-app-auth.ts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: bump version to 3.6.0 New features in this release: - GitHub App installation token authentication - Multi-provider OAuth (GitHub, Azure AD, Okta, Google) - Public/marketplace app mode with org auto-detection - GitHub App install callback/welcome page - Improved error page with actionable 403 messaging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: redirect /callback to /install for GitHub App compat GitHub App registrations with Callback URL or Setup URL pointing to /callback (old route name) now redirect seamlessly to /install, passing through all query params (code, installation_id, setup_action). Avoids requiring changes to the GitHub App settings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This PR implements a new authentication architecture that decouples user authentication from GitHub API credentials, addressing the security and flexibility concerns raised in the issue.
Problem
Previously, the application tightly coupled user authentication with GitHub API access:
Solution
Implemented a flexible authentication scheme with three priority levels:
1. GitHub App Authentication (Recommended - New)
Uses GitHub App credentials for API calls while supporting multiple OAuth providers for user authentication:
2. Personal Access Token (Legacy - Existing)
Continues to work as before for backward compatibility.
3. User OAuth Token (Deprecated - Existing)
Legacy GitHub OAuth mode continues to work but is now deprecated.
Key Features
🔐 Enhanced Security
🌍 Multiple Identity Providers
/auth/github/auth/google/auth/microsoft👥 Flexible Authorization
NUXT_AUTHORIZED_USERS🔄 Backward Compatibility
Implementation Details
Testing
Migration Guide
For existing deployments using
NUXT_PUBLIC_USING_GITHUB_AUTH=true:NUXT_AUTHORIZED_USERSfor access controlNUXT_PUBLIC_USING_GITHUB_AUTH(defaults to false)The new system provides better security, more flexibility, and easier user management while maintaining full backward compatibility.
Fixes #244.
Warning
Firewall rules blocked me from connecting to one or more addresses
I tried to connect to the following addresses, but was blocked by firewall rules:
api.fontshare.comnode (vitest)(dns block)node /home/REDACTED/work/copilot-metrics-viewer/copilot-metrics-viewer/node_modules/.bin/nuxt build(dns block)/home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js(dns block)api.fontsource.orgnode (vitest)(dns block)node /home/REDACTED/work/copilot-metrics-viewer/copilot-metrics-viewer/node_modules/.bin/nuxt build(dns block)fonts.bunny.netnode (vitest)(dns block)node /home/REDACTED/work/copilot-metrics-viewer/copilot-metrics-viewer/node_modules/.bin/nuxt build(dns block)fonts.google.comnode (vitest)(dns block)node /home/REDACTED/work/copilot-metrics-viewer/copilot-metrics-viewer/node_modules/.bin/nuxt build(dns block)fonts.googleapis.comnode (vitest 2)(dns block)node (vitest 1)(dns block)node (vitest 3)(dns block)If you need me to access, download, or install something from one of these locations, you can either:
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.