owltide/server/utils/signed-cookie.ts

49 lines
1.6 KiB
TypeScript
Raw Normal View History

/*
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
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;
}