From 3c7850481190e5a015f6eef287a8c638a977cbac Mon Sep 17 00:00:00 2001 From: santosral Date: Mon, 11 May 2026 16:29:58 +0800 Subject: [PATCH] feat: improve google oauth error logs --- src/lib/server/auth/google.ts | 56 ++++++++++++------- .../(main)/auth/google/callback/+server.ts | 9 +-- .../admin/auth/google/callback/+server.ts | 9 +-- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/lib/server/auth/google.ts b/src/lib/server/auth/google.ts index 1cff870d..afb9cee3 100644 --- a/src/lib/server/auth/google.ts +++ b/src/lib/server/auth/google.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto'; -import { CodeChallengeMethod, OAuth2Client } from 'google-auth-library'; +import { CodeChallengeMethod, type LoginTicket, OAuth2Client } from 'google-auth-library'; import { env } from '$env/dynamic/private'; @@ -124,36 +124,50 @@ export async function exchangeCodeForIdToken({ } /** - * Verifies the Google ID token and extracts the profile information from the - * Google OAuth2 ID token. + * Verifies the Google ID token and extracts the profile information. * - * @param idToken - The Google ID token to verify. - * @returns The Google profile or `null` if the ID token is invalid. + * Throws `InvalidIdTokenError` when the token cannot be trusted — bad + * signature, expired, missing claims, or hosted-domain mismatch. * - * @example - * ```ts - * const profile = await verifyIdToken(idToken); - * ``` + * @param idToken - The Google ID token to verify. + * @returns The Google profile. + * @throws InvalidIdTokenError */ -export async function verifyIdToken(idToken: string): Promise { +export async function verifyIdToken(idToken: string): Promise { const client = getOAuth2Client(); - const ticket = await client.verifyIdToken({ idToken }); + let ticket: LoginTicket; + try { + ticket = await client.verifyIdToken({ idToken }); + } catch { + throw new InvalidIdTokenError(`Google ID token rejected by verifier`); + } const payload = ticket.getPayload(); - if (!payload || !payload.sub || !payload.email || !payload.name || !payload.picture) { - return null; + if (!payload) { + throw new InvalidIdTokenError('Google ID token payload missing'); + } + const { sub, email, name, picture } = payload; + if (!sub || !email || !name || !picture) { + const missing = !sub ? 'sub' : !email ? 'email' : !name ? 'name' : 'picture'; + throw new InvalidIdTokenError(`Google ID token missing claim: ${missing}`); } - // If the Google hosted domain is set, make sure the `hd` claim matches the hosted domain. if (env.GOOGLE_HOSTED_DOMAIN && payload.hd !== env.GOOGLE_HOSTED_DOMAIN) { - return null; + throw new InvalidIdTokenError( + 'Google ID token hosted domain does not match the configured hosted domain', + ); } - return { - id: payload.sub, - email: payload.email, - name: payload.name, - picture: payload.picture, - }; + return { id: sub, email, name, picture }; +} + +/** + * Thrown when token verification fails. + */ +export class InvalidIdTokenError extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; + } } diff --git a/src/routes/(main)/auth/google/callback/+server.ts b/src/routes/(main)/auth/google/callback/+server.ts index 2ea0e3c8..3c953e99 100644 --- a/src/routes/(main)/auth/google/callback/+server.ts +++ b/src/routes/(main)/auth/google/callback/+server.ts @@ -68,16 +68,11 @@ export const GET: RequestHandler = async (event) => { return redirect(302, '/login?error=oauth2_callback_failed'); } - let profile: GoogleProfile | null = null; + let profile: GoogleProfile; try { profile = await verifyIdToken(idToken); - - if (!profile) { - logger.error('Invalid Google profile'); - return redirect(302, '/login?error=oauth2_callback_failed'); - } } catch (err) { - logger.error(err, 'Failed to verify ID token'); + logger.error({ err }, 'Failed to verify ID token'); return redirect(302, '/login?error=oauth2_callback_failed'); } diff --git a/src/routes/admin/auth/google/callback/+server.ts b/src/routes/admin/auth/google/callback/+server.ts index cdc46d85..47f1bc7f 100644 --- a/src/routes/admin/auth/google/callback/+server.ts +++ b/src/routes/admin/auth/google/callback/+server.ts @@ -62,16 +62,11 @@ export const GET: RequestHandler = async (event) => { return redirect(302, '/admin?error=auth_failed'); } - let profile: GoogleProfile | null = null; + let profile: GoogleProfile; try { profile = await verifyIdToken(idToken); - - if (!profile) { - logger.error('Invalid Google profile'); - return redirect(302, '/admin?error=auth_failed'); - } } catch (err) { - logger.error(err, 'Failed to verify ID token'); + logger.error({ err }, 'Failed to verify ID token'); return redirect(302, '/admin?error=auth_failed'); }