refactor: replace custom HMAC CSRF with hono/csrf middleware

Removes 38-line hand-rolled HMAC-SHA256 implementation in favour of
the built-in hono/csrf, which validates the Origin header natively.

- Delete src/utils/csrf.ts
- Replace custom CSRF middleware with hono/csrf (Origin-header check)
- Remove csrfToken from ContextVariableMap, layout(), forms, and JS fetch() calls
- Update admin tests: swap X-CSRF-Token for Origin header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-22 10:28:26 +02:00
parent 7d375693b9
commit a0415cdc41
4 changed files with 26 additions and 116 deletions
-38
View File
@@ -1,38 +0,0 @@
const BUCKET_MS = 10 * 60 * 1000; // 10-minute window
async function hmacHex(secret: string, message: string): Promise<string> {
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const sig = await crypto.subtle.sign(
"HMAC",
key,
new TextEncoder().encode(message),
);
return Array.from(new Uint8Array(sig))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
export async function generateCsrfToken(secret: string): Promise<string> {
const bucket = Math.floor(Date.now() / BUCKET_MS).toString();
return hmacHex(secret, bucket);
}
export async function verifyCsrfToken(
secret: string,
token: string,
): Promise<boolean> {
if (!token) return false;
const now = Math.floor(Date.now() / BUCKET_MS);
// Accept current and previous bucket to handle boundary cases
for (const bucket of [now, now - 1]) {
const expected = await hmacHex(secret, bucket.toString());
if (token === expected) return true;
}
return false;
}