diff --git a/LAUNCH_CHECKLIST.md b/LAUNCH_CHECKLIST.md index 76b217533..5ce2c6b1e 100644 --- a/LAUNCH_CHECKLIST.md +++ b/LAUNCH_CHECKLIST.md @@ -101,14 +101,14 @@ production `assertEntitlementDurability()` **throws on boot** without Upstash. ### 1.5 Google OAuth + NextAuth (sign-in) — ✅ LIVE (providers 200) Verified 2026-07-14: `/api/auth/providers` returns Google and `/api/auth/csrf` returns a token, so `NEXTAUTH_SECRET` + Google creds are set in Production. Auth is Google-only and stays **off until `NEXTAUTH_SECRET` is set**. -- Create a Google OAuth app (Authorized redirect URI: - `https:///api/auth/callback/google`). +- Create a Google OAuth app and set Authorized redirect URI to the canonical + production callback: `https://uvai.io/api/auth/callback/google`. - Set in `apps/web/.env.local`: ``` NEXTAUTH_SECRET=... # openssl rand -base64 32 NEXTAUTH_URL=https:// - GOOGLE_OAUTH_CLIENT_ID=... - GOOGLE_OAUTH_CLIENT_SECRET=... + GOOGLE_CLIENT_ID=... + GOOGLE_CLIENT_SECRET=... ``` - Decision: confirm Google-only sign-up is acceptable for paying customers (no email/password path exists today). diff --git a/apps/web/.env.example b/apps/web/.env.example index 31d0fd384..2bf2394e3 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -24,9 +24,8 @@ SUPABASE_SERVICE_ROLE_KEY=your-service-role-key # NextAuth / Google OAuth NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=your-secret-here -GOOGLE_OAUTH_CLIENT_ID=your-google-client-id.apps.googleusercontent.com -GOOGLE_OAUTH_CLIENT_SECRET=your-google-client-secret -# GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET are also supported. +GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=your-google-client-secret # Stripe (test keys for local; production via Vercel env) STRIPE_SECRET_KEY=sk_test_... diff --git a/apps/web/src/lib/__tests__/auth-config-source.test.ts b/apps/web/src/lib/__tests__/auth-config-source.test.ts index e2b687de3..d6fe86307 100644 --- a/apps/web/src/lib/__tests__/auth-config-source.test.ts +++ b/apps/web/src/lib/__tests__/auth-config-source.test.ts @@ -15,12 +15,16 @@ describe('auth configuration source safety', () => { expect(source).not.toContain("signIn: '/api/auth/signin'"); }); - it('accepts both project-specific and common Google OAuth env names', () => { + it('uses a single Google OAuth env naming convention', () => { const source = readSource('lib/auth.ts'); - expect(source).toContain('GOOGLE_OAUTH_CLIENT_ID'); - expect(source).toContain('GOOGLE_CLIENT_ID'); - expect(source).toContain('GOOGLE_OAUTH_CLIENT_SECRET'); - expect(source).toContain('GOOGLE_CLIENT_SECRET'); + expect(source).toContain("const googleClientId = (process.env.GOOGLE_CLIENT_ID || '').trim();"); + expect(source).toContain( + "const googleClientSecret = (process.env.GOOGLE_CLIENT_SECRET || '').trim();", + ); + expect(source).not.toContain('process.env.GOOGLE_OAUTH_CLIENT_ID || process.env.GOOGLE_CLIENT_ID'); + expect(source).not.toContain( + 'process.env.GOOGLE_OAUTH_CLIENT_SECRET || process.env.GOOGLE_CLIENT_SECRET', + ); }); it('keeps the root route as a landing page instead of redirecting to the app', () => { diff --git a/apps/web/src/lib/auth.ts b/apps/web/src/lib/auth.ts index bc6cac564..6c1b94f68 100644 --- a/apps/web/src/lib/auth.ts +++ b/apps/web/src/lib/auth.ts @@ -4,23 +4,14 @@ import type { NextAuthOptions } from 'next-auth'; import GoogleProvider from 'next-auth/providers/google'; const allowedDomain = process.env.AUTH_ALLOWED_EMAIL_DOMAIN?.trim().toLowerCase(); -const googleClientId = ( - process.env.GOOGLE_OAUTH_CLIENT_ID || - process.env.GOOGLE_CLIENT_ID || - '' -).trim(); -const googleClientSecret = ( - process.env.GOOGLE_OAUTH_CLIENT_SECRET || - process.env.GOOGLE_CLIENT_SECRET || - '' -).trim(); +const googleClientId = (process.env.GOOGLE_CLIENT_ID || '').trim(); +const googleClientSecret = (process.env.GOOGLE_CLIENT_SECRET || '').trim(); /** * NextAuth configuration (Google OAuth by default). * * Required env to activate login-gating: NEXTAUTH_SECRET, NEXTAUTH_URL, - * GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET. - * Also accepts NextAuth's common GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET names. + * GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET. * Optional: AUTH_ALLOWED_EMAIL_DOMAIN restricts sign-in to a single domain * (e.g. `yourcompany.com` → only *@yourcompany.com). * @@ -28,10 +19,21 @@ const googleClientSecret = ( * in proxy.ts is provider-agnostic (it only checks for a valid JWT session). */ function buildProviders(): NextAuthOptions['providers'] { + if (process.env.NODE_ENV === 'production') { + if ( + process.env.GOOGLE_OAUTH_CLIENT_ID || + process.env.GOOGLE_OAUTH_CLIENT_SECRET + ) { + console.error( + '[auth] Deprecated Google OAuth env vars detected. Use GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in Vercel production.', + ); + } + } + if (!googleClientId || !googleClientSecret) { if (process.env.NODE_ENV === 'production') { console.error( - '[auth] Google OAuth client id/secret missing — set GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_CLIENT_SECRET or GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET.', + '[auth] Google OAuth client id/secret missing — set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in Vercel production.', ); } }