← back to Norma Platform

app/api/gmail/oauth/callback/route.ts

67 lines

import { NextRequest, NextResponse } from 'next/server';
import { getOAuth2Client, storeTokens, verifyOAuthState } from '@/lib/gmail';

/**
 * GET /api/gmail/oauth/callback — Google OAuth2 redirect target.
 * Exchanges the authorization code for tokens and stores them on the mailbox
 * named by the `state` parameter (we set state=mailboxEmail in /api/gmail/oauth).
 */
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const code  = searchParams.get('code');
  const stateValue = searchParams.get('state') || '';
  const error = searchParams.get('error');

  if (error) {
    return new NextResponse(
      `<html><body><h2>OAuth Error</h2><p>${error}</p><p><a href="/">Back to Norma</a></p></body></html>`,
      { headers: { 'Content-Type': 'text/html' } },
    );
  }

  if (!code) {
    return new NextResponse(
      `<html><body><h2>Missing code</h2><p>No authorization code received.</p><p><a href="/">Back to Norma</a></p></body></html>`,
      { headers: { 'Content-Type': 'text/html' } },
    );
  }

  const cookieState = request.cookies.get('gmail-oauth-state')?.value;
  const state = cookieState && decodeURIComponent(cookieState) === stateValue
    ? verifyOAuthState(stateValue)
    : null;
  if (!state) {
    return new NextResponse(
      `<html><body><h2>Expired connection</h2><p>The Gmail connection request is invalid or expired. Restart the connect flow from inside Norma.</p><p><a href="/">Back to Norma</a></p></body></html>`,
      { headers: { 'Content-Type': 'text/html' } },
    );
  }
  const mailboxEmail = state.mailboxEmail;

  try {
    const client = getOAuth2Client();
    const { tokens } = await client.getToken(code);
    await storeTokens(tokens as unknown as Record<string, unknown>, mailboxEmail);

    console.log(`[gmail-oauth] Tokens stored for ${mailboxEmail}`);

    const response = new NextResponse(
      `<html><body style="font-family:system-ui;padding:40px;text-align:center">
        <h2 style="color:#10b981">Gmail Connected!</h2>
        <p>${mailboxEmail} is now linked to Norma.</p>
        <p>You can close this tab and return to Norma. Inbox will populate after the next sync.</p>
        <script>setTimeout(()=>window.close(),3000)</script>
      </body></html>`,
      { headers: { 'Content-Type': 'text/html' } },
    );
    response.headers.append('Set-Cookie', 'gmail-oauth-state=; Path=/api/gmail/oauth; HttpOnly; SameSite=Lax; Max-Age=0');
    return response;
  } catch (err) {
    console.error('[gmail-oauth] Token exchange failed:', (err as Error).message);
    return new NextResponse(
      `<html><body><h2>Token Exchange Failed</h2><p>${(err as Error).message}</p><p><a href="/">Back to Norma</a></p></body></html>`,
      { headers: { 'Content-Type': 'text/html' } },
    );
  }
}