From 9b7b3f180c903de6e21fb404de006a8bb3d9a5e7 Mon Sep 17 00:00:00 2001 From: Franz Unger Date: Mon, 4 Nov 2024 08:32:49 +0100 Subject: [PATCH] Use separate jwt's for invoking preview and storing preview state (#2691) --- .../src/page-tree/site-preview.resolver.ts | 2 +- .../src/sitePreview/SitePreviewUtils.ts | 13 +++++++++---- .../sitePreview/appRouter/sitePreviewRoute.ts | 18 +++++++++++++++--- .../legacyPagesRouterSitePreviewApiHandler.ts | 5 ++++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/api/cms-api/src/page-tree/site-preview.resolver.ts b/packages/api/cms-api/src/page-tree/site-preview.resolver.ts index 16680500e8..f38b417985 100644 --- a/packages/api/cms-api/src/page-tree/site-preview.resolver.ts +++ b/packages/api/cms-api/src/page-tree/site-preview.resolver.ts @@ -30,7 +30,7 @@ export class SitePreviewResolver { }, }) .setProtectedHeader({ alg: "HS256" }) - .setExpirationTime("1 day") + .setExpirationTime("10 seconds") .sign(new TextEncoder().encode(this.config.secret)); } } diff --git a/packages/site/cms-site/src/sitePreview/SitePreviewUtils.ts b/packages/site/cms-site/src/sitePreview/SitePreviewUtils.ts index a7d0852b60..3e30b9fcbb 100644 --- a/packages/site/cms-site/src/sitePreview/SitePreviewUtils.ts +++ b/packages/site/cms-site/src/sitePreview/SitePreviewUtils.ts @@ -1,4 +1,4 @@ -import { jwtVerify } from "jose"; +import { errors, jwtVerify } from "jose"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Scope = Record; @@ -12,10 +12,15 @@ export type SitePreviewParams = { previewData?: SitePreviewData; }; -export async function verifySitePreviewJwt(jwt: string): Promise { +export async function verifySitePreviewJwt(jwt: string): Promise { if (!process.env.SITE_PREVIEW_SECRET) { throw new Error("SITE_PREVIEW_SECRET environment variable is required."); } - const data = await jwtVerify(jwt, new TextEncoder().encode(process.env.SITE_PREVIEW_SECRET)); - return data.payload; + try { + const data = await jwtVerify(jwt, new TextEncoder().encode(process.env.SITE_PREVIEW_SECRET)); + return data.payload; + } catch (e) { + if (e instanceof errors.JOSEError) return null; + throw e; + } } diff --git a/packages/site/cms-site/src/sitePreview/appRouter/sitePreviewRoute.ts b/packages/site/cms-site/src/sitePreview/appRouter/sitePreviewRoute.ts index f0650c9207..9e55b30303 100644 --- a/packages/site/cms-site/src/sitePreview/appRouter/sitePreviewRoute.ts +++ b/packages/site/cms-site/src/sitePreview/appRouter/sitePreviewRoute.ts @@ -1,8 +1,9 @@ import "server-only"; +import { SignJWT } from "jose"; import { cookies, draftMode } from "next/headers"; import { redirect } from "next/navigation"; -import type { NextRequest } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; import { SitePreviewParams, verifySitePreviewJwt } from "../SitePreviewUtils"; @@ -10,12 +11,23 @@ export async function sitePreviewRoute(request: NextRequest, _graphQLFetch: unkn const params = request.nextUrl.searchParams; const jwt = params.get("jwt"); if (!jwt) { - throw new Error("Missing jwt parameter"); + return NextResponse.json({ error: "JWT-Parameter is missing." }, { status: 400 }); } const data = await verifySitePreviewJwt(jwt); + if (!data) { + return NextResponse.json({ error: "JWT-validation failed." }, { status: 400 }); + } - cookies().set("__comet_preview", jwt); + const cookieJwt = await new SignJWT({ + scope: data.scope, + path: data.path, + previewData: data.previewData, + }) + .setProtectedHeader({ alg: "HS256" }) + .setExpirationTime("1 day") + .sign(new TextEncoder().encode(process.env.SITE_PREVIEW_SECRET)); + cookies().set("__comet_preview", cookieJwt, { httpOnly: true, sameSite: "lax" }); draftMode().enable(); diff --git a/packages/site/cms-site/src/sitePreview/pagesRouter/legacyPagesRouterSitePreviewApiHandler.ts b/packages/site/cms-site/src/sitePreview/pagesRouter/legacyPagesRouterSitePreviewApiHandler.ts index f6447e3739..9c770473ca 100644 --- a/packages/site/cms-site/src/sitePreview/pagesRouter/legacyPagesRouterSitePreviewApiHandler.ts +++ b/packages/site/cms-site/src/sitePreview/pagesRouter/legacyPagesRouterSitePreviewApiHandler.ts @@ -11,10 +11,13 @@ async function legacyPagesRouterSitePreviewApiHandler( const jwt = params.jwt; if (typeof jwt !== "string") { - throw new Error("Missing jwt parameter"); + return res.status(400).json({ error: "JWT-Parameter is missing." }); } const data = await verifySitePreviewJwt(jwt); + if (!data) { + return res.status(400).json({ error: "JWT-validation failed." }); + } res.setPreviewData(data); res.redirect(data.path);