@amodalai/runtime 0.3.69 → 0.3.71

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 (85) hide show
  1. package/dist/src/auth/__fixtures__/custom-strategy.d.ts +8 -0
  2. package/dist/src/auth/__fixtures__/custom-strategy.js +27 -0
  3. package/dist/src/auth/__fixtures__/custom-strategy.js.map +1 -0
  4. package/dist/src/auth/compose.d.ts +55 -0
  5. package/dist/src/auth/compose.js +142 -0
  6. package/dist/src/auth/compose.js.map +1 -0
  7. package/dist/src/auth/compose.test.d.ts +6 -0
  8. package/dist/src/auth/compose.test.js +159 -0
  9. package/dist/src/auth/compose.test.js.map +1 -0
  10. package/dist/src/auth/config.d.ts +261 -0
  11. package/dist/src/auth/config.js +107 -0
  12. package/dist/src/auth/config.js.map +1 -0
  13. package/dist/src/auth/config.test.d.ts +6 -0
  14. package/dist/src/auth/config.test.js +85 -0
  15. package/dist/src/auth/config.test.js.map +1 -0
  16. package/dist/src/auth/factory.d.ts +19 -0
  17. package/dist/src/auth/factory.js +57 -0
  18. package/dist/src/auth/factory.js.map +1 -0
  19. package/dist/src/auth/factory.test.d.ts +6 -0
  20. package/dist/src/auth/factory.test.js +60 -0
  21. package/dist/src/auth/factory.test.js.map +1 -0
  22. package/dist/src/auth/index.d.ts +48 -0
  23. package/dist/src/auth/index.js +19 -0
  24. package/dist/src/auth/index.js.map +1 -0
  25. package/dist/src/auth/strategies/amodal.d.ts +36 -0
  26. package/dist/src/auth/strategies/amodal.js +28 -0
  27. package/dist/src/auth/strategies/amodal.js.map +1 -0
  28. package/dist/src/auth/strategies/api-key.d.ts +41 -0
  29. package/dist/src/auth/strategies/api-key.js +63 -0
  30. package/dist/src/auth/strategies/api-key.js.map +1 -0
  31. package/dist/src/auth/strategies/auth-modes.integration.test.d.ts +6 -0
  32. package/dist/src/auth/strategies/auth-modes.integration.test.js +363 -0
  33. package/dist/src/auth/strategies/auth-modes.integration.test.js.map +1 -0
  34. package/dist/src/auth/strategies/cookie.d.ts +41 -0
  35. package/dist/src/auth/strategies/cookie.js +84 -0
  36. package/dist/src/auth/strategies/cookie.js.map +1 -0
  37. package/dist/src/auth/strategies/custom-loader.d.ts +17 -0
  38. package/dist/src/auth/strategies/custom-loader.js +37 -0
  39. package/dist/src/auth/strategies/custom-loader.js.map +1 -0
  40. package/dist/src/auth/strategies/header.d.ts +35 -0
  41. package/dist/src/auth/strategies/header.js +42 -0
  42. package/dist/src/auth/strategies/header.js.map +1 -0
  43. package/dist/src/auth/strategies/jwks.d.ts +38 -0
  44. package/dist/src/auth/strategies/jwks.js +86 -0
  45. package/dist/src/auth/strategies/jwks.js.map +1 -0
  46. package/dist/src/auth/strategies/jwt-secret.d.ts +25 -0
  47. package/dist/src/auth/strategies/jwt-secret.js +82 -0
  48. package/dist/src/auth/strategies/jwt-secret.js.map +1 -0
  49. package/dist/src/auth/strategies/jwt-secret.test.d.ts +6 -0
  50. package/dist/src/auth/strategies/jwt-secret.test.js +75 -0
  51. package/dist/src/auth/strategies/jwt-secret.test.js.map +1 -0
  52. package/dist/src/auth/strategies/none.d.ts +37 -0
  53. package/dist/src/auth/strategies/none.js +31 -0
  54. package/dist/src/auth/strategies/none.js.map +1 -0
  55. package/dist/src/auth/strategies/oidc.d.ts +48 -0
  56. package/dist/src/auth/strategies/oidc.integration.test.d.ts +6 -0
  57. package/dist/src/auth/strategies/oidc.integration.test.js +290 -0
  58. package/dist/src/auth/strategies/oidc.integration.test.js.map +1 -0
  59. package/dist/src/auth/strategies/oidc.js +284 -0
  60. package/dist/src/auth/strategies/oidc.js.map +1 -0
  61. package/dist/src/auth/strategies/oidc.test.d.ts +6 -0
  62. package/dist/src/auth/strategies/oidc.test.js +111 -0
  63. package/dist/src/auth/strategies/oidc.test.js.map +1 -0
  64. package/dist/src/auth/strategies/strategies.test.d.ts +6 -0
  65. package/dist/src/auth/strategies/strategies.test.js +190 -0
  66. package/dist/src/auth/strategies/strategies.test.js.map +1 -0
  67. package/dist/src/auth/types.d.ts +95 -0
  68. package/dist/src/auth/types.js +7 -0
  69. package/dist/src/auth/types.js.map +1 -0
  70. package/dist/src/index.d.ts +2 -0
  71. package/dist/src/index.js +2 -0
  72. package/dist/src/index.js.map +1 -1
  73. package/dist/src/routes/session-resolver.d.ts +10 -10
  74. package/dist/src/routes/session-resolver.js +33 -17
  75. package/dist/src/routes/session-resolver.js.map +1 -1
  76. package/dist/src/routes/session-resolver.test.js +121 -80
  77. package/dist/src/routes/session-resolver.test.js.map +1 -1
  78. package/dist/src/session/drizzle-session-store.d.ts +8 -12
  79. package/dist/src/session/drizzle-session-store.js +38 -37
  80. package/dist/src/session/drizzle-session-store.js.map +1 -1
  81. package/dist/src/session/store.d.ts +5 -5
  82. package/dist/src/session/store.js +54 -38
  83. package/dist/src/session/store.js.map +1 -1
  84. package/dist/tsconfig.tsbuildinfo +1 -1
  85. package/package.json +9 -4
