@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.
- 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 +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/routes/session-resolver.d.ts +10 -10
- package/dist/src/routes/session-resolver.js +33 -17
- package/dist/src/routes/session-resolver.js.map +1 -1
- package/dist/src/routes/session-resolver.test.js +121 -80
- package/dist/src/routes/session-resolver.test.js.map +1 -1
- package/dist/src/session/drizzle-session-store.d.ts +8 -12
- package/dist/src/session/drizzle-session-store.js +38 -37
- package/dist/src/session/drizzle-session-store.js.map +1 -1
- package/dist/src/session/store.d.ts +5 -5
- package/dist/src/session/store.js +54 -38
- package/dist/src/session/store.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -4
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* CookieSessionStrategy — reads a session JWT from a cookie and verifies
|
|
8
|
+
* it against a JWKS endpoint. Useful for SPA + runtime stacks that don't
|
|
9
|
+
* sit behind cf-worker (or any other Bearer-injecting proxy) and need
|
|
10
|
+
* cookie-based session continuity in the browser.
|
|
11
|
+
*
|
|
12
|
+
* The JWT contract is the same as `JwksAuthStrategy` — same JWKS, same
|
|
13
|
+
* issuer check, same claim shape. The only difference is where the token
|
|
14
|
+
* lives (cookie vs Authorization header).
|
|
15
|
+
*/
|
|
16
|
+
import { createRemoteJWKSet } from 'jose';
|
|
17
|
+
import type { Request } from 'express';
|
|
18
|
+
import type { AuthResult, AuthStrategy } from '../types.js';
|
|
19
|
+
export interface CookieSessionStrategyOptions {
|
|
20
|
+
/** Cookie name to read. Defaults to `'amodal_session'`. */
|
|
21
|
+
cookieName?: string;
|
|
22
|
+
/** JWKS endpoint URL — same shape as `JwksAuthStrategy`. */
|
|
23
|
+
jwksUrl: string;
|
|
24
|
+
/** Required `iss` claim. Defaults to no issuer check. */
|
|
25
|
+
issuer?: string;
|
|
26
|
+
/** Override `AuthSession.method`. Defaults to `'cookie'`. */
|
|
27
|
+
authMethod?: string;
|
|
28
|
+
/** Pass-through to `jose.createRemoteJWKSet`. */
|
|
29
|
+
jwksOptions?: Parameters<typeof createRemoteJWKSet>[1];
|
|
30
|
+
}
|
|
31
|
+
export declare class CookieSessionStrategy implements AuthStrategy {
|
|
32
|
+
readonly name = "cookie";
|
|
33
|
+
private readonly cookieName;
|
|
34
|
+
private readonly jwks;
|
|
35
|
+
private readonly issuer?;
|
|
36
|
+
private readonly authMethod;
|
|
37
|
+
constructor(options: CookieSessionStrategyOptions);
|
|
38
|
+
valid(req: Request): boolean;
|
|
39
|
+
authenticate(req: Request): Promise<AuthResult>;
|
|
40
|
+
private readCookie;
|
|
41
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* CookieSessionStrategy — reads a session JWT from a cookie and verifies
|
|
8
|
+
* it against a JWKS endpoint. Useful for SPA + runtime stacks that don't
|
|
9
|
+
* sit behind cf-worker (or any other Bearer-injecting proxy) and need
|
|
10
|
+
* cookie-based session continuity in the browser.
|
|
11
|
+
*
|
|
12
|
+
* The JWT contract is the same as `JwksAuthStrategy` — same JWKS, same
|
|
13
|
+
* issuer check, same claim shape. The only difference is where the token
|
|
14
|
+
* lives (cookie vs Authorization header).
|
|
15
|
+
*/
|
|
16
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
17
|
+
export class CookieSessionStrategy {
|
|
18
|
+
name = 'cookie';
|
|
19
|
+
cookieName;
|
|
20
|
+
jwks;
|
|
21
|
+
issuer;
|
|
22
|
+
authMethod;
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.cookieName = options.cookieName ?? 'amodal_session';
|
|
25
|
+
this.jwks = createRemoteJWKSet(new URL(options.jwksUrl), options.jwksOptions);
|
|
26
|
+
if (options.issuer !== undefined)
|
|
27
|
+
this.issuer = options.issuer;
|
|
28
|
+
this.authMethod = options.authMethod ?? 'cookie';
|
|
29
|
+
}
|
|
30
|
+
valid(req) {
|
|
31
|
+
return Boolean(this.readCookie(req));
|
|
32
|
+
}
|
|
33
|
+
async authenticate(req) {
|
|
34
|
+
const token = this.readCookie(req);
|
|
35
|
+
if (!token)
|
|
36
|
+
return { kind: 'rejected', reason: 'No session cookie' };
|
|
37
|
+
let payload;
|
|
38
|
+
try {
|
|
39
|
+
const result = await jwtVerify(token, this.jwks, this.issuer ? { issuer: this.issuer } : {});
|
|
40
|
+
payload = result.payload;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
44
|
+
return { kind: 'rejected', reason: `Cookie session JWT invalid: ${detail}` };
|
|
45
|
+
}
|
|
46
|
+
return { kind: 'authenticated', session: buildSession(payload, this.authMethod) };
|
|
47
|
+
}
|
|
48
|
+
readCookie(req) {
|
|
49
|
+
const header = req.headers['cookie'];
|
|
50
|
+
if (typeof header !== 'string' || header.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
for (const pair of header.split(';')) {
|
|
53
|
+
const trimmed = pair.trim();
|
|
54
|
+
const eq = trimmed.indexOf('=');
|
|
55
|
+
if (eq === -1)
|
|
56
|
+
continue;
|
|
57
|
+
const name = trimmed.slice(0, eq);
|
|
58
|
+
if (name !== this.cookieName)
|
|
59
|
+
continue;
|
|
60
|
+
const value = trimmed.slice(eq + 1);
|
|
61
|
+
return value.length > 0 ? decodeURIComponent(value) : null;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function buildSession(payload, method) {
|
|
67
|
+
const userId = typeof payload['user_id'] === 'string'
|
|
68
|
+
? payload['user_id']
|
|
69
|
+
: typeof payload['sub'] === 'string'
|
|
70
|
+
? payload['sub']
|
|
71
|
+
: '';
|
|
72
|
+
const session = { user: { id: userId }, method };
|
|
73
|
+
if (typeof payload['email'] === 'string')
|
|
74
|
+
session.user.email = payload['email'];
|
|
75
|
+
if (typeof payload['name'] === 'string')
|
|
76
|
+
session.user.name = payload['name'];
|
|
77
|
+
if (typeof payload['agent_id'] === 'string')
|
|
78
|
+
session.agentId = payload['agent_id'];
|
|
79
|
+
if (typeof payload['scope_id'] === 'string')
|
|
80
|
+
session.scopeId = payload['scope_id'];
|
|
81
|
+
session.claims = { ...payload };
|
|
82
|
+
return session;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=cookie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.js","sourceRoot":"","sources":["../../../../src/auth/strategies/cookie.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Load a custom AuthStrategy from a user-supplied module path. Used by
|
|
8
|
+
* `AuthConfig` `{type: 'custom', module: './path'}` to let agent owners
|
|
9
|
+
* drop in a verifier function for any auth system they already use
|
|
10
|
+
* (Auth0 SDK, Supabase getUser, internal verify endpoint, etc.).
|
|
11
|
+
*
|
|
12
|
+
* The module's default export must be an `AuthStrategy` (object with
|
|
13
|
+
* `name`, `valid`, `authenticate`). The shape is validated; a wrong-
|
|
14
|
+
* shape export throws at startup with a clear message.
|
|
15
|
+
*/
|
|
16
|
+
import type { AuthStrategy } from '../types.js';
|
|
17
|
+
export declare function loadCustomStrategy(modulePath: string): Promise<AuthStrategy>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
export async function loadCustomStrategy(modulePath) {
|
|
7
|
+
let mod;
|
|
8
|
+
try {
|
|
9
|
+
mod = await import(modulePath);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
13
|
+
throw new Error(`Failed to load custom auth strategy from '${modulePath}': ${detail}`, { cause: err });
|
|
14
|
+
}
|
|
15
|
+
if (typeof mod !== 'object' || mod === null) {
|
|
16
|
+
throw new Error(`Custom auth strategy at '${modulePath}' did not export anything`);
|
|
17
|
+
}
|
|
18
|
+
const candidate = 'default' in mod
|
|
19
|
+
? mod.default
|
|
20
|
+
: mod;
|
|
21
|
+
if (!isStrategy(candidate)) {
|
|
22
|
+
throw new Error(`Custom auth strategy at '${modulePath}' default export does not match the AuthStrategy shape ` +
|
|
23
|
+
`({name: string, valid(req): boolean, authenticate(req): Promise<AuthResult>})`);
|
|
24
|
+
}
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
function isStrategy(value) {
|
|
28
|
+
if (typeof value !== 'object' || value === null)
|
|
29
|
+
return false;
|
|
30
|
+
if (!('name' in value) || !('valid' in value) || !('authenticate' in value)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return (typeof value.name === 'string' &&
|
|
34
|
+
typeof value.valid === 'function' &&
|
|
35
|
+
typeof value.authenticate === 'function');
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=custom-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-loader.js","sourceRoot":"","sources":["../../../../src/auth/strategies/custom-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,UAAkB;IAElB,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,6CAA6C,UAAU,MAAM,MAAM,EAAE,EACrE,EAAC,KAAK,EAAE,GAAG,EAAC,CACb,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,4BAA4B,UAAU,2BAA2B,CAClE,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GACb,SAAS,IAAI,GAAG;QACd,CAAC,CAAE,GAA0B,CAAC,OAAO;QACrC,CAAC,CAAE,GAAe,CAAC;IACvB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,4BAA4B,UAAU,yDAAyD;YAC7F,+EAA+E,CAClF,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,cAAc,IAAI,KAAK,CAAC,EAAE,CAAC;QAC5E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,CACL,OAAQ,KAAyB,CAAC,IAAI,KAAK,QAAQ;QACnD,OAAQ,KAA0B,CAAC,KAAK,KAAK,UAAU;QACvD,OAAQ,KAAiC,CAAC,YAAY,KAAK,UAAU,CACtE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* HeaderAuthStrategy — reads the user identity from a configured request
|
|
8
|
+
* header (default `X-Auth-User`). For service-to-service calls behind a
|
|
9
|
+
* trusted proxy that authenticates upstream and writes the verified
|
|
10
|
+
* identity into a header.
|
|
11
|
+
*
|
|
12
|
+
* Security: never expose a route protected by this strategy directly to
|
|
13
|
+
* the public internet. Trusted-header strategies presume an upstream
|
|
14
|
+
* proxy strips the header on inbound requests and writes its own.
|
|
15
|
+
*/
|
|
16
|
+
import type { Request } from 'express';
|
|
17
|
+
import type { AuthResult, AuthStrategy } from '../types.js';
|
|
18
|
+
export interface HeaderAuthStrategyOptions {
|
|
19
|
+
/** Header name to read (case-insensitive). Defaults to `'X-Auth-User'`. */
|
|
20
|
+
headerName?: string;
|
|
21
|
+
/** Optional `agentId` to attach to the resulting session. */
|
|
22
|
+
agentId?: string;
|
|
23
|
+
/** Override `AuthSession.method`. Defaults to `'header'`. */
|
|
24
|
+
authMethod?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class HeaderAuthStrategy implements AuthStrategy {
|
|
27
|
+
readonly name = "header";
|
|
28
|
+
private readonly headerName;
|
|
29
|
+
private readonly agentId?;
|
|
30
|
+
private readonly authMethod;
|
|
31
|
+
constructor(options?: HeaderAuthStrategyOptions);
|
|
32
|
+
valid(req: Request): boolean;
|
|
33
|
+
authenticate(req: Request): Promise<AuthResult>;
|
|
34
|
+
private read;
|
|
35
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
export class HeaderAuthStrategy {
|
|
7
|
+
name = 'header';
|
|
8
|
+
headerName;
|
|
9
|
+
agentId;
|
|
10
|
+
authMethod;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.headerName = (options.headerName ?? 'X-Auth-User').toLowerCase();
|
|
13
|
+
if (options.agentId !== undefined)
|
|
14
|
+
this.agentId = options.agentId;
|
|
15
|
+
this.authMethod = options.authMethod ?? 'header';
|
|
16
|
+
}
|
|
17
|
+
valid(req) {
|
|
18
|
+
return Boolean(this.read(req));
|
|
19
|
+
}
|
|
20
|
+
async authenticate(req) {
|
|
21
|
+
const userId = this.read(req);
|
|
22
|
+
if (!userId)
|
|
23
|
+
return { kind: 'rejected', reason: 'Missing identity header' };
|
|
24
|
+
return {
|
|
25
|
+
kind: 'authenticated',
|
|
26
|
+
session: {
|
|
27
|
+
user: { id: userId },
|
|
28
|
+
method: this.authMethod,
|
|
29
|
+
...(this.agentId ? { agentId: this.agentId } : {}),
|
|
30
|
+
scopeId: userId,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
read(req) {
|
|
35
|
+
const value = req.headers[this.headerName];
|
|
36
|
+
const id = Array.isArray(value) ? value[0] : value;
|
|
37
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
38
|
+
return null;
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../../../src/auth/strategies/header.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,MAAM,OAAO,kBAAkB;IACpB,IAAI,GAAG,QAAQ,CAAC;IACR,UAAU,CAAS;IACnB,OAAO,CAAU;IACjB,UAAU,CAAS;IAEpC,YAAY,UAAqC,EAAE;QACjD,IAAI,CAAC,UAAU,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,aAAa,CAAC,CAAC,WAAW,EAAE,CAAC;QACtE,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAClE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,QAAQ,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,GAAY;QAChB,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,GAAY;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM;YAAE,OAAO,EAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,yBAAyB,EAAC,CAAC;QAC1E,OAAO;YACL,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE;gBACP,IAAI,EAAE,EAAC,EAAE,EAAE,MAAM,EAAC;gBAClB,MAAM,EAAE,IAAI,CAAC,UAAU;gBACvB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChD,OAAO,EAAE,MAAM;aAChB;SACF,CAAC;IACJ,CAAC;IAEO,IAAI,CAAC,GAAY;QACvB,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACnD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3D,OAAO,EAAE,CAAC;IACZ,CAAC;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* JwksAuthStrategy — verifies Bearer JWTs against a remote JWKS endpoint.
|
|
8
|
+
*
|
|
9
|
+
* Cloud uses this for cf-worker-injected platform JWTs; self-hosters can
|
|
10
|
+
* point it at any JWKS-publishing IdP (their own auth server, Auth0,
|
|
11
|
+
* Keycloak, etc.) when they want a stateless verifier-only flow.
|
|
12
|
+
*/
|
|
13
|
+
import { createRemoteJWKSet } from 'jose';
|
|
14
|
+
import type { Request } from 'express';
|
|
15
|
+
import type { AuthResult, AuthStrategy } from '../types.js';
|
|
16
|
+
export interface JwksAuthStrategyOptions {
|
|
17
|
+
/** URL of the JWKS endpoint (e.g. `https://api.example.com/.well-known/jwks.json`). */
|
|
18
|
+
jwksUrl: string;
|
|
19
|
+
/** Required `iss` claim. Defaults to no issuer check. */
|
|
20
|
+
issuer?: string;
|
|
21
|
+
/** Override the auth method label written to `AuthSession.method`. Defaults to `'jwks'`. */
|
|
22
|
+
authMethod?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Pass-through to `jose.createRemoteJWKSet` for tighter control over
|
|
25
|
+
* JWKS caching / cooldown / refresh. Useful for ops visibility into
|
|
26
|
+
* key rotation across multi-instance fleets.
|
|
27
|
+
*/
|
|
28
|
+
jwksOptions?: Parameters<typeof createRemoteJWKSet>[1];
|
|
29
|
+
}
|
|
30
|
+
export declare class JwksAuthStrategy implements AuthStrategy {
|
|
31
|
+
readonly name = "jwks";
|
|
32
|
+
private readonly jwks;
|
|
33
|
+
private readonly issuer?;
|
|
34
|
+
private readonly authMethod;
|
|
35
|
+
constructor(options: JwksAuthStrategyOptions);
|
|
36
|
+
valid(req: Request): boolean;
|
|
37
|
+
authenticate(req: Request): Promise<AuthResult>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* JwksAuthStrategy — verifies Bearer JWTs against a remote JWKS endpoint.
|
|
8
|
+
*
|
|
9
|
+
* Cloud uses this for cf-worker-injected platform JWTs; self-hosters can
|
|
10
|
+
* point it at any JWKS-publishing IdP (their own auth server, Auth0,
|
|
11
|
+
* Keycloak, etc.) when they want a stateless verifier-only flow.
|
|
12
|
+
*/
|
|
13
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
14
|
+
export class JwksAuthStrategy {
|
|
15
|
+
name = 'jwks';
|
|
16
|
+
jwks;
|
|
17
|
+
issuer;
|
|
18
|
+
authMethod;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.jwks = createRemoteJWKSet(new URL(options.jwksUrl), options.jwksOptions);
|
|
21
|
+
if (options.issuer !== undefined)
|
|
22
|
+
this.issuer = options.issuer;
|
|
23
|
+
this.authMethod = options.authMethod ?? 'jwks';
|
|
24
|
+
}
|
|
25
|
+
valid(req) {
|
|
26
|
+
const token = readBearer(req);
|
|
27
|
+
if (!token)
|
|
28
|
+
return false;
|
|
29
|
+
return looksLikeJwt(token);
|
|
30
|
+
}
|
|
31
|
+
async authenticate(req) {
|
|
32
|
+
const token = readBearer(req);
|
|
33
|
+
if (!token)
|
|
34
|
+
return { kind: 'rejected', reason: 'No bearer token' };
|
|
35
|
+
let payload;
|
|
36
|
+
try {
|
|
37
|
+
const result = await jwtVerify(token, this.jwks, this.issuer ? { issuer: this.issuer } : {});
|
|
38
|
+
payload = result.payload;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
42
|
+
return { kind: 'rejected', reason: `JWT verification failed: ${detail}` };
|
|
43
|
+
}
|
|
44
|
+
return { kind: 'authenticated', session: buildSession(payload, this.authMethod) };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function buildSession(payload, method) {
|
|
48
|
+
const userId = typeof payload['user_id'] === 'string'
|
|
49
|
+
? payload['user_id']
|
|
50
|
+
: typeof payload['sub'] === 'string'
|
|
51
|
+
? payload['sub']
|
|
52
|
+
: '';
|
|
53
|
+
const session = { user: { id: userId }, method };
|
|
54
|
+
if (typeof payload['email'] === 'string')
|
|
55
|
+
session.user.email = payload['email'];
|
|
56
|
+
if (typeof payload['name'] === 'string')
|
|
57
|
+
session.user.name = payload['name'];
|
|
58
|
+
if (typeof payload['agent_id'] === 'string') {
|
|
59
|
+
session.agentId = payload['agent_id'];
|
|
60
|
+
}
|
|
61
|
+
else if (typeof payload['app_id'] === 'string') {
|
|
62
|
+
session.agentId = payload['app_id'];
|
|
63
|
+
}
|
|
64
|
+
if (typeof payload['scope_id'] === 'string')
|
|
65
|
+
session.scopeId = payload['scope_id'];
|
|
66
|
+
// Stash the full payload under `claims` so callers can read provider-
|
|
67
|
+
// specific data (groups, custom roles, etc.) without us trying to
|
|
68
|
+
// narrow it.
|
|
69
|
+
session.claims = { ...payload };
|
|
70
|
+
return session;
|
|
71
|
+
}
|
|
72
|
+
function readBearer(req) {
|
|
73
|
+
const auth = req.headers['authorization'];
|
|
74
|
+
if (typeof auth !== 'string')
|
|
75
|
+
return null;
|
|
76
|
+
if (!auth.startsWith('Bearer '))
|
|
77
|
+
return null;
|
|
78
|
+
const token = auth.slice(7);
|
|
79
|
+
return token.length > 0 ? token : null;
|
|
80
|
+
}
|
|
81
|
+
function looksLikeJwt(token) {
|
|
82
|
+
if (token.startsWith('ak_'))
|
|
83
|
+
return false;
|
|
84
|
+
return token.split('.').length === 3;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=jwks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.js","sourceRoot":"","sources":["../../../../src/auth/strategies/jwks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 JwtSecretAuthStrategyOptions {
|
|
9
|
+
secret: string;
|
|
10
|
+
algorithm?: 'HS256' | 'HS384' | 'HS512';
|
|
11
|
+
issuer?: string;
|
|
12
|
+
audience?: string;
|
|
13
|
+
authMethod?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class JwtSecretAuthStrategy implements AuthStrategy {
|
|
16
|
+
readonly name = "jwt-secret";
|
|
17
|
+
private readonly secretKey;
|
|
18
|
+
private readonly algorithm;
|
|
19
|
+
private readonly issuer?;
|
|
20
|
+
private readonly audience?;
|
|
21
|
+
private readonly authMethod;
|
|
22
|
+
constructor(options: JwtSecretAuthStrategyOptions);
|
|
23
|
+
valid(req: Request): boolean;
|
|
24
|
+
authenticate(req: Request): Promise<AuthResult>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* JwtSecretAuthStrategy — verifies HMAC-signed Bearer JWTs against a
|
|
8
|
+
* shared secret. For ISV / server-mediated flows where another server
|
|
9
|
+
* (the agent owner's backend) mints JWTs and amodal verifies them with
|
|
10
|
+
* the same shared secret.
|
|
11
|
+
*/
|
|
12
|
+
import { jwtVerify } from 'jose';
|
|
13
|
+
export class JwtSecretAuthStrategy {
|
|
14
|
+
name = 'jwt-secret';
|
|
15
|
+
secretKey;
|
|
16
|
+
algorithm;
|
|
17
|
+
issuer;
|
|
18
|
+
audience;
|
|
19
|
+
authMethod;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.secretKey = new TextEncoder().encode(options.secret);
|
|
22
|
+
this.algorithm = options.algorithm ?? 'HS256';
|
|
23
|
+
if (options.issuer !== undefined)
|
|
24
|
+
this.issuer = options.issuer;
|
|
25
|
+
if (options.audience !== undefined)
|
|
26
|
+
this.audience = options.audience;
|
|
27
|
+
this.authMethod = options.authMethod ?? 'jwt-secret';
|
|
28
|
+
}
|
|
29
|
+
valid(req) {
|
|
30
|
+
const token = readBearer(req);
|
|
31
|
+
if (!token)
|
|
32
|
+
return false;
|
|
33
|
+
if (token.startsWith('ak_'))
|
|
34
|
+
return false;
|
|
35
|
+
return token.split('.').length === 3;
|
|
36
|
+
}
|
|
37
|
+
async authenticate(req) {
|
|
38
|
+
const token = readBearer(req);
|
|
39
|
+
if (!token)
|
|
40
|
+
return { kind: 'rejected', reason: 'No bearer token' };
|
|
41
|
+
let payload;
|
|
42
|
+
try {
|
|
43
|
+
const result = await jwtVerify(token, this.secretKey, {
|
|
44
|
+
algorithms: [this.algorithm],
|
|
45
|
+
...(this.issuer ? { issuer: this.issuer } : {}),
|
|
46
|
+
...(this.audience ? { audience: this.audience } : {}),
|
|
47
|
+
});
|
|
48
|
+
payload = result.payload;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
52
|
+
return { kind: 'rejected', reason: `JWT verification failed: ${detail}` };
|
|
53
|
+
}
|
|
54
|
+
return { kind: 'authenticated', session: buildSession(payload, this.authMethod) };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function buildSession(payload, method) {
|
|
58
|
+
const userId = typeof payload['user_id'] === 'string'
|
|
59
|
+
? payload['user_id']
|
|
60
|
+
: typeof payload['sub'] === 'string'
|
|
61
|
+
? payload['sub']
|
|
62
|
+
: '';
|
|
63
|
+
const session = { user: { id: userId }, method };
|
|
64
|
+
if (typeof payload['email'] === 'string')
|
|
65
|
+
session.user.email = payload['email'];
|
|
66
|
+
if (typeof payload['name'] === 'string')
|
|
67
|
+
session.user.name = payload['name'];
|
|
68
|
+
if (typeof payload['agent_id'] === 'string')
|
|
69
|
+
session.agentId = payload['agent_id'];
|
|
70
|
+
if (typeof payload['scope_id'] === 'string')
|
|
71
|
+
session.scopeId = payload['scope_id'];
|
|
72
|
+
session.claims = { ...payload };
|
|
73
|
+
return session;
|
|
74
|
+
}
|
|
75
|
+
function readBearer(req) {
|
|
76
|
+
const auth = req.headers['authorization'];
|
|
77
|
+
if (typeof auth !== 'string' || !auth.startsWith('Bearer '))
|
|
78
|
+
return null;
|
|
79
|
+
const token = auth.slice(7);
|
|
80
|
+
return token.length > 0 ? token : null;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=jwt-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-secret.js","sourceRoot":"","sources":["../../../../src/auth/strategies/jwt-secret.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { SignJWT } from 'jose';
|
|
8
|
+
import { JwtSecretAuthStrategy } from './jwt-secret.js';
|
|
9
|
+
const SECRET = 'a-secret-of-at-least-16-chars';
|
|
10
|
+
const KEY = new TextEncoder().encode(SECRET);
|
|
11
|
+
async function mintHs256(payload, { iss, aud } = {}) {
|
|
12
|
+
let jwt = new SignJWT(payload).setProtectedHeader({ alg: 'HS256' }).setIssuedAt().setExpirationTime('5m');
|
|
13
|
+
if (iss)
|
|
14
|
+
jwt = jwt.setIssuer(iss);
|
|
15
|
+
if (aud)
|
|
16
|
+
jwt = jwt.setAudience(aud);
|
|
17
|
+
return jwt.sign(KEY);
|
|
18
|
+
}
|
|
19
|
+
function reqWith(token) {
|
|
20
|
+
return {
|
|
21
|
+
headers: token === null ? {} : { authorization: `Bearer ${token}` },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
describe('JwtSecretAuthStrategy', () => {
|
|
25
|
+
it('valid() is false without a Bearer header', () => {
|
|
26
|
+
const s = new JwtSecretAuthStrategy({ secret: SECRET });
|
|
27
|
+
expect(s.valid(reqWith(null))).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
it('valid() is false on ak_-prefixed tokens', () => {
|
|
30
|
+
const s = new JwtSecretAuthStrategy({ secret: SECRET });
|
|
31
|
+
expect(s.valid(reqWith('ak_x'))).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('valid() is true on a 3-segment Bearer', () => {
|
|
34
|
+
const s = new JwtSecretAuthStrategy({ secret: SECRET });
|
|
35
|
+
expect(s.valid(reqWith('aa.bb.cc'))).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it('authenticates a JWT signed with the matching secret', async () => {
|
|
38
|
+
const token = await mintHs256({ sub: 'u-1', email: 'a@b' });
|
|
39
|
+
const s = new JwtSecretAuthStrategy({ secret: SECRET });
|
|
40
|
+
const r = await s.authenticate(reqWith(token));
|
|
41
|
+
expect(r.kind).toBe('authenticated');
|
|
42
|
+
if (r.kind === 'authenticated') {
|
|
43
|
+
expect(r.session.user).toEqual({ id: 'u-1', email: 'a@b' });
|
|
44
|
+
expect(r.session.method).toBe('jwt-secret');
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it('rejects a JWT signed with a different secret', async () => {
|
|
48
|
+
const token = await new SignJWT({ sub: 'u-1' })
|
|
49
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
50
|
+
.setIssuedAt()
|
|
51
|
+
.setExpirationTime('5m')
|
|
52
|
+
.sign(new TextEncoder().encode('different-secret-of-16-chars'));
|
|
53
|
+
const s = new JwtSecretAuthStrategy({ secret: SECRET });
|
|
54
|
+
const r = await s.authenticate(reqWith(token));
|
|
55
|
+
expect(r.kind).toBe('rejected');
|
|
56
|
+
});
|
|
57
|
+
it('enforces issuer when configured', async () => {
|
|
58
|
+
const goodToken = await mintHs256({ sub: 'u' }, { iss: 'expected-issuer' });
|
|
59
|
+
const badToken = await mintHs256({ sub: 'u' }, { iss: 'wrong-issuer' });
|
|
60
|
+
const s = new JwtSecretAuthStrategy({ secret: SECRET, issuer: 'expected-issuer' });
|
|
61
|
+
expect((await s.authenticate(reqWith(goodToken))).kind).toBe('authenticated');
|
|
62
|
+
expect((await s.authenticate(reqWith(badToken))).kind).toBe('rejected');
|
|
63
|
+
});
|
|
64
|
+
it('rejects an expired token', async () => {
|
|
65
|
+
const expired = await new SignJWT({ sub: 'u' })
|
|
66
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
67
|
+
.setIssuedAt(Math.floor(Date.now() / 1000) - 3600)
|
|
68
|
+
.setExpirationTime(Math.floor(Date.now() / 1000) - 60)
|
|
69
|
+
.sign(KEY);
|
|
70
|
+
const s = new JwtSecretAuthStrategy({ secret: SECRET });
|
|
71
|
+
const r = await s.authenticate(reqWith(expired));
|
|
72
|
+
expect(r.kind).toBe('rejected');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=jwt-secret.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-secret.test.js","sourceRoot":"","sources":["../../../../src/auth/strategies/jwt-secret.test.ts"],"names":[],"mappings":"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"}
|