/* SPDX-FileCopyrightText: © 2025 Hornwitser SPDX-License-Identifier: AGPL-3.0-or-later */ import type { H3Event } from "h3"; import * as fs from "node:fs/promises"; let cachedCookieSecret: CryptoKey; export async function useCookieSecret(event: H3Event) { if (cachedCookieSecret) return cachedCookieSecret; const runtimeConfig = useRuntimeConfig(event); if (!runtimeConfig.cookieSecretKeyFile) throw new Error("NUXT_COOKIE_SECRET_KEY_FILE not set."); return cachedCookieSecret = await crypto.subtle.importKey( "raw", Buffer.from(await fs.readFile(runtimeConfig.cookieSecretKeyFile, "utf-8"), "base64url"), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"], ); } export async function setSignedCookie(event: H3Event, name: string, value: string, maxAge?: number) { const secret = await useCookieSecret(event); const signature = await crypto.subtle.sign("HMAC", secret, Buffer.from(`${name}=${value}`)); const cookie = `${value}.${Buffer.from(signature).toString("base64url")}` setCookie(event, name, cookie, { httpOnly: true, secure: true, sameSite: true, maxAge }); } export async function getSignedCookie(event: H3Event, name: string) { const cookie = getCookie(event, name); if (!cookie) return; const rightDot = cookie.lastIndexOf("."); if (rightDot === -1) return; const value = cookie.slice(0, rightDot); const secret = await useCookieSecret(event); const signature = Buffer.from(cookie.slice(rightDot + 1), "base64url"); const valid = await crypto.subtle.verify("HMAC", secret, signature, Buffer.from(`${name}=${value}`)); if (!valid) return return value; }