-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcookie.ts
More file actions
148 lines (133 loc) · 4.17 KB
/
Copy pathcookie.ts
File metadata and controls
148 lines (133 loc) · 4.17 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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import type { Request, Response } from 'express';
import type { AuthSettings } from '../types';
import { pgIntervalToSeconds } from '../utils/pg-interval';
export const SESSION_COOKIE_NAME = 'constructive_session';
export const DEVICE_TOKEN_COOKIE_NAME = 'constructive_device_token';
const DEVICE_TOKEN_MAX_AGE = 90 * 24 * 60 * 60; // 90 days in seconds
export interface CookieConfig {
secure: boolean;
sameSite: 'strict' | 'lax' | 'none';
domain?: string;
httpOnly: boolean;
maxAge: number;
path: string;
}
/**
* Build cookie config from AuthSettings with optional remember_me override.
*/
export const getSessionCookieConfig = (
authSettings?: AuthSettings,
rememberMe = false
): CookieConfig => {
const DEFAULT_MAX_AGE = 86400; // 24 hours
let maxAge = DEFAULT_MAX_AGE;
const configuredMaxAge =
rememberMe
? pgIntervalToSeconds(authSettings?.rememberMeDuration) ??
pgIntervalToSeconds(authSettings?.cookieMaxAge)
: pgIntervalToSeconds(authSettings?.cookieMaxAge);
if (configuredMaxAge !== null) {
maxAge = configuredMaxAge;
}
return {
secure: authSettings?.cookieSecure ?? process.env.NODE_ENV === 'production',
sameSite: (authSettings?.cookieSamesite as 'strict' | 'lax' | 'none') ?? 'lax',
domain: authSettings?.cookieDomain ?? undefined,
httpOnly: authSettings?.cookieHttponly ?? true,
maxAge,
path: authSettings?.cookiePath ?? '/',
};
};
/**
* Build cookie config for device token (long-lived, 90 days).
*/
export const getDeviceTokenCookieConfig = (authSettings?: AuthSettings): CookieConfig => {
return {
secure: authSettings?.cookieSecure ?? process.env.NODE_ENV === 'production',
sameSite: (authSettings?.cookieSamesite as 'strict' | 'lax' | 'none') ?? 'lax',
domain: authSettings?.cookieDomain ?? undefined,
httpOnly: true,
maxAge: DEVICE_TOKEN_MAX_AGE,
path: authSettings?.cookiePath ?? '/',
};
};
/**
* Set the session cookie with the access token.
*/
export const setSessionCookie = (
res: Response,
accessToken: string,
config: CookieConfig
): void => {
res.cookie(SESSION_COOKIE_NAME, accessToken, {
secure: config.secure,
sameSite: config.sameSite,
domain: config.domain,
httpOnly: config.httpOnly,
maxAge: config.maxAge * 1000, // Express expects milliseconds
path: config.path,
});
};
/**
* Clear the session cookie.
*/
export const clearSessionCookie = (res: Response, config: CookieConfig): void => {
res.clearCookie(SESSION_COOKIE_NAME, {
secure: config.secure,
sameSite: config.sameSite,
domain: config.domain,
httpOnly: config.httpOnly,
path: config.path,
});
};
/**
* Set the device token cookie (long-lived for trusted device tracking).
*/
export const setDeviceTokenCookie = (
res: Response,
deviceToken: string,
config: CookieConfig
): void => {
res.cookie(DEVICE_TOKEN_COOKIE_NAME, deviceToken, {
secure: config.secure,
sameSite: config.sameSite,
domain: config.domain,
httpOnly: config.httpOnly,
maxAge: config.maxAge * 1000,
path: config.path,
});
};
/**
* Clear the device token cookie.
*/
export const clearDeviceTokenCookie = (res: Response, config: CookieConfig): void => {
res.clearCookie(DEVICE_TOKEN_COOKIE_NAME, {
secure: config.secure,
sameSite: config.sameSite,
domain: config.domain,
httpOnly: config.httpOnly,
path: config.path,
});
};
/**
* Parse a cookie value from the raw Cookie header.
* Avoids pulling in cookie-parser as a dependency.
*/
export const parseCookieValue = (req: Request, cookieName: string): string | undefined => {
const header = req.headers.cookie;
if (!header) return undefined;
const match = header.split(';').find((c) => c.trim().startsWith(`${cookieName}=`));
return match ? decodeURIComponent(match.split('=')[1].trim()) : undefined;
};
/**
* Get the device token from the request cookie.
*/
export const getDeviceTokenFromRequest = (req: Request): string | undefined => {
return parseCookieValue(req, DEVICE_TOKEN_COOKIE_NAME);
};
/**
* Get the session token from the request cookie.
*/
export const getSessionTokenFromRequest = (req: Request): string | undefined => {
return parseCookieValue(req, SESSION_COOKIE_NAME);
};