Skip to content
File

Blob: src/worker/services/cookies.ts

typescript71 lines
1import { deleteCookie, getCookie, getSignedCookie, setCookie, setSignedCookie } from "hono/cookie";
2import type { CookieOptions } from "hono/utils/cookie";
3import type { AppContext } from "@/worker/types";
4 
5export const AUTH_COOKIE_PREFIX = "host";
6export const ADMIN_COOKIE_NAME = "flamemail-admin";
7export const OIDC_TRANSACTION_COOKIE_NAME = "flamemail-oidc";
8export const ADMIN_COOKIE_HEADER_NAME = `__Host-${ADMIN_COOKIE_NAME}`;
9export const OIDC_TRANSACTION_COOKIE_HEADER_NAME = `__Host-${OIDC_TRANSACTION_COOKIE_NAME}`;
10 
11const OIDC_TRANSACTION_MAX_AGE_SECONDS = 5 * 60;
12 
13const SHARED_COOKIE_OPTIONS = {
14 httpOnly: true,
15 sameSite: "Lax",
16 prefix: AUTH_COOKIE_PREFIX,
17} as const satisfies CookieOptions;
18 
19export type SignedCookieReadResult =
20 | { kind: "ok"; value: string }
21 | { kind: "missing" }
22 | { kind: "invalid_signature" };
23export type CookieSigningSecret = BufferSource;
24 
25export function setAdminCookie(c: AppContext, token: string, ttlSeconds: number) {
26 setCookie(c, ADMIN_COOKIE_NAME, token, {
27 ...SHARED_COOKIE_OPTIONS,
28 maxAge: Math.max(60, Math.floor(ttlSeconds)),
29 });
30}
31 
32export function getAdminCookie(c: AppContext): string | undefined {
33 return getCookie(c, ADMIN_COOKIE_NAME, AUTH_COOKIE_PREFIX);
34}
35 
36export function clearAdminCookie(c: AppContext) {
37 deleteCookie(c, ADMIN_COOKIE_NAME, SHARED_COOKIE_OPTIONS);
38}
39 
40export async function setOidcTransactionCookie(
41 c: AppContext,
42 value: string,
43 secret: CookieSigningSecret,
44): Promise<void> {
45 await setSignedCookie(c, OIDC_TRANSACTION_COOKIE_NAME, value, secret, {
46 ...SHARED_COOKIE_OPTIONS,
47 maxAge: OIDC_TRANSACTION_MAX_AGE_SECONDS,
48 });
49}
50 
51export async function getOidcTransactionCookie(
52 c: AppContext,
53 secret: CookieSigningSecret,
54): Promise<SignedCookieReadResult> {
55 const rawValue = getCookie(c, OIDC_TRANSACTION_COOKIE_NAME, AUTH_COOKIE_PREFIX);
56 if (rawValue === undefined) {
57 return { kind: "missing" };
58 }
59 
60 const value = await getSignedCookie(c, secret, OIDC_TRANSACTION_COOKIE_NAME, AUTH_COOKIE_PREFIX);
61 if (value === undefined || value === false) {
62 return { kind: "invalid_signature" };
63 }
64 
65 return { kind: "ok", value };
66}
67 
68export function clearOidcTransactionCookie(c: AppContext) {
69 deleteCookie(c, OIDC_TRANSACTION_COOKIE_NAME, SHARED_COOKIE_OPTIONS);
70}