sign cooies

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-03-09 18:29:15 +01:00
parent fc256124b5
commit 7a96e7b038
4 changed files with 51 additions and 19 deletions

View File

@@ -39,23 +39,51 @@ export async function gscGoogleCallback(
} }
const { code, state } = query.data; 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 rawStoredState = req.cookies.gsc_oauth_state ?? null;
const hasCodeVerifier = codeVerifier !== null; const rawCodeVerifier = req.cookies.gsc_code_verifier ?? null;
const hasProjectId = projectId !== null; const rawProjectId = req.cookies.gsc_project_id ?? null;
const hasAllCookies = hasStoredState && hasCodeVerifier && hasProjectId;
if (!hasAllCookies) { 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', { throw new LogError('Missing GSC OAuth cookies', {
storedState: !hasStoredState, storedState: !storedStateResult?.value,
codeVerifier: !hasCodeVerifier, codeVerifier: !codeVerifierResult?.value,
projectId: !hasProjectId, 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', { throw new LogError('GSC OAuth state mismatch', {
hasState: true, hasState: true,
hasStoredState: true, hasStoredState: true,
@@ -65,7 +93,7 @@ export async function gscGoogleCallback(
const tokens = await googleGsc.validateAuthorizationCode( const tokens = await googleGsc.validateAuthorizationCode(
code, code,
codeVerifier codeVerifierStr
); );
const accessToken = tokens.accessToken(); const accessToken = tokens.accessToken();
@@ -79,18 +107,20 @@ export async function gscGoogleCallback(
} }
const project = await db.project.findUnique({ const project = await db.project.findUnique({
where: { id: projectId }, where: { id: projectIdStr },
select: { id: true, organizationId: true }, select: { id: true, organizationId: true },
}); });
if (!project) { 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({ await db.gscConnection.upsert({
where: { projectId }, where: { projectId: projectIdStr },
create: { create: {
projectId, projectId: projectIdStr,
accessToken: encrypt(accessToken), accessToken: encrypt(accessToken),
refreshToken: encrypt(refreshToken), refreshToken: encrypt(refreshToken),
accessTokenExpiresAt, accessTokenExpiresAt,
@@ -111,7 +141,7 @@ export async function gscGoogleCallback(
const dashboardUrl = const dashboardUrl =
process.env.DASHBOARD_URL || process.env.NEXT_PUBLIC_DASHBOARD_URL!; 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); return reply.redirect(redirectUrl);
} catch (error) { } catch (error) {
req.log.error(error); req.log.error(error);

View File

@@ -92,7 +92,7 @@ export const gscRouter = createTRPCRouter({
url.searchParams.set('access_type', 'offline'); url.searchParams.set('access_type', 'offline');
url.searchParams.set('prompt', 'consent'); 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_oauth_state', state, cookieOpts);
ctx.setCookie('gsc_code_verifier', codeVerifier, cookieOpts); ctx.setCookie('gsc_code_verifier', codeVerifier, cookieOpts);
ctx.setCookie('gsc_project_id', input.projectId, cookieOpts); ctx.setCookie('gsc_project_id', input.projectId, cookieOpts);

View File

@@ -37,6 +37,7 @@ export async function createContext({ req, res }: CreateFastifyContextOptions) {
// @ts-ignore // @ts-ignore
res.setCookie(key, value, { res.setCookie(key, value, {
maxAge: options.maxAge, maxAge: options.maxAge,
signed: options.signed,
...COOKIE_OPTIONS, ...COOKIE_OPTIONS,
}); });
}; };

View File

@@ -112,5 +112,6 @@ export type ISetCookie = (
sameSite?: 'lax' | 'strict' | 'none'; sameSite?: 'lax' | 'strict' | 'none';
secure?: boolean; secure?: boolean;
httpOnly?: boolean; httpOnly?: boolean;
signed?: boolean;
}, },
) => void; ) => void;