File size: 4,486 Bytes
eaab0a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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);