bezzie 0.1.4 → 0.1.6

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.
Files changed (86) hide show
  1. package/dist/adapters/cloudflare-kv.d.ts +10 -0
  2. package/dist/adapters/cloudflare-kv.d.ts.map +1 -0
  3. package/dist/{src/adapters → adapters}/cloudflare-kv.js +1 -2
  4. package/dist/adapters/cloudflare-kv.js.map +1 -0
  5. package/dist/adapters/index.d.ts.map +1 -0
  6. package/dist/adapters/index.js.map +1 -0
  7. package/dist/adapters/memory.d.ts +9 -0
  8. package/dist/adapters/memory.d.ts.map +1 -0
  9. package/dist/adapters/memory.js.map +1 -0
  10. package/dist/{src/adapters → adapters}/redis.d.ts +3 -3
  11. package/dist/adapters/redis.d.ts.map +1 -0
  12. package/dist/adapters/redis.js.map +1 -0
  13. package/dist/{src/adapters → adapters}/types.d.ts +7 -3
  14. package/dist/adapters/types.d.ts.map +1 -0
  15. package/dist/{src/adapters → adapters}/types.js.map +1 -1
  16. package/dist/discovery.d.ts.map +1 -0
  17. package/dist/discovery.js +24 -0
  18. package/dist/discovery.js.map +1 -0
  19. package/dist/{src/index.d.ts → index.d.ts} +14 -8
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/{src/index.js → index.js} +3 -2
  22. package/dist/index.js.map +1 -0
  23. package/dist/middleware.d.ts +28 -0
  24. package/dist/middleware.d.ts.map +1 -0
  25. package/dist/middleware.js +121 -0
  26. package/dist/middleware.js.map +1 -0
  27. package/dist/routes.d.ts +5 -0
  28. package/dist/routes.d.ts.map +1 -0
  29. package/dist/{src/routes.js → routes.js} +14 -4
  30. package/dist/routes.js.map +1 -0
  31. package/dist/{src/session.d.ts → session.d.ts} +6 -3
  32. package/dist/session.d.ts.map +1 -0
  33. package/dist/session.js.map +1 -0
  34. package/package.json +8 -4
  35. package/dist/src/adapters/cloudflare-kv.d.ts +0 -10
  36. package/dist/src/adapters/cloudflare-kv.d.ts.map +0 -1
  37. package/dist/src/adapters/cloudflare-kv.js.map +0 -1
  38. package/dist/src/adapters/index.d.ts.map +0 -1
  39. package/dist/src/adapters/index.js.map +0 -1
  40. package/dist/src/adapters/memory.d.ts +0 -9
  41. package/dist/src/adapters/memory.d.ts.map +0 -1
  42. package/dist/src/adapters/memory.js.map +0 -1
  43. package/dist/src/adapters/redis.d.ts.map +0 -1
  44. package/dist/src/adapters/redis.js.map +0 -1
  45. package/dist/src/adapters/types.d.ts.map +0 -1
  46. package/dist/src/discovery.d.ts.map +0 -1
  47. package/dist/src/discovery.js +0 -19
  48. package/dist/src/discovery.js.map +0 -1
  49. package/dist/src/index.d.ts.map +0 -1
  50. package/dist/src/index.js.map +0 -1
  51. package/dist/src/middleware.d.ts +0 -22
  52. package/dist/src/middleware.d.ts.map +0 -1
  53. package/dist/src/middleware.js +0 -95
  54. package/dist/src/middleware.js.map +0 -1
  55. package/dist/src/routes.d.ts +0 -5
  56. package/dist/src/routes.d.ts.map +0 -1
  57. package/dist/src/routes.js.map +0 -1
  58. package/dist/src/session.d.ts.map +0 -1
  59. package/dist/src/session.js.map +0 -1
  60. package/dist/test/index.test.d.ts +0 -2
  61. package/dist/test/index.test.d.ts.map +0 -1
  62. package/dist/test/index.test.js +0 -96
  63. package/dist/test/index.test.js.map +0 -1
  64. package/dist/test/middleware.test.d.ts +0 -2
  65. package/dist/test/middleware.test.d.ts.map +0 -1
  66. package/dist/test/middleware.test.js +0 -340
  67. package/dist/test/middleware.test.js.map +0 -1
  68. package/dist/test/routes.test.d.ts +0 -2
  69. package/dist/test/routes.test.d.ts.map +0 -1
  70. package/dist/test/routes.test.js +0 -284
  71. package/dist/test/routes.test.js.map +0 -1
  72. package/dist/test/session.test.d.ts +0 -2
  73. package/dist/test/session.test.d.ts.map +0 -1
  74. package/dist/test/session.test.js +0 -96
  75. package/dist/test/session.test.js.map +0 -1
  76. package/dist/vitest.config.d.ts +0 -3
  77. package/dist/vitest.config.d.ts.map +0 -1
  78. package/dist/vitest.config.js +0 -10
  79. package/dist/vitest.config.js.map +0 -1
  80. /package/dist/{src/adapters → adapters}/index.d.ts +0 -0
  81. /package/dist/{src/adapters → adapters}/index.js +0 -0
  82. /package/dist/{src/adapters → adapters}/memory.js +0 -0
  83. /package/dist/{src/adapters → adapters}/redis.js +0 -0
  84. /package/dist/{src/adapters → adapters}/types.js +0 -0
  85. /package/dist/{src/discovery.d.ts → discovery.d.ts} +0 -0
  86. /package/dist/{src/session.js → session.js} +0 -0
