diff --git a/README.md b/README.md index 89597f0..a8eff89 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ MEN Du må spesifisere URL for webhook i Sanity sitt dashbord. URLen dette webhooket skal peke på er `https://uiogaming.no/api/revalidate` med _POST_ forespørselstype. +Sett feltet _Secret_ på webhooket til samme verdi som `SANITY_REVALIDATE_SECRET` i `.env`. Sanity signerer da hver forespørsel, og APIet avviser forespørsler uten gyldig signatur. + Du kan lese mer om det [her](https://www.sanity.io/docs/webhooks). ## Vil du bidra? diff --git a/package-lock.json b/package-lock.json index 7f47a7c..cfac592 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@portabletext/react": "^3.2.1", "@sanity/client": "^7.8.1", "@sanity/image-url": "^1.1.0", + "@sanity/webhook": "^4.0.4", "country-flag-icons": "^1.5.19", "install": "^0.13.0", "next": "^16.3.3", @@ -1433,6 +1434,15 @@ "node": ">=10.0.0" } }, + "node_modules/@sanity/webhook": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@sanity/webhook/-/webhook-4.0.4.tgz", + "integrity": "sha512-c8LmzR5Wx93zJ10ohhp0XlmPrTNSFyvPcLbNY/sN45Wj5VOngz4FbBMbX9j64sSQLt+676U6OhiVfjs1yw3YCw==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@schummar/icu-type-parser": { "version": "1.21.5", "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", diff --git a/package.json b/package.json index c0da613..b264d1f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@portabletext/react": "^3.2.1", "@sanity/client": "^7.8.1", "@sanity/image-url": "^1.1.0", + "@sanity/webhook": "^4.0.4", "country-flag-icons": "^1.5.19", "install": "^0.13.0", "next": "^16.3.3", diff --git a/src/app/api/revalidate/route.ts b/src/app/api/revalidate/route.ts index d9404c0..10a825d 100644 --- a/src/app/api/revalidate/route.ts +++ b/src/app/api/revalidate/route.ts @@ -1,3 +1,4 @@ +import { isValidSignature, SIGNATURE_HEADER_NAME } from "@sanity/webhook" import { revalidateTag } from "next/cache" import { NextRequest, NextResponse } from "next/server" @@ -6,14 +7,37 @@ import { NextRequest, NextResponse } from "next/server" const expireTag = (tag: string) => revalidateTag(tag, { expire: 0 }) export async function POST(request: NextRequest) { - const secret = request.nextUrl.searchParams.get("secret") + const secret = process.env.SANITY_REVALIDATE_SECRET + if (!secret) { + console.error("SANITY_REVALIDATE_SECRET is not set") + return NextResponse.json( + { message: "Webhook is not configured" }, + { status: 500 } + ) + } - if (secret !== process.env.SANITY_REVALIDATE_SECRET) { - return NextResponse.json({ message: "Invalid secret" }, { status: 401 }) + // Sanity signs the raw body with the webhook secret, so verify it before + // parsing + const rawBody = await request.text() + const signature = request.headers.get(SIGNATURE_HEADER_NAME) ?? "" + + // Throws on a malformed signature header instead of returning false + const isValid = await isValidSignature(rawBody, signature, secret).catch( + () => false + ) + + if (!isValid) { + return NextResponse.json({ message: "Invalid signature" }, { status: 401 }) + } + + let body + try { + body = JSON.parse(rawBody) + } catch { + return NextResponse.json({ message: "Invalid JSON body" }, { status: 400 }) } try { - const body = await request.json() const { _type, slug } = body console.log(