-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathoauthCookie.ts
More file actions
100 lines (82 loc) · 4.24 KB
/
Copy pathoauthCookie.ts
File metadata and controls
100 lines (82 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* HttpOnly cookie helpers for the Cloudflare OAuth encrypted token blob.
*
* The blob is the same base64 payload produced by `encryptTokens()`; this module
* just centralises the cookie name and attributes so every call site agrees.
* Prod uses the `__Host-` prefix (requires Secure + Path=/ + no Domain). Dev
* (HTTP) drops the prefix and `Secure` because browsers reject Secure cookies
* on plain http.
*/
import { isDev } from './envs';
import { parseCookies } from './authUtils';
const PROD_COOKIE = '__Host-cf_oauth_token';
const DEV_COOKIE = 'cf_oauth_token';
/** Short-lived PKCE verifier cookie set during /oauth/login and cleared on callback. */
const VERIFIER_COOKIE = '__cf_oauth_verifier';
export const VERIFIER_COOKIE_TTL_SECONDS = 10 * 60;
/**
* Short-lived nonce cookie that binds an OAuth login `state` to the browser that
* initiated the flow. Prevents login-CSRF / session fixation:
* a callback replayed in a different browser will not carry the matching nonce.
* Prod uses the `__Host-` prefix; dev drops it (and Secure) for plain http.
*/
const PROD_NONCE_COOKIE = '__Host-oauth_nonce';
const DEV_NONCE_COOKIE = 'oauth_nonce';
export const NONCE_COOKIE_TTL_SECONDS = 10 * 60;
function nonceCookieName(env: Env): string {
return isDev(env) ? DEV_NONCE_COOKIE : PROD_NONCE_COOKIE;
}
/** Default to 30 days; callers can override based on the refresh-token lifetime. */
const DEFAULT_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
function tokenCookieName(env: Env): string {
return isDev(env) ? DEV_COOKIE : PROD_COOKIE;
}
function baseAttributes(env: Env): string {
// SameSite=Lax so the cookie rides top-level redirects back from the OAuth provider.
return isDev(env)
? 'Path=/; HttpOnly; SameSite=Lax'
: 'Path=/; HttpOnly; Secure; SameSite=Lax';
}
/** Build a Set-Cookie header value that installs the encrypted blob. */
export function buildTokenCookie(env: Env, blob: string, maxAgeSeconds: number = DEFAULT_MAX_AGE_SECONDS): string {
return `${tokenCookieName(env)}=${encodeURIComponent(blob)}; ${baseAttributes(env)}; Max-Age=${maxAgeSeconds}`;
}
/** Build a Set-Cookie header value that clears the token cookie. */
export function buildClearTokenCookie(env: Env): string {
return `${tokenCookieName(env)}=; ${baseAttributes(env)}; Max-Age=0`;
}
/** Read a named cookie value from the request's Cookie header, if present. */
export function readRequestCookie(request: Request | undefined | null, name: string): string | null {
const header = request?.headers.get('Cookie');
if (!header) return null;
const cookies = parseCookies(header);
return cookies[name] ?? null;
}
/** Read the encrypted token blob from the request's Cookie header, if present. */
export function readTokenCookie(request: Request | undefined | null, env: Env): string | null {
return readRequestCookie(request, tokenCookieName(env));
}
/** Build a Set-Cookie header value for the short-lived PKCE verifier. */
export function buildVerifierCookie(env: Env, value: string, maxAgeSeconds: number = VERIFIER_COOKIE_TTL_SECONDS): string {
return `${VERIFIER_COOKIE}=${encodeURIComponent(value)}; ${baseAttributes(env)}; Max-Age=${maxAgeSeconds}`;
}
/** Build a Set-Cookie header value that clears the PKCE verifier cookie. */
export function buildClearVerifierCookie(env: Env): string {
return `${VERIFIER_COOKIE}=; ${baseAttributes(env)}; Max-Age=0`;
}
/** Read the PKCE verifier cookie value, if present. */
export function readVerifierCookie(request: Request | undefined | null): string | null {
return readRequestCookie(request, VERIFIER_COOKIE);
}
/** Build a Set-Cookie header value that installs the OAuth state nonce. */
export function buildOAuthNonceCookie(env: Env, nonce: string, maxAgeSeconds: number = NONCE_COOKIE_TTL_SECONDS): string {
return `${nonceCookieName(env)}=${encodeURIComponent(nonce)}; ${baseAttributes(env)}; Max-Age=${maxAgeSeconds}`;
}
/** Build a Set-Cookie header value that clears the OAuth state nonce cookie. */
export function buildClearOAuthNonceCookie(env: Env): string {
return `${nonceCookieName(env)}=; ${baseAttributes(env)}; Max-Age=0`;
}
/** Read the OAuth state nonce cookie value, if present. */
export function readOAuthNonceCookie(request: Request | undefined | null, env: Env): string | null {
return readRequestCookie(request, nonceCookieName(env));
}