@@ -0,0 +1,284 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * OidcAuthStrategy — full OAuth2.1/OIDC dance via `openid-client`.
8
+ *
9
+ * Handles:
10
+ * - Discovery against the IdP's `.well-known/openid-configuration`
11
+ * - Authorization-code + PKCE login flow at `/auth/login`
12
+ * - Callback at `/auth/callback` — exchanges code for tokens, verifies
13
+ * the id_token, and issues an HMAC-signed session cookie
14
+ * - Per-request verification of the session cookie (via JWKS for the
15
+ * session JWT — actually HMAC since we sign it ourselves)
16
+ * - Optional `authorize(claims)` rule for group/role gating
17
+ *
18
+ * Covers Okta, Azure AD, Google Workspace, Auth0 OIDC, OneLogin,
19
+ * Authentik, Keycloak — anything OIDC-compliant.
20
+ */
21
+ import { Router } from 'express';
22
+ import rateLimit from 'express-rate-limit';
23
+ import * as oidc from 'openid-client';
24
+ import { SignJWT, jwtVerify } from 'jose';
25
+ const STATE_COOKIE = 'amodal_oauth_state';
26
+ const STATE_TTL_SECONDS = 5 * 60;
27
+ export class OidcAuthStrategy {
28
+ name = 'oidc';
29
+ opts;
30
+ sessionKey;
31
+ discoveryPromise = null;
32
+ constructor(options) {
33
+ this.opts = {
34
+ issuer: options.issuer,
35
+ clientId: options.clientId,
36
+ clientSecret: options.clientSecret,
37
+ callbackUrl: options.callbackUrl,
38
+ scopes: options.scopes ?? ['openid', 'email', 'profile'],
39
+ sessionSecret: options.sessionSecret,
40
+ cookieName: options.cookieName ?? 'amodal_session',
41
+ sessionTtlSeconds: options.sessionTtlSeconds ?? 24 * 3600,
42
+ authMethod: options.authMethod ?? 'oidc',
43
+ allowInsecureRequests: options.allowInsecureRequests ?? false,
44
+ };
45
+ if (options.authorize)
46
+ this.opts.authorize = options.authorize;
47
+ this.sessionKey = new TextEncoder().encode(options.sessionSecret);
48
+ }
49
+ valid(req) {
50
+ return readCookie(req, this.opts.cookieName) !== null;
51
+ }
52
+ async authenticate(req) {
53
+ const cookie = readCookie(req, this.opts.cookieName);
54
+ if (!cookie)
55
+ return { kind: 'rejected', reason: 'No session cookie' };
56
+ let payload;
57
+ try {
58
+ const result = await jwtVerify(cookie, this.sessionKey, {
59
+ algorithms: ['HS256'],
60
+ issuer: 'amodal-runtime',
61
+ });
62
+ payload = result.payload;
63
+ }
64
+ catch (err) {
65
+ const detail = err instanceof Error ? err.message : String(err);
66
+ return { kind: 'rejected', reason: `Session JWT invalid: ${detail}` };
67
+ }
68
+ const session = {
69
+ user: {
70
+ id: typeof payload['sub'] === 'string' ? payload['sub'] : '',
71
+ ...(typeof payload['email'] === 'string' ? { email: payload['email'] } : {}),
72
+ ...(typeof payload['name'] === 'string' ? { name: payload['name'] } : {}),
73
+ },
74
+ method: this.opts.authMethod,
75
+ claims: readClaims(payload['claims']),
76
+ };
77
+ return { kind: 'authenticated', session };
78
+ }
79
+ routes() {
80
+ const router = Router();
81
+ // Rate-limit the OAuth-dance endpoints. Both `/auth/login` and
82
+ // `/auth/callback` perform authorization side-effects (issuing
83
+ // signed cookies, exchanging codes with the IdP) and would
84
+ // otherwise be vulnerable to scrape / discovery flooding /
85
+ // credential-stuffing-adjacent abuse. Logout doesn't need to be
86
+ // rate-limited — it only clears a cookie.
87
+ const authLimiter = rateLimit({
88
+ windowMs: 15 * 60 * 1000,
89
+ max: 60,
90
+ standardHeaders: true,
91
+ legacyHeaders: false,
92
+ });
93
+ router.get('/auth/login', authLimiter, (req, res) => {
94
+ void this.handleLogin(req, res);
95
+ });
96
+ router.get('/auth/callback', authLimiter, (req, res) => {
97
+ void this.handleCallback(req, res);
98
+ });
99
+ router.post('/auth/logout', (_req, res) => {
100
+ clearCookie(res, this.opts.cookieName);
101
+ res.json({ ok: true });
102
+ });
103
+ return router;
104
+ }
105
+ // ---- internals ----------------------------------------------------------
106
+ async config() {
107
+ if (!this.discoveryPromise) {
108
+ this.discoveryPromise = oidc.discovery(new URL(this.opts.issuer), this.opts.clientId, this.opts.clientSecret, undefined, this.opts.allowInsecureRequests
109
+ ? { execute: [oidc.allowInsecureRequests] }
110
+ : undefined);
111
+ }
112
+ return this.discoveryPromise;
113
+ }
114
+ async handleLogin(req, res) {
115
+ try {
116
+ const config = await this.config();
117
+ const codeVerifier = oidc.randomPKCECodeVerifier();
118
+ const codeChallenge = await oidc.calculatePKCECodeChallenge(codeVerifier);
119
+ const state = oidc.randomState();
120
+ const returnTo = typeof req.query['return_to'] === 'string' ? req.query['return_to'] : '/';
121
+ // Stash state + codeVerifier + return_to in a short-lived signed cookie
122
+ // so the callback can recover them without server-side state.
123
+ const stateCookie = await new SignJWT({
124
+ state,
125
+ codeVerifier,
126
+ returnTo,
127
+ })
128
+ .setProtectedHeader({ alg: 'HS256' })
129
+ .setIssuer('amodal-runtime')
130
+ .setIssuedAt()
131
+ .setExpirationTime(`${STATE_TTL_SECONDS}s`)
132
+ .sign(this.sessionKey);
133
+ setCookie(res, STATE_COOKIE, stateCookie, STATE_TTL_SECONDS);
134
+ const url = oidc.buildAuthorizationUrl(config, {
135
+ redirect_uri: this.opts.callbackUrl,
136
+ scope: this.opts.scopes.join(' '),
137
+ code_challenge: codeChallenge,
138
+ code_challenge_method: 'S256',
139
+ state,
140
+ });
141
+ res.redirect(url.href);
142
+ }
143
+ catch (err) {
144
+ const message = err instanceof Error ? err.message : String(err);
145
+ res.status(500).json({ error: { code: 'OIDC_LOGIN_FAILED', message } });
146
+ }
147
+ }
148
+ async handleCallback(req, res) {
149
+ try {
150
+ const stateCookie = readCookie(req, STATE_COOKIE);
151
+ if (!stateCookie) {
152
+ res.status(400).json({ error: { code: 'NO_STATE', message: 'OAuth state cookie missing' } });
153
+ return;
154
+ }
155
+ let stateClaims;
156
+ try {
157
+ const verified = await jwtVerify(stateCookie, this.sessionKey, {
158
+ algorithms: ['HS256'],
159
+ issuer: 'amodal-runtime',
160
+ });
161
+ stateClaims = verified.payload;
162
+ }
163
+ catch (err) {
164
+ const detail = err instanceof Error ? err.message : String(err);
165
+ res.status(400).json({ error: { code: 'BAD_STATE', message: `OAuth state invalid: ${detail}` } });
166
+ return;
167
+ }
168
+ clearCookie(res, STATE_COOKIE);
169
+ const expectedState = typeof stateClaims['state'] === 'string' ? stateClaims['state'] : '';
170
+ const codeVerifier = typeof stateClaims['codeVerifier'] === 'string' ? stateClaims['codeVerifier'] : '';
171
+ const returnTo = typeof stateClaims['returnTo'] === 'string' ? stateClaims['returnTo'] : '/';
172
+ if (!expectedState || !codeVerifier) {
173
+ res.status(400).json({ error: { code: 'BAD_STATE', message: 'State payload malformed' } });
174
+ return;
175
+ }
176
+ const config = await this.config();
177
+ const fullUrl = new URL(req.originalUrl, `${req.protocol}://${req.get('host') ?? 'localhost'}`);
178
+ const tokens = await oidc.authorizationCodeGrant(config, fullUrl, {
179
+ pkceCodeVerifier: codeVerifier,
180
+ expectedState,
181
+ });
182
+ const claims = tokens.claims();
183
+ if (!claims) {
184
+ res.status(401).json({ error: { code: 'NO_CLAIMS', message: 'id_token missing claims' } });
185
+ return;
186
+ }
187
+ const user = await this.resolveUser(claims);
188
+ if (!user) {
189
+ res.status(403).json({ error: { code: 'FORBIDDEN', message: 'User not authorized for this agent' } });
190
+ return;
191
+ }
192
+ const sessionJwt = await new SignJWT({
193
+ sub: user.id,
194
+ ...(user.email ? { email: user.email } : {}),
195
+ ...(user.name ? { name: user.name } : {}),
196
+ claims: { ...claims },
197
+ })
198
+ .setProtectedHeader({ alg: 'HS256' })
199
+ .setIssuer('amodal-runtime')
200
+ .setSubject(user.id)
201
+ .setIssuedAt()
202
+ .setExpirationTime(`${this.opts.sessionTtlSeconds}s`)
203
+ .sign(this.sessionKey);
204
+ setCookie(res, this.opts.cookieName, sessionJwt, this.opts.sessionTtlSeconds);
205
+ res.redirect(returnTo);
206
+ }
207
+ catch (err) {
208
+ const message = err instanceof Error ? err.message : String(err);
209
+ res.status(500).json({ error: { code: 'OIDC_CALLBACK_FAILED', message } });
210
+ }
211
+ }
212
+ async resolveUser(claims) {
213
+ if (this.opts.authorize) {
214
+ const result = await this.opts.authorize(claims);
215
+ return result;
216
+ }
217
+ const id = typeof claims['sub'] === 'string' ? claims['sub'] : null;
218
+ if (!id)
219
+ return null;
220
+ const out = { id };
221
+ if (typeof claims['email'] === 'string')
222
+ out.email = claims['email'];
223
+ if (typeof claims['name'] === 'string')
224
+ out.name = claims['name'];
225
+ return out;
226
+ }
227
+ }
228
+ /**
229
+ * Defensive narrowing for the `claims` blob we round-trip through the
230
+ * session JWT. Anything other than a plain object yields an empty
231
+ * record; avoids a blanket `as Record<string, unknown>` cast on data
232
+ * that started life as JSON.
233
+ */
234
+ function readClaims(raw) {
235
+ if (raw == null || typeof raw !== 'object' || Array.isArray(raw))
236
+ return {};
237
+ const out = {};
238
+ for (const [k, v] of Object.entries(raw))
239
+ out[k] = v;
240
+ return out;
241
+ }
242
+ function readCookie(req, name) {
243
+ const header = req.headers['cookie'];
244
+ if (typeof header !== 'string' || header.length === 0)
245
+ return null;
246
+ for (const pair of header.split(';')) {
247
+ const trimmed = pair.trim();
248
+ const eq = trimmed.indexOf('=');
249
+ if (eq === -1)
250
+ continue;
251
+ if (trimmed.slice(0, eq) !== name)
252
+ continue;
253
+ const value = trimmed.slice(eq + 1);
254
+ return value.length > 0 ? decodeURIComponent(value) : null;
255
+ }
256
+ return null;
257
+ }
258
+ function setCookie(res, name, value, ttlSeconds) {
259
+ const parts = [
260
+ `${name}=${encodeURIComponent(value)}`,
261
+ 'Path=/',
262
+ `Max-Age=${ttlSeconds}`,
263
+ 'HttpOnly',
264
+ 'Secure',
265
+ 'SameSite=Lax',
266
+ ];
267
+ appendSetCookie(res, parts.join('; '));
268
+ }
269
+ function clearCookie(res, name) {
270
+ appendSetCookie(res, `${name}=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax`);
271
+ }
272
+ function appendSetCookie(res, value) {
273
+ const existing = res.getHeader('Set-Cookie');
274
+ if (existing == null) {
275
+ res.setHeader('Set-Cookie', value);
276
+ return;
277
+ }
278
+ if (Array.isArray(existing)) {
279
+ res.setHeader('Set-Cookie', [...existing, value]);
280
+ return;
281
+ }
282
+ res.setHeader('Set-Cookie', [String(existing), value]);
283
+ }
284
+ //# sourceMappingURL=oidc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc.js","sourceRoot":"","sources":["../../../../src/auth/strategies/oidc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * OIDC strategy tests focus on the bits we own: cookie verification on
8
+ * subsequent requests, route mounting, logout. The OAuth dance itself
9
+ * (login redirect, callback exchange) is delegated to `openid-client`
10
+ * and exercised at the boundary by mocking that module.
11
+ */
12
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
13
+ import { SignJWT } from 'jose';
14
+ import express from 'express';
15
+ import supertest from 'supertest';
16
+ const oidcMocks = {
17
+ discovery: vi.fn(),
18
+ randomPKCECodeVerifier: vi.fn(() => 'pkce-verifier'),
19
+ calculatePKCECodeChallenge: vi.fn(() => Promise.resolve('pkce-challenge')),
20
+ randomState: vi.fn(() => 'state-abc'),
21
+ buildAuthorizationUrl: vi.fn((_cfg, params) => {
22
+ const u = new URL('https://acme.okta.com/oauth2/v1/authorize');
23
+ for (const [k, v] of Object.entries(params))
24
+ u.searchParams.set(k, String(v));
25
+ return u;
26
+ }),
27
+ authorizationCodeGrant: vi.fn(),
28
+ };
29
+ vi.mock('openid-client', () => oidcMocks);
30
+ const { OidcAuthStrategy } = await import('./oidc.js');
31
+ const SESSION_SECRET = 'a-secret-of-at-least-16-chars';
32
+ const KEY = new TextEncoder().encode(SESSION_SECRET);
33
+ function reqWith(cookie) {
34
+ return { headers: cookie ? { cookie } : {} };
35
+ }
36
+ async function mintSession(payload) {
37
+ return new SignJWT(payload)
38
+ .setProtectedHeader({ alg: 'HS256' })
39
+ .setIssuer('amodal-runtime')
40
+ .setSubject('u-1')
41
+ .setIssuedAt()
42
+ .setExpirationTime('1h')
43
+ .sign(KEY);
44
+ }
45
+ function newStrategy() {
46
+ return new OidcAuthStrategy({
47
+ issuer: 'https://acme.okta.com',
48
+ clientId: 'abc',
49
+ clientSecret: 'shh',
50
+ callbackUrl: 'https://agent.example.com/auth/callback',
51
+ sessionSecret: SESSION_SECRET,
52
+ });
53
+ }
54
+ describe('OidcAuthStrategy — cookie verification', () => {
55
+ beforeEach(() => {
56
+ Object.values(oidcMocks).forEach((m) => {
57
+ if (typeof m.mockReset === 'function') {
58
+ m.mockReset();
59
+ }
60
+ });
61
+ });
62
+ it('valid() is false without the session cookie', () => {
63
+ const s = newStrategy();
64
+ expect(s.valid(reqWith())).toBe(false);
65
+ });
66
+ it('authenticates with a valid session cookie', async () => {
67
+ const token = await mintSession({ sub: 'u-1', email: 'a@b', name: 'Alice', claims: { groups: ['eng'] } });
68
+ const s = newStrategy();
69
+ const r = await s.authenticate(reqWith(`amodal_session=${token}`));
70
+ expect(r.kind).toBe('authenticated');
71
+ if (r.kind === 'authenticated') {
72
+ expect(r.session.user).toEqual({ id: 'u-1', email: 'a@b', name: 'Alice' });
73
+ expect(r.session.method).toBe('oidc');
74
+ expect(r.session.claims).toEqual({ groups: ['eng'] });
75
+ }
76
+ });
77
+ it('rejects a session cookie signed with the wrong secret', async () => {
78
+ const token = await new SignJWT({ sub: 'u' })
79
+ .setProtectedHeader({ alg: 'HS256' })
80
+ .setIssuer('amodal-runtime')
81
+ .setIssuedAt()
82
+ .setExpirationTime('1h')
83
+ .sign(new TextEncoder().encode('different-secret-of-16-chars'));
84
+ const s = newStrategy();
85
+ const r = await s.authenticate(reqWith(`amodal_session=${token}`));
86
+ expect(r.kind).toBe('rejected');
87
+ });
88
+ });
89
+ describe('OidcAuthStrategy — routes', () => {
90
+ it('GET /auth/login redirects to the IdP authorize URL', async () => {
91
+ const s = newStrategy();
92
+ const app = express();
93
+ app.use(s.routes());
94
+ const res = await supertest(app).get('/auth/login');
95
+ expect(res.status).toBe(302);
96
+ expect(res.headers['location']).toContain('https://acme.okta.com/oauth2/v1/authorize');
97
+ expect(res.headers['location']).toContain('state=state-abc');
98
+ // Sets the OAuth state cookie for the callback to recover
99
+ expect(res.headers['set-cookie']?.[0]).toMatch(/amodal_oauth_state=/);
100
+ });
101
+ it('POST /auth/logout clears the session cookie', async () => {
102
+ const s = newStrategy();
103
+ const app = express();
104
+ app.use(s.routes());
105
+ const res = await supertest(app).post('/auth/logout');
106
+ expect(res.status).toBe(200);
107
+ expect(res.body).toEqual({ ok: true });
108
+ expect(res.headers['set-cookie']?.[0]).toMatch(/amodal_session=; .*Max-Age=0/);
109
+ });
110
+ });
111
+ //# sourceMappingURL=oidc.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc.test.js","sourceRoot":"","sources":["../../../../src/auth/strategies/oidc.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ import { ApiKeyAuthStrategy } from './api-key.js';
8
+ import { HeaderAuthStrategy } from './header.js';
9
+ import { NoneAuthStrategy } from './none.js';
10
+ const jwtVerifyMock = vi.fn();
11
+ const createRemoteJWKSetMock = vi.fn(() => () => Promise.resolve({}));
12
+ vi.mock('jose', () => ({
13
+ jwtVerify: jwtVerifyMock,
14
+ createRemoteJWKSet: createRemoteJWKSetMock,
15
+ }));
16
+ const { JwksAuthStrategy } = await import('./jwks.js');
17
+ const { CookieSessionStrategy } = await import('./cookie.js');
18
+ function reqAuth(value) {
19
+ return { headers: value === null ? {} : { authorization: value } };
20
+ }
21
+ function reqHeaders(headers) {
22
+ return { headers };
23
+ }
24
+ describe('JwksAuthStrategy', () => {
25
+ beforeEach(() => {
26
+ jwtVerifyMock.mockReset();
27
+ createRemoteJWKSetMock.mockClear();
28
+ });
29
+ it('valid() is false when there is no Bearer header', () => {
30
+ const s = new JwksAuthStrategy({ jwksUrl: 'https://example.com/.well-known/jwks.json' });
31
+ expect(s.valid(reqAuth(null))).toBe(false);
32
+ });
33
+ it('valid() is false on ak_-prefixed tokens (lets ApiKeyAuthStrategy handle)', () => {
34
+ const s = new JwksAuthStrategy({ jwksUrl: 'https://example.com/.well-known/jwks.json' });
35
+ expect(s.valid(reqAuth('Bearer ak_xyz'))).toBe(false);
36
+ });
37
+ it('valid() is true on a 3-segment Bearer token', () => {
38
+ const s = new JwksAuthStrategy({ jwksUrl: 'https://example.com/.well-known/jwks.json' });
39
+ expect(s.valid(reqAuth('Bearer aa.bb.cc'))).toBe(true);
40
+ });
41
+ it('rejects with reason when jwtVerify rejects', async () => {
42
+ jwtVerifyMock.mockRejectedValue(new Error('signature verification failed'));
43
+ const s = new JwksAuthStrategy({ jwksUrl: 'https://example.com/.well-known/jwks.json' });
44
+ const r = await s.authenticate(reqAuth('Bearer aa.bb.cc'));
45
+ expect(r.kind).toBe('rejected');
46
+ if (r.kind === 'rejected') {
47
+ expect(r.reason).toMatch(/signature verification failed/);
48
+ }
49
+ });
50
+ it('builds an authenticated session from the verified payload', async () => {
51
+ jwtVerifyMock.mockResolvedValue({
52
+ payload: { sub: 'user-42', email: 'user@example.com', agent_id: 'agent-1', scope_id: 'tenant-7' },
53
+ });
54
+ const s = new JwksAuthStrategy({ jwksUrl: 'https://example.com/.well-known/jwks.json' });
55
+ const r = await s.authenticate(reqAuth('Bearer aa.bb.cc'));
56
+ expect(r.kind).toBe('authenticated');
57
+ if (r.kind === 'authenticated') {
58
+ expect(r.session.user).toEqual({ id: 'user-42', email: 'user@example.com' });
59
+ expect(r.session.agentId).toBe('agent-1');
60
+ expect(r.session.scopeId).toBe('tenant-7');
61
+ expect(r.session.method).toBe('jwks');
62
+ }
63
+ });
64
+ it('passes jwksOptions through to createRemoteJWKSet', () => {
65
+ new JwksAuthStrategy({
66
+ jwksUrl: 'https://example.com/.well-known/jwks.json',
67
+ jwksOptions: { cooldownDuration: 30_000 },
68
+ });
69
+ expect(createRemoteJWKSetMock).toHaveBeenCalledWith(expect.any(URL), expect.objectContaining({ cooldownDuration: 30_000 }));
70
+ });
71
+ });
72
+ describe('ApiKeyAuthStrategy', () => {
73
+ it('valid() is false without ak_ prefix', () => {
74
+ const s = new ApiKeyAuthStrategy({ validate: () => Promise.resolve(null) });
75
+ expect(s.valid(reqAuth('Bearer aa.bb.cc'))).toBe(false);
76
+ expect(s.valid(reqAuth(null))).toBe(false);
77
+ });
78
+ it('valid() is true on ak_ Bearer', () => {
79
+ const s = new ApiKeyAuthStrategy({ validate: () => Promise.resolve(null) });
80
+ expect(s.valid(reqAuth('Bearer ak_abc'))).toBe(true);
81
+ });
82
+ it('rejects when validate returns null', async () => {
83
+ const s = new ApiKeyAuthStrategy({ validate: () => Promise.resolve(null) });
84
+ const r = await s.authenticate(reqAuth('Bearer ak_bad'));
85
+ expect(r.kind).toBe('rejected');
86
+ });
87
+ it('authenticates when validate returns a result', async () => {
88
+ const s = new ApiKeyAuthStrategy({
89
+ validate: () => Promise.resolve({ userId: 'u1', email: 'a@b', agentId: 'agent-1' }),
90
+ });
91
+ const r = await s.authenticate(reqAuth('Bearer ak_good'));
92
+ expect(r.kind).toBe('authenticated');
93
+ if (r.kind === 'authenticated') {
94
+ expect(r.session.user).toEqual({ id: 'u1', email: 'a@b' });
95
+ expect(r.session.agentId).toBe('agent-1');
96
+ expect(r.session.method).toBe('api-key');
97
+ }
98
+ });
99
+ it('LRU cache hits skip the validate callback', async () => {
100
+ const validate = vi.fn(() => Promise.resolve({ userId: 'u1' }));
101
+ const s = new ApiKeyAuthStrategy({ validate, cacheMaxEntries: 100 });
102
+ await s.authenticate(reqAuth('Bearer ak_x'));
103
+ await s.authenticate(reqAuth('Bearer ak_x'));
104
+ expect(validate).toHaveBeenCalledTimes(1);
105
+ });
106
+ it('LRU evicts oldest when cap is hit', async () => {
107
+ const validate = vi.fn(() => Promise.resolve({ userId: 'u' }));
108
+ const s = new ApiKeyAuthStrategy({ validate, cacheMaxEntries: 2 });
109
+ await s.authenticate(reqAuth('Bearer ak_a'));
110
+ await s.authenticate(reqAuth('Bearer ak_b'));
111
+ await s.authenticate(reqAuth('Bearer ak_c')); // evicts ak_a
112
+ await s.authenticate(reqAuth('Bearer ak_a')); // miss → revalidate
113
+ expect(validate).toHaveBeenCalledTimes(4);
114
+ });
115
+ });
116
+ describe('HeaderAuthStrategy', () => {
117
+ it('valid() is false without the configured header', () => {
118
+ const s = new HeaderAuthStrategy({});
119
+ expect(s.valid(reqHeaders({}))).toBe(false);
120
+ });
121
+ it('reads from default X-Auth-User', async () => {
122
+ const s = new HeaderAuthStrategy({});
123
+ const r = await s.authenticate(reqHeaders({ 'x-auth-user': 'u-1' }));
124
+ expect(r.kind).toBe('authenticated');
125
+ if (r.kind === 'authenticated') {
126
+ expect(r.session.user.id).toBe('u-1');
127
+ expect(r.session.scopeId).toBe('u-1');
128
+ }
129
+ });
130
+ it('honors custom header name (case-insensitive)', async () => {
131
+ const s = new HeaderAuthStrategy({ headerName: 'X-Subject' });
132
+ const r = await s.authenticate(reqHeaders({ 'x-subject': 'u-2' }));
133
+ expect(r.kind).toBe('authenticated');
134
+ });
135
+ });
136
+ describe('NoneAuthStrategy', () => {
137
+ it('valid() is always true (chain stops here)', () => {
138
+ const s = new NoneAuthStrategy();
139
+ expect(s.valid({})).toBe(true);
140
+ });
141
+ it('produces a synthetic anonymous session', async () => {
142
+ const s = new NoneAuthStrategy();
143
+ const r = await s.authenticate({});
144
+ expect(r.kind).toBe('authenticated');
145
+ if (r.kind === 'authenticated') {
146
+ expect(r.session.user.id).toBe('anonymous');
147
+ expect(r.session.method).toBe('none');
148
+ }
149
+ });
150
+ it('honors a custom userId', async () => {
151
+ const s = new NoneAuthStrategy({ userId: 'guest' });
152
+ const r = await s.authenticate({});
153
+ if (r.kind === 'authenticated') {
154
+ expect(r.session.user.id).toBe('guest');
155
+ }
156
+ });
157
+ });
158
+ describe('CookieSessionStrategy', () => {
159
+ beforeEach(() => {
160
+ jwtVerifyMock.mockReset();
161
+ });
162
+ it('valid() is false when the cookie is absent', () => {
163
+ const s = new CookieSessionStrategy({ jwksUrl: 'https://example.com/jwks' });
164
+ expect(s.valid(reqHeaders({}))).toBe(false);
165
+ });
166
+ it('valid() is true when the configured cookie is present', () => {
167
+ const s = new CookieSessionStrategy({ jwksUrl: 'https://example.com/jwks' });
168
+ expect(s.valid(reqHeaders({ cookie: 'amodal_session=abc.def.ghi; other=x' }))).toBe(true);
169
+ });
170
+ it('verifies the cookie JWT and builds a session', async () => {
171
+ jwtVerifyMock.mockResolvedValue({ payload: { sub: 'cu-1', email: 'c@x' } });
172
+ const s = new CookieSessionStrategy({ jwksUrl: 'https://example.com/jwks' });
173
+ const r = await s.authenticate(reqHeaders({ cookie: 'amodal_session=aa.bb.cc' }));
174
+ expect(r.kind).toBe('authenticated');
175
+ if (r.kind === 'authenticated') {
176
+ expect(r.session.user).toEqual({ id: 'cu-1', email: 'c@x' });
177
+ expect(r.session.method).toBe('cookie');
178
+ }
179
+ });
180
+ it('rejects when JWT verify fails', async () => {
181
+ jwtVerifyMock.mockRejectedValue(new Error('expired'));
182
+ const s = new CookieSessionStrategy({ jwksUrl: 'https://example.com/jwks' });
183
+ const r = await s.authenticate(reqHeaders({ cookie: 'amodal_session=aa.bb.cc' }));
184
+ expect(r.kind).toBe('rejected');
185
+ if (r.kind === 'rejected') {
186
+ expect(r.reason).toMatch(/expired/);
187
+ }
188
+ });
189
+ });
190
+ //# sourceMappingURL=strategies.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategies.test.js","sourceRoot":"","sources":["../../../../src/auth/strategies/strategies.test.ts"],"names":[],"mappings":"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"}