@run402/functions 2.9.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/errors.d.ts +106 -0
- package/dist/auth/errors.d.ts.map +1 -0
- package/dist/auth/errors.js +205 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth/index.d.ts +112 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +585 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/types.d.ts +86 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +13 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/auth/url-validation.d.ts +31 -0
- package/dist/auth/url-validation.d.ts.map +1 -0
- package/dist/auth/url-validation.js +83 -0
- package/dist/auth/url-validation.js.map +1 -0
- package/dist/auth.d.ts +25 -50
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +34 -103
- package/dist/auth.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +40 -0
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/actor-context-verify.d.ts +95 -0
- package/dist/lib/actor-context-verify.d.ts.map +1 -0
- package/dist/lib/actor-context-verify.js +200 -0
- package/dist/lib/actor-context-verify.js.map +1 -0
- package/dist/runtime-context.d.ts +23 -1
- package/dist/runtime-context.d.ts.map +1 -1
- package/dist/runtime-context.js +64 -0
- package/dist/runtime-context.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
export { db, adminDb, QueryBuilder } from "./db.js";
|
|
2
2
|
export { getUser, getUserId, getRole } from "./auth.js";
|
|
3
|
+
// Capability `auth-aware-ssr` (v3.0). The canonical server-side auth
|
|
4
|
+
// namespace. `auth.user()` / `auth.requireUser()` / `auth.requireRole(...)`
|
|
5
|
+
// / `auth.requireMembership(...)` / `auth.requireFresh({...})` /
|
|
6
|
+
// `auth.fetch(...)` / `auth.csrfField()` / `auth.sessions.*` /
|
|
7
|
+
// `auth.identities.link(...)` are the only documented identity surfaces.
|
|
8
|
+
// See openspec/changes/auth-aware-ssr/specs/auth-sdk-namespace/spec.md.
|
|
9
|
+
export { auth } from "./auth/index.js";
|
|
10
|
+
export { Run402AuthError, AuthRequiredError, InsufficientRoleError, InsufficientMembershipError, FreshnessRequiredError, FetchAbsoluteUrlError, PrerenderedError, UnknownExportError, SessionBridgeUnverifiedError, IdentityLinkConflictError, UnknownIdentityError, InvalidCredentialsError, TenantSubjectInvalidError, } from "./auth/index.js";
|
|
11
|
+
// Throwing-sentinel exports for the top legacy bare-name imports. These
|
|
12
|
+
// fire `R402_AUTH_UNKNOWN_EXPORT` at runtime with a structured fix-it,
|
|
13
|
+
// catching the case where `run402 doctor` and the ESLint rule didn't run
|
|
14
|
+
// (e.g. agent paste straight into a route handler). Excluded from public
|
|
15
|
+
// docs intentionally — they exist to fail loudly, not as API.
|
|
16
|
+
export { getSession, currentUser, getCurrentUser, getServerSession } from "./auth/index.js";
|
|
3
17
|
export { email } from "./email.js";
|
|
4
18
|
export { ai } from "./ai.js";
|
|
5
19
|
export { assets } from "./assets.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGxD,qEAAqE;AACrE,4EAA4E;AAC5E,iEAAiE;AACjE,+DAA+D;AAC/D,yEAAyE;AACzE,wEAAwE;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAOvC,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,4BAA4B,EAC5B,yBAAyB,EACzB,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,wEAAwE;AACxE,uEAAuE;AACvE,yEAAyE;AACzE,yEAAyE;AACzE,8DAA8D;AAC9D,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC5F,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAS7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,qEAAqE;AACrE,yEAAyE;AACzE,8BAA8B;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,qEAAqE;AACrE,gEAAgE;AAChE,qEAAqE;AACrE,2EAA2E;AAC3E,2DAA2D;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAiBxD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAO5E,0CAA0C;AAC1C,mEAAmE;AACnE,2DAA2D;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAOnC,OAAO,EACL,kCAAkC,EAClC,mCAAmC,GACpC,MAAM,YAAY,CAAC;AACpB,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,iDAAiD;AACjD,OAAO,EACL,GAAG,EACH,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,gCAAgC,GACjC,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK runtime verifier for the gateway-signed actor-context envelope.
|
|
3
|
+
*
|
|
4
|
+
* Why this lives in `packages/functions/src/lib/` (vendored shape):
|
|
5
|
+
* - `@run402/functions` is published independently; it must NOT depend
|
|
6
|
+
* on `@run402/gateway` types or runtime.
|
|
7
|
+
* - The verifier is small, deterministic, and security-critical — easier
|
|
8
|
+
* to audit when it lives next to the consumer.
|
|
9
|
+
* - Matches the convention used for `jwt.ts` (vendored upstream copy of
|
|
10
|
+
* the verifier we trust).
|
|
11
|
+
*
|
|
12
|
+
* Contract — the encoded envelope is `base64url(canonical_json) + "." +
|
|
13
|
+
* base64url(hmac_sha256)`. Canonical JSON has a fixed key order; the
|
|
14
|
+
* verifier doesn't re-canonicalise, it just rehmac's the body bytes that
|
|
15
|
+
* were base64-decoded. (Trying to re-derive canonical JSON from the parsed
|
|
16
|
+
* object would re-introduce key-order brittleness; verify the bytes you
|
|
17
|
+
* received, parse only to read fields.)
|
|
18
|
+
*
|
|
19
|
+
* Lookup of `actor_context_signing_key[kid]`:
|
|
20
|
+
* 1. `ACTOR_CONTEXT_SIGNING_KEY_MAP_JSON` env (`{kid: base64}` JSON).
|
|
21
|
+
* 2. `ACTOR_CONTEXT_SIGNING_KEY_<KID_UPPER>` env (one var per kid).
|
|
22
|
+
* 3. Test injection via `_setActorContextKeyMapForTest` on this module.
|
|
23
|
+
*
|
|
24
|
+
* Verification failure modes (mirrors the gateway side):
|
|
25
|
+
* - "malformed" — encoded shape rejected before parse
|
|
26
|
+
* - "unknown_kid" — envelope's kid not in the verifier map
|
|
27
|
+
* - "bad_signature" — HMAC mismatch
|
|
28
|
+
* - "iss_mismatch" — envelope.iss !== "run402-gateway"
|
|
29
|
+
* - "aud_mismatch" — envelope.aud !== "run402-functions-runtime"
|
|
30
|
+
* - "expired" — envelope.exp <= now
|
|
31
|
+
* - "lifetime_too_long" — envelope.exp - envelope.iat > 60s
|
|
32
|
+
* - "project_id_mismatch" — envelope.project_id !== request's
|
|
33
|
+
* - "request_id_mismatch" — envelope.request_id !== request's
|
|
34
|
+
* - "method_mismatch" — different HTTP method
|
|
35
|
+
* - "host_mismatch" — different host (after default-port normalise)
|
|
36
|
+
* - "path_mismatch" — different path (compared by sha256)
|
|
37
|
+
* - "version_mismatch" — schema version not what we compiled
|
|
38
|
+
*
|
|
39
|
+
* On ANY failure the SDK runtime treats the request as anonymous AND
|
|
40
|
+
* logs `R402_AUTH_ACTOR_HEADER_SPOOF` so spoofs / replays are visible
|
|
41
|
+
* in observability.
|
|
42
|
+
*
|
|
43
|
+
* @see openspec/changes/auth-aware-ssr/specs/routed-http-functions/spec.md
|
|
44
|
+
*/
|
|
45
|
+
export declare const ACTOR_CONTEXT_ENVELOPE_VERSION: 1;
|
|
46
|
+
export declare const ACTOR_CONTEXT_ENVELOPE_ISS = "run402-gateway";
|
|
47
|
+
export declare const ACTOR_CONTEXT_ENVELOPE_AUD = "run402-functions-runtime";
|
|
48
|
+
export declare const ACTOR_CONTEXT_MAX_LIFETIME_SEC = 60;
|
|
49
|
+
/** Inbound header carrying the encoded envelope. The runtime reads from
|
|
50
|
+
* the request headers in `RunRequestContext.request.headers`. */
|
|
51
|
+
export declare const ACTOR_CONTEXT_HEADER = "x-run402-actor-context";
|
|
52
|
+
export interface VerifiedActorPayload {
|
|
53
|
+
id: string;
|
|
54
|
+
email: string;
|
|
55
|
+
emailVerified: boolean;
|
|
56
|
+
authTime: number;
|
|
57
|
+
amr: string[];
|
|
58
|
+
amrTimes: Record<string, number>;
|
|
59
|
+
authzVersion: number;
|
|
60
|
+
}
|
|
61
|
+
export interface VerifiedEnvelope {
|
|
62
|
+
v: 1;
|
|
63
|
+
kid: string;
|
|
64
|
+
iss: typeof ACTOR_CONTEXT_ENVELOPE_ISS;
|
|
65
|
+
aud: typeof ACTOR_CONTEXT_ENVELOPE_AUD;
|
|
66
|
+
project_id: string;
|
|
67
|
+
request_id: string;
|
|
68
|
+
method: string;
|
|
69
|
+
host: string;
|
|
70
|
+
path_hash: string;
|
|
71
|
+
iat: number;
|
|
72
|
+
exp: number;
|
|
73
|
+
actor: VerifiedActorPayload;
|
|
74
|
+
}
|
|
75
|
+
export interface VerifyRequestContext {
|
|
76
|
+
projectId: string;
|
|
77
|
+
requestId: string;
|
|
78
|
+
method: string;
|
|
79
|
+
host: string;
|
|
80
|
+
path: string;
|
|
81
|
+
/** Override for tests; production uses `new Date()`. */
|
|
82
|
+
now?: Date;
|
|
83
|
+
}
|
|
84
|
+
export type VerifyFailureReason = "malformed" | "unknown_kid" | "bad_signature" | "iss_mismatch" | "aud_mismatch" | "expired" | "lifetime_too_long" | "project_id_mismatch" | "request_id_mismatch" | "method_mismatch" | "host_mismatch" | "path_mismatch" | "version_mismatch";
|
|
85
|
+
export type VerifyOutcome = {
|
|
86
|
+
ok: true;
|
|
87
|
+
envelope: VerifiedEnvelope;
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
reason: VerifyFailureReason;
|
|
91
|
+
};
|
|
92
|
+
/** Test injection. NEVER call from production code. */
|
|
93
|
+
export declare function _setActorContextKeyMapForTest(map: Record<string, Buffer | string> | null): void;
|
|
94
|
+
export declare function verifyActorContextEnvelope(encoded: string, ctx: VerifyRequestContext): VerifyOutcome;
|
|
95
|
+
//# sourceMappingURL=actor-context-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actor-context-verify.d.ts","sourceRoot":"","sources":["../../src/lib/actor-context-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAIH,eAAO,MAAM,8BAA8B,EAAG,CAAU,CAAC;AACzD,eAAO,MAAM,0BAA0B,mBAAmB,CAAC;AAC3D,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AACrE,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAEjD;kEACkE;AAClE,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAE7D,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,CAAC,CAAC;IACL,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,OAAO,0BAA0B,CAAC;IACvC,GAAG,EAAE,OAAO,0BAA0B,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,oBAAoB,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,aAAa,GACb,eAAe,GACf,cAAc,GACd,cAAc,GACd,SAAS,GACT,mBAAmB,GACnB,qBAAqB,GACrB,qBAAqB,GACrB,iBAAiB,GACjB,eAAe,GACf,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,GACxC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAqD/C,uDAAuD;AACvD,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,GAC1C,IAAI,CAcN;AAMD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,oBAAoB,GACxB,aAAa,CA0Ef"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK runtime verifier for the gateway-signed actor-context envelope.
|
|
3
|
+
*
|
|
4
|
+
* Why this lives in `packages/functions/src/lib/` (vendored shape):
|
|
5
|
+
* - `@run402/functions` is published independently; it must NOT depend
|
|
6
|
+
* on `@run402/gateway` types or runtime.
|
|
7
|
+
* - The verifier is small, deterministic, and security-critical — easier
|
|
8
|
+
* to audit when it lives next to the consumer.
|
|
9
|
+
* - Matches the convention used for `jwt.ts` (vendored upstream copy of
|
|
10
|
+
* the verifier we trust).
|
|
11
|
+
*
|
|
12
|
+
* Contract — the encoded envelope is `base64url(canonical_json) + "." +
|
|
13
|
+
* base64url(hmac_sha256)`. Canonical JSON has a fixed key order; the
|
|
14
|
+
* verifier doesn't re-canonicalise, it just rehmac's the body bytes that
|
|
15
|
+
* were base64-decoded. (Trying to re-derive canonical JSON from the parsed
|
|
16
|
+
* object would re-introduce key-order brittleness; verify the bytes you
|
|
17
|
+
* received, parse only to read fields.)
|
|
18
|
+
*
|
|
19
|
+
* Lookup of `actor_context_signing_key[kid]`:
|
|
20
|
+
* 1. `ACTOR_CONTEXT_SIGNING_KEY_MAP_JSON` env (`{kid: base64}` JSON).
|
|
21
|
+
* 2. `ACTOR_CONTEXT_SIGNING_KEY_<KID_UPPER>` env (one var per kid).
|
|
22
|
+
* 3. Test injection via `_setActorContextKeyMapForTest` on this module.
|
|
23
|
+
*
|
|
24
|
+
* Verification failure modes (mirrors the gateway side):
|
|
25
|
+
* - "malformed" — encoded shape rejected before parse
|
|
26
|
+
* - "unknown_kid" — envelope's kid not in the verifier map
|
|
27
|
+
* - "bad_signature" — HMAC mismatch
|
|
28
|
+
* - "iss_mismatch" — envelope.iss !== "run402-gateway"
|
|
29
|
+
* - "aud_mismatch" — envelope.aud !== "run402-functions-runtime"
|
|
30
|
+
* - "expired" — envelope.exp <= now
|
|
31
|
+
* - "lifetime_too_long" — envelope.exp - envelope.iat > 60s
|
|
32
|
+
* - "project_id_mismatch" — envelope.project_id !== request's
|
|
33
|
+
* - "request_id_mismatch" — envelope.request_id !== request's
|
|
34
|
+
* - "method_mismatch" — different HTTP method
|
|
35
|
+
* - "host_mismatch" — different host (after default-port normalise)
|
|
36
|
+
* - "path_mismatch" — different path (compared by sha256)
|
|
37
|
+
* - "version_mismatch" — schema version not what we compiled
|
|
38
|
+
*
|
|
39
|
+
* On ANY failure the SDK runtime treats the request as anonymous AND
|
|
40
|
+
* logs `R402_AUTH_ACTOR_HEADER_SPOOF` so spoofs / replays are visible
|
|
41
|
+
* in observability.
|
|
42
|
+
*
|
|
43
|
+
* @see openspec/changes/auth-aware-ssr/specs/routed-http-functions/spec.md
|
|
44
|
+
*/
|
|
45
|
+
import crypto from "node:crypto";
|
|
46
|
+
export const ACTOR_CONTEXT_ENVELOPE_VERSION = 1;
|
|
47
|
+
export const ACTOR_CONTEXT_ENVELOPE_ISS = "run402-gateway";
|
|
48
|
+
export const ACTOR_CONTEXT_ENVELOPE_AUD = "run402-functions-runtime";
|
|
49
|
+
export const ACTOR_CONTEXT_MAX_LIFETIME_SEC = 60;
|
|
50
|
+
/** Inbound header carrying the encoded envelope. The runtime reads from
|
|
51
|
+
* the request headers in `RunRequestContext.request.headers`. */
|
|
52
|
+
export const ACTOR_CONTEXT_HEADER = "x-run402-actor-context";
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Key store (env-loaded, test-injectable)
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
const KEY_MIN_BYTES = 32;
|
|
57
|
+
let keyMap = null;
|
|
58
|
+
function loadKeyMap() {
|
|
59
|
+
if (keyMap)
|
|
60
|
+
return keyMap;
|
|
61
|
+
const map = new Map();
|
|
62
|
+
const mapJson = process.env.ACTOR_CONTEXT_SIGNING_KEY_MAP_JSON;
|
|
63
|
+
if (mapJson) {
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(mapJson);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new Error(`ACTOR_CONTEXT_SIGNING_KEY_MAP_JSON is not valid JSON: ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
72
|
+
throw new Error("ACTOR_CONTEXT_SIGNING_KEY_MAP_JSON must be an object {kid: base64}");
|
|
73
|
+
}
|
|
74
|
+
for (const [kid, b64] of Object.entries(parsed)) {
|
|
75
|
+
if (typeof b64 !== "string")
|
|
76
|
+
continue;
|
|
77
|
+
const buf = Buffer.from(b64, "base64");
|
|
78
|
+
if (buf.length >= KEY_MIN_BYTES)
|
|
79
|
+
map.set(kid, buf);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const [envName, envValue] of Object.entries(process.env)) {
|
|
83
|
+
const match = envName.match(/^ACTOR_CONTEXT_SIGNING_KEY_([A-Z0-9_-]+)$/);
|
|
84
|
+
if (!match)
|
|
85
|
+
continue;
|
|
86
|
+
if (envName === "ACTOR_CONTEXT_SIGNING_KEY_CURRENT_KID" ||
|
|
87
|
+
envName === "ACTOR_CONTEXT_SIGNING_KEY_MAP_JSON") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (typeof envValue !== "string" || envValue.length === 0)
|
|
91
|
+
continue;
|
|
92
|
+
const kid = match[1].toLowerCase().replace(/_/g, "-");
|
|
93
|
+
if (map.has(kid))
|
|
94
|
+
continue;
|
|
95
|
+
const buf = Buffer.from(envValue, "base64");
|
|
96
|
+
if (buf.length >= KEY_MIN_BYTES)
|
|
97
|
+
map.set(kid, buf);
|
|
98
|
+
}
|
|
99
|
+
keyMap = map;
|
|
100
|
+
return map;
|
|
101
|
+
}
|
|
102
|
+
/** Test injection. NEVER call from production code. */
|
|
103
|
+
export function _setActorContextKeyMapForTest(map) {
|
|
104
|
+
if (map === null) {
|
|
105
|
+
keyMap = null;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const m = new Map();
|
|
109
|
+
for (const [kid, value] of Object.entries(map)) {
|
|
110
|
+
const buf = Buffer.isBuffer(value) ? value : Buffer.from(value, "base64");
|
|
111
|
+
if (buf.length < KEY_MIN_BYTES) {
|
|
112
|
+
throw new Error(`Actor-context key for kid "${kid}" is too short`);
|
|
113
|
+
}
|
|
114
|
+
m.set(kid, buf);
|
|
115
|
+
}
|
|
116
|
+
keyMap = m;
|
|
117
|
+
}
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Verifier
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
export function verifyActorContextEnvelope(encoded, ctx) {
|
|
122
|
+
if (typeof encoded !== "string" || encoded.length === 0) {
|
|
123
|
+
return { ok: false, reason: "malformed" };
|
|
124
|
+
}
|
|
125
|
+
const dot = encoded.indexOf(".");
|
|
126
|
+
if (dot <= 0 || dot === encoded.length - 1) {
|
|
127
|
+
return { ok: false, reason: "malformed" };
|
|
128
|
+
}
|
|
129
|
+
const bodyB64 = encoded.slice(0, dot);
|
|
130
|
+
const sigB64 = encoded.slice(dot + 1);
|
|
131
|
+
if (!/^[A-Za-z0-9_-]+$/.test(bodyB64) || !/^[A-Za-z0-9_-]+$/.test(sigB64)) {
|
|
132
|
+
return { ok: false, reason: "malformed" };
|
|
133
|
+
}
|
|
134
|
+
let bodyBuf;
|
|
135
|
+
let envelope;
|
|
136
|
+
try {
|
|
137
|
+
bodyBuf = Buffer.from(bodyB64, "base64url");
|
|
138
|
+
envelope = JSON.parse(bodyBuf.toString("utf8"));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return { ok: false, reason: "malformed" };
|
|
142
|
+
}
|
|
143
|
+
if (!envelope || typeof envelope !== "object") {
|
|
144
|
+
return { ok: false, reason: "malformed" };
|
|
145
|
+
}
|
|
146
|
+
if (envelope.v !== ACTOR_CONTEXT_ENVELOPE_VERSION) {
|
|
147
|
+
return { ok: false, reason: "version_mismatch" };
|
|
148
|
+
}
|
|
149
|
+
if (typeof envelope.kid !== "string")
|
|
150
|
+
return { ok: false, reason: "malformed" };
|
|
151
|
+
const key = loadKeyMap().get(envelope.kid);
|
|
152
|
+
if (!key)
|
|
153
|
+
return { ok: false, reason: "unknown_kid" };
|
|
154
|
+
const sigBuf = Buffer.from(sigB64, "base64url");
|
|
155
|
+
const expectedSig = crypto.createHmac("sha256", key).update(bodyBuf).digest();
|
|
156
|
+
if (sigBuf.length !== expectedSig.length) {
|
|
157
|
+
return { ok: false, reason: "bad_signature" };
|
|
158
|
+
}
|
|
159
|
+
if (!crypto.timingSafeEqual(sigBuf, expectedSig)) {
|
|
160
|
+
return { ok: false, reason: "bad_signature" };
|
|
161
|
+
}
|
|
162
|
+
if (envelope.iss !== ACTOR_CONTEXT_ENVELOPE_ISS) {
|
|
163
|
+
return { ok: false, reason: "iss_mismatch" };
|
|
164
|
+
}
|
|
165
|
+
if (envelope.aud !== ACTOR_CONTEXT_ENVELOPE_AUD) {
|
|
166
|
+
return { ok: false, reason: "aud_mismatch" };
|
|
167
|
+
}
|
|
168
|
+
if (typeof envelope.iat !== "number" || typeof envelope.exp !== "number") {
|
|
169
|
+
return { ok: false, reason: "malformed" };
|
|
170
|
+
}
|
|
171
|
+
if (envelope.exp - envelope.iat > ACTOR_CONTEXT_MAX_LIFETIME_SEC) {
|
|
172
|
+
return { ok: false, reason: "lifetime_too_long" };
|
|
173
|
+
}
|
|
174
|
+
const nowSec = Math.floor((ctx.now ?? new Date()).getTime() / 1000);
|
|
175
|
+
if (envelope.exp <= nowSec)
|
|
176
|
+
return { ok: false, reason: "expired" };
|
|
177
|
+
if (envelope.project_id !== ctx.projectId) {
|
|
178
|
+
return { ok: false, reason: "project_id_mismatch" };
|
|
179
|
+
}
|
|
180
|
+
if (envelope.request_id !== ctx.requestId) {
|
|
181
|
+
return { ok: false, reason: "request_id_mismatch" };
|
|
182
|
+
}
|
|
183
|
+
if (envelope.method !== ctx.method.toUpperCase()) {
|
|
184
|
+
return { ok: false, reason: "method_mismatch" };
|
|
185
|
+
}
|
|
186
|
+
if (envelope.host !== normaliseHost(ctx.host)) {
|
|
187
|
+
return { ok: false, reason: "host_mismatch" };
|
|
188
|
+
}
|
|
189
|
+
if (envelope.path_hash !== sha256Hex(ctx.path)) {
|
|
190
|
+
return { ok: false, reason: "path_mismatch" };
|
|
191
|
+
}
|
|
192
|
+
return { ok: true, envelope };
|
|
193
|
+
}
|
|
194
|
+
function normaliseHost(host) {
|
|
195
|
+
return host.toLowerCase().replace(/:80$/, "").replace(/:443$/, "");
|
|
196
|
+
}
|
|
197
|
+
function sha256Hex(s) {
|
|
198
|
+
return crypto.createHash("sha256").update(s, "utf8").digest("hex");
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=actor-context-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actor-context-verify.js","sourceRoot":"","sources":["../../src/lib/actor-context-verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,15 @@
|
|
|
27
27
|
* @see openspec/changes/astro-ssr-runtime/specs/routed-http-functions/spec.md
|
|
28
28
|
*/
|
|
29
29
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
30
|
+
import { type VerifiedActorPayload } from "./lib/actor-context-verify.js";
|
|
31
|
+
/** Capability `auth-aware-ssr`: the verified actor payload the SDK
|
|
32
|
+
* exposes via `auth.user()` etc. Populated by `runWithContext` after
|
|
33
|
+
* it verifies the inbound actor-context envelope. `null` when the
|
|
34
|
+
* request is anonymous OR when verification failed (defense in depth:
|
|
35
|
+
* forged envelopes never surface as a non-null actor). */
|
|
36
|
+
export type ActorContext = VerifiedActorPayload & {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
};
|
|
30
39
|
/** The shape stored in AsyncLocalStorage. See the spec referenced above
|
|
31
40
|
* for the canonical definition. */
|
|
32
41
|
export interface RunRequestContext {
|
|
@@ -52,6 +61,19 @@ export interface RunRequestContext {
|
|
|
52
61
|
url: string;
|
|
53
62
|
headers: Record<string, string | string[] | undefined>;
|
|
54
63
|
};
|
|
64
|
+
/** Capability `auth-aware-ssr`: actor populated from the verified
|
|
65
|
+
* gateway-signed envelope. The SDK's `auth.user()` returns this
|
|
66
|
+
* verbatim; consumer code never sees the raw envelope. */
|
|
67
|
+
actor: ActorContext | null;
|
|
68
|
+
/** Capability `auth-hosted-surface-parity`: invocation class. For
|
|
69
|
+
* `"routed_http"` (browser SSR) the actor is resolved ONLY from the
|
|
70
|
+
* verified cookie envelope above — `auth.user()` does NOT fall back
|
|
71
|
+
* to decoding an `Authorization: Bearer` header, so the
|
|
72
|
+
* "cookie is the only browser actor input" invariant holds. For
|
|
73
|
+
* `"direct"` (machine / mobile / CI function invocation) the Bearer
|
|
74
|
+
* fallback is preserved. Defaults to `"direct"` when unset so
|
|
75
|
+
* pre-existing callers keep the machine contract. */
|
|
76
|
+
invocationKind: "routed_http" | "direct";
|
|
55
77
|
/** Mutable ref: SDK functions that read request-scoped auth or invoke
|
|
56
78
|
* payment primitives set `value = true`. The SSR Lambda runtime
|
|
57
79
|
* returns the final value to the gateway in the response metadata
|
|
@@ -85,7 +107,7 @@ export declare function getCurrentContext(): RunRequestContext | undefined;
|
|
|
85
107
|
* runtime) flips it to `false` after the response body is materialized.
|
|
86
108
|
* Don't call this from user code — it's the runtime's primitive.
|
|
87
109
|
*/
|
|
88
|
-
export declare function runWithContext<T>(context: Omit<RunRequestContext, "cacheBypassTainted" | "active"> & Partial<Pick<RunRequestContext, "cacheBypassTainted" | "active">>, callback: () => Promise<T> | T): Promise<T> | T;
|
|
110
|
+
export declare function runWithContext<T>(context: Omit<RunRequestContext, "cacheBypassTainted" | "active" | "actor" | "invocationKind"> & Partial<Pick<RunRequestContext, "cacheBypassTainted" | "active" | "actor" | "invocationKind">>, callback: () => Promise<T> | T): Promise<T> | T;
|
|
89
111
|
/**
|
|
90
112
|
* Throw a structured `R402_SDK_OUTSIDE_REQUEST_CONTEXT` error. Used by
|
|
91
113
|
* SDK functions when they're invoked with no ALS store OR while the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-context.d.ts","sourceRoot":"","sources":["../src/runtime-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-context.d.ts","sourceRoot":"","sources":["../src/runtime-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,+BAA+B,CAAC;AAEvC;;;;2DAI2D;AAC3D,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE;oCACoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;yBACqB;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;qDACiD;IACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;yDACqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb;;;uBAGmB;IACnB,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;KACxD,CAAC;IACF;;+DAE2D;IAC3D,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3B;;;;;;;0DAOsD;IACtD,cAAc,EAAE,aAAa,GAAG,QAAQ,CAAC;IACzC;;;oBAGgB;IAChB,kBAAkB,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IACvC;;;8CAG0C;IAC1C,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;CAC5B;AAED;oEACoE;AACpE,eAAO,MAAM,GAAG,sCAA6C,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,GAAG,SAAS,CAEjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,IAAI,CACX,iBAAiB,EACjB,oBAAoB,GAAG,QAAQ,GAAG,OAAO,GAAG,gBAAgB,CAC7D,GACC,OAAO,CACL,IAAI,CACF,iBAAiB,EACjB,oBAAoB,GAAG,QAAQ,GAAG,OAAO,GAAG,gBAAgB,CAC7D,CACF,EACH,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAoBhB;AA6DD;;;;;;;;;;;GAWG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;IACzD,QAAQ,CAAC,IAAI,sCAAsC;IACnD,QAAQ,CAAC,IAAI,iEAAiE;IAC9E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,kBAAkB,CAAC;gBAEtC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,kBAAkB;CAc1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,CAS3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAUjD,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EAC/D,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,GAChC,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAe7B"}
|
package/dist/runtime-context.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* @see openspec/changes/astro-ssr-runtime/specs/routed-http-functions/spec.md
|
|
28
28
|
*/
|
|
29
29
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
30
|
+
import { verifyActorContextEnvelope, ACTOR_CONTEXT_HEADER, } from "./lib/actor-context-verify.js";
|
|
30
31
|
/** The shared ALS instance. Modules in @run402/functions read from
|
|
31
32
|
* this; the SSR Lambda runtime (in @run402/astro) writes to it. */
|
|
32
33
|
export const als = new AsyncLocalStorage();
|
|
@@ -48,13 +49,76 @@ export function getCurrentContext() {
|
|
|
48
49
|
* Don't call this from user code — it's the runtime's primitive.
|
|
49
50
|
*/
|
|
50
51
|
export function runWithContext(context, callback) {
|
|
52
|
+
// Resolve the actor either from the caller-provided override (tests, the
|
|
53
|
+
// SSR runtime when it has pre-verified the envelope) or by verifying the
|
|
54
|
+
// inbound actor-context header against the request bound to this context.
|
|
55
|
+
// Failure is non-fatal: the request becomes anonymous AND we log
|
|
56
|
+
// `R402_AUTH_ACTOR_HEADER_SPOOF` so spoof attempts surface in metrics.
|
|
57
|
+
const actor = context.actor !== undefined
|
|
58
|
+
? context.actor
|
|
59
|
+
: verifyEnvelopeFromRequest(context);
|
|
51
60
|
const full = {
|
|
52
61
|
...context,
|
|
62
|
+
actor,
|
|
63
|
+
// Default to "direct" so pre-existing callers (and tests) keep the
|
|
64
|
+
// machine Bearer-fallback contract; the gateway's routed-HTTP path
|
|
65
|
+
// sets "routed_http" explicitly.
|
|
66
|
+
invocationKind: context.invocationKind ?? "direct",
|
|
53
67
|
cacheBypassTainted: context.cacheBypassTainted ?? { value: false },
|
|
54
68
|
active: context.active ?? { value: true },
|
|
55
69
|
};
|
|
56
70
|
return als.run(full, callback);
|
|
57
71
|
}
|
|
72
|
+
function verifyEnvelopeFromRequest(ctx) {
|
|
73
|
+
const headers = ctx.request.headers;
|
|
74
|
+
const headerValue = headers[ACTOR_CONTEXT_HEADER];
|
|
75
|
+
const encoded = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
76
|
+
if (!encoded || typeof encoded !== "string")
|
|
77
|
+
return null;
|
|
78
|
+
// The runtime sees `request.url` as a full path-only string from the
|
|
79
|
+
// SSR Lambda envelope (e.g. `/forum/topic-1?q=astro`). The signing side
|
|
80
|
+
// hashed the path-only URL, so we hash the same here. The query string
|
|
81
|
+
// is part of the binding so `/forum?topic=1` and `/forum?topic=2` can't
|
|
82
|
+
// accept the same envelope.
|
|
83
|
+
const path = ctx.request.url;
|
|
84
|
+
const outcome = verifyActorContextEnvelope(encoded, {
|
|
85
|
+
projectId: ctx.projectId,
|
|
86
|
+
requestId: ctx.requestId,
|
|
87
|
+
method: ctx.request.method,
|
|
88
|
+
host: ctx.host,
|
|
89
|
+
path,
|
|
90
|
+
});
|
|
91
|
+
if (!outcome.ok) {
|
|
92
|
+
logActorContextSpoof(outcome.reason, ctx.requestId);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
id: outcome.envelope.actor.id,
|
|
97
|
+
email: outcome.envelope.actor.email,
|
|
98
|
+
emailVerified: outcome.envelope.actor.emailVerified,
|
|
99
|
+
authTime: outcome.envelope.actor.authTime,
|
|
100
|
+
amr: outcome.envelope.actor.amr,
|
|
101
|
+
amrTimes: outcome.envelope.actor.amrTimes,
|
|
102
|
+
authzVersion: outcome.envelope.actor.authzVersion,
|
|
103
|
+
// session_id is NOT in the envelope — it's gateway-internal state.
|
|
104
|
+
// For now we mint a deterministic-by-request id so downstream code
|
|
105
|
+
// has a string to hang `db()` set_config on. Once the gateway adds
|
|
106
|
+
// session_id to the envelope (planned in section 4 of the spec),
|
|
107
|
+
// populate it here from outcome.envelope.actor.sessionId.
|
|
108
|
+
sessionId: outcome.envelope.request_id,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function logActorContextSpoof(reason, requestId) {
|
|
112
|
+
// Structured one-line log so observability can alert on rate. The
|
|
113
|
+
// SDK runs in Lambda; CloudWatch picks this up.
|
|
114
|
+
// eslint-disable-next-line no-console
|
|
115
|
+
console.warn(JSON.stringify({
|
|
116
|
+
level: "warn",
|
|
117
|
+
event: "R402_AUTH_ACTOR_HEADER_SPOOF",
|
|
118
|
+
reason,
|
|
119
|
+
request_id: requestId,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
58
122
|
/**
|
|
59
123
|
* Throw a structured `R402_SDK_OUTSIDE_REQUEST_CONTEXT` error. Used by
|
|
60
124
|
* SDK functions when they're invoked with no ALS store OR while the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-context.js","sourceRoot":"","sources":["../src/runtime-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-context.js","sourceRoot":"","sources":["../src/runtime-context.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@run402/functions",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "In-function helper library for Run402 serverless functions — db, adminDb, getUser, email, ai, assets, verifyWebhook. Auto-bundled into deployed functions; also installable for local TypeScript autocomplete.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc",
|
|
21
|
-
"test": "node --experimental-test-module-mocks --test --import tsx src/db.test.ts src/auth.test.ts src/ai.test.ts src/email.test.ts src/assets.test.ts src/routed-http.test.ts"
|
|
21
|
+
"test": "node --experimental-test-module-mocks --test --import tsx src/db.test.ts src/auth.test.ts src/auth/index.test.ts src/ai.test.ts src/email.test.ts src/assets.test.ts src/routed-http.test.ts src/request-context.test.ts src/runtime-context.test.ts src/lib/actor-context-verify.test.ts src/verify-webhook.test.ts"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=18"
|