| 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)); |
| } |
|
|
| |
| 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 }; |
|
|
| |
| 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)); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| 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]}`; |
|
|
| |
| 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); |