import { NextRequest, NextResponse } from 'next/server'; import { withDb } from '@/lib/api-handler'; import { db } from '@/lib/db'; import { createSession, hashPassword, generateApiKey, SESSION_COOKIE_NAME } from '@/lib/auth'; import { logActivity } from '@/lib/activity'; import crypto from 'crypto'; async function handler(request: NextRequest) { try { const { searchParams } = new URL(request.url); const code = searchParams.get('code'); if (!code) { return NextResponse.redirect(new URL('/?error=no_code', request.url)); } const clientId = process.env.GOOGLE_CLIENT_ID; const clientSecret = process.env.GOOGLE_CLIENT_SECRET; const redirectUri = `${process.env.NEXT_PUBLIC_BASE_URL || ''}/api/auth/google/callback`; if (!clientId || !clientSecret) { return NextResponse.redirect(new URL('/?error=not_configured', request.url)); } // ── Exchange code for tokens ────────────────────────────────────── const tokenResponse = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ code, client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri, grant_type: 'authorization_code', }), }); if (!tokenResponse.ok) { console.error('[GOOGLE_TOKEN_ERROR]', await tokenResponse.text()); return NextResponse.redirect(new URL('/?error=token_exchange_failed', request.url)); } const tokens = await tokenResponse.json() as { access_token: string }; // ── Fetch user info from Google ─────────────────────────────────── const userInfoResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', { headers: { Authorization: `Bearer ${tokens.access_token}` }, }); if (!userInfoResponse.ok) { console.error('[GOOGLE_USER_ERROR]', await userInfoResponse.text()); return NextResponse.redirect(new URL('/?error=user_fetch_failed', request.url)); } const googleUser = await userInfoResponse.json() as { email: string; name?: string; picture?: string; }; if (!googleUser.email) { return NextResponse.redirect(new URL('/?error=no_email', request.url)); } // ── Find or create user ─────────────────────────────────────────── let user = await db.user.findUnique({ where: { email: googleUser.email } }); if (!user) { const randomPw = crypto.randomBytes(32).toString('hex'); const hashedPw = await hashPassword(randomPw); const apiKey = generateApiKey(); user = await db.user.create({ data: { email: googleUser.email, name: googleUser.name || googleUser.email.split('@')[0], password: hashedPw, apiKey, plan: 'free', }, }); logActivity(user.id, 'signup', 'Account created via Google', user.email); } // ── Create session ──────────────────────────────────────────────── const token = await createSession(user.id); const forwardedProto = request.headers.get('x-forwarded-proto'); const isSecure = forwardedProto === 'https' || process.env.NODE_ENV === 'production'; const host = request.headers.get('host') || ''; const domain = host.includes('localhost') ? undefined : `.${host.split(':')[0]}`; // ── Redirect to / with cookie ───────────────────────────────────── const response = NextResponse.redirect(new URL('/', request.url)); response.cookies.set(SESSION_COOKIE_NAME, token, { httpOnly: true, secure: isSecure, sameSite: 'lax', path: '/', maxAge: 7 * 24 * 60 * 60, ...(domain ? { domain } : {}), }); logActivity(user.id, 'login', 'Logged in via Google', 'Signed in with Google OAuth'); return response; } catch (error) { console.error('[GOOGLE_CALLBACK_ERROR]', error); return NextResponse.redirect(new URL('/?error=internal', request.url)); } } export const GET = withDb(handler);