← back to Norma

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

61 lines

import { NextRequest, NextResponse } from 'next/server';
import { getOAuth2Client, storeTokens } 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 state = searchParams.get('state'); // mailbox email
  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 mailboxEmail = state && state.includes('@') ? state : '';
  if (!mailboxEmail) {
    return new NextResponse(
      `<html><body><h2>Missing mailbox</h2><p>OAuth flow returned without a target mailbox in <code>state</code>. Restart the connect flow from inside Norma.</p><p><a href="/">Back to Norma</a></p></body></html>`,
      { headers: { 'Content-Type': 'text/html' } },
    );
  }

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

    return 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' } },
    );
  } 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' } },
    );
  }
}