@dotpress/auth 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -110,6 +110,8 @@ describe('Handlers', () => {
110
110
  userId: '101',
111
111
  accessToken: 'acc-111222333',
112
112
  refreshToken: 'ref-999888777',
113
+ expiresIn: expect.any(Number),
114
+ expiresAt: expect.any(String),
113
115
  });
114
116
  });
115
117
  });
@@ -159,6 +161,8 @@ describe('Handlers', () => {
159
161
  userId: '102',
160
162
  accessToken: '111222333',
161
163
  refreshToken: '111111111',
164
+ expiresIn: expect.any(Number),
165
+ expiresAt: expect.any(String),
162
166
  });
163
167
  expect(generateTokenFn).toHaveBeenCalledTimes(1);
164
168
  expect(generateTokenFn).toHaveBeenCalledWith({
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,466 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { getCookieSettings, setAuthCookies, clearAuthCookies, detectAuthMode, extractTokensFromRequest, extractAccessToken, calculateExpiration, } from '../../lib/cookies.js';
3
+ import { setSettings } from '../../lib/settings.js';
4
+ describe('Cookie Utilities', () => {
5
+ beforeEach(() => {
6
+ // Reset settings before each test
7
+ setSettings({
8
+ keys: {
9
+ secretKey: 'test-secret',
10
+ publicKey: 'test-public',
11
+ },
12
+ dataProvider: {
13
+ storeRefreshToken: vi.fn(),
14
+ findRefreshToken: vi.fn(),
15
+ markRefreshTokenAsUsed: vi.fn(),
16
+ revokeTokens: vi.fn(),
17
+ findUserById: vi.fn(),
18
+ findUserIdentifiers: vi.fn(),
19
+ },
20
+ });
21
+ });
22
+ describe('getCookieSettings', () => {
23
+ it('should return default settings when no cookies config provided', () => {
24
+ const settings = getCookieSettings();
25
+ expect(settings).toMatchObject({
26
+ enabled: false,
27
+ httpOnly: true,
28
+ secure: expect.any(Boolean),
29
+ sameSite: 'lax',
30
+ path: '/',
31
+ accessTokenName: 'dotpress_access_token',
32
+ refreshTokenName: 'dotpress_refresh_token',
33
+ accessTokenInCookie: false,
34
+ });
35
+ });
36
+ it('should merge custom settings with defaults', () => {
37
+ setSettings({
38
+ keys: {
39
+ secretKey: 'test-secret',
40
+ publicKey: 'test-public',
41
+ },
42
+ dataProvider: {},
43
+ cookies: {
44
+ enabled: true,
45
+ domain: '.example.com',
46
+ accessTokenName: 'custom_access',
47
+ },
48
+ });
49
+ const settings = getCookieSettings();
50
+ expect(settings).toMatchObject({
51
+ enabled: true,
52
+ domain: '.example.com',
53
+ accessTokenName: 'custom_access',
54
+ refreshTokenName: 'dotpress_refresh_token', // Default
55
+ });
56
+ });
57
+ });
58
+ describe('setAuthCookies', () => {
59
+ it('should not set cookies when cookies are disabled', () => {
60
+ // Ensure cookies are disabled
61
+ setSettings({
62
+ keys: {
63
+ secretKey: 'test-secret',
64
+ publicKey: 'test-public',
65
+ },
66
+ dataProvider: {},
67
+ cookies: {
68
+ enabled: false,
69
+ },
70
+ });
71
+ const mockRes = {
72
+ cookie: vi.fn(),
73
+ };
74
+ setAuthCookies(mockRes, { accessToken: 'acc-token', refreshToken: 'ref-token' }, 3600);
75
+ expect(mockRes.cookie).not.toHaveBeenCalled();
76
+ });
77
+ it('should set refresh token cookie when enabled', () => {
78
+ setSettings({
79
+ keys: {
80
+ secretKey: 'test-secret',
81
+ publicKey: 'test-public',
82
+ },
83
+ dataProvider: {},
84
+ cookies: {
85
+ enabled: true,
86
+ secure: false,
87
+ },
88
+ });
89
+ const mockRes = {
90
+ cookie: vi.fn(),
91
+ };
92
+ setAuthCookies(mockRes, { accessToken: 'acc-token', refreshToken: 'ref-token' }, 3600);
93
+ expect(mockRes.cookie).toHaveBeenCalledWith('dotpress_refresh_token', 'ref-token', expect.objectContaining({
94
+ httpOnly: true,
95
+ secure: false,
96
+ sameSite: 'lax',
97
+ path: '/',
98
+ maxAge: 3600000,
99
+ }));
100
+ // Access token should NOT be set by default
101
+ expect(mockRes.cookie).toHaveBeenCalledTimes(1);
102
+ });
103
+ it('should set both cookies when accessTokenInCookie is true', () => {
104
+ setSettings({
105
+ keys: {
106
+ secretKey: 'test-secret',
107
+ publicKey: 'test-public',
108
+ },
109
+ dataProvider: {},
110
+ cookies: {
111
+ enabled: true,
112
+ secure: false,
113
+ accessTokenInCookie: true,
114
+ },
115
+ });
116
+ const mockRes = {
117
+ cookie: vi.fn(),
118
+ };
119
+ setAuthCookies(mockRes, { accessToken: 'acc-token', refreshToken: 'ref-token' }, 3600);
120
+ expect(mockRes.cookie).toHaveBeenCalledTimes(2);
121
+ expect(mockRes.cookie).toHaveBeenCalledWith('dotpress_access_token', 'acc-token', expect.any(Object));
122
+ expect(mockRes.cookie).toHaveBeenCalledWith('dotpress_refresh_token', 'ref-token', expect.any(Object));
123
+ });
124
+ it('should use custom cookie names', () => {
125
+ setSettings({
126
+ keys: {
127
+ secretKey: 'test-secret',
128
+ publicKey: 'test-public',
129
+ },
130
+ dataProvider: {},
131
+ cookies: {
132
+ enabled: true,
133
+ secure: false,
134
+ accessTokenName: 'my_access',
135
+ refreshTokenName: 'my_refresh',
136
+ },
137
+ });
138
+ const mockRes = {
139
+ cookie: vi.fn(),
140
+ };
141
+ setAuthCookies(mockRes, { accessToken: 'acc-token', refreshToken: 'ref-token' }, 3600);
142
+ expect(mockRes.cookie).toHaveBeenCalledWith('my_refresh', 'ref-token', expect.any(Object));
143
+ });
144
+ it('should set domain when configured', () => {
145
+ setSettings({
146
+ keys: {
147
+ secretKey: 'test-secret',
148
+ publicKey: 'test-public',
149
+ },
150
+ dataProvider: {},
151
+ cookies: {
152
+ enabled: true,
153
+ secure: false,
154
+ domain: '.example.com',
155
+ },
156
+ });
157
+ const mockRes = {
158
+ cookie: vi.fn(),
159
+ };
160
+ setAuthCookies(mockRes, { accessToken: 'acc-token', refreshToken: 'ref-token' }, 3600);
161
+ expect(mockRes.cookie).toHaveBeenCalledWith('dotpress_refresh_token', 'ref-token', expect.objectContaining({
162
+ domain: '.example.com',
163
+ }));
164
+ });
165
+ });
166
+ describe('clearAuthCookies', () => {
167
+ it('should not clear cookies when disabled', () => {
168
+ // Ensure cookies are disabled
169
+ setSettings({
170
+ keys: {
171
+ secretKey: 'test-secret',
172
+ publicKey: 'test-public',
173
+ },
174
+ dataProvider: {},
175
+ cookies: {
176
+ enabled: false,
177
+ },
178
+ });
179
+ const mockRes = {
180
+ clearCookie: vi.fn(),
181
+ };
182
+ clearAuthCookies(mockRes);
183
+ expect(mockRes.clearCookie).not.toHaveBeenCalled();
184
+ });
185
+ it('should clear refresh token cookie when enabled', () => {
186
+ setSettings({
187
+ keys: {
188
+ secretKey: 'test-secret',
189
+ publicKey: 'test-public',
190
+ },
191
+ dataProvider: {},
192
+ cookies: {
193
+ enabled: true,
194
+ secure: false,
195
+ },
196
+ });
197
+ const mockRes = {
198
+ clearCookie: vi.fn(),
199
+ };
200
+ clearAuthCookies(mockRes);
201
+ expect(mockRes.clearCookie).toHaveBeenCalledWith('dotpress_refresh_token', expect.objectContaining({
202
+ httpOnly: true,
203
+ secure: false,
204
+ sameSite: 'lax',
205
+ path: '/',
206
+ }));
207
+ });
208
+ it('should clear both cookies when accessTokenInCookie is true', () => {
209
+ setSettings({
210
+ keys: {
211
+ secretKey: 'test-secret',
212
+ publicKey: 'test-public',
213
+ },
214
+ dataProvider: {},
215
+ cookies: {
216
+ enabled: true,
217
+ secure: false,
218
+ accessTokenInCookie: true,
219
+ },
220
+ });
221
+ const mockRes = {
222
+ clearCookie: vi.fn(),
223
+ };
224
+ clearAuthCookies(mockRes);
225
+ expect(mockRes.clearCookie).toHaveBeenCalledTimes(2);
226
+ expect(mockRes.clearCookie).toHaveBeenCalledWith('dotpress_access_token', expect.any(Object));
227
+ expect(mockRes.clearCookie).toHaveBeenCalledWith('dotpress_refresh_token', expect.any(Object));
228
+ });
229
+ });
230
+ describe('detectAuthMode', () => {
231
+ it('should return "cookie" when refresh token cookie is present', () => {
232
+ const mockReq = {
233
+ cookies: {
234
+ dotpress_refresh_token: 'ref-token',
235
+ },
236
+ };
237
+ const mode = detectAuthMode(mockReq);
238
+ expect(mode).toBe('cookie');
239
+ });
240
+ it('should return "body" when refresh token is in body', () => {
241
+ const mockReq = {
242
+ cookies: {},
243
+ body: {
244
+ refreshToken: 'ref-token',
245
+ },
246
+ };
247
+ const mode = detectAuthMode(mockReq);
248
+ expect(mode).toBe('body');
249
+ });
250
+ it('should prioritize cookies over body', () => {
251
+ const mockReq = {
252
+ cookies: {
253
+ dotpress_refresh_token: 'cookie-ref-token',
254
+ },
255
+ body: {
256
+ refreshToken: 'body-ref-token',
257
+ },
258
+ };
259
+ const mode = detectAuthMode(mockReq);
260
+ expect(mode).toBe('cookie');
261
+ });
262
+ it('should return null when no tokens found', () => {
263
+ const mockReq = {
264
+ cookies: {},
265
+ body: {},
266
+ };
267
+ const mode = detectAuthMode(mockReq);
268
+ expect(mode).toBeNull();
269
+ });
270
+ it('should work with custom cookie names', () => {
271
+ setSettings({
272
+ keys: {
273
+ secretKey: 'test-secret',
274
+ publicKey: 'test-public',
275
+ },
276
+ dataProvider: {},
277
+ cookies: {
278
+ enabled: true,
279
+ refreshTokenName: 'custom_refresh',
280
+ },
281
+ });
282
+ const mockReq = {
283
+ cookies: {
284
+ custom_refresh: 'ref-token',
285
+ },
286
+ };
287
+ const mode = detectAuthMode(mockReq);
288
+ expect(mode).toBe('cookie');
289
+ });
290
+ });
291
+ describe('extractTokensFromRequest', () => {
292
+ it('should extract tokens from cookies', () => {
293
+ // Ensure cookies are enabled with default settings
294
+ setSettings({
295
+ keys: {
296
+ secretKey: 'test-secret',
297
+ publicKey: 'test-public',
298
+ },
299
+ dataProvider: {},
300
+ cookies: {
301
+ enabled: true,
302
+ },
303
+ });
304
+ const mockReq = {
305
+ cookies: {
306
+ dotpress_refresh_token: 'ref-token',
307
+ },
308
+ body: {
309
+ accessToken: 'acc-token-from-body',
310
+ },
311
+ };
312
+ const tokens = extractTokensFromRequest(mockReq);
313
+ expect(tokens).toEqual({
314
+ accessToken: 'acc-token-from-body',
315
+ refreshToken: 'ref-token',
316
+ });
317
+ });
318
+ it('should extract tokens from body when no cookies', () => {
319
+ const mockReq = {
320
+ cookies: {},
321
+ body: {
322
+ accessToken: 'acc-token',
323
+ refreshToken: 'ref-token',
324
+ },
325
+ };
326
+ const tokens = extractTokensFromRequest(mockReq);
327
+ expect(tokens).toEqual({
328
+ accessToken: 'acc-token',
329
+ refreshToken: 'ref-token',
330
+ });
331
+ });
332
+ it('should extract both tokens from cookies when accessTokenInCookie is true', () => {
333
+ setSettings({
334
+ keys: {
335
+ secretKey: 'test-secret',
336
+ publicKey: 'test-public',
337
+ },
338
+ dataProvider: {},
339
+ cookies: {
340
+ enabled: true,
341
+ accessTokenInCookie: true,
342
+ },
343
+ });
344
+ const mockReq = {
345
+ cookies: {
346
+ dotpress_access_token: 'acc-token-cookie',
347
+ dotpress_refresh_token: 'ref-token-cookie',
348
+ },
349
+ body: {},
350
+ };
351
+ const tokens = extractTokensFromRequest(mockReq);
352
+ expect(tokens).toEqual({
353
+ accessToken: 'acc-token-cookie',
354
+ refreshToken: 'ref-token-cookie',
355
+ });
356
+ });
357
+ it('should return null when tokens are missing', () => {
358
+ const mockReq = {
359
+ cookies: {
360
+ dotpress_refresh_token: 'ref-token',
361
+ },
362
+ body: {},
363
+ };
364
+ const tokens = extractTokensFromRequest(mockReq);
365
+ expect(tokens).toBeNull();
366
+ });
367
+ });
368
+ describe('extractAccessToken', () => {
369
+ it('should prioritize Authorization header', () => {
370
+ const mockReq = {
371
+ headers: {
372
+ authorization: 'Bearer header-token',
373
+ },
374
+ cookies: {
375
+ dotpress_access_token: 'cookie-token',
376
+ },
377
+ body: {
378
+ accessToken: 'body-token',
379
+ },
380
+ };
381
+ const token = extractAccessToken(mockReq);
382
+ expect(token).toBe('header-token');
383
+ });
384
+ it('should extract from cookie when no header and accessTokenInCookie is true', () => {
385
+ setSettings({
386
+ keys: {
387
+ secretKey: 'test-secret',
388
+ publicKey: 'test-public',
389
+ },
390
+ dataProvider: {},
391
+ cookies: {
392
+ enabled: true,
393
+ accessTokenInCookie: true,
394
+ },
395
+ });
396
+ const mockReq = {
397
+ headers: {},
398
+ cookies: {
399
+ dotpress_access_token: 'cookie-token',
400
+ },
401
+ body: {
402
+ accessToken: 'body-token',
403
+ },
404
+ };
405
+ const token = extractAccessToken(mockReq);
406
+ expect(token).toBe('cookie-token');
407
+ });
408
+ it('should extract from body as fallback', () => {
409
+ const mockReq = {
410
+ headers: {},
411
+ cookies: {},
412
+ body: {
413
+ accessToken: 'body-token',
414
+ },
415
+ };
416
+ const token = extractAccessToken(mockReq);
417
+ expect(token).toBe('body-token');
418
+ });
419
+ it('should return null when no token found', () => {
420
+ const mockReq = {
421
+ headers: {},
422
+ cookies: {},
423
+ body: {},
424
+ };
425
+ const token = extractAccessToken(mockReq);
426
+ expect(token).toBeNull();
427
+ });
428
+ it('should handle Authorization header without Bearer prefix', () => {
429
+ const mockReq = {
430
+ headers: {
431
+ authorization: 'invalid-format',
432
+ },
433
+ cookies: {},
434
+ body: {},
435
+ };
436
+ const token = extractAccessToken(mockReq);
437
+ expect(token).toBeNull();
438
+ });
439
+ });
440
+ describe('calculateExpiration', () => {
441
+ it('should calculate correct expiration timestamp', () => {
442
+ const expiresInSeconds = 3600;
443
+ const before = Date.now();
444
+ const result = calculateExpiration(expiresInSeconds);
445
+ const after = Date.now();
446
+ expect(result.expiresIn).toBe(3600);
447
+ expect(result.expiresAt).toBeDefined();
448
+ const expiresAt = new Date(result.expiresAt).getTime();
449
+ expect(expiresAt).toBeGreaterThanOrEqual(before + 3600 * 1000);
450
+ expect(expiresAt).toBeLessThanOrEqual(after + 3600 * 1000);
451
+ });
452
+ it('should return ISO string format for expiresAt', () => {
453
+ const result = calculateExpiration(1800);
454
+ expect(result.expiresAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
455
+ });
456
+ it('should handle different TTL values', () => {
457
+ const result1 = calculateExpiration(60); // 1 minute
458
+ const result2 = calculateExpiration(86400); // 1 day
459
+ expect(result1.expiresIn).toBe(60);
460
+ expect(result2.expiresIn).toBe(86400);
461
+ const date1 = new Date(result1.expiresAt);
462
+ const date2 = new Date(result2.expiresAt);
463
+ expect(date2.getTime() - date1.getTime()).toBeGreaterThan(86000000); // Roughly 1 day
464
+ });
465
+ });
466
+ });
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import bcrypt from 'bcryptjs';
2
+ import cookieParser from 'cookie-parser';
2
3
  import { setSettings } from './lib/settings.js';
3
4
  import { registerHandlers } from './lib/handlers.js';
4
5
  import { attachAuthenticatedUser } from './lib/middleware.js';
@@ -7,6 +8,9 @@ export const configurePlugin = (settings) => {
7
8
  setSettings(settings);
8
9
  return (plugin) => {
9
10
  plugin.useBeforeRoutes((app) => {
11
+ if (settings.cookies?.enabled) {
12
+ app.use(cookieParser());
13
+ }
10
14
  app.use(attachAuthenticatedUser);
11
15
  });
12
16
  registerHandlers(plugin);
@@ -0,0 +1,42 @@
1
+ import type { Response, Request } from 'express';
2
+ import type { CookieSettings, AuthMode } from '../types/types.js';
3
+ type ResolvedCookieSettings = Required<Omit<CookieSettings, 'domain'>> & {
4
+ domain?: string;
5
+ };
6
+ export declare const getCookieSettings: () => ResolvedCookieSettings;
7
+ /**
8
+ * Set authentication cookies on the response
9
+ */
10
+ export declare const setAuthCookies: (res: Response, tokens: {
11
+ accessToken: string;
12
+ refreshToken: string;
13
+ }, expiresInSeconds: number) => void;
14
+ /**
15
+ * Clear authentication cookies
16
+ */
17
+ export declare const clearAuthCookies: (res: Response) => void;
18
+ /**
19
+ * Detect authentication mode from request
20
+ * Priority: cookies first, then body
21
+ */
22
+ export declare const detectAuthMode: (req: Request) => AuthMode | null;
23
+ /**
24
+ * Extract tokens from request based on detected mode
25
+ */
26
+ export declare const extractTokensFromRequest: (req: Request) => {
27
+ accessToken: string;
28
+ refreshToken: string;
29
+ } | null;
30
+ /**
31
+ * Extract access token from request
32
+ * Priority: Authorization header > cookie > body
33
+ */
34
+ export declare const extractAccessToken: (req: Request) => string | null;
35
+ /**
36
+ * Calculate expiration timestamp
37
+ */
38
+ export declare const calculateExpiration: (expiresInSeconds: number) => {
39
+ expiresIn: number;
40
+ expiresAt: string;
41
+ };
42
+ export {};
@@ -0,0 +1,136 @@
1
+ import { getSettings } from './settings.js';
2
+ const DEFAULT_COOKIE_SETTINGS = {
3
+ enabled: false,
4
+ httpOnly: true,
5
+ secure: process.env.NODE_ENV === 'production',
6
+ sameSite: 'lax',
7
+ path: '/',
8
+ accessTokenName: 'dotpress_access_token',
9
+ refreshTokenName: 'dotpress_refresh_token',
10
+ accessTokenInCookie: false,
11
+ domain: undefined,
12
+ };
13
+ export const getCookieSettings = () => {
14
+ const settings = getSettings();
15
+ return {
16
+ ...DEFAULT_COOKIE_SETTINGS,
17
+ ...settings.cookies,
18
+ };
19
+ };
20
+ /**
21
+ * Set authentication cookies on the response
22
+ */
23
+ export const setAuthCookies = (res, tokens, expiresInSeconds) => {
24
+ const cookieSettings = getCookieSettings();
25
+ if (!cookieSettings.enabled) {
26
+ return;
27
+ }
28
+ const cookieOptions = {
29
+ httpOnly: cookieSettings.httpOnly,
30
+ secure: cookieSettings.secure,
31
+ sameSite: cookieSettings.sameSite,
32
+ path: cookieSettings.path,
33
+ domain: cookieSettings.domain,
34
+ maxAge: expiresInSeconds * 1000,
35
+ };
36
+ // Always set refresh token in cookie for 'cookie' mode
37
+ res.cookie(cookieSettings.refreshTokenName, tokens.refreshToken, cookieOptions);
38
+ // Optionally set access token in cookie
39
+ if (cookieSettings.accessTokenInCookie) {
40
+ res.cookie(cookieSettings.accessTokenName, tokens.accessToken, cookieOptions);
41
+ }
42
+ };
43
+ /**
44
+ * Clear authentication cookies
45
+ */
46
+ export const clearAuthCookies = (res) => {
47
+ const cookieSettings = getCookieSettings();
48
+ if (!cookieSettings.enabled) {
49
+ return;
50
+ }
51
+ const cookieOptions = {
52
+ httpOnly: cookieSettings.httpOnly,
53
+ secure: cookieSettings.secure,
54
+ sameSite: cookieSettings.sameSite,
55
+ path: cookieSettings.path,
56
+ domain: cookieSettings.domain,
57
+ };
58
+ res.clearCookie(cookieSettings.refreshTokenName, cookieOptions);
59
+ if (cookieSettings.accessTokenInCookie) {
60
+ res.clearCookie(cookieSettings.accessTokenName, cookieOptions);
61
+ }
62
+ };
63
+ /**
64
+ * Detect authentication mode from request
65
+ * Priority: cookies first, then body
66
+ */
67
+ export const detectAuthMode = (req) => {
68
+ const cookieSettings = getCookieSettings();
69
+ // Check if refresh token exists in cookies
70
+ if (req.cookies?.[cookieSettings.refreshTokenName]) {
71
+ return 'cookie';
72
+ }
73
+ // Check if refresh token exists in body
74
+ if (req.body?.refreshToken) {
75
+ return 'body';
76
+ }
77
+ return null;
78
+ };
79
+ /**
80
+ * Extract tokens from request based on detected mode
81
+ */
82
+ export const extractTokensFromRequest = (req) => {
83
+ const mode = detectAuthMode(req);
84
+ const cookieSettings = getCookieSettings();
85
+ if (mode === 'cookie') {
86
+ const refreshToken = req.cookies?.[cookieSettings.refreshTokenName];
87
+ const accessToken = cookieSettings.accessTokenInCookie
88
+ ? req.cookies?.[cookieSettings.accessTokenName]
89
+ : req.body?.accessToken;
90
+ if (!refreshToken || !accessToken) {
91
+ return null;
92
+ }
93
+ return { accessToken, refreshToken };
94
+ }
95
+ if (mode === 'body') {
96
+ const { accessToken, refreshToken } = req.body || {};
97
+ if (!accessToken || !refreshToken) {
98
+ return null;
99
+ }
100
+ return { accessToken, refreshToken };
101
+ }
102
+ return null;
103
+ };
104
+ /**
105
+ * Extract access token from request
106
+ * Priority: Authorization header > cookie > body
107
+ */
108
+ export const extractAccessToken = (req) => {
109
+ // Priority 1: Authorization header
110
+ const authHeader = req.headers.authorization;
111
+ if (authHeader?.startsWith('Bearer ')) {
112
+ return authHeader.replace('Bearer ', '');
113
+ }
114
+ const cookieSettings = getCookieSettings();
115
+ // Priority 2: Cookie (if enabled)
116
+ if (cookieSettings.enabled &&
117
+ cookieSettings.accessTokenInCookie &&
118
+ req.cookies?.[cookieSettings.accessTokenName]) {
119
+ return req.cookies[cookieSettings.accessTokenName];
120
+ }
121
+ // Priority 3: Body
122
+ if (req.body?.accessToken) {
123
+ return req.body.accessToken;
124
+ }
125
+ return null;
126
+ };
127
+ /**
128
+ * Calculate expiration timestamp
129
+ */
130
+ export const calculateExpiration = (expiresInSeconds) => {
131
+ const expiresAt = new Date(Date.now() + expiresInSeconds * 1000);
132
+ return {
133
+ expiresIn: expiresInSeconds,
134
+ expiresAt: expiresAt.toISOString(),
135
+ };
136
+ };