Skip to content
File

Blob: src/worker/middleware/origin.ts

typescript59 lines
1import { createMiddleware } from "hono/factory";
2import { ErrorResponse } from "@/shared/contracts";
3import type { AppBindings, AppContext } from "@/worker/types";
4 
5const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
6 
7function jsonError(message: string, status: number) {
8 return new Response(
9 JSON.stringify(
10 ErrorResponse.create({
11 error: message,
12 }),
13 ),
14 {
15 status,
16 headers: {
17 "content-type": "application/json; charset=utf-8",
18 },
19 },
20 );
21}
22 
23// Returns a 403 Response if the request is a non-safe method without an
24// Origin header matching the request origin; null otherwise. SameSite=Lax
25// already keeps cookies off most cross-site mutations; the explicit check
26// covers same-site sibling subdomains and any future cookie-attribute
27// drift.
28export function sameOriginViolation(c: AppContext): Response | null {
29 if (SAFE_METHODS.has(c.req.method)) {
30 return null;
31 }
32 
33 const origin = c.req.header("origin");
34 if (!origin) {
35 return jsonError("Forbidden", 403);
36 }
37 
38 let originUrl: URL;
39 try {
40 originUrl = new URL(origin);
41 } catch {
42 return jsonError("Forbidden", 403);
43 }
44 
45 if (originUrl.origin !== new URL(c.req.url).origin) {
46 return jsonError("Forbidden", 403);
47 }
48 
49 return null;
50}
51 
52export const requireSameOriginForCookieMutations = createMiddleware<AppBindings>(async (c, next) => {
53 const violation = sameOriginViolation(c);
54 if (violation) {
55 return violation;
56 }
57 await next();
58});