revops + onboarding
This commit is contained in:
@@ -1,14 +1,32 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { cookies } from 'next/headers';
|
||||
import { getAuthCookieOptions } from '@/lib/cookieConfig';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const code = searchParams.get('code');
|
||||
|
||||
// If no code, redirect to Google OAuth
|
||||
if (!code) {
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getAuthCookieOptions } from '@/lib/cookieConfig';
|
||||
import {
|
||||
appendRedirectParam,
|
||||
GOOGLE_OAUTH_STATE_COOKIE_NAME,
|
||||
POST_AUTH_REDIRECT_COOKIE_NAME,
|
||||
sanitizeRedirectPath,
|
||||
} from '@/lib/auth-flow';
|
||||
import {
|
||||
ATTRIBUTION_COOKIE_NAME,
|
||||
getEmailDomain,
|
||||
parseAttributionCookie,
|
||||
shouldResumeOnboarding,
|
||||
} from '@/lib/revops';
|
||||
import { triggerLifecycleScoring } from '@/lib/revops-server';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const code = searchParams.get('code');
|
||||
const state = searchParams.get('state');
|
||||
const firstTouch = parseAttributionCookie(request.cookies.get(ATTRIBUTION_COOKIE_NAME)?.value);
|
||||
const savedOauthState = request.cookies.get(GOOGLE_OAUTH_STATE_COOKIE_NAME)?.value;
|
||||
const savedRedirect = sanitizeRedirectPath(request.cookies.get(POST_AUTH_REDIRECT_COOKIE_NAME)?.value);
|
||||
|
||||
// If no code, redirect to Google OAuth
|
||||
if (!code) {
|
||||
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
||||
|
||||
if (!googleClientId) {
|
||||
@@ -17,19 +35,56 @@ export async function GET(request: NextRequest) {
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/google`;
|
||||
const scope = 'openid email profile';
|
||||
|
||||
const googleAuthUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${googleClientId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}`;
|
||||
|
||||
return NextResponse.redirect(googleAuthUrl);
|
||||
}
|
||||
|
||||
// Handle callback with code
|
||||
try {
|
||||
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
||||
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
||||
|
||||
const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/google`;
|
||||
const scope = 'openid email profile';
|
||||
const redirectTarget = sanitizeRedirectPath(searchParams.get('redirect'));
|
||||
const oauthState = crypto.randomUUID();
|
||||
|
||||
const googleAuthUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
|
||||
googleAuthUrl.searchParams.set('client_id', googleClientId);
|
||||
googleAuthUrl.searchParams.set('redirect_uri', redirectUri);
|
||||
googleAuthUrl.searchParams.set('response_type', 'code');
|
||||
googleAuthUrl.searchParams.set('scope', scope);
|
||||
googleAuthUrl.searchParams.set('state', oauthState);
|
||||
|
||||
const response = NextResponse.redirect(googleAuthUrl);
|
||||
response.cookies.set(GOOGLE_OAUTH_STATE_COOKIE_NAME, oauthState, {
|
||||
httpOnly: true,
|
||||
secure: isProduction,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 60 * 10,
|
||||
});
|
||||
|
||||
if (redirectTarget) {
|
||||
response.cookies.set(POST_AUTH_REDIRECT_COOKIE_NAME, redirectTarget, {
|
||||
httpOnly: true,
|
||||
secure: isProduction,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 60 * 10,
|
||||
});
|
||||
} else {
|
||||
response.cookies.delete(POST_AUTH_REDIRECT_COOKIE_NAME);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Handle callback with code
|
||||
try {
|
||||
if (!state || !savedOauthState || state !== savedOauthState) {
|
||||
const invalidStateResponse = NextResponse.redirect(
|
||||
`${process.env.NEXT_PUBLIC_APP_URL}/login?error=google-state-invalid`
|
||||
);
|
||||
invalidStateResponse.cookies.delete(GOOGLE_OAUTH_STATE_COOKIE_NAME);
|
||||
invalidStateResponse.cookies.delete(POST_AUTH_REDIRECT_COOKIE_NAME);
|
||||
return invalidStateResponse;
|
||||
}
|
||||
|
||||
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
||||
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
||||
|
||||
if (!googleClientId || !googleClientSecret) {
|
||||
return NextResponse.json(
|
||||
@@ -50,9 +105,9 @@ export async function GET(request: NextRequest) {
|
||||
code,
|
||||
client_id: googleClientId,
|
||||
client_secret: googleClientSecret,
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: 'authorization_code',
|
||||
}),
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: 'authorization_code',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
@@ -82,16 +137,27 @@ export async function GET(request: NextRequest) {
|
||||
const isNewUser = !user;
|
||||
|
||||
// Create user if they don't exist
|
||||
if (!user) {
|
||||
user = await db.user.create({
|
||||
data: {
|
||||
email: userInfo.email,
|
||||
name: userInfo.name || userInfo.email.split('@')[0],
|
||||
image: userInfo.picture,
|
||||
emailVerified: new Date(), // Google already verified the email
|
||||
password: null, // OAuth users don't need a password
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
const onboardingStartedAt = new Date();
|
||||
user = await db.user.create({
|
||||
data: {
|
||||
email: userInfo.email,
|
||||
name: userInfo.name || userInfo.email.split('@')[0],
|
||||
image: userInfo.picture,
|
||||
emailVerified: new Date(), // Google already verified the email
|
||||
password: null, // OAuth users don't need a password
|
||||
onboardingStartedAt,
|
||||
emailDomain: getEmailDomain(userInfo.email),
|
||||
signupSource: firstTouch?.signupSource || null,
|
||||
signupMedium: firstTouch?.signupMedium || null,
|
||||
signupCampaign: firstTouch?.signupCampaign || null,
|
||||
signupContent: firstTouch?.signupContent || null,
|
||||
signupTerm: firstTouch?.signupTerm || null,
|
||||
signupReferrer: firstTouch?.signupReferrer || null,
|
||||
signupLandingPath: firstTouch?.signupLandingPath || '/signup',
|
||||
signupFirstSeenAt: firstTouch?.signupFirstSeenAt ? new Date(firstTouch.signupFirstSeenAt) : onboardingStartedAt,
|
||||
},
|
||||
});
|
||||
|
||||
// Create Account entry for the OAuth provider
|
||||
await db.account.create({
|
||||
@@ -144,22 +210,35 @@ export async function GET(request: NextRequest) {
|
||||
id_token: tokens.id_token,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Set authentication cookie
|
||||
cookies().set('userId', user.id, getAuthCookieOptions());
|
||||
|
||||
// Redirect to dashboard with tracking params
|
||||
const redirectUrl = new URL(`${process.env.NEXT_PUBLIC_APP_URL}/dashboard`);
|
||||
redirectUrl.searchParams.set('authMethod', 'google');
|
||||
redirectUrl.searchParams.set('isNewUser', isNewUser.toString());
|
||||
|
||||
return NextResponse.redirect(redirectUrl.toString());
|
||||
} catch (error) {
|
||||
console.error('Google OAuth error:', error);
|
||||
return NextResponse.redirect(
|
||||
`${process.env.NEXT_PUBLIC_APP_URL}/login?error=google-signin-failed`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
triggerLifecycleScoring(user.id, isNewUser ? 'signup' : 'subscription_changed');
|
||||
|
||||
const onboardingTarget = isNewUser || shouldResumeOnboarding(user)
|
||||
? appendRedirectParam('/onboarding', savedRedirect, {
|
||||
authMethod: 'google',
|
||||
isNewUser: isNewUser.toString(),
|
||||
})
|
||||
: (savedRedirect || appendRedirectParam('/dashboard', null, {
|
||||
authMethod: 'google',
|
||||
isNewUser: isNewUser.toString(),
|
||||
}));
|
||||
const redirectUrl = new URL(`${process.env.NEXT_PUBLIC_APP_URL}${onboardingTarget}`);
|
||||
|
||||
const response = NextResponse.redirect(redirectUrl.toString());
|
||||
response.cookies.set('userId', user.id, getAuthCookieOptions());
|
||||
response.cookies.delete(GOOGLE_OAUTH_STATE_COOKIE_NAME);
|
||||
response.cookies.delete(POST_AUTH_REDIRECT_COOKIE_NAME);
|
||||
response.cookies.delete(ATTRIBUTION_COOKIE_NAME);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Google OAuth error:', error);
|
||||
const errorResponse = NextResponse.redirect(
|
||||
`${process.env.NEXT_PUBLIC_APP_URL}/login?error=google-signin-failed`
|
||||
);
|
||||
errorResponse.cookies.delete(GOOGLE_OAUTH_STATE_COOKIE_NAME);
|
||||
errorResponse.cookies.delete(POST_AUTH_REDIRECT_COOKIE_NAME);
|
||||
return errorResponse;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user