feat:google oauth

This commit is contained in:
2025-10-03 17:00:21 +02:00
parent 6fddf426b6
commit 0caa5dc9d6
12 changed files with 292 additions and 1 deletions

View File

@@ -36,6 +36,10 @@ export const actions: Actions = {
return fail(400, { message: 'Incorrect username or password' });
}
if (!existingUser.passwordHash) {
return fail(400, { message: 'Please sign in with Google for this account' });
}
const validPassword = await verify(existingUser.passwordHash, password, {
memoryCost: 19456,
timeCost: 2,

View File

@@ -0,0 +1,30 @@
import { generateState, generateCodeVerifier } from 'arctic';
import { google } from '$lib/server/oauth';
import type { RequestEvent } from '@sveltejs/kit';
export async function GET(event: RequestEvent): Promise<Response> {
const state = generateState();
const codeVerifier = generateCodeVerifier();
const url = google.createAuthorizationURL(state, codeVerifier, ['openid', 'profile']);
event.cookies.set('google_oauth_state', state, {
path: '/',
httpOnly: true,
maxAge: 60 * 10, // 10 minutes
sameSite: 'lax'
});
event.cookies.set('google_code_verifier', codeVerifier, {
path: '/',
httpOnly: true,
maxAge: 60 * 10, // 10 minutes
sameSite: 'lax'
});
return new Response(null, {
status: 302,
headers: {
Location: url.toString()
}
});
}

View File

@@ -0,0 +1,78 @@
import {
generateSessionToken,
createSession,
setSessionTokenCookie,
getUserFromGoogleId,
createUser
} from '$lib/server/auth';
import { google } from '$lib/server/oauth';
import { decodeIdToken } from 'arctic';
import type { RequestEvent } from '@sveltejs/kit';
import type { OAuth2Tokens } from 'arctic';
export async function GET(event: RequestEvent): Promise<Response> {
const code = event.url.searchParams.get('code');
const state = event.url.searchParams.get('state');
const storedState = event.cookies.get('google_oauth_state') ?? null;
const codeVerifier = event.cookies.get('google_code_verifier') ?? null;
if (code === null || state === null || storedState === null || codeVerifier === null) {
return new Response(null, {
status: 400
});
}
if (state !== storedState) {
return new Response(null, {
status: 400
});
}
let tokens: OAuth2Tokens;
try {
tokens = await google.validateAuthorizationCode(code, codeVerifier);
} catch {
// Invalid code or client credentials
return new Response(null, {
status: 400
});
}
const claims = decodeIdToken(tokens.idToken()) as { sub?: string; name?: string };
const googleUserId = claims.sub;
const username = claims.name;
if (!googleUserId || !username) {
return new Response(null, {
status: 400
});
}
const existingUser = await getUserFromGoogleId(googleUserId);
if (existingUser !== null) {
const sessionToken = generateSessionToken();
const session = await createSession(sessionToken, existingUser.id);
setSessionTokenCookie(event, sessionToken, session.expiresAt);
return new Response(null, {
status: 302,
headers: {
Location: '/'
}
});
}
const user = await createUser(googleUserId, username);
const sessionToken = generateSessionToken();
const session = await createSession(sessionToken, user.id);
setSessionTokenCookie(event, sessionToken, session.expiresAt);
return new Response(null, {
status: 302,
headers: {
Location: '/'
}
});
}