import { NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth/session' import { exchangeCode, verifyIdToken, isAllowedUser } from '@/lib/auth/zitadel' export async function GET(req: NextRequest) { const url = new URL(req.url) const code = url.searchParams.get('code') const state = url.searchParams.get('state') if (!code || !state) return NextResponse.json({ error: 'missing code/state' }, { status: 400 }) const session = await getSession() const pending = session.loginInProgress if (!pending || pending.state !== state) { return NextResponse.json({ error: 'state mismatch' }, { status: 400 }) } // Consume the pending login atomically before doing the token exchange // to prevent state replay on concurrent callbacks. const { codeVerifier } = pending delete session.loginInProgress await session.save() const redirectUri = `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/callback` const tokens = await exchangeCode({ code, codeVerifier, redirectUri }) const claims = await verifyIdToken(tokens.id_token) if (!isAllowedUser(claims.sub)) { session.destroy() return NextResponse.json({ error: 'user not allowed' }, { status: 403 }) } session.userId = claims.sub session.email = claims.email session.name = claims.name await session.save() return NextResponse.redirect(new URL('/', req.url)) }