@amodalai/runtime 0.3.70 → 0.3.72
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.
- package/dist/src/agent/local-server.js +7 -0
- package/dist/src/agent/local-server.js.map +1 -1
- package/dist/src/agent/local-server.test.js +96 -1
- package/dist/src/agent/local-server.test.js.map +1 -1
- package/dist/src/agent/routes/logs.d.ts +12 -0
- package/dist/src/agent/routes/logs.js +46 -0
- package/dist/src/agent/routes/logs.js.map +1 -0
- package/dist/src/agent/snapshot-server.js +4 -0
- package/dist/src/agent/snapshot-server.js.map +1 -1
- package/dist/src/auth/__fixtures__/custom-strategy.d.ts +8 -0
- package/dist/src/auth/__fixtures__/custom-strategy.js +27 -0
- package/dist/src/auth/__fixtures__/custom-strategy.js.map +1 -0
- package/dist/src/auth/compose.d.ts +55 -0
- package/dist/src/auth/compose.js +142 -0
- package/dist/src/auth/compose.js.map +1 -0
- package/dist/src/auth/compose.test.d.ts +6 -0
- package/dist/src/auth/compose.test.js +159 -0
- package/dist/src/auth/compose.test.js.map +1 -0
- package/dist/src/auth/config.d.ts +261 -0
- package/dist/src/auth/config.js +107 -0
- package/dist/src/auth/config.js.map +1 -0
- package/dist/src/auth/config.test.d.ts +6 -0
- package/dist/src/auth/config.test.js +85 -0
- package/dist/src/auth/config.test.js.map +1 -0
- package/dist/src/auth/factory.d.ts +19 -0
- package/dist/src/auth/factory.js +57 -0
- package/dist/src/auth/factory.js.map +1 -0
- package/dist/src/auth/factory.test.d.ts +6 -0
- package/dist/src/auth/factory.test.js +60 -0
- package/dist/src/auth/factory.test.js.map +1 -0
- package/dist/src/auth/index.d.ts +48 -0
- package/dist/src/auth/index.js +19 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/strategies/amodal.d.ts +36 -0
- package/dist/src/auth/strategies/amodal.js +28 -0
- package/dist/src/auth/strategies/amodal.js.map +1 -0
- package/dist/src/auth/strategies/api-key.d.ts +41 -0
- package/dist/src/auth/strategies/api-key.js +63 -0
- package/dist/src/auth/strategies/api-key.js.map +1 -0
- package/dist/src/auth/strategies/auth-modes.integration.test.d.ts +6 -0
- package/dist/src/auth/strategies/auth-modes.integration.test.js +363 -0
- package/dist/src/auth/strategies/auth-modes.integration.test.js.map +1 -0
- package/dist/src/auth/strategies/cookie.d.ts +41 -0
- package/dist/src/auth/strategies/cookie.js +84 -0
- package/dist/src/auth/strategies/cookie.js.map +1 -0
- package/dist/src/auth/strategies/custom-loader.d.ts +17 -0
- package/dist/src/auth/strategies/custom-loader.js +37 -0
- package/dist/src/auth/strategies/custom-loader.js.map +1 -0
- package/dist/src/auth/strategies/header.d.ts +35 -0
- package/dist/src/auth/strategies/header.js +42 -0
- package/dist/src/auth/strategies/header.js.map +1 -0
- package/dist/src/auth/strategies/jwks.d.ts +38 -0
- package/dist/src/auth/strategies/jwks.js +86 -0
- package/dist/src/auth/strategies/jwks.js.map +1 -0
- package/dist/src/auth/strategies/jwt-secret.d.ts +25 -0
- package/dist/src/auth/strategies/jwt-secret.js +82 -0
- package/dist/src/auth/strategies/jwt-secret.js.map +1 -0
- package/dist/src/auth/strategies/jwt-secret.test.d.ts +6 -0
- package/dist/src/auth/strategies/jwt-secret.test.js +75 -0
- package/dist/src/auth/strategies/jwt-secret.test.js.map +1 -0
- package/dist/src/auth/strategies/none.d.ts +37 -0
- package/dist/src/auth/strategies/none.js +31 -0
- package/dist/src/auth/strategies/none.js.map +1 -0
- package/dist/src/auth/strategies/oidc.d.ts +48 -0
- package/dist/src/auth/strategies/oidc.integration.test.d.ts +6 -0
- package/dist/src/auth/strategies/oidc.integration.test.js +290 -0
- package/dist/src/auth/strategies/oidc.integration.test.js.map +1 -0
- package/dist/src/auth/strategies/oidc.js +284 -0
- package/dist/src/auth/strategies/oidc.js.map +1 -0
- package/dist/src/auth/strategies/oidc.test.d.ts +6 -0
- package/dist/src/auth/strategies/oidc.test.js +111 -0
- package/dist/src/auth/strategies/oidc.test.js.map +1 -0
- package/dist/src/auth/strategies/strategies.test.d.ts +6 -0
- package/dist/src/auth/strategies/strategies.test.js +190 -0
- package/dist/src/auth/strategies/strategies.test.js.map +1 -0
- package/dist/src/auth/types.d.ts +95 -0
- package/dist/src/auth/types.js +7 -0
- package/dist/src/auth/types.js.map +1 -0
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/logger.d.ts +4 -3
- package/dist/src/logger.js +2 -1
- package/dist/src/logger.js.map +1 -1
- package/dist/src/logger.test.js +22 -1
- package/dist/src/logger.test.js.map +1 -1
- package/dist/src/runtime-log-store.d.ts +37 -0
- package/dist/src/runtime-log-store.js +132 -0
- package/dist/src/runtime-log-store.js.map +1 -0
- package/dist/src/server.js +7 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +18 -7
- package/dist/src/session/manager.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -4
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Config-driven auth — discriminated union the agent owner sets in
|
|
8
|
+
* `amodal.json` (or whatever surfaces the runtime through). The host
|
|
9
|
+
* resolves any `env:` references against its secrets store before
|
|
10
|
+
* passing the config to `createAuthStrategy(config)`.
|
|
11
|
+
*
|
|
12
|
+
* {
|
|
13
|
+
* "auth": {
|
|
14
|
+
* "type": "oidc",
|
|
15
|
+
* "issuer": "https://acme.okta.com",
|
|
16
|
+
* ...
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
const NoneAuthConfigSchema = z.object({
|
|
22
|
+
type: z.literal('none'),
|
|
23
|
+
/** Synthesized user id for the anonymous session. Defaults to `'anonymous'`. */
|
|
24
|
+
userId: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
const JwtSecretAuthConfigSchema = z.object({
|
|
27
|
+
type: z.literal('jwt_secret'),
|
|
28
|
+
/** Shared HMAC secret used to verify incoming JWTs. */
|
|
29
|
+
secret: z.string().min(1),
|
|
30
|
+
/** HMAC algorithm. Defaults to HS256. */
|
|
31
|
+
algorithm: z.enum(['HS256', 'HS384', 'HS512']).optional(),
|
|
32
|
+
/** Expected `iss` claim. Optional. */
|
|
33
|
+
issuer: z.string().optional(),
|
|
34
|
+
/** Expected `aud` claim. Optional. */
|
|
35
|
+
audience: z.string().optional(),
|
|
36
|
+
});
|
|
37
|
+
const OidcAuthConfigSchema = z.object({
|
|
38
|
+
type: z.literal('oidc'),
|
|
39
|
+
/** OIDC issuer URL — the IdP's discovery base (`https://acme.okta.com`). */
|
|
40
|
+
issuer: z.string().url(),
|
|
41
|
+
clientId: z.string().min(1),
|
|
42
|
+
clientSecret: z.string().min(1),
|
|
43
|
+
/** Where the IdP redirects back after login. Must be registered with the IdP. */
|
|
44
|
+
callbackUrl: z.string().url(),
|
|
45
|
+
/** OAuth scopes to request. Defaults to `['openid', 'email', 'profile']`. */
|
|
46
|
+
scopes: z.array(z.string()).optional(),
|
|
47
|
+
/**
|
|
48
|
+
* HMAC secret used to sign the session JWT stored in the runtime's cookie.
|
|
49
|
+
* Distinct from the IdP — this is purely the runtime's session signing key.
|
|
50
|
+
*/
|
|
51
|
+
sessionSecret: z.string().min(16),
|
|
52
|
+
/** Cookie name for the session JWT. Defaults to `'amodal_session'`. */
|
|
53
|
+
cookieName: z.string().optional(),
|
|
54
|
+
/** Session TTL in seconds. Defaults to 24h. */
|
|
55
|
+
sessionTtlSeconds: z.number().int().positive().optional(),
|
|
56
|
+
});
|
|
57
|
+
const HeaderAuthConfigSchema = z.object({
|
|
58
|
+
type: z.literal('header'),
|
|
59
|
+
headerName: z.string().optional(),
|
|
60
|
+
});
|
|
61
|
+
const CustomAuthConfigSchema = z.object({
|
|
62
|
+
type: z.literal('custom'),
|
|
63
|
+
/**
|
|
64
|
+
* Module path (relative to the agent's runtime directory) whose default
|
|
65
|
+
* export implements the `AuthStrategy` interface. The runtime imports
|
|
66
|
+
* this file at startup and validates the shape.
|
|
67
|
+
*/
|
|
68
|
+
module: z.string().min(1),
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* "Sign in with Amodal" — verifies platform JWTs minted by the Amodal
|
|
72
|
+
* cloud (or any self-hosted Amodal-equivalent platform). Agent owners on
|
|
73
|
+
* the Amodal cloud need zero config; the cloud's deploy pipeline injects
|
|
74
|
+
* the JWKS URL via the `AMODAL_JWKS_URL` env var. Self-hosters override
|
|
75
|
+
* via the optional `jwksUrl` field.
|
|
76
|
+
*/
|
|
77
|
+
const AmodalAuthConfigSchema = z.object({
|
|
78
|
+
type: z.literal('amodal'),
|
|
79
|
+
jwksUrl: z.string().url().optional(),
|
|
80
|
+
issuer: z.string().optional(),
|
|
81
|
+
});
|
|
82
|
+
export const AuthConfigSchema = z.discriminatedUnion('type', [
|
|
83
|
+
NoneAuthConfigSchema,
|
|
84
|
+
JwtSecretAuthConfigSchema,
|
|
85
|
+
OidcAuthConfigSchema,
|
|
86
|
+
HeaderAuthConfigSchema,
|
|
87
|
+
CustomAuthConfigSchema,
|
|
88
|
+
AmodalAuthConfigSchema,
|
|
89
|
+
]);
|
|
90
|
+
/**
|
|
91
|
+
* Parse + validate an `auth` block from agent config. Throws a clear
|
|
92
|
+
* error on any malformed shape so misconfiguration fails at startup
|
|
93
|
+
* rather than at first request. Pass `undefined` / missing config to
|
|
94
|
+
* default to `{type: 'none'}`.
|
|
95
|
+
*/
|
|
96
|
+
export function parseAuthConfig(raw) {
|
|
97
|
+
if (raw == null)
|
|
98
|
+
return { type: 'none' };
|
|
99
|
+
const parsed = AuthConfigSchema.safeParse(raw);
|
|
100
|
+
if (!parsed.success) {
|
|
101
|
+
throw new Error(`Invalid auth config: ${parsed.error.errors
|
|
102
|
+
.map((e) => `${e.path.join('.') || '<root>'}: ${e.message}`)
|
|
103
|
+
.join('; ')}`);
|
|
104
|
+
}
|
|
105
|
+
return parsed.data;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/auth/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACvB,gFAAgF;IAChF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IAC7B,uDAAuD;IACvD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,yCAAyC;IACzC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;IACzD,sCAAsC;IACtC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,sCAAsC;IACtC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACvB,4EAA4E;IAC5E,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACxB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,iFAAiF;IACjF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC7B,6EAA6E;IAC7E,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACtC;;;OAGG;IACH,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACjC,uEAAuE;IACvE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,+CAA+C;IAC/C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC1D,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACzB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACzB;;;;OAIG;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC1B,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACzB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACpC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAC3D,oBAAoB;IACpB,yBAAyB;IACzB,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;IACtB,sBAAsB;CACvB,CAAC,CAAC;AAUH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,EAAC,IAAI,EAAE,MAAM,EAAC,CAAC;IACvC,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,CAAC,KAAK,CAAC,MAAM;aACxC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,EAAE,CAChB,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { parseAuthConfig } from './config.js';
|
|
8
|
+
describe('parseAuthConfig', () => {
|
|
9
|
+
it('defaults to {type: none} when undefined', () => {
|
|
10
|
+
expect(parseAuthConfig(undefined)).toEqual({ type: 'none' });
|
|
11
|
+
expect(parseAuthConfig(null)).toEqual({ type: 'none' });
|
|
12
|
+
});
|
|
13
|
+
it('accepts a valid none config', () => {
|
|
14
|
+
expect(parseAuthConfig({ type: 'none' })).toEqual({ type: 'none' });
|
|
15
|
+
expect(parseAuthConfig({ type: 'none', userId: 'guest' })).toEqual({
|
|
16
|
+
type: 'none',
|
|
17
|
+
userId: 'guest',
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
it('accepts a valid jwt_secret config', () => {
|
|
21
|
+
const cfg = parseAuthConfig({ type: 'jwt_secret', secret: 'sssh' });
|
|
22
|
+
expect(cfg.type).toBe('jwt_secret');
|
|
23
|
+
});
|
|
24
|
+
it('rejects jwt_secret without secret', () => {
|
|
25
|
+
expect(() => parseAuthConfig({ type: 'jwt_secret' })).toThrow(/secret/);
|
|
26
|
+
});
|
|
27
|
+
it('accepts a valid oidc config', () => {
|
|
28
|
+
const cfg = parseAuthConfig({
|
|
29
|
+
type: 'oidc',
|
|
30
|
+
issuer: 'https://acme.okta.com',
|
|
31
|
+
clientId: 'abc',
|
|
32
|
+
clientSecret: 'shh',
|
|
33
|
+
callbackUrl: 'https://agent.example.com/auth/callback',
|
|
34
|
+
sessionSecret: 'a-secret-of-at-least-16-chars-long',
|
|
35
|
+
});
|
|
36
|
+
expect(cfg.type).toBe('oidc');
|
|
37
|
+
});
|
|
38
|
+
it('rejects oidc with a non-URL issuer', () => {
|
|
39
|
+
expect(() => parseAuthConfig({
|
|
40
|
+
type: 'oidc',
|
|
41
|
+
issuer: 'not-a-url',
|
|
42
|
+
clientId: 'a',
|
|
43
|
+
clientSecret: 'b',
|
|
44
|
+
callbackUrl: 'https://x.test/cb',
|
|
45
|
+
sessionSecret: 'a-secret-of-at-least-16-chars-long',
|
|
46
|
+
})).toThrow(/url/i);
|
|
47
|
+
});
|
|
48
|
+
it('rejects oidc with too-short sessionSecret', () => {
|
|
49
|
+
expect(() => parseAuthConfig({
|
|
50
|
+
type: 'oidc',
|
|
51
|
+
issuer: 'https://x.test',
|
|
52
|
+
clientId: 'a',
|
|
53
|
+
clientSecret: 'b',
|
|
54
|
+
callbackUrl: 'https://x.test/cb',
|
|
55
|
+
sessionSecret: 'short',
|
|
56
|
+
})).toThrow(/sessionSecret/);
|
|
57
|
+
});
|
|
58
|
+
it('accepts custom config with module path', () => {
|
|
59
|
+
expect(parseAuthConfig({ type: 'custom', module: './my-auth.ts' })).toEqual({
|
|
60
|
+
type: 'custom',
|
|
61
|
+
module: './my-auth.ts',
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('accepts a bare amodal config (zero-config sign-in with Amodal)', () => {
|
|
65
|
+
expect(parseAuthConfig({ type: 'amodal' })).toEqual({ type: 'amodal' });
|
|
66
|
+
});
|
|
67
|
+
it('accepts amodal config with optional jwksUrl + issuer overrides', () => {
|
|
68
|
+
expect(parseAuthConfig({
|
|
69
|
+
type: 'amodal',
|
|
70
|
+
jwksUrl: 'https://example.com/.well-known/jwks.json',
|
|
71
|
+
issuer: 'custom-issuer',
|
|
72
|
+
})).toEqual({
|
|
73
|
+
type: 'amodal',
|
|
74
|
+
jwksUrl: 'https://example.com/.well-known/jwks.json',
|
|
75
|
+
issuer: 'custom-issuer',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
it('rejects amodal config with a non-URL jwksUrl', () => {
|
|
79
|
+
expect(() => parseAuthConfig({ type: 'amodal', jwksUrl: 'not-a-url' })).toThrow(/url/i);
|
|
80
|
+
});
|
|
81
|
+
it('rejects unknown type', () => {
|
|
82
|
+
expect(() => parseAuthConfig({ type: 'wat', whatever: true })).toThrow(/Invalid auth config/);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../../src/auth/config.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Translate a parsed `AuthConfig` into an `AuthStrategy` instance.
|
|
8
|
+
* The host (cloud's runtime, self-hoster's app) calls this once at boot
|
|
9
|
+
* with the agent's already-resolved config.
|
|
10
|
+
*/
|
|
11
|
+
import type { AuthConfig } from './config.js';
|
|
12
|
+
import type { AuthStrategy } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Build an `AuthStrategy` from a validated `AuthConfig`. For `'custom'`
|
|
15
|
+
* type configs this is async (the user's module is dynamically imported);
|
|
16
|
+
* everything else resolves synchronously but the function is uniformly
|
|
17
|
+
* async for ergonomics.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createAuthStrategy(config: AuthConfig): Promise<AuthStrategy>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { NoneAuthStrategy } from './strategies/none.js';
|
|
7
|
+
import { HeaderAuthStrategy } from './strategies/header.js';
|
|
8
|
+
import { JwtSecretAuthStrategy } from './strategies/jwt-secret.js';
|
|
9
|
+
import { OidcAuthStrategy } from './strategies/oidc.js';
|
|
10
|
+
import { AmodalAuthStrategy } from './strategies/amodal.js';
|
|
11
|
+
import { loadCustomStrategy } from './strategies/custom-loader.js';
|
|
12
|
+
/**
|
|
13
|
+
* Build an `AuthStrategy` from a validated `AuthConfig`. For `'custom'`
|
|
14
|
+
* type configs this is async (the user's module is dynamically imported);
|
|
15
|
+
* everything else resolves synchronously but the function is uniformly
|
|
16
|
+
* async for ergonomics.
|
|
17
|
+
*/
|
|
18
|
+
export async function createAuthStrategy(config) {
|
|
19
|
+
switch (config.type) {
|
|
20
|
+
case 'none':
|
|
21
|
+
return new NoneAuthStrategy(config.userId !== undefined ? { userId: config.userId } : {});
|
|
22
|
+
case 'header':
|
|
23
|
+
return new HeaderAuthStrategy(config.headerName !== undefined ? { headerName: config.headerName } : {});
|
|
24
|
+
case 'jwt_secret':
|
|
25
|
+
return new JwtSecretAuthStrategy({
|
|
26
|
+
secret: config.secret,
|
|
27
|
+
...(config.algorithm ? { algorithm: config.algorithm } : {}),
|
|
28
|
+
...(config.issuer ? { issuer: config.issuer } : {}),
|
|
29
|
+
...(config.audience ? { audience: config.audience } : {}),
|
|
30
|
+
});
|
|
31
|
+
case 'oidc':
|
|
32
|
+
return new OidcAuthStrategy({
|
|
33
|
+
issuer: config.issuer,
|
|
34
|
+
clientId: config.clientId,
|
|
35
|
+
clientSecret: config.clientSecret,
|
|
36
|
+
callbackUrl: config.callbackUrl,
|
|
37
|
+
...(config.scopes ? { scopes: config.scopes } : {}),
|
|
38
|
+
sessionSecret: config.sessionSecret,
|
|
39
|
+
...(config.cookieName ? { cookieName: config.cookieName } : {}),
|
|
40
|
+
...(config.sessionTtlSeconds
|
|
41
|
+
? { sessionTtlSeconds: config.sessionTtlSeconds }
|
|
42
|
+
: {}),
|
|
43
|
+
});
|
|
44
|
+
case 'custom':
|
|
45
|
+
return loadCustomStrategy(config.module);
|
|
46
|
+
case 'amodal':
|
|
47
|
+
return new AmodalAuthStrategy({
|
|
48
|
+
...(config.jwksUrl ? { jwksUrl: config.jwksUrl } : {}),
|
|
49
|
+
...(config.issuer ? { issuer: config.issuer } : {}),
|
|
50
|
+
});
|
|
51
|
+
default: {
|
|
52
|
+
const _exhaustive = config;
|
|
53
|
+
throw new Error(`Unknown auth config type: ${JSON.stringify(_exhaustive)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/auth/factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
7
|
+
import { createAuthStrategy } from './factory.js';
|
|
8
|
+
vi.mock('openid-client', () => ({
|
|
9
|
+
// OidcAuthStrategy doesn't call discovery() at construction — it
|
|
10
|
+
// lazy-init's on first request. The factory only needs the module
|
|
11
|
+
// to import cleanly.
|
|
12
|
+
discovery: vi.fn(),
|
|
13
|
+
randomPKCECodeVerifier: vi.fn(),
|
|
14
|
+
calculatePKCECodeChallenge: vi.fn(),
|
|
15
|
+
randomState: vi.fn(),
|
|
16
|
+
buildAuthorizationUrl: vi.fn(),
|
|
17
|
+
authorizationCodeGrant: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
describe('createAuthStrategy', () => {
|
|
20
|
+
it('returns NoneAuthStrategy for type: none', async () => {
|
|
21
|
+
const s = await createAuthStrategy({ type: 'none' });
|
|
22
|
+
expect(s.name).toBe('none');
|
|
23
|
+
});
|
|
24
|
+
it('returns HeaderAuthStrategy for type: header', async () => {
|
|
25
|
+
const s = await createAuthStrategy({ type: 'header' });
|
|
26
|
+
expect(s.name).toBe('header');
|
|
27
|
+
});
|
|
28
|
+
it('returns JwtSecretAuthStrategy for type: jwt_secret', async () => {
|
|
29
|
+
const s = await createAuthStrategy({ type: 'jwt_secret', secret: 'shh' });
|
|
30
|
+
expect(s.name).toBe('jwt-secret');
|
|
31
|
+
});
|
|
32
|
+
it('returns OidcAuthStrategy for type: oidc', async () => {
|
|
33
|
+
const s = await createAuthStrategy({
|
|
34
|
+
type: 'oidc',
|
|
35
|
+
issuer: 'https://acme.okta.com',
|
|
36
|
+
clientId: 'abc',
|
|
37
|
+
clientSecret: 'shh',
|
|
38
|
+
callbackUrl: 'https://agent.example.com/auth/callback',
|
|
39
|
+
sessionSecret: 'a-secret-of-at-least-16-chars-long',
|
|
40
|
+
});
|
|
41
|
+
expect(s.name).toBe('oidc');
|
|
42
|
+
expect(s.routes).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
it('returns AmodalAuthStrategy for type: amodal', async () => {
|
|
45
|
+
const s = await createAuthStrategy({ type: 'amodal' });
|
|
46
|
+
expect(s.name).toBe('amodal');
|
|
47
|
+
});
|
|
48
|
+
it('passes overrides through to AmodalAuthStrategy', async () => {
|
|
49
|
+
const s = await createAuthStrategy({
|
|
50
|
+
type: 'amodal',
|
|
51
|
+
jwksUrl: 'https://example.com/.well-known/jwks.json',
|
|
52
|
+
issuer: 'custom-issuer',
|
|
53
|
+
});
|
|
54
|
+
expect(s.name).toBe('amodal');
|
|
55
|
+
});
|
|
56
|
+
it('throws when custom module fails to load', async () => {
|
|
57
|
+
await expect(createAuthStrategy({ type: 'custom', module: '/nonexistent/path.ts' })).rejects.toThrow(/Failed to load custom auth strategy/);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=factory.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.test.js","sourceRoot":"","sources":["../../../src/auth/factory.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Pluggable auth — the Warden-shape primitive layer.
|
|
8
|
+
*
|
|
9
|
+
* import {authenticate, JwksAuthStrategy} from '@amodalai/runtime/auth';
|
|
10
|
+
*
|
|
11
|
+
* const platformJwt = new JwksAuthStrategy({jwksUrl: '...'});
|
|
12
|
+
* const endUser = new OidcStrategy({...});
|
|
13
|
+
*
|
|
14
|
+
* // Per-route strategy lists. Studio routes only ever see platformJwt;
|
|
15
|
+
* // chat routes accept either platformJwt (Studio testing) OR endUser.
|
|
16
|
+
* app.use('/api/files', authenticate(platformJwt));
|
|
17
|
+
* app.use('/chat', authenticate(platformJwt, endUser));
|
|
18
|
+
*
|
|
19
|
+
* // Mount any login-flow routes a strategy registers.
|
|
20
|
+
* app.use(authRouter(endUser));
|
|
21
|
+
*
|
|
22
|
+
* Single-winner chain semantics: first strategy whose `valid(req)` is
|
|
23
|
+
* true and `authenticate(req)` returns `'authenticated'` wins. No
|
|
24
|
+
* merging, no implicit identity overrides.
|
|
25
|
+
*/
|
|
26
|
+
export type { AuthStrategy, AuthResult, AuthSession } from './types.js';
|
|
27
|
+
export { authenticate, authRouter, getSession } from './compose.js';
|
|
28
|
+
export type { AuthLogEvent, AuthenticateOptions } from './compose.js';
|
|
29
|
+
export { JwksAuthStrategy } from './strategies/jwks.js';
|
|
30
|
+
export type { JwksAuthStrategyOptions } from './strategies/jwks.js';
|
|
31
|
+
export { ApiKeyAuthStrategy } from './strategies/api-key.js';
|
|
32
|
+
export type { ApiKeyAuthStrategyOptions, ApiKeyValidationResult, } from './strategies/api-key.js';
|
|
33
|
+
export { HeaderAuthStrategy } from './strategies/header.js';
|
|
34
|
+
export type { HeaderAuthStrategyOptions } from './strategies/header.js';
|
|
35
|
+
export { NoneAuthStrategy } from './strategies/none.js';
|
|
36
|
+
export type { NoneAuthStrategyOptions } from './strategies/none.js';
|
|
37
|
+
export { CookieSessionStrategy } from './strategies/cookie.js';
|
|
38
|
+
export type { CookieSessionStrategyOptions } from './strategies/cookie.js';
|
|
39
|
+
export { JwtSecretAuthStrategy } from './strategies/jwt-secret.js';
|
|
40
|
+
export type { JwtSecretAuthStrategyOptions } from './strategies/jwt-secret.js';
|
|
41
|
+
export { OidcAuthStrategy } from './strategies/oidc.js';
|
|
42
|
+
export type { OidcAuthStrategyOptions } from './strategies/oidc.js';
|
|
43
|
+
export { AmodalAuthStrategy } from './strategies/amodal.js';
|
|
44
|
+
export type { AmodalAuthStrategyOptions } from './strategies/amodal.js';
|
|
45
|
+
export { AuthConfigSchema, parseAuthConfig } from './config.js';
|
|
46
|
+
export type { AuthConfig, NoneAuthConfig, JwtSecretAuthConfig, OidcAuthConfig, HeaderAuthConfig, CustomAuthConfig, AmodalAuthConfig, } from './config.js';
|
|
47
|
+
export { createAuthStrategy } from './factory.js';
|
|
48
|
+
export { loadCustomStrategy } from './strategies/custom-loader.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
export { authenticate, authRouter, getSession } from './compose.js';
|
|
7
|
+
export { JwksAuthStrategy } from './strategies/jwks.js';
|
|
8
|
+
export { ApiKeyAuthStrategy } from './strategies/api-key.js';
|
|
9
|
+
export { HeaderAuthStrategy } from './strategies/header.js';
|
|
10
|
+
export { NoneAuthStrategy } from './strategies/none.js';
|
|
11
|
+
export { CookieSessionStrategy } from './strategies/cookie.js';
|
|
12
|
+
export { JwtSecretAuthStrategy } from './strategies/jwt-secret.js';
|
|
13
|
+
export { OidcAuthStrategy } from './strategies/oidc.js';
|
|
14
|
+
export { AmodalAuthStrategy } from './strategies/amodal.js';
|
|
15
|
+
// Config-driven auth — `auth` field in `amodal.json` → AuthStrategy
|
|
16
|
+
export { AuthConfigSchema, parseAuthConfig } from './config.js';
|
|
17
|
+
export { createAuthStrategy } from './factory.js';
|
|
18
|
+
export { loadCustomStrategy } from './strategies/custom-loader.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,OAAO,EAAC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAC,MAAM,cAAc,CAAC;AAGlE,OAAO,EAAC,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAC,kBAAkB,EAAC,MAAM,yBAAyB,CAAC;AAM3D,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAC;AAG1D,OAAO,EAAC,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAC,qBAAqB,EAAC,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAC,qBAAqB,EAAC,MAAM,4BAA4B,CAAC;AAGjE,OAAO,EAAC,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAC;AAG1D,oEAAoE;AACpE,OAAO,EAAC,gBAAgB,EAAE,eAAe,EAAC,MAAM,aAAa,CAAC;AAU9D,OAAO,EAAC,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAChD,OAAO,EAAC,kBAAkB,EAAC,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* AmodalAuthStrategy — "Sign in with Amodal."
|
|
8
|
+
*
|
|
9
|
+
* The zero-config option for agent owners on the Amodal cloud. End users
|
|
10
|
+
* sign in with their existing Amodal account; the runtime verifies the
|
|
11
|
+
* platform-issued JWT against Amodal's JWKS. cf-worker handles the
|
|
12
|
+
* redirect-to-login dance upstream — by the time a request reaches the
|
|
13
|
+
* runtime, it carries a valid platform JWT in `Authorization: Bearer`.
|
|
14
|
+
*
|
|
15
|
+
* Internally just a `JwksAuthStrategy` preconfigured with Amodal's JWKS
|
|
16
|
+
* URL and issuer. Self-hosters running their own Amodal-equivalent
|
|
17
|
+
* platform can override both via constructor options.
|
|
18
|
+
*/
|
|
19
|
+
import type { Request } from 'express';
|
|
20
|
+
import type { AuthResult, AuthStrategy } from '../types.js';
|
|
21
|
+
export interface AmodalAuthStrategyOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Override the platform JWKS URL. Defaults to `AMODAL_JWKS_URL` env
|
|
24
|
+
* var, then to `https://api.amodalai.com/.well-known/jwks.json`.
|
|
25
|
+
*/
|
|
26
|
+
jwksUrl?: string;
|
|
27
|
+
/** Override the expected `iss` claim. Defaults to `'aitize-platform'`. */
|
|
28
|
+
issuer?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare class AmodalAuthStrategy implements AuthStrategy {
|
|
31
|
+
readonly name = "amodal";
|
|
32
|
+
private readonly inner;
|
|
33
|
+
constructor(options?: AmodalAuthStrategyOptions);
|
|
34
|
+
valid(req: Request): boolean;
|
|
35
|
+
authenticate(req: Request): Promise<AuthResult>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { JwksAuthStrategy } from './jwks.js';
|
|
7
|
+
const DEFAULT_AMODAL_JWKS_URL = 'https://api.amodalai.com/.well-known/jwks.json';
|
|
8
|
+
const DEFAULT_AMODAL_ISSUER = 'aitize-platform';
|
|
9
|
+
export class AmodalAuthStrategy {
|
|
10
|
+
name = 'amodal';
|
|
11
|
+
inner;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.inner = new JwksAuthStrategy({
|
|
14
|
+
jwksUrl: options.jwksUrl ??
|
|
15
|
+
process.env['AMODAL_JWKS_URL'] ??
|
|
16
|
+
DEFAULT_AMODAL_JWKS_URL,
|
|
17
|
+
issuer: options.issuer ?? DEFAULT_AMODAL_ISSUER,
|
|
18
|
+
authMethod: 'amodal',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
valid(req) {
|
|
22
|
+
return this.inner.valid(req);
|
|
23
|
+
}
|
|
24
|
+
authenticate(req) {
|
|
25
|
+
return this.inner.authenticate(req);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=amodal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amodal.js","sourceRoot":"","sources":["../../../../src/auth/strategies/amodal.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,OAAO,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAY3C,MAAM,uBAAuB,GAAG,gDAAgD,CAAC;AACjF,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AAEhD,MAAM,OAAO,kBAAkB;IACpB,IAAI,GAAG,QAAQ,CAAC;IACR,KAAK,CAAmB;IAEzC,YAAY,UAAqC,EAAE;QACjD,IAAI,CAAC,KAAK,GAAG,IAAI,gBAAgB,CAAC;YAChC,OAAO,EACL,OAAO,CAAC,OAAO;gBACf,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;gBAC9B,uBAAuB;YACzB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,qBAAqB;YAC/C,UAAU,EAAE,QAAQ;SACrB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAY;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,YAAY,CAAC,GAAY;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC;CACF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import type { Request } from 'express';
|
|
7
|
+
import type { AuthResult, AuthStrategy } from '../types.js';
|
|
8
|
+
export interface ApiKeyValidationResult {
|
|
9
|
+
/** Required — the principal id this key represents. */
|
|
10
|
+
userId: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
/** Optional — the agent this key is scoped to. */
|
|
13
|
+
agentId?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ApiKeyAuthStrategyOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Validate the API key. Return `null` for "not a valid key" (chain
|
|
18
|
+
* rejects with 401). Throw to signal an upstream error (callers can
|
|
19
|
+
* upgrade these to 502s by wrapping the strategy if desired).
|
|
20
|
+
*/
|
|
21
|
+
validate: (apiKey: string) => Promise<ApiKeyValidationResult | null>;
|
|
22
|
+
/** Cache TTL for successful validations (ms). Defaults to 5 minutes. */
|
|
23
|
+
cacheTtlMs?: number;
|
|
24
|
+
/** Cap on the LRU cache. Defaults to 10,000 entries. */
|
|
25
|
+
cacheMaxEntries?: number;
|
|
26
|
+
/** Token prefix this strategy matches. Defaults to `'ak_'`. */
|
|
27
|
+
keyPrefix?: string;
|
|
28
|
+
/** Override `AuthSession.method`. Defaults to `'api-key'`. */
|
|
29
|
+
authMethod?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class ApiKeyAuthStrategy implements AuthStrategy {
|
|
32
|
+
readonly name = "api-key";
|
|
33
|
+
private readonly validate;
|
|
34
|
+
private readonly keyPrefix;
|
|
35
|
+
private readonly authMethod;
|
|
36
|
+
private readonly cache;
|
|
37
|
+
constructor(options: ApiKeyAuthStrategyOptions);
|
|
38
|
+
valid(req: Request): boolean;
|
|
39
|
+
authenticate(req: Request): Promise<AuthResult>;
|
|
40
|
+
private buildSession;
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ApiKeyAuthStrategy — `ak_*` Bearer tokens, validated via a caller-
|
|
8
|
+
* supplied callback (cloud's `/api/me`, a self-hoster's own DB query,
|
|
9
|
+
* etc.). LRU-cached so repeated requests with the same key skip the
|
|
10
|
+
* validation round-trip.
|
|
11
|
+
*/
|
|
12
|
+
import { LRUCache } from 'lru-cache';
|
|
13
|
+
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
14
|
+
const DEFAULT_CACHE_MAX = 10_000;
|
|
15
|
+
export class ApiKeyAuthStrategy {
|
|
16
|
+
name = 'api-key';
|
|
17
|
+
validate;
|
|
18
|
+
keyPrefix;
|
|
19
|
+
authMethod;
|
|
20
|
+
cache;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.validate = options.validate;
|
|
23
|
+
this.keyPrefix = options.keyPrefix ?? 'ak_';
|
|
24
|
+
this.authMethod = options.authMethod ?? 'api-key';
|
|
25
|
+
this.cache = new LRUCache({
|
|
26
|
+
max: options.cacheMaxEntries ?? DEFAULT_CACHE_MAX,
|
|
27
|
+
ttl: options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
valid(req) {
|
|
31
|
+
const auth = req.headers['authorization'];
|
|
32
|
+
if (typeof auth !== 'string' || !auth.startsWith('Bearer '))
|
|
33
|
+
return false;
|
|
34
|
+
const token = auth.slice(7);
|
|
35
|
+
return token.startsWith(this.keyPrefix);
|
|
36
|
+
}
|
|
37
|
+
async authenticate(req) {
|
|
38
|
+
const auth = req.headers['authorization'];
|
|
39
|
+
if (typeof auth !== 'string')
|
|
40
|
+
return { kind: 'rejected', reason: 'No bearer token' };
|
|
41
|
+
const token = auth.slice(7);
|
|
42
|
+
const cached = this.cache.get(token);
|
|
43
|
+
if (cached) {
|
|
44
|
+
return { kind: 'authenticated', session: this.buildSession(cached) };
|
|
45
|
+
}
|
|
46
|
+
const result = await this.validate(token);
|
|
47
|
+
if (!result)
|
|
48
|
+
return { kind: 'rejected', reason: 'Invalid API key' };
|
|
49
|
+
this.cache.set(token, result);
|
|
50
|
+
return { kind: 'authenticated', session: this.buildSession(result) };
|
|
51
|
+
}
|
|
52
|
+
buildSession(result) {
|
|
53
|
+
return {
|
|
54
|
+
user: {
|
|
55
|
+
id: result.userId,
|
|
56
|
+
...(result.email ? { email: result.email } : {}),
|
|
57
|
+
},
|
|
58
|
+
method: this.authMethod,
|
|
59
|
+
...(result.agentId ? { agentId: result.agentId } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=api-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.js","sourceRoot":"","sources":["../../../../src/auth/strategies/api-key.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,OAAO,EAAC,QAAQ,EAAC,MAAM,WAAW,CAAC;AA6BnC,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAC3C,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC,MAAM,OAAO,kBAAkB;IACpB,IAAI,GAAG,SAAS,CAAC;IACT,QAAQ,CAAwC;IAChD,SAAS,CAAS;IAClB,UAAU,CAAS;IACnB,KAAK,CAA2C;IAEjE,YAAY,OAAkC;QAC5C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;QAC5C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,SAAS,CAAC;QAClD,IAAI,CAAC,KAAK,GAAG,IAAI,QAAQ,CAAiC;YACxD,GAAG,EAAE,OAAO,CAAC,eAAe,IAAI,iBAAiB;YACjD,GAAG,EAAE,OAAO,CAAC,UAAU,IAAI,oBAAoB;SAChD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAY;QAChB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC1C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,GAAY;QAC7B,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC1C,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAC,CAAC;QACnF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAE5B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,EAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAC,CAAC;QACrE,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM;YAAE,OAAO,EAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAC,CAAC;QAClE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC9B,OAAO,EAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAC,CAAC;IACrE,CAAC;IAEO,YAAY,CAAC,MAA8B;QACjD,OAAO;YACL,IAAI,EAAE;gBACJ,EAAE,EAAE,MAAM,CAAC,MAAM;gBACjB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/C;YACD,MAAM,EAAE,IAAI,CAAC,UAAU;YACvB,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACrD,CAAC;IACJ,CAAC;CACF"}
|