mirror of
https://github.com/UiO-Gaming/uiogaming.no.git
synced 2026-11-05 13:56:47 +01:00
Verify Sanity webhook signatures in the revalidate route
The secret was passed in the query string, which ends up in access logs and doesn't protect the request body. Verify the signed header with @sanity/webhook instead, and return 400 for invalid JSON. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c5c366884a
commit
c997b74d01
4 files changed
+41
-4
No files matched your search
@@ -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?
|
||||
|
||||
Generated
+10
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user