feat(web): support Google OAuth as alternative auth provider#431
feat(web): support Google OAuth as alternative auth provider#431duboff wants to merge 4 commits intoColeMurray:mainfrom
Conversation
Add AUTH_PROVIDER env var to select between GitHub (default) and Google OAuth. When set to "google", uses Google OAuth with optional Workspace domain restriction. ALLOWED_USERS accepts email addresses for Google auth (in addition to GitHub usernames). PR creation falls back to the GitHub App token when the user has no SCM OAuth token.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds selectable auth provider via Changes
Sequence DiagramsequenceDiagram
actor User
participant Client as Web Client
participant NextAuth as NextAuth
participant Provider as OAuth Provider (GitHub/Google)
participant ACL as Access Control
participant Session as Session Store
User->>Client: Click "Sign in"
Client->>NextAuth: signIn(request)
NextAuth->>NextAuth: buildProvider() -> GitHub or Google
NextAuth->>Provider: Redirect for auth
Provider->>NextAuth: OAuth callback (profile, tokens)
alt Google
NextAuth->>NextAuth: callbacks.signIn -> check GOOGLE_WORKSPACE_DOMAIN
alt domain mismatch
NextAuth->>Client: Deny sign-in
else domain ok
NextAuth->>ACL: checkAccessAllowed(email)
end
else GitHub
NextAuth->>ACL: checkAccessAllowed(githubUsername)
end
ACL->>NextAuth: allow or deny
alt allowed
NextAuth->>NextAuth: callbacks.jwt -> map profile -> JWT.userId/userLogin
NextAuth->>Session: callbacks.session -> populate session.user.id/login
Session->>Client: Return session
else denied
NextAuth->>Client: Deny authentication
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/.env.example`:
- Around line 28-31: Sign-in for Google in auth.ts currently only checks
GOOGLE_WORKSPACE_DOMAIN and returns true, so ALLOWED_USERS and
ALLOWED_EMAIL_DOMAINS are not enforced; update the Google branch of the signIn
callback (the signIn function in auth.ts) to call the existing
checkAccessAllowed(email) helper (the same used for GitHub) after validating the
user email (and still allow bypass if GOOGLE_WORKSPACE_DOMAIN is set and
matches), ensuring ALLOWED_USERS and ALLOWED_EMAIL_DOMAINS are evaluated for
Google logins as well; ensure you handle missing email from the profile and
return false when checkAccessAllowed denies access.
In `@packages/web/src/lib/auth.ts`:
- Around line 69-80: In signIn (the async signIn handler) the Google path
returns true after enforcing GOOGLE_WORKSPACE_DOMAIN, which skips
ALLOWED_USERS/ALLOWED_EMAIL_DOMAINS checks; update signIn so that after the
workspaceDomain check (AUTH_PROVIDER === "google" and workspaceDomain validated)
it calls the existing checkAccessAllowed helper with the authenticated user's
email (and/or id) and returns its boolean result instead of unconditionally
returning true, ensuring ALLOWED_USERS and ALLOWED_EMAIL_DOMAINS are enforced
for Google users as well.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5b3a34e0-01c3-4715-98c7-7e595b1fbc01
📒 Files selected for processing (4)
packages/web/.env.examplepackages/web/src/lib/access-control.test.tspackages/web/src/lib/access-control.tspackages/web/src/lib/auth.ts
| # Access Control (comma-separated, leave empty to allow all users) | ||
| # ALLOWED_USERS accepts GitHub usernames (AUTH_PROVIDER=github) or email addresses (AUTH_PROVIDER=google) | ||
| ALLOWED_EMAIL_DOMAINS= | ||
| ALLOWED_USERS= |
There was a problem hiding this comment.
Documentation claims ALLOWED_USERS works with Google auth, but the implementation doesn't enforce it.
Looking at auth.ts, the signIn callback for Google auth (lines 71-79) only checks GOOGLE_WORKSPACE_DOMAIN and then returns true without calling checkAccessAllowed. This means ALLOWED_USERS and ALLOWED_EMAIL_DOMAINS are not enforced for Google users.
Either update the documentation to reflect the actual behavior, or update auth.ts to call checkAccessAllowed for Google auth as well.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/.env.example` around lines 28 - 31, Sign-in for Google in
auth.ts currently only checks GOOGLE_WORKSPACE_DOMAIN and returns true, so
ALLOWED_USERS and ALLOWED_EMAIL_DOMAINS are not enforced; update the Google
branch of the signIn callback (the signIn function in auth.ts) to call the
existing checkAccessAllowed(email) helper (the same used for GitHub) after
validating the user email (and still allow bypass if GOOGLE_WORKSPACE_DOMAIN is
set and matches), ensuring ALLOWED_USERS and ALLOWED_EMAIL_DOMAINS are evaluated
for Google logins as well; ensure you handle missing email from the profile and
return false when checkAccessAllowed denies access.
| async signIn({ profile, user }) { | ||
| // Google Workspace domain check (server-side enforcement) | ||
| if (AUTH_PROVIDER === "google") { | ||
| const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN; | ||
| if (workspaceDomain && user.email) { | ||
| const emailDomain = user.email.split("@")[1]; | ||
| if (emailDomain !== workspaceDomain) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Critical: Google auth bypasses ALLOWED_USERS and ALLOWED_EMAIL_DOMAINS access control.
The Google sign-in path only enforces GOOGLE_WORKSPACE_DOMAIN and then returns true, completely skipping the checkAccessAllowed call. This means:
ALLOWED_USERSwith email addresses won't restrict Google usersALLOWED_EMAIL_DOMAINSwon't restrict Google users
The PR objectives and .env.example documentation state that ALLOWED_USERS should work with email addresses for Google auth, but this code doesn't implement that behavior.
🐛 Proposed fix to enforce access control for Google auth
// Google Workspace domain check (server-side enforcement)
if (AUTH_PROVIDER === "google") {
const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN;
if (workspaceDomain && user.email) {
const emailDomain = user.email.split("@")[1];
if (emailDomain !== workspaceDomain) {
return false;
}
}
- return true;
+ // Apply access control (allowedUsers with emails, allowedDomains)
+ const config = {
+ allowedDomains: parseAllowlist(process.env.ALLOWED_EMAIL_DOMAINS),
+ allowedUsers: parseAllowlist(process.env.ALLOWED_USERS),
+ };
+ return checkAccessAllowed(config, {
+ email: user.email ?? undefined,
+ });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async signIn({ profile, user }) { | |
| // Google Workspace domain check (server-side enforcement) | |
| if (AUTH_PROVIDER === "google") { | |
| const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN; | |
| if (workspaceDomain && user.email) { | |
| const emailDomain = user.email.split("@")[1]; | |
| if (emailDomain !== workspaceDomain) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| async signIn({ profile, user }) { | |
| // Google Workspace domain check (server-side enforcement) | |
| if (AUTH_PROVIDER === "google") { | |
| const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN; | |
| if (workspaceDomain && user.email) { | |
| const emailDomain = user.email.split("@")[1]; | |
| if (emailDomain !== workspaceDomain) { | |
| return false; | |
| } | |
| } | |
| // Apply access control (allowedUsers with emails, allowedDomains) | |
| const config = { | |
| allowedDomains: parseAllowlist(process.env.ALLOWED_EMAIL_DOMAINS), | |
| allowedUsers: parseAllowlist(process.env.ALLOWED_USERS), | |
| }; | |
| return checkAccessAllowed(config, { | |
| email: user.email ?? undefined, | |
| }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/lib/auth.ts` around lines 69 - 80, In signIn (the async
signIn handler) the Google path returns true after enforcing
GOOGLE_WORKSPACE_DOMAIN, which skips ALLOWED_USERS/ALLOWED_EMAIL_DOMAINS checks;
update signIn so that after the workspaceDomain check (AUTH_PROVIDER ===
"google" and workspaceDomain validated) it calls the existing checkAccessAllowed
helper with the authenticated user's email (and/or id) and returns its boolean
result instead of unconditionally returning true, ensuring ALLOWED_USERS and
ALLOWED_EMAIL_DOMAINS are enforced for Google users as well.
Google sign-in path was skipping checkAccessAllowed, so ALLOWED_USERS and ALLOWED_EMAIL_DOMAINS were not enforced. Now both providers go through the same access control check. Also make sign-in button provider-agnostic — signIn() without args redirects to whichever provider is configured.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/components/sidebar-layout.tsx`:
- Around line 95-98: The Sign in button currently calls signIn() which opens
NextAuth's provider selection page; change it to call signIn with the GitHub
provider id to preserve one-click OAuth. Locate the Sign in button onClick in
sidebar-layout.tsx and replace signIn() with signIn("github") (or derive the
active provider id from whatever providers prop/state you already fetch, e.g.,
providers.github.id) so the click immediately starts the GitHub OAuth flow.
In `@packages/web/src/lib/auth.ts`:
- Around line 93-120: The jwt/session handlers currently populate SCM-specific
fields (token.userId, token.userLogin and session.user.id/login) with Google
auth values; change this so SCM fields remain GitHub-only: in async jwt({ token,
account, profile }) only set token.userId/token.userLogin from the GitHub branch
(githubProfile.id/githubProfile.login) and do not map Google sub or access_token
into those fields; instead add provider-specific fields such as
token.authProvider, token.providerUserId, token.providerLogin and
token.providerAccessToken (set for any provider including Google) and propagate
those to session in async session({ session, token }) as
session.user.authProvider/session.user.providerId/session.user.providerLogin
while leaving session.user.id/login reserved for SCM (GitHub) usage, or
alternatively update the downstream contract in the same PR to accept the new
provider-specific fields used in
packages/web/src/app/api/sessions/[id]/ws-token/route.ts and
packages/web/src/app/api/sessions/route.ts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 40f7ff6e-7d8d-460e-92ba-f62de229599f
📒 Files selected for processing (2)
packages/web/src/components/sidebar-layout.tsxpackages/web/src/lib/auth.ts
| onClick={() => signIn()} | ||
| className="flex items-center gap-2 bg-primary text-primary-foreground px-6 py-3 font-medium hover:opacity-90 transition" | ||
| > | ||
| <GitHubIcon className="w-5 h-5" /> | ||
| Sign in with GitHub | ||
| Sign in |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In the NextAuth.js v4 Client API, what happens when signIn() is called without a provider id, and how do you start the OAuth flow immediately for a specific provider?
💡 Result:
In NextAuth.js v4 Client API, calling signIn without a provider id redirects the user to the built-in NextAuth.js sign-in page, which lists available providers if multiple are configured. To start the OAuth flow immediately for a specific provider (skipping the sign-in page), call signIn with the provider's id as the first argument, e.g., signIn("google").
Citations:
- 1: https://next-auth.js.org/getting-started/client
- 2: https://stackoverflow.com/questions/66401921/skip-signin-page-with-next-auth-js-to-go-directly-to-provider
- 3: Skip providers page if there's only one provider listed nextauthjs/next-auth#3079
signIn() changes flow from direct GitHub auth to provider selection page.
Calling signIn() without arguments redirects to the built-in NextAuth.js sign-in page listing available providers, whereas signIn("github") started the GitHub OAuth flow immediately. This changes the CTA from one-click auth to a two-step flow. If the direct provider redirect is needed, derive and pass the active provider id explicitly: signIn(providerId).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/web/src/components/sidebar-layout.tsx` around lines 95 - 98, The
Sign in button currently calls signIn() which opens NextAuth's provider
selection page; change it to call signIn with the GitHub provider id to preserve
one-click OAuth. Locate the Sign in button onClick in sidebar-layout.tsx and
replace signIn() with signIn("github") (or derive the active provider id from
whatever providers prop/state you already fetch, e.g., providers.github.id) so
the click immediately starts the GitHub OAuth flow.
- Google access tokens are not SCM tokens — only store OAuth tokens in JWT for GitHub auth. Google auth users fall back to app tokens for PR creation (existing fallback path). - Pass callbackUrl to signIn() so NextAuth redirects correctly with a single provider.
ColeMurray
left a comment
There was a problem hiding this comment.
[Automated Review]
Summary
Adds Google OAuth as an alternative to GitHub for web sign-in via AUTH_PROVIDER env var. Access control is unified — both providers go through checkAccessAllowed. For Google auth, SCM operations fall back to the GitHub App token. Clean, well-scoped change overall, but has one security gap and a few design concerns.
Critical Issues
-
[Security]
auth.ts:71-78— Workspace domain check silently bypassed when email is missing. IfGOOGLE_WORKSPACE_DOMAINis set but the Google profile returns no email (null/undefined), the conditionif (workspaceDomain && user.email)is false, and the check is skipped entirely. The user then falls through tocheckAccessAllowed, which — if both allowlists are empty — grants access. This defeats the workspace domain restriction.Fix:
if (AUTH_PROVIDER === "google") { const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN; if (workspaceDomain) { if (!user.email) return false; // No email → deny const emailDomain = user.email.split("@")[1]; if (emailDomain !== workspaceDomain) return false; } }
Suggestions
-
[Security]
auth.ts:74— Use Google'shdclaim instead of parsing the email domain. The Google ID token includes anhd(hosted domain) claim which is the authoritative signal for Workspace membership. Email domain is a proxy. NextAuth's Google provider exposes this on the profile object. Consider:const googleProfile = profile as { hd?: string }; if (workspaceDomain && googleProfile.hd !== workspaceDomain) { return false; }
This is more robust and is the pattern recommended by Google's own docs.
-
[Correctness]
auth.ts:105-109— Googlesubis forwarded asscmUserIdto the control plane.token.userIdis set togoogleProfile.sub, which eventually flows intoscmUserIdin bothws-token/route.ts:43andsessions/route.ts:73. The control plane usesscmUserIdto look up and refresh GitHub OAuth tokens in D1 (seeparticipant-service.ts:195-198). While functionally harmless today (no D1 tokens will exist for a Google user ID, and the App token fallback handles it), the naming is semantically misleading. Consider either:- Sending
scmUserId: nullfor Google auth users explicitly, or - Adding an
authProviderfield to the session/ws-token payload so the control plane knows not to expect SCM identity.
- Sending
-
[Robustness]
auth.ts:11— ValidateAUTH_PROVIDERvalue. An invalid value likeAUTH_PROVIDER=gitlabsilently falls through to GitHub. Consider:const AUTH_PROVIDER = process.env.AUTH_PROVIDER || "github"; if (AUTH_PROVIDER !== "github" && AUTH_PROVIDER !== "google") { throw new Error(`Invalid AUTH_PROVIDER: ${AUTH_PROVIDER}. Must be "github" or "google".`); }
-
[Testing] — The
auth.tschanges (buildProvider, signIn callback with workspace domain enforcement, JWT population per provider) are the core of this PR but have no test coverage. The workspace domain bypass bug above would have been caught by a test like"denies Google user when email is missing and workspace domain is set".
Nitpicks
- Nit:
sidebar-layout.tsx:96— Thegap-2class in the button'sclassNameis vestigial now that the GitHub icon was removed. The button only has a single text child, so the gap has no effect.
Positive Feedback
- Clean
buildProvider()factoring — Extracting provider construction into a function keepsauthOptionsreadable and makes it easy to add providers in the future. - Unified access control — Both providers go through
checkAccessAllowedwith proper parameter mapping. The CodeRabbit concern about Google bypassing access control was addressed correctly. - Thoughtful SCM token handling — Only storing OAuth tokens for GitHub and relying on the App token fallback for Google users is pragmatic and avoids leaking Google tokens into an SCM context.
Verdict
Request Changes — The workspace domain bypass when user.email is missing is a security gap that should be fixed before merging. The other suggestions are improvements but not blocking.
| * Auth provider selection. | ||
| * Set AUTH_PROVIDER=google to use Google OAuth, otherwise GitHub is used. | ||
| */ | ||
| const AUTH_PROVIDER = process.env.AUTH_PROVIDER || "github"; |
There was a problem hiding this comment.
[Automated Review] [Robustness] Validate AUTH_PROVIDER value. An invalid value like AUTH_PROVIDER=gitlab silently falls through to GitHub. Consider:
const AUTH_PROVIDER = process.env.AUTH_PROVIDER || "github";
if (AUTH_PROVIDER !== "github" && AUTH_PROVIDER !== "google") {
throw new Error(`Invalid AUTH_PROVIDER: ${AUTH_PROVIDER}. Must be "github" or "google".`);
}| if (AUTH_PROVIDER === "google") { | ||
| const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN; | ||
| if (workspaceDomain && user.email) { | ||
| const emailDomain = user.email.split("@")[1]; | ||
| if (emailDomain !== workspaceDomain) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
[Automated Review] [Security — Critical] Workspace domain check silently bypassed when email is missing. If GOOGLE_WORKSPACE_DOMAIN is set but the Google profile returns no email (null/undefined), the condition if (workspaceDomain && user.email) is false and the check is skipped entirely. The user then falls through to checkAccessAllowed, which — if both allowlists are empty — grants access. This defeats the workspace domain restriction.
Fix:
if (AUTH_PROVIDER === "google") {
const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN;
if (workspaceDomain) {
if (!user.email) return false; // No email → deny
const emailDomain = user.email.split("@")[1];
if (emailDomain !== workspaceDomain) return false;
}
}| if (AUTH_PROVIDER === "google") { | ||
| const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN; | ||
| if (workspaceDomain && user.email) { | ||
| const emailDomain = user.email.split("@")[1]; |
There was a problem hiding this comment.
[Automated Review] [Security] Consider using Google's hd claim instead of parsing the email domain. The Google ID token includes an hd (hosted domain) claim which is the authoritative signal for Workspace membership. Email domain is a proxy. NextAuth's Google provider exposes this on the profile object:
const googleProfile = profile as { hd?: string };
if (workspaceDomain && googleProfile.hd !== workspaceDomain) {
return false;
}This is more robust and is the pattern recommended by Google's own docs.
| if (AUTH_PROVIDER === "google") { | ||
| const googleProfile = profile as { sub?: string }; | ||
| if (googleProfile.sub) { | ||
| token.userId = googleProfile.sub; | ||
| } |
There was a problem hiding this comment.
[Automated Review] [Correctness] Google sub is forwarded as scmUserId to the control plane. token.userId is set to googleProfile.sub, which eventually flows into scmUserId in both ws-token/route.ts:43 and sessions/route.ts:73. The control plane uses scmUserId to look up and refresh GitHub OAuth tokens in D1 (see participant-service.ts:195-198). While functionally harmless today (no D1 tokens will exist for a Google user ID, and the App token fallback handles it), the naming is semantically misleading. Consider either:
- Sending
scmUserId: nullfor Google auth users explicitly, or - Adding an
authProviderfield to the session/ws-token payload so the control plane knows not to expect SCM identity.
| async signIn({ profile, user }) { | ||
| // Google Workspace domain check (server-side enforcement) | ||
| if (AUTH_PROVIDER === "google") { | ||
| const workspaceDomain = process.env.GOOGLE_WORKSPACE_DOMAIN; | ||
| if (workspaceDomain && user.email) { | ||
| const emailDomain = user.email.split("@")[1]; | ||
| if (emailDomain !== workspaceDomain) { | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Access control (applies to both providers) | ||
| const config = { | ||
| allowedDomains: parseAllowlist(process.env.ALLOWED_EMAIL_DOMAINS), | ||
| allowedUsers: parseAllowlist(process.env.ALLOWED_USERS), | ||
| }; | ||
|
|
||
| const githubProfile = profile as { login?: string }; | ||
| const isAllowed = checkAccessAllowed(config, { | ||
| githubUsername: githubProfile.login, | ||
| return checkAccessAllowed(config, { | ||
| githubUsername: AUTH_PROVIDER === "github" ? githubProfile.login : undefined, | ||
| email: user.email ?? undefined, | ||
| }); |
There was a problem hiding this comment.
[Automated Review] [Testing] The auth.ts changes (buildProvider, signIn callback with workspace domain enforcement, JWT population per provider) are the core of this PR but have no test coverage. The workspace domain bypass bug flagged above would have been caught by a test like "denies Google user when email is missing and workspace domain is set".
| <button | ||
| onClick={() => signIn("github")} | ||
| onClick={() => signIn(undefined, { callbackUrl: "/" })} | ||
| className="flex items-center gap-2 bg-primary text-primary-foreground px-6 py-3 font-medium hover:opacity-90 transition" |
There was a problem hiding this comment.
[Automated Review] Nit: The gap-2 class is vestigial now that the GitHub icon was removed. The button only has a single text child, so the gap has no effect.
| <button | ||
| onClick={() => signIn("github")} | ||
| onClick={() => signIn(undefined, { callbackUrl: "/" })} | ||
| className="flex items-center gap-2 bg-primary text-primary-foreground px-6 py-3 font-medium hover:opacity-90 transition" | ||
| > | ||
| <GitHubIcon className="w-5 h-5" /> | ||
| Sign in with GitHub | ||
| Sign in | ||
| </button> |
There was a problem hiding this comment.
[Automated Review] [UX] The sign-in button is now fully generic — no icon, just "Sign in". When AUTH_PROVIDER=google, consider showing Google branding (icon + "Sign in with Google") to match Google's sign-in branding guidelines. Similarly, the GitHub icon should be preserved when AUTH_PROVIDER=github.
Since AUTH_PROVIDER is a server-side env var and this is a client component, one approach would be to expose it as a NEXT_PUBLIC_AUTH_PROVIDER var and conditionally render the appropriate icon and label.
[Automated Review] Architecture Deep Dive: Separating Auth Identity from SCM IdentityThe Current CouplingToday, auth identity = SCM identity. The system assumes the user who logs in is also the GitHub user whose token is used for repo operations. This coupling runs deep: Every layer — web client, control plane router, Durable Object, D1 store, sandbox runtime — assumes these three facts hold. Here is where:
What Breaks With This PRWhen
Net effect for a Google-authed user: Sessions work (prompts flow, code is generated), but PRs are created by the GitHub App bot with no user attribution, avatars are missing, and display names are degraded. Long-Term Features This Impacts1. Multi-user sessions / collaborationThe 2. User-scoped session listingToday sessions are not user-scoped in D1 (no 3. Audit trails and activity logsAny future audit logging that records "who did what" needs a canonical user identity. Mixing GitHub numeric IDs and Google emails in the same 4. PR review assignments and commit attributionThe 5. GitLab / other SCM providersThe codebase already has a GitLab source control provider ( 6. Account linkingEnterprise users may want: "I log in with my Google Workspace account, but my GitHub identity is linked for PR creation." This is a common pattern (Slack, Linear, etc. all support GitHub account linking). The current PR's one-or-the-other toggle makes this impossible. 7. Centralized token managementThe D1 Recommended ArchitectureRather than a toggle between auth providers, we recommend cleanly separating auth identity from SCM identity at the data model level: Core principle: Auth tells us WHO, SCM tells us HOWProposed changes1. Introduce a canonical // NextAuth session — auth identity only
interface Session {
user: {
authProvider: "github" | "google";
authId: string; // provider-specific ID
email?: string;
name?: string;
image?: string;
};
}
// Separate SCM identity — only populated when the user has linked a GitHub account
interface ScmIdentity {
provider: "github";
userId: string; // GitHub numeric ID
login: string; // GitHub username
accessToken: string;
refreshToken?: string;
tokenExpiresAt?: number;
}2. Support both providers simultaneously // NEXT_PUBLIC_AUTH_PROVIDERS=github,google
function buildProviders(): Provider[] {
const providers: Provider[] = [];
if (enabledProviders.includes("github")) providers.push(GitHubProvider({...}));
if (enabledProviders.includes("google")) providers.push(GoogleProvider({...}));
return providers;
}When GitHub is one of the providers, users who sign in with GitHub automatically have an SCM identity. When users sign in with Google only, SCM identity is 3. Account linking (future, optional) Add a 4. Update the participant model -- DO SQLite participants table
ALTER TABLE participants ADD COLUMN auth_provider TEXT; -- "github" | "google"
ALTER TABLE participants ADD COLUMN auth_id TEXT; -- canonical auth identity
-- scm_* fields remain as-is but are only populated when SCM identity exists
-- scm_user_id is NULL for Google-only users (no GitHub account linked)5. Update D1 token store key The ALTER TABLE user_scm_tokens ADD COLUMN scm_provider TEXT NOT NULL DEFAULT 'github';
-- New PK: (scm_provider, provider_user_id)6. Guard all SCM operations on SCM identity existence // participant-service.ts — token refresh
if (!participant.scm_user_id) {
this.log.info("No SCM identity, skipping token refresh");
return null; // Caller falls back to App token
}
// pull-request-service.ts — already has the fallback, just needs explicit guard
const prAuth = scmIdentity ? userOAuth : appAuth;SummaryThe current PR treats auth provider as a deployment-level toggle — GitHub or Google, never both. This works as a short-term solution but creates long-term architectural debt:
The recommended approach separates auth identity from SCM identity at the data model level, supports both providers simultaneously, and preserves a clean path to account linking and multi-SCM support. |
|
closing due to inactivity |
Why
This is particularly useful for two cases:
Summary
AUTH_PROVIDERenv var to select betweengithub(default) andgoogleOAuthgoogle, uses Google OAuth with optional Workspace domain restriction viaGOOGLE_WORKSPACE_DOMAINALLOWED_USERSnow accepts email addresses (for Google auth) in addition to GitHub usernamesChanges
auth.ts—buildProvider()selects GitHub or Google based onAUTH_PROVIDER. Callbacks handle both provider profile shapes. JWT stores genericuserId/userLogininstead ofgithubUserId/githubLogin.access-control.ts—checkAccessAllowednow also checks ifemailis inallowedUsers(OR logic with existing GitHub username check)access-control.test.ts— added tests for email-in-allowedUsers case.env.example— documented all auth-related env vars for both providersConfiguration
GitHub (default, no changes needed):
Google:
Test plan
Summary by CodeRabbit
New Features
Configuration Updates
Tests
Documentation