From 7a96e7b0385f6558f19a70787f3d39c5c92d36b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Mon, 9 Mar 2026 18:29:15 +0100 Subject: [PATCH] sign cooies --- .../gsc-oauth-callback.controller.ts | 66 ++++++++++++++----- packages/trpc/src/routers/gsc.ts | 2 +- packages/trpc/src/trpc.ts | 1 + packages/validation/src/types.validation.ts | 1 + 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/apps/api/src/controllers/gsc-oauth-callback.controller.ts b/apps/api/src/controllers/gsc-oauth-callback.controller.ts index 39b33c16..ec9998f5 100644 --- a/apps/api/src/controllers/gsc-oauth-callback.controller.ts +++ b/apps/api/src/controllers/gsc-oauth-callback.controller.ts @@ -39,23 +39,51 @@ export async function gscGoogleCallback( } const { code, state } = query.data; - const storedState = req.cookies.gsc_oauth_state ?? null; - const codeVerifier = req.cookies.gsc_code_verifier ?? null; - const projectId = req.cookies.gsc_project_id ?? null; - const hasStoredState = storedState !== null; - const hasCodeVerifier = codeVerifier !== null; - const hasProjectId = projectId !== null; - const hasAllCookies = hasStoredState && hasCodeVerifier && hasProjectId; - if (!hasAllCookies) { + const rawStoredState = req.cookies.gsc_oauth_state ?? null; + const rawCodeVerifier = req.cookies.gsc_code_verifier ?? null; + const rawProjectId = req.cookies.gsc_project_id ?? null; + + const storedStateResult = + rawStoredState !== null ? req.unsignCookie(rawStoredState) : null; + const codeVerifierResult = + rawCodeVerifier !== null ? req.unsignCookie(rawCodeVerifier) : null; + const projectIdResult = + rawProjectId !== null ? req.unsignCookie(rawProjectId) : null; + + if ( + !( + storedStateResult?.value && + codeVerifierResult?.value && + projectIdResult?.value + ) + ) { throw new LogError('Missing GSC OAuth cookies', { - storedState: !hasStoredState, - codeVerifier: !hasCodeVerifier, - projectId: !hasProjectId, + storedState: !storedStateResult?.value, + codeVerifier: !codeVerifierResult?.value, + projectId: !projectIdResult?.value, }); } - if (state !== storedState) { + if ( + !( + storedStateResult?.valid && + codeVerifierResult?.valid && + projectIdResult?.valid + ) + ) { + throw new LogError('Invalid GSC OAuth cookies', { + storedState: !storedStateResult?.value, + codeVerifier: !codeVerifierResult?.value, + projectId: !projectIdResult?.value, + }); + } + + const stateStr = storedStateResult?.value; + const codeVerifierStr = codeVerifierResult?.value; + const projectIdStr = projectIdResult?.value; + + if (state !== stateStr) { throw new LogError('GSC OAuth state mismatch', { hasState: true, hasStoredState: true, @@ -65,7 +93,7 @@ export async function gscGoogleCallback( const tokens = await googleGsc.validateAuthorizationCode( code, - codeVerifier + codeVerifierStr ); const accessToken = tokens.accessToken(); @@ -79,18 +107,20 @@ export async function gscGoogleCallback( } const project = await db.project.findUnique({ - where: { id: projectId }, + where: { id: projectIdStr }, select: { id: true, organizationId: true }, }); if (!project) { - throw new LogError('Project not found for GSC connection', { projectId }); + throw new LogError('Project not found for GSC connection', { + projectId: projectIdStr, + }); } await db.gscConnection.upsert({ - where: { projectId }, + where: { projectId: projectIdStr }, create: { - projectId, + projectId: projectIdStr, accessToken: encrypt(accessToken), refreshToken: encrypt(refreshToken), accessTokenExpiresAt, @@ -111,7 +141,7 @@ export async function gscGoogleCallback( const dashboardUrl = process.env.DASHBOARD_URL || process.env.NEXT_PUBLIC_DASHBOARD_URL!; - const redirectUrl = `${dashboardUrl}/${project.organizationId}/${projectId}/settings/gsc`; + const redirectUrl = `${dashboardUrl}/${project.organizationId}/${projectIdStr}/settings/gsc`; return reply.redirect(redirectUrl); } catch (error) { req.log.error(error); diff --git a/packages/trpc/src/routers/gsc.ts b/packages/trpc/src/routers/gsc.ts index 0c0bb142..743340c4 100644 --- a/packages/trpc/src/routers/gsc.ts +++ b/packages/trpc/src/routers/gsc.ts @@ -92,7 +92,7 @@ export const gscRouter = createTRPCRouter({ url.searchParams.set('access_type', 'offline'); url.searchParams.set('prompt', 'consent'); - const cookieOpts = { maxAge: 60 * 10 }; + const cookieOpts = { maxAge: 60 * 10, signed: true }; ctx.setCookie('gsc_oauth_state', state, cookieOpts); ctx.setCookie('gsc_code_verifier', codeVerifier, cookieOpts); ctx.setCookie('gsc_project_id', input.projectId, cookieOpts); diff --git a/packages/trpc/src/trpc.ts b/packages/trpc/src/trpc.ts index e40997f2..20835bd9 100644 --- a/packages/trpc/src/trpc.ts +++ b/packages/trpc/src/trpc.ts @@ -37,6 +37,7 @@ export async function createContext({ req, res }: CreateFastifyContextOptions) { // @ts-ignore res.setCookie(key, value, { maxAge: options.maxAge, + signed: options.signed, ...COOKIE_OPTIONS, }); }; diff --git a/packages/validation/src/types.validation.ts b/packages/validation/src/types.validation.ts index 920e10da..bccedcbc 100644 --- a/packages/validation/src/types.validation.ts +++ b/packages/validation/src/types.validation.ts @@ -112,5 +112,6 @@ export type ISetCookie = ( sameSite?: 'lax' | 'strict' | 'none'; secure?: boolean; httpOnly?: boolean; + signed?: boolean; }, ) => void;