@@ -1,340 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { Hono } from 'hono';
3
- import { createBezzie, MemoryAdapter } from '../src';
4
- import * as oauth from 'oauth4webapi';
5
- // Mock oauth4webapi
6
- vi.mock('oauth4webapi', async () => {
7
- const actual = await vi.importActual('oauth4webapi');
8
- return {
9
- ...actual,
10
- discoveryRequest: vi.fn(),
11
- processDiscoveryResponse: vi.fn(),
12
- refreshTokenGrantRequest: vi.fn(),
13
- processRefreshTokenResponse: vi.fn(),
14
- validateJwtAccessToken: vi.fn(),
15
- };
16
- });
17
- describe('Middleware', () => {
18
- let adapter;
19
- let auth;
20
- let app;
21
- const issuer = 'https://test.auth0.com';
22
- beforeEach(async () => {
23
- vi.clearAllMocks();
24
- adapter = new MemoryAdapter();
25
- const config = {
26
- issuer,
27
- clientId: 'test-client-id',
28
- clientSecret: 'test-client-secret',
29
- audience: 'https://api.test.com',
30
- adapter,
31
- baseUrl: 'https://app.test.com',
32
- };
33
- auth = createBezzie(config);
34
- app = new Hono();
35
- app.use('/api/*', auth.middleware());
36
- app.get('/api/me', (c) => {
37
- return c.json({ user: c.get('user'), accessToken: c.get('accessToken') });
38
- });
39
- // Default mock for discovery
40
- const mockAs = { issuer, jwks_uri: `${issuer}/jwks` };
41
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
42
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
43
- });
44
- it('returns 401 with no cookie', async () => {
45
- const res = await app.request('/api/me');
46
- expect(res.status).toBe(401);
47
- expect(await res.text()).toBe('Unauthorized');
48
- });
49
- it('returns 401 with invalid session (not in KV)', async () => {
50
- const res = await app.request('/api/me', {
51
- headers: {
52
- Cookie: '__Host-session=non-existent',
53
- },
54
- });
55
- expect(res.status).toBe(401);
56
- });
57
- it('valid session passes through and sets user on context', async () => {
58
- const sessionId = 'test-session-id';
59
- const user = { sub: 'user-123', email: 'user@example.com' };
60
- await adapter.set(sessionId, {
61
- accessToken: 'valid-token',
62
- refreshToken: 'valid-refresh',
63
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
64
- createdAt: Math.floor(Date.now() / 1000),
65
- user,
66
- }, 3600);
67
- // Mock successful JWT validation
68
- vi.mocked(oauth.validateJwtAccessToken).mockResolvedValue({});
69
- const res = await app.request('/api/me', {
70
- headers: {
71
- Cookie: `__Host-session=${sessionId}`,
72
- },
73
- });
74
- expect(res.status).toBe(200);
75
- const data = (await res.json());
76
- expect(data.user).toEqual(user);
77
- expect(data.accessToken).toBe('valid-token');
78
- });
79
- it('expired access token triggers refresh, updates KV, passes through', async () => {
80
- const sessionId = 'test-session-id';
81
- const user = { sub: 'user-123' };
82
- await adapter.set(sessionId, {
83
- accessToken: 'expired-token',
84
- refreshToken: 'valid-refresh',
85
- expiresAt: Math.floor(Date.now() / 1000) - 10, // expired 10s ago
86
- createdAt: Math.floor(Date.now() / 1000) - 1000,
87
- user,
88
- }, 86400);
89
- // Mock successful refresh
90
- vi.mocked(oauth.refreshTokenGrantRequest).mockResolvedValue({});
91
- vi.mocked(oauth.processRefreshTokenResponse).mockResolvedValue({
92
- access_token: 'new-token',
93
- refresh_token: 'new-refresh',
94
- expires_in: 3600,
95
- token_type: 'bearer',
96
- });
97
- vi.mocked(oauth.validateJwtAccessToken).mockResolvedValue({});
98
- const res = await app.request('/api/me', {
99
- headers: {
100
- Cookie: `__Host-session=${sessionId}`,
101
- },
102
- });
103
- expect(res.status).toBe(200);
104
- const data = (await res.json());
105
- expect(data.accessToken).toBe('new-token');
106
- // Check adapter updated
107
- const stored = (await adapter.get(sessionId));
108
- expect(stored.accessToken).toBe('new-token');
109
- expect(stored.refreshToken).toBe('new-refresh');
110
- expect(stored.expiresAt).toBeGreaterThan(Date.now() / 1000);
111
- });
112
- it('failed refresh deletes session and returns 401', async () => {
113
- const sessionId = 'test-session-id';
114
- await adapter.set(sessionId, {
115
- accessToken: 'expired-token',
116
- refreshToken: 'invalid-refresh',
117
- expiresAt: Math.floor(Date.now() / 1000) - 10,
118
- createdAt: Math.floor(Date.now() / 1000) - 1000,
119
- user: { sub: '123' },
120
- }, 3600);
121
- // Mock failed refresh
122
- vi.mocked(oauth.refreshTokenGrantRequest).mockResolvedValue({});
123
- const error = new oauth.ResponseBodyError('invalid_grant', {
124
- cause: { error: 'invalid_grant' },
125
- response: new Response()
126
- });
127
- vi.mocked(oauth.processRefreshTokenResponse).mockRejectedValue(error);
128
- const res = await app.request('/api/me', {
129
- headers: {
130
- Cookie: `__Host-session=${sessionId}`,
131
- },
132
- });
133
- expect(res.status).toBe(401);
134
- // Check session deleted
135
- expect(await adapter.get(sessionId)).toBeNull();
136
- });
137
- it('invalid JWT returns 401', async () => {
138
- const sessionId = 'test-session-id';
139
- await adapter.set(sessionId, {
140
- accessToken: 'invalid-jwt',
141
- refreshToken: 'refresh',
142
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
143
- createdAt: Math.floor(Date.now() / 1000),
144
- user: { sub: '123' },
145
- }, 3600);
146
- // Mock JWT validation failure
147
- vi.mocked(oauth.validateJwtAccessToken).mockRejectedValue(new Error('Invalid token'));
148
- const res = await app.request('/api/me', {
149
- headers: {
150
- Cookie: `__Host-session=${sessionId}`,
151
- },
152
- });
153
- expect(res.status).toBe(401);
154
- });
155
- it('triggers refresh with 60s buffer', async () => {
156
- const sessionId = 'test-session-id';
157
- await adapter.set(sessionId, {
158
- accessToken: 'near-expiry-token',
159
- refreshToken: 'valid-refresh',
160
- expiresAt: Math.floor(Date.now() / 1000) + 30, // expires in 30s
161
- createdAt: Math.floor(Date.now() / 1000) - 1000,
162
- user: { sub: '123' },
163
- }, 3600);
164
- vi.mocked(oauth.refreshTokenGrantRequest).mockResolvedValue({});
165
- vi.mocked(oauth.processRefreshTokenResponse).mockResolvedValue({
166
- access_token: 'new-token',
167
- expires_in: 3600,
168
- token_type: 'bearer',
169
- });
170
- vi.mocked(oauth.validateJwtAccessToken).mockResolvedValue({});
171
- const res = await app.request('/api/me', {
172
- headers: {
173
- Cookie: `__Host-session=${sessionId}`,
174
- },
175
- });
176
- expect(res.status).toBe(200);
177
- const data = (await res.json());
178
- expect(data.accessToken).toBe('new-token');
179
- });
180
- it('skips JWT validation when no audience is configured', async () => {
181
- // Create a new app/middleware with no audience
182
- const configNoAudience = {
183
- issuer,
184
- clientId: 'test-client-id',
185
- clientSecret: 'test-client-secret',
186
- // no audience
187
- adapter,
188
- baseUrl: 'https://app.test.com',
189
- };
190
- const authNoAudience = createBezzie(configNoAudience);
191
- const appNoAudience = new Hono();
192
- appNoAudience.use('/api/*', authNoAudience.middleware());
193
- appNoAudience.get('/api/me', (c) => c.json({ ok: true }));
194
- const sessionId = 'test-session-id';
195
- await adapter.set(sessionId, {
196
- accessToken: 'valid-token',
197
- refreshToken: 'valid-refresh',
198
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
199
- createdAt: Math.floor(Date.now() / 1000),
200
- user: { sub: '123' },
201
- }, 3600);
202
- const res = await appNoAudience.request('/api/me', {
203
- headers: {
204
- Cookie: `__Host-session=${sessionId}`,
205
- },
206
- });
207
- expect(res.status).toBe(200);
208
- // Should NOT have called validateJwtAccessToken
209
- expect(oauth.validateJwtAccessToken).not.toHaveBeenCalled();
210
- });
211
- it('skips JWT validation when validateAccessToken is false', async () => {
212
- const configWithValidateFalse = {
213
- issuer,
214
- clientId: 'test-client-id',
215
- clientSecret: 'test-client-secret',
216
- audience: 'https://api.test.com',
217
- adapter,
218
- baseUrl: 'https://app.test.com',
219
- validateAccessToken: false,
220
- };
221
- const authWithValidateFalse = createBezzie(configWithValidateFalse);
222
- const appWithValidateFalse = new Hono();
223
- appWithValidateFalse.use('/api/*', authWithValidateFalse.middleware());
224
- appWithValidateFalse.get('/api/me', (c) => c.json({ ok: true }));
225
- const sessionId = 'test-session-id';
226
- await adapter.set(sessionId, {
227
- accessToken: 'opaque-token',
228
- refreshToken: 'valid-refresh',
229
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
230
- createdAt: Math.floor(Date.now() / 1000),
231
- user: { sub: '123' },
232
- }, 3600);
233
- const res = await appWithValidateFalse.request('/api/me', {
234
- headers: {
235
- Cookie: `__Host-session=${sessionId}`,
236
- },
237
- });
238
- expect(res.status).toBe(200);
239
- expect(oauth.validateJwtAccessToken).not.toHaveBeenCalled();
240
- });
241
- it('re-reads session from store when refresh fails with invalid_grant (race condition)', async () => {
242
- const sessionId = 'test-session-id';
243
- const user = { sub: 'user-123' };
244
- const oldAccessToken = 'expired-token';
245
- const newAccessToken = 'already-refreshed-token';
246
- // Initial state: near-expiry token
247
- await adapter.set(sessionId, {
248
- accessToken: oldAccessToken,
249
- refreshToken: 'valid-refresh',
250
- expiresAt: Math.floor(Date.now() / 1000) + 30, // expires in 30s
251
- createdAt: Math.floor(Date.now() / 1000),
252
- user,
253
- }, 86400);
254
- // Mock failed refresh with invalid_grant
255
- vi.mocked(oauth.refreshTokenGrantRequest).mockResolvedValue({});
256
- const error = new oauth.ResponseBodyError('invalid_grant', {
257
- cause: { error: 'invalid_grant' },
258
- response: new Response()
259
- });
260
- vi.mocked(oauth.processRefreshTokenResponse).mockRejectedValue(error);
261
- vi.mocked(oauth.validateJwtAccessToken).mockResolvedValue({});
262
- const originalGet = adapter.get.bind(adapter);
263
- let getCount = 0;
264
- vi.spyOn(adapter, 'get').mockImplementation(async (id) => {
265
- getCount++;
266
- const result = await originalGet(id);
267
- if (getCount === 1) {
268
- // After first GET, update adapter to simulate concurrent refresh by another request
269
- await adapter.set(id, {
270
- accessToken: newAccessToken,
271
- refreshToken: 'new-refresh',
272
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
273
- createdAt: Math.floor(Date.now() / 1000),
274
- user,
275
- }, 86400);
276
- }
277
- return result;
278
- });
279
- const res = await app.request('/api/me', {
280
- headers: {
281
- Cookie: `__Host-session=${sessionId}`,
282
- },
283
- });
284
- expect(res.status).toBe(200);
285
- const data = (await res.json());
286
- expect(data.accessToken).toBe(newAccessToken);
287
- // Verify it was re-read (1st in middleware start, 2nd after invalid_grant)
288
- expect(getCount).toBe(2);
289
- });
290
- it('redirects to login when session is older than 90 days (absolute expiry)', async () => {
291
- const sessionId = 'old-session-id';
292
- const MAX_SESSION_AGE = 90 * 24 * 60 * 60;
293
- await adapter.set(sessionId, {
294
- accessToken: 'token',
295
- refreshToken: 'refresh',
296
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
297
- createdAt: Math.floor(Date.now() / 1000) - (MAX_SESSION_AGE + 1), // 90 days + 1 second ago
298
- user: { sub: 'user-123' },
299
- }, 3600);
300
- const res = await app.request('/api/me', {
301
- headers: {
302
- Cookie: `__Host-session=${sessionId}`,
303
- },
304
- });
305
- expect(res.status).toBe(302);
306
- expect(res.headers.get('Location')).toBe('/auth/login');
307
- // Session should be deleted from store
308
- expect(await adapter.get(sessionId)).toBeNull();
309
- });
310
- it('redirects to custom login path when session is older than 90 days', async () => {
311
- const configWithCustomLogin = {
312
- issuer,
313
- clientId: 'test-client-id',
314
- clientSecret: 'test-client-secret',
315
- adapter,
316
- baseUrl: 'https://app.test.com',
317
- loginPath: '/custom/login',
318
- };
319
- const authCustomLogin = createBezzie(configWithCustomLogin);
320
- const appCustomLogin = new Hono();
321
- appCustomLogin.use('/api/*', authCustomLogin.middleware());
322
- const sessionId = 'old-session-id';
323
- const MAX_SESSION_AGE = 90 * 24 * 60 * 60;
324
- await adapter.set(sessionId, {
325
- accessToken: 'token',
326
- refreshToken: 'refresh',
327
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
328
- createdAt: Math.floor(Date.now() / 1000) - (MAX_SESSION_AGE + 1),
329
- user: { sub: 'user-123' },
330
- }, 3600);
331
- const res = await appCustomLogin.request('/api/me', {
332
- headers: {
333
- Cookie: `__Host-session=${sessionId}`,
334
- },
335
- });
336
- expect(res.status).toBe(302);
337
- expect(res.headers.get('Location')).toBe('/custom/login');
338
- });
339
- });
340
- //# sourceMappingURL=middleware.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.test.js","sourceRoot":"","sources":["../../test/middleware.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=routes.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"routes.test.d.ts","sourceRoot":"","sources":["../../test/routes.test.ts"],"names":[],"mappings":""}
@@ -1,284 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { createBezzie, MemoryAdapter } from '../src';
3
- import * as oauth from 'oauth4webapi';
4
- // Mock oauth4webapi
5
- vi.mock('oauth4webapi', async () => {
6
- const actual = await vi.importActual('oauth4webapi');
7
- return {
8
- ...actual,
9
- discoveryRequest: vi.fn(),
10
- processDiscoveryResponse: vi.fn(),
11
- authorizationCodeGrantRequest: vi.fn(),
12
- processAuthorizationCodeResponse: vi.fn(),
13
- getValidatedIdTokenClaims: vi.fn(),
14
- };
15
- });
16
- describe('OAuth Routes', () => {
17
- const adapter = new MemoryAdapter();
18
- const config = {
19
- issuer: 'https://test.auth0.com',
20
- clientId: 'test-client-id',
21
- clientSecret: 'test-client-secret',
22
- audience: 'https://api.test.com',
23
- adapter,
24
- baseUrl: 'https://app.test.com',
25
- };
26
- const auth = createBezzie(config);
27
- const app = auth.routes();
28
- describe('GET /login', () => {
29
- it('redirects to the provider authorization URL', async () => {
30
- auth.cache.cachedAS = null;
31
- auth.cache.cacheExpiresAt = 0;
32
- const mockAs = {
33
- issuer: config.issuer,
34
- authorization_endpoint: `${config.issuer}/authorize`
35
- };
36
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
37
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
38
- const res = await app.request('/login');
39
- expect(res.status).toBe(302);
40
- const location = res.headers.get('Location');
41
- expect(location).toContain(`${config.issuer}/authorize`);
42
- expect(location).toContain(`client_id=${config.clientId}`);
43
- expect(location).toContain('response_type=code');
44
- expect(location).toContain(`redirect_uri=${encodeURIComponent(config.baseUrl + '/auth/callback')}`);
45
- expect(location).toContain('scope=openid+profile+email+offline_access');
46
- expect(location).toContain('code_challenge=');
47
- expect(location).toContain('code_challenge_method=S256');
48
- expect(location).toContain(`audience=${encodeURIComponent(config.audience)}`);
49
- });
50
- it('stores PKCE state in adapter', async () => {
51
- auth.cache.cachedAS = null;
52
- auth.cache.cacheExpiresAt = 0;
53
- const mockAs = {
54
- issuer: config.issuer,
55
- authorization_endpoint: `${config.issuer}/authorize`
56
- };
57
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
58
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
59
- const res = await app.request('/login');
60
- const location = new URL(res.headers.get('Location'));
61
- const state = location.searchParams.get('state');
62
- const stored = await adapter.get(`pkce:${state}`);
63
- expect(stored).toBeDefined();
64
- expect(typeof stored.codeVerifier).toBe('string');
65
- });
66
- it('stores returnTo in PKCE state if provided', async () => {
67
- auth.cache.cachedAS = null;
68
- auth.cache.cacheExpiresAt = 0;
69
- const mockAs = {
70
- issuer: config.issuer,
71
- authorization_endpoint: `${config.issuer}/authorize`
72
- };
73
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
74
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
75
- const res = await app.request('/login?returnTo=/dashboard');
76
- const location = new URL(res.headers.get('Location'));
77
- const state = location.searchParams.get('state');
78
- const stored = await adapter.get(`pkce:${state}`);
79
- expect(stored.returnTo).toBe('/dashboard');
80
- });
81
- });
82
- describe('GET /callback', () => {
83
- it('returns 400 with error parameter', async () => {
84
- const res = await app.request('/callback?error=access_denied');
85
- expect(res.status).toBe(400);
86
- expect(await res.text()).toBe('OAuth error: access_denied');
87
- });
88
- it('returns 400 with invalid state', async () => {
89
- const res = await app.request('/callback?state=invalid&code=123');
90
- expect(res.status).toBe(400);
91
- expect(await res.text()).toBe('Invalid or expired state');
92
- });
93
- it('with valid state exchanges code, stores session, sets cookie, redirects', async () => {
94
- const state = 'test-state';
95
- const code = 'test-code';
96
- const codeVerifier = 'test-verifier';
97
- await adapter.set(`pkce:${state}`, { codeVerifier }, 600);
98
- // Setup mocks
99
- const mockAs = { issuer: config.issuer };
100
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
101
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
102
- vi.mocked(oauth.authorizationCodeGrantRequest).mockResolvedValue({});
103
- vi.mocked(oauth.processAuthorizationCodeResponse).mockResolvedValue({
104
- access_token: 'mock-access-token',
105
- refresh_token: 'mock-refresh-token',
106
- expires_in: 3600,
107
- id_token: 'mock-id-token',
108
- });
109
- vi.mocked(oauth.getValidatedIdTokenClaims).mockReturnValue({
110
- sub: 'user-123',
111
- email: 'user@example.com',
112
- });
113
- const res = await app.request(`/callback?state=${state}&code=${code}`);
114
- expect(res.status).toBe(302);
115
- expect(res.headers.get('Location')).toBe('/');
116
- // Check cookie
117
- const cookie = res.headers.get('Set-Cookie');
118
- expect(cookie).toContain('__Host-session=');
119
- expect(cookie).toContain('HttpOnly');
120
- expect(cookie).toContain('Secure');
121
- expect(cookie).toContain('SameSite=Strict');
122
- expect(cookie).toContain('Max-Age=2592000');
123
- // Check session in adapter
124
- const sessionId = cookie.match(/__Host-session=([^;]+)/)[1];
125
- const session = await adapter.get(sessionId);
126
- expect(session).toBeDefined();
127
- expect(session.accessToken).toBe('mock-access-token');
128
- expect(session.idToken).toBe('mock-id-token');
129
- expect(session.user.sub).toBe('user-123');
130
- expect(session.createdAt).toBeTypeOf('number');
131
- expect(session.createdAt).toBeLessThanOrEqual(Math.floor(Date.now() / 1000));
132
- expect(await adapter.get(`pkce:${state}`)).toBeNull();
133
- });
134
- it('redirects to returnTo after successful login', async () => {
135
- const state = 'test-state-ret';
136
- const code = 'test-code';
137
- const codeVerifier = 'test-verifier';
138
- const returnTo = '/dashboard';
139
- await adapter.set(`pkce:${state}`, { codeVerifier, returnTo }, 600);
140
- // Setup mocks
141
- const mockAs = { issuer: config.issuer };
142
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
143
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
144
- vi.mocked(oauth.authorizationCodeGrantRequest).mockResolvedValue({});
145
- vi.mocked(oauth.processAuthorizationCodeResponse).mockResolvedValue({
146
- access_token: 'mock-access-token',
147
- expires_in: 3600,
148
- id_token: 'mock-id-token',
149
- });
150
- vi.mocked(oauth.getValidatedIdTokenClaims).mockReturnValue({
151
- sub: 'user-123',
152
- });
153
- const res = await app.request(`/callback?state=${state}&code=${code}`);
154
- expect(res.status).toBe(302);
155
- expect(res.headers.get('Location')).toBe('/dashboard');
156
- });
157
- it('rejects external returnTo and falls back to /', async () => {
158
- const state = 'test-state-evil';
159
- const code = 'test-code';
160
- const codeVerifier = 'test-verifier';
161
- const returnTo = 'https://evil.com/malicious';
162
- await adapter.set(`pkce:${state}`, { codeVerifier, returnTo }, 600);
163
- const res = await app.request(`/callback?state=${state}&code=${code}`);
164
- expect(res.status).toBe(302);
165
- expect(res.headers.get('Location')).toBe('/');
166
- });
167
- it('rejects protocol-relative returnTo (//) and falls back to /', async () => {
168
- const state = 'test-state-proto';
169
- const code = 'test-code';
170
- const codeVerifier = 'test-verifier';
171
- const returnTo = '//evil.com';
172
- await adapter.set(`pkce:${state}`, { codeVerifier, returnTo }, 600);
173
- const res = await app.request(`/callback?state=${state}&code=${code}`);
174
- expect(res.status).toBe(302);
175
- expect(res.headers.get('Location')).toBe('/');
176
- });
177
- });
178
- describe('POST /logout', () => {
179
- it('redirects to / if no logout URL can be determined', async () => {
180
- auth.cache.cachedAS = null;
181
- auth.cache.cacheExpiresAt = 0;
182
- const mockAs = { issuer: config.issuer };
183
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
184
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
185
- const res = await app.request('/logout', { method: 'POST' });
186
- expect(res.status).toBe(302);
187
- expect(res.headers.get('Location')).toBe('/');
188
- });
189
- it('uses providerHints.logoutUrl if provided', async () => {
190
- const customConfig = { ...config, providerHints: { logoutUrl: 'https://test.auth0.com/v2/logout' } };
191
- const customAuth = createBezzie(customConfig);
192
- const customApp = customAuth.routes();
193
- const mockAs = { issuer: customConfig.issuer };
194
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
195
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
196
- const res = await customApp.request('/logout', { method: 'POST' });
197
- expect(res.status).toBe(302);
198
- const location = res.headers.get('Location');
199
- expect(location).toContain('https://test.auth0.com/v2/logout');
200
- expect(location).toContain(`client_id=${customConfig.clientId}`);
201
- expect(location).toContain(`returnTo=${encodeURIComponent(customConfig.baseUrl)}`);
202
- });
203
- it('redirects to OIDC end_session_endpoint if available', async () => {
204
- auth.cache.cachedAS = null;
205
- auth.cache.cacheExpiresAt = 0;
206
- const mockAs = {
207
- issuer: config.issuer,
208
- end_session_endpoint: `${config.issuer}/oidc/logout`
209
- };
210
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
211
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
212
- const res = await app.request('/logout', { method: 'POST' });
213
- expect(res.status).toBe(302);
214
- const location = res.headers.get('Location');
215
- expect(location).toContain(`${config.issuer}/oidc/logout`);
216
- expect(location).toContain(`client_id=${config.clientId}`);
217
- expect(location).toContain(`post_logout_redirect_uri=${encodeURIComponent(config.baseUrl)}`);
218
- });
219
- it('with valid session cookie - deletes session from adapter, clears cookie, and adds id_token_hint', async () => {
220
- auth.cache.cachedAS = null;
221
- auth.cache.cacheExpiresAt = 0;
222
- const sessionId = 'test-session-id';
223
- const idToken = 'mock-id-token';
224
- await adapter.set(sessionId, {
225
- accessToken: 'test',
226
- refreshToken: 'refresh',
227
- idToken,
228
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
229
- createdAt: Math.floor(Date.now() / 1000),
230
- user: { sub: 'user-123' },
231
- }, 3600);
232
- const mockAs = {
233
- issuer: config.issuer,
234
- end_session_endpoint: `${config.issuer}/logout`
235
- };
236
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
237
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
238
- const res = await app.request('/logout', {
239
- method: 'POST',
240
- headers: {
241
- Cookie: `__Host-session=${sessionId}`,
242
- },
243
- });
244
- expect(res.status).toBe(302);
245
- const location = res.headers.get('Location');
246
- expect(location).toContain(`id_token_hint=${idToken}`);
247
- // Check cookie cleared
248
- const setCookie = res.headers.get('Set-Cookie');
249
- expect(setCookie).toContain('__Host-session=;');
250
- expect(setCookie).toContain('Max-Age=0');
251
- // Check session deleted from adapter
252
- expect(await adapter.get(sessionId)).toBeNull();
253
- });
254
- it('uses providerHints.logoutUrl and adds id_token_hint if session present', async () => {
255
- const customConfig = { ...config, providerHints: { logoutUrl: 'https://test.auth0.com/v2/logout' } };
256
- const customAuth = createBezzie(customConfig);
257
- const customApp = customAuth.routes();
258
- const sessionId = 'test-session-id';
259
- const idToken = 'mock-id-token';
260
- await adapter.set(sessionId, {
261
- accessToken: 'test',
262
- idToken,
263
- expiresAt: Math.floor(Date.now() / 1000) + 3600,
264
- createdAt: Math.floor(Date.now() / 1000),
265
- user: { sub: 'user-123' },
266
- }, 3600);
267
- const mockAs = { issuer: customConfig.issuer };
268
- vi.mocked(oauth.discoveryRequest).mockResolvedValue({});
269
- vi.mocked(oauth.processDiscoveryResponse).mockResolvedValue(mockAs);
270
- const res = await customApp.request('/logout', {
271
- method: 'POST',
272
- headers: {
273
- Cookie: `__Host-session=${sessionId}`,
274
- },
275
- });
276
- expect(res.status).toBe(302);
277
- const location = res.headers.get('Location');
278
- expect(location).toContain('https://test.auth0.com/v2/logout');
279
- expect(location).toContain(`client_id=${customConfig.clientId}`);
280
- expect(location).toContain(`id_token_hint=${idToken}`);
281
- });
282
- });
283
- });
284
- //# sourceMappingURL=routes.test.js.map