@stigmer/server 3.23.1-dev.20260923075205 → 3.25.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/authorization/authorizer.d.ts +10 -4
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +11 -4
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/model/index.d.ts +4 -4
- package/dist/authorization/model/index.d.ts.map +1 -1
- package/dist/authorization/model/index.js +2 -0
- package/dist/authorization/model/index.js.map +1 -1
- package/dist/authorization/model/oauth_app.d.ts.map +1 -1
- package/dist/authorization/model/oauth_app.js +3 -2
- package/dist/authorization/model/oauth_app.js.map +1 -1
- package/dist/authorization/model/platform_client.d.ts +2 -0
- package/dist/authorization/model/platform_client.d.ts.map +1 -0
- package/dist/authorization/model/platform_client.js +31 -0
- package/dist/authorization/model/platform_client.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +88 -5
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/route-shadowing.d.ts +25 -0
- package/dist/boot/route-shadowing.d.ts.map +1 -0
- package/dist/boot/route-shadowing.js +17 -0
- package/dist/boot/route-shadowing.js.map +1 -0
- package/dist/domain/iampolicy/membership.d.ts +6 -1
- package/dist/domain/iampolicy/membership.d.ts.map +1 -1
- package/dist/domain/iampolicy/membership.js +8 -2
- package/dist/domain/iampolicy/membership.js.map +1 -1
- package/dist/domain/identityaccount/constants.d.ts +13 -0
- package/dist/domain/identityaccount/constants.d.ts.map +1 -1
- package/dist/domain/identityaccount/constants.js +17 -0
- package/dist/domain/identityaccount/constants.js.map +1 -1
- package/dist/domain/identityaccount/controller.d.ts +3 -1
- package/dist/domain/identityaccount/controller.d.ts.map +1 -1
- package/dist/domain/identityaccount/controller.js +15 -6
- package/dist/domain/identityaccount/controller.js.map +1 -1
- package/dist/domain/identityaccount/operator.d.ts.map +1 -1
- package/dist/domain/identityaccount/operator.js +1 -0
- package/dist/domain/identityaccount/operator.js.map +1 -1
- package/dist/domain/identityaccount/provisioning.d.ts +18 -0
- package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
- package/dist/domain/identityaccount/provisioning.js +1 -0
- package/dist/domain/identityaccount/provisioning.js.map +1 -1
- package/dist/domain/identityaccount/resource-store.js +9 -5
- package/dist/domain/identityaccount/resource-store.js.map +1 -1
- package/dist/domain/identityaccount/steps.d.ts +11 -2
- package/dist/domain/identityaccount/steps.d.ts.map +1 -1
- package/dist/domain/identityaccount/steps.js +42 -4
- package/dist/domain/identityaccount/steps.js.map +1 -1
- package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
- package/dist/domain/identityaccount/store-contract.js +33 -0
- package/dist/domain/identityaccount/store-contract.js.map +1 -1
- package/dist/domain/identityaccount/store.d.ts +7 -5
- package/dist/domain/identityaccount/store.d.ts.map +1 -1
- package/dist/domain/identityaccount/store.js.map +1 -1
- package/dist/domain/platform/controller.d.ts +10 -0
- package/dist/domain/platform/controller.d.ts.map +1 -1
- package/dist/domain/platform/controller.js +2 -1
- package/dist/domain/platform/controller.js.map +1 -1
- package/dist/domain/platformclient/constants.d.ts +60 -0
- package/dist/domain/platformclient/constants.d.ts.map +1 -0
- package/dist/domain/platformclient/constants.js +78 -0
- package/dist/domain/platformclient/constants.js.map +1 -0
- package/dist/domain/platformclient/controller.d.ts +59 -0
- package/dist/domain/platformclient/controller.d.ts.map +1 -0
- package/dist/domain/platformclient/controller.js +191 -0
- package/dist/domain/platformclient/controller.js.map +1 -0
- package/dist/domain/platformclient/credentials.d.ts +13 -0
- package/dist/domain/platformclient/credentials.d.ts.map +1 -0
- package/dist/domain/platformclient/credentials.js +56 -0
- package/dist/domain/platformclient/credentials.js.map +1 -0
- package/dist/domain/platformclient/mint.d.ts +23 -0
- package/dist/domain/platformclient/mint.d.ts.map +1 -0
- package/dist/domain/platformclient/mint.js +254 -0
- package/dist/domain/platformclient/mint.js.map +1 -0
- package/dist/domain/platformclient/origin-guard.d.ts +9 -0
- package/dist/domain/platformclient/origin-guard.d.ts.map +1 -0
- package/dist/domain/platformclient/origin-guard.js +81 -0
- package/dist/domain/platformclient/origin-guard.js.map +1 -0
- package/dist/domain/platformclient/resource-store.d.ts +4 -0
- package/dist/domain/platformclient/resource-store.d.ts.map +1 -0
- package/dist/domain/platformclient/resource-store.js +117 -0
- package/dist/domain/platformclient/resource-store.js.map +1 -0
- package/dist/domain/platformclient/steps.d.ts +51 -0
- package/dist/domain/platformclient/steps.d.ts.map +1 -0
- package/dist/domain/platformclient/steps.js +339 -0
- package/dist/domain/platformclient/steps.js.map +1 -0
- package/dist/domain/platformclient/store-contract.d.ts +8 -0
- package/dist/domain/platformclient/store-contract.d.ts.map +1 -0
- package/dist/domain/platformclient/store-contract.js +208 -0
- package/dist/domain/platformclient/store-contract.js.map +1 -0
- package/dist/domain/platformclient/store.d.ts +58 -0
- package/dist/domain/platformclient/store.d.ts.map +1 -0
- package/dist/domain/platformclient/store.js +13 -0
- package/dist/domain/platformclient/store.js.map +1 -0
- package/dist/domain/platformclient/system-managed.d.ts +18 -0
- package/dist/domain/platformclient/system-managed.d.ts.map +1 -0
- package/dist/domain/platformclient/system-managed.js +80 -0
- package/dist/domain/platformclient/system-managed.js.map +1 -0
- package/dist/domain/platformclient/token-controller.d.ts +11 -0
- package/dist/domain/platformclient/token-controller.d.ts.map +1 -0
- package/dist/domain/platformclient/token-controller.js +32 -0
- package/dist/domain/platformclient/token-controller.js.map +1 -0
- package/dist/domain/platformclient/verifier.d.ts +10 -0
- package/dist/domain/platformclient/verifier.d.ts.map +1 -0
- package/dist/domain/platformclient/verifier.js +76 -0
- package/dist/domain/platformclient/verifier.js.map +1 -0
- package/dist/domain/workflow/agent-call-references.d.ts +60 -33
- package/dist/domain/workflow/agent-call-references.d.ts.map +1 -1
- package/dist/domain/workflow/agent-call-references.js +43 -23
- package/dist/domain/workflow/agent-call-references.js.map +1 -1
- package/dist/domain/workflow/registry/data/task-kind-registry.json +2 -2
- package/dist/encryption/key-manager.d.ts +20 -3
- package/dist/encryption/key-manager.d.ts.map +1 -1
- package/dist/encryption/key-manager.js +64 -42
- package/dist/encryption/key-manager.js.map +1 -1
- package/dist/extensions/caller-guards.d.ts +8 -3
- package/dist/extensions/caller-guards.d.ts.map +1 -1
- package/dist/extensions/drivers.d.ts +37 -0
- package/dist/extensions/drivers.d.ts.map +1 -1
- package/dist/extensions/guest-token-minting.d.ts +26 -0
- package/dist/extensions/guest-token-minting.d.ts.map +1 -0
- package/dist/extensions/guest-token-minting.js +2 -0
- package/dist/extensions/guest-token-minting.js.map +1 -0
- package/dist/extensions/identity.d.ts +6 -4
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/registry.d.ts +30 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/extensions/registry.js +34 -1
- package/dist/extensions/registry.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +10 -9
- package/dist/pipeline/apiresource-labels.d.ts.map +1 -1
- package/dist/pipeline/apiresource-labels.js +10 -9
- package/dist/pipeline/apiresource-labels.js.map +1 -1
- package/dist/pipeline/interceptors/auth.d.ts +14 -0
- package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
- package/dist/pipeline/interceptors/auth.js +24 -0
- package/dist/pipeline/interceptors/auth.js.map +1 -1
- package/dist/pipeline/steps/build-update-state.d.ts +11 -1
- package/dist/pipeline/steps/build-update-state.d.ts.map +1 -1
- package/dist/pipeline/steps/build-update-state.js +5 -2
- package/dist/pipeline/steps/build-update-state.js.map +1 -1
- package/dist/pipeline/steps/references.d.ts +30 -4
- package/dist/pipeline/steps/references.d.ts.map +1 -1
- package/dist/pipeline/steps/references.js +13 -1
- package/dist/pipeline/steps/references.js.map +1 -1
- package/dist/platformtoken/envelope.d.ts +100 -0
- package/dist/platformtoken/envelope.d.ts.map +1 -0
- package/dist/platformtoken/envelope.js +184 -0
- package/dist/platformtoken/envelope.js.map +1 -0
- package/dist/platformtoken/key-ring.d.ts +85 -0
- package/dist/platformtoken/key-ring.d.ts.map +1 -0
- package/dist/platformtoken/key-ring.js +164 -0
- package/dist/platformtoken/key-ring.js.map +1 -0
- package/package.json +6 -6
- package/src/authorization/README.md +1 -1
- package/src/authorization/__tests__/authorizer.test.ts +6 -2
- package/src/authorization/__tests__/wire-permissions.test.ts +1 -0
- package/src/authorization/authorizer.ts +11 -4
- package/src/authorization/model/__tests__/registry.test.ts +8 -1
- package/src/authorization/model/index.ts +6 -4
- package/src/authorization/model/oauth_app.ts +3 -2
- package/src/authorization/model/platform_client.ts +43 -0
- package/src/boot/__tests__/auth-enabled-composition.test.ts +2 -1
- package/src/boot/__tests__/route-shadowing.test.ts +67 -0
- package/src/boot/compose.ts +90 -5
- package/src/boot/route-shadowing.ts +47 -0
- package/src/domain/iampolicy/__tests__/membership.test.ts +7 -0
- package/src/domain/iampolicy/membership.ts +8 -2
- package/src/domain/identityaccount/__tests__/platform-client-accounts.test.ts +124 -0
- package/src/domain/identityaccount/__tests__/resource-store.test.ts +1 -0
- package/src/domain/identityaccount/constants.ts +20 -0
- package/src/domain/identityaccount/controller.ts +16 -4
- package/src/domain/identityaccount/operator.ts +1 -0
- package/src/domain/identityaccount/provisioning.ts +17 -0
- package/src/domain/identityaccount/resource-store.ts +9 -5
- package/src/domain/identityaccount/steps.ts +58 -3
- package/src/domain/identityaccount/store-contract.ts +51 -0
- package/src/domain/identityaccount/store.ts +7 -5
- package/src/domain/platform/__tests__/platform.test.ts +7 -1
- package/src/domain/platform/controller.ts +12 -1
- package/src/domain/platformclient/__tests__/controller.test.ts +397 -0
- package/src/domain/platformclient/__tests__/credentials.test.ts +46 -0
- package/src/domain/platformclient/__tests__/mint.test.ts +415 -0
- package/src/domain/platformclient/__tests__/resource-store.test.ts +140 -0
- package/src/domain/platformclient/__tests__/system-managed.test.ts +101 -0
- package/src/domain/platformclient/__tests__/verifier-and-guard.test.ts +207 -0
- package/src/domain/platformclient/constants.ts +120 -0
- package/src/domain/platformclient/controller.ts +449 -0
- package/src/domain/platformclient/credentials.ts +68 -0
- package/src/domain/platformclient/mint.ts +412 -0
- package/src/domain/platformclient/origin-guard.ts +117 -0
- package/src/domain/platformclient/resource-store.ts +150 -0
- package/src/domain/platformclient/steps.ts +472 -0
- package/src/domain/platformclient/store-contract.ts +318 -0
- package/src/domain/platformclient/store.ts +63 -0
- package/src/domain/platformclient/system-managed.ts +115 -0
- package/src/domain/platformclient/token-controller.ts +49 -0
- package/src/domain/platformclient/verifier.ts +106 -0
- package/src/domain/workflow/__tests__/agent-call-references.test.ts +178 -27
- package/src/domain/workflow/agent-call-references.ts +95 -48
- package/src/domain/workflow/registry/data/task-kind-registry.json +2 -2
- package/src/encryption/key-manager.ts +87 -44
- package/src/extensions/__tests__/extension-composition.test.ts +3 -2
- package/src/extensions/__tests__/platform-client-composed.test.ts +288 -0
- package/src/extensions/__tests__/registry.test.ts +4 -1
- package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
- package/src/extensions/caller-guards.ts +8 -3
- package/src/extensions/drivers.ts +37 -0
- package/src/extensions/guest-token-minting.ts +32 -0
- package/src/extensions/identity.ts +6 -4
- package/src/extensions/registry.ts +77 -3
- package/src/index.ts +55 -0
- package/src/pipeline/apiresource-labels.ts +10 -9
- package/src/pipeline/interceptors/auth.ts +27 -0
- package/src/pipeline/steps/__tests__/references.test.ts +37 -1
- package/src/pipeline/steps/build-update-state.ts +5 -2
- package/src/pipeline/steps/references.ts +49 -5
- package/src/platformtoken/__tests__/envelope.test.ts +277 -0
- package/src/platformtoken/__tests__/key-ring.test.ts +205 -0
- package/src/platformtoken/envelope.ts +276 -0
- package/src/platformtoken/key-ring.ts +266 -0
- package/src/runnerauth/__tests__/runner-subject-composed.test.ts +9 -3
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform-token envelope: the RS256 JWT every token the server signs
|
|
3
|
+
* for itself rides — the PlatformClient user token in open source, and in
|
|
4
|
+
* the cloud its typed lanes (guest, schedule, channel, the sandbox family)
|
|
5
|
+
* as well. One envelope, byte-compatible with the tokens Stigmer Cloud has
|
|
6
|
+
* signed since the Java issuer, so a token minted before the cloud serves
|
|
7
|
+
* this code verifies after it.
|
|
8
|
+
*
|
|
9
|
+
* The envelope knows the issuer, the four claims it owns (`iss`, `iat`,
|
|
10
|
+
* `exp`, `jti`, plus `aud` when the ring carries an audience) and the
|
|
11
|
+
* signature. It does not know lanes: a lane's vocabulary (its claims, its
|
|
12
|
+
* `token_type`, the caller class it stamps) belongs to the verifier that
|
|
13
|
+
* claims it — open source verifies only the untyped PlatformClient lane
|
|
14
|
+
* (domain/platformclient/verifier.ts); the typed lanes stay the cloud's.
|
|
15
|
+
*
|
|
16
|
+
* A token's lifetime is the lane's to choose: the ring carries the default
|
|
17
|
+
* (the user token's), and a lane that lives longer or shorter — the cloud's
|
|
18
|
+
* sandbox tokens outlive a user token sixteen-fold — says so per token.
|
|
19
|
+
* Either way the envelope alone computes `exp` and hands the expiry back,
|
|
20
|
+
* so no lane re-derives it beside the claim it signed.
|
|
21
|
+
*
|
|
22
|
+
* Verification is the cloud verifier's, exactly (its verifiers/jwt.ts and
|
|
23
|
+
* platform-token.ts), because the tokens in the field were accepted by it:
|
|
24
|
+
* - A token that is not a three-part JWT with `iss: "stigmer"` is
|
|
25
|
+
* `foreign` — some other verifier's to judge, never refused here.
|
|
26
|
+
* - The signature is RS256 over every key in the ring; the header's
|
|
27
|
+
* `alg` and `kid` are never read to choose (so neither `alg: none` nor
|
|
28
|
+
* an HMAC-with-the-public-key forgery can pass, and a constant kid
|
|
29
|
+
* across a rotation still verifies).
|
|
30
|
+
* - Then `exp` (required, a number, strictly in the future; no leeway),
|
|
31
|
+
* then `aud` (tolerated when absent, refused when it names another
|
|
32
|
+
* audience), then `sub` (a non-empty string) — in that order, each with
|
|
33
|
+
* its pinned copy.
|
|
34
|
+
* The refusals are returned, not thrown: the envelope has no transport,
|
|
35
|
+
* and a verifier turns a refusal into the wire error with
|
|
36
|
+
* `platformTokenRefusalError`.
|
|
37
|
+
*/
|
|
38
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
39
|
+
import { randomUUID, sign, verify } from "node:crypto";
|
|
40
|
+
/** The `iss` of every platform token (the cloud's STIGMER_ISSUER). */
|
|
41
|
+
export const PLATFORM_TOKEN_ISSUER = "stigmer";
|
|
42
|
+
/** The claim a typed lane names itself by; absent on the PlatformClient user token. */
|
|
43
|
+
export const TOKEN_TYPE_CLAIM = "token_type";
|
|
44
|
+
/** The claims the envelope owns; a lane may not set them. */
|
|
45
|
+
const ENVELOPE_CLAIMS = new Set(["iss", "iat", "exp", "jti", "aud"]);
|
|
46
|
+
/** Wire copy, byte-pinned: the cloud verifier's sentences since the Java issuer. */
|
|
47
|
+
export const PLATFORM_TOKEN_REFUSAL_MESSAGES = {
|
|
48
|
+
signature: "platform token signature verification failed",
|
|
49
|
+
expired: "platform token is expired",
|
|
50
|
+
audience: "platform token was minted for another environment",
|
|
51
|
+
subject: "platform token carries no subject",
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Signs `claims` inside the envelope: header `{alg, typ, kid}`, then `iss`,
|
|
55
|
+
* `iat`, `exp` (iat + the lifetime), `jti` and `aud` when the ring has one,
|
|
56
|
+
* then the lane's claims. Throws when a lane claim collides with an
|
|
57
|
+
* envelope claim, or when a lifetime is not a positive whole number of
|
|
58
|
+
* seconds — programming errors, never a request's.
|
|
59
|
+
*/
|
|
60
|
+
export function signPlatformToken(ring, claims, options = {}) {
|
|
61
|
+
for (const name of Object.keys(claims)) {
|
|
62
|
+
if (ENVELOPE_CLAIMS.has(name)) {
|
|
63
|
+
throw new Error(`platform-token claim '${name}' belongs to the envelope and cannot be set by a lane`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const ttlSeconds = options.ttlSeconds ?? ring.ttlSeconds;
|
|
67
|
+
if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
|
|
68
|
+
throw new Error(`platform-token lifetime must be a positive integer number of seconds, got ${ttlSeconds}`);
|
|
69
|
+
}
|
|
70
|
+
const issuedAt = Math.floor((options.now ?? new Date()).getTime() / 1000);
|
|
71
|
+
const expiresAtSeconds = issuedAt + ttlSeconds;
|
|
72
|
+
const header = { alg: "RS256", typ: "JWT", kid: ring.signer.kid };
|
|
73
|
+
const payload = {
|
|
74
|
+
iss: PLATFORM_TOKEN_ISSUER,
|
|
75
|
+
iat: issuedAt,
|
|
76
|
+
exp: expiresAtSeconds,
|
|
77
|
+
jti: randomUUID(),
|
|
78
|
+
...(ring.audience !== "" ? { aud: ring.audience } : {}),
|
|
79
|
+
...claims,
|
|
80
|
+
};
|
|
81
|
+
const signingInput = `${base64UrlJson(header)}.${base64UrlJson(payload)}`;
|
|
82
|
+
const signature = sign("sha256", Buffer.from(signingInput), ring.signer.key);
|
|
83
|
+
return {
|
|
84
|
+
token: `${signingInput}.${signature.toString("base64url")}`,
|
|
85
|
+
expiresAt: new Date(expiresAtSeconds * 1000),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Verifies a bearer token against the ring (see the header for the order). */
|
|
89
|
+
export function verifyPlatformToken(ring, token, now = new Date()) {
|
|
90
|
+
const decoded = decode(token);
|
|
91
|
+
if (decoded === undefined || decoded.payload.iss !== PLATFORM_TOKEN_ISSUER) {
|
|
92
|
+
return { outcome: "foreign" };
|
|
93
|
+
}
|
|
94
|
+
const signingInput = Buffer.from(`${decoded.encodedHeader}.${decoded.encodedPayload}`);
|
|
95
|
+
const signatureValid = ring.verificationKeys.some((key) => verify("sha256", signingInput, key, decoded.signature));
|
|
96
|
+
if (!signatureValid) {
|
|
97
|
+
return { outcome: "refused", refusal: "signature" };
|
|
98
|
+
}
|
|
99
|
+
const exp = decoded.payload.exp;
|
|
100
|
+
if (typeof exp !== "number" || exp * 1000 <= now.getTime()) {
|
|
101
|
+
return { outcome: "refused", refusal: "expired" };
|
|
102
|
+
}
|
|
103
|
+
const aud = decoded.payload.aud;
|
|
104
|
+
if (ring.audience !== "" &&
|
|
105
|
+
aud !== undefined &&
|
|
106
|
+
!audienceMatches(aud, ring.audience)) {
|
|
107
|
+
return { outcome: "refused", refusal: "audience" };
|
|
108
|
+
}
|
|
109
|
+
const sub = decoded.payload.sub;
|
|
110
|
+
if (typeof sub !== "string" || sub === "") {
|
|
111
|
+
return { outcome: "refused", refusal: "subject" };
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
outcome: "verified",
|
|
115
|
+
token: {
|
|
116
|
+
subject: sub,
|
|
117
|
+
tokenType: stringClaim(decoded.payload, TOKEN_TYPE_CLAIM),
|
|
118
|
+
payload: decoded.payload,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** The UNAUTHENTICATED error a verifier answers for a refused platform token. */
|
|
123
|
+
export function platformTokenRefusalError(refusal) {
|
|
124
|
+
return new ConnectError(PLATFORM_TOKEN_REFUSAL_MESSAGES[refusal], Code.Unauthenticated);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The payload of a platform token WITHOUT verifying it — for a caller
|
|
128
|
+
* guard reading the claims of a token the verifier chain already verified
|
|
129
|
+
* on this request (caller-guards.ts: guards decode `rawToken`). Undefined
|
|
130
|
+
* for anything that is not a platform token, an API key or an empty
|
|
131
|
+
* trusted-local token included. Never a substitute for
|
|
132
|
+
* `verifyPlatformToken`.
|
|
133
|
+
*/
|
|
134
|
+
export function decodeVerifiedPlatformTokenPayload(token) {
|
|
135
|
+
const decoded = decode(token);
|
|
136
|
+
return decoded !== undefined && decoded.payload.iss === PLATFORM_TOKEN_ISSUER
|
|
137
|
+
? decoded.payload
|
|
138
|
+
: undefined;
|
|
139
|
+
}
|
|
140
|
+
/** A payload claim when it is a non-empty string; undefined otherwise. */
|
|
141
|
+
export function stringClaim(payload, name) {
|
|
142
|
+
const value = payload[name];
|
|
143
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
144
|
+
}
|
|
145
|
+
/** A three-part JWT whose header and payload are JSON objects; anything else is undefined. */
|
|
146
|
+
function decode(token) {
|
|
147
|
+
const parts = token.split(".");
|
|
148
|
+
if (parts.length !== 3) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const [encodedHeader = "", encodedPayload = "", encodedSignature = ""] = parts;
|
|
152
|
+
const header = parseJsonObject(encodedHeader);
|
|
153
|
+
const payload = parseJsonObject(encodedPayload);
|
|
154
|
+
if (header === undefined || payload === undefined) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
encodedHeader,
|
|
159
|
+
encodedPayload,
|
|
160
|
+
payload,
|
|
161
|
+
signature: Buffer.from(encodedSignature, "base64url"),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function parseJsonObject(segment) {
|
|
165
|
+
try {
|
|
166
|
+
const value = JSON.parse(Buffer.from(segment, "base64url").toString("utf8"));
|
|
167
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
168
|
+
? value
|
|
169
|
+
: undefined;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function audienceMatches(aud, expected) {
|
|
176
|
+
if (typeof aud === "string") {
|
|
177
|
+
return aud === expected;
|
|
178
|
+
}
|
|
179
|
+
return Array.isArray(aud) && aud.includes(expected);
|
|
180
|
+
}
|
|
181
|
+
function base64UrlJson(value) {
|
|
182
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/platformtoken/envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { KeyObject } from "node:crypto";
|
|
2
|
+
import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
|
|
3
|
+
import type { KeyCodec, KeyLoaderOptions } from "../encryption/key-manager.js";
|
|
4
|
+
/** Env var carrying open source's signing key: Base64 of a PKCS#8 PEM. */
|
|
5
|
+
export declare const PLATFORM_TOKEN_KEY_ENV_VAR = "STIGMER_PLATFORM_TOKEN_KEY";
|
|
6
|
+
/** Key file under ~/.stigmer for the generated signing key (PKCS#8 PEM). */
|
|
7
|
+
export declare const PLATFORM_TOKEN_KEY_FILE_NAME = "platform-token.key";
|
|
8
|
+
/**
|
|
9
|
+
* The default lifetime of a signed token: the cloud's user-token lifetime
|
|
10
|
+
* since the Java issuer (STIGMER_JWT_USER_TOKEN_TTL_SECONDS' default). It
|
|
11
|
+
* bounds how long a user token outlives a rotated secret or a lost key; a
|
|
12
|
+
* deleted client's tokens stop at the next request regardless (the
|
|
13
|
+
* verifier's liveness).
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_PLATFORM_TOKEN_TTL_SECONDS = 900;
|
|
16
|
+
/** The smallest RSA modulus the ring accepts (NIST SP 800-131A's floor). */
|
|
17
|
+
export declare const MIN_RSA_MODULUS_BITS = 2048;
|
|
18
|
+
/** The key that signs, and the kid its tokens' header names. */
|
|
19
|
+
export interface PlatformTokenSigner {
|
|
20
|
+
readonly key: KeyObject;
|
|
21
|
+
readonly kid: string;
|
|
22
|
+
}
|
|
23
|
+
export interface PlatformTokenKeyRing {
|
|
24
|
+
/** Absent: the ring verifies and never signs (minting is disabled). */
|
|
25
|
+
readonly signer?: PlatformTokenSigner;
|
|
26
|
+
/** Every public key a token may verify against, the active one first. */
|
|
27
|
+
readonly verificationKeys: ReadonlyArray<KeyObject>;
|
|
28
|
+
/** Stamped as `aud` and checked when present; "" means none. */
|
|
29
|
+
readonly audience: string;
|
|
30
|
+
/**
|
|
31
|
+
* The lifetime of a token signed without one of its own: the user
|
|
32
|
+
* token's. A lane that lives longer or shorter passes its own to
|
|
33
|
+
* `signPlatformToken`.
|
|
34
|
+
*/
|
|
35
|
+
readonly ttlSeconds: number;
|
|
36
|
+
}
|
|
37
|
+
/** A ring that can sign — what `signPlatformToken` takes. */
|
|
38
|
+
export type SigningPlatformTokenKeyRing = PlatformTokenKeyRing & {
|
|
39
|
+
readonly signer: PlatformTokenSigner;
|
|
40
|
+
};
|
|
41
|
+
export declare function canSign(ring: PlatformTokenKeyRing): ring is SigningPlatformTokenKeyRing;
|
|
42
|
+
/** A composition's configured key material, as PEM text. */
|
|
43
|
+
export interface PlatformTokenKeyMaterial {
|
|
44
|
+
/** PKCS#8 or PKCS#1 private key; absent disables signing. */
|
|
45
|
+
readonly privateKeyPem?: string;
|
|
46
|
+
/** The header kid; defaults to one derived from the private key. */
|
|
47
|
+
readonly kid?: string;
|
|
48
|
+
/** SPKI public keys, the active one first; at least one. */
|
|
49
|
+
readonly publicKeyPems: ReadonlyArray<string>;
|
|
50
|
+
readonly audience?: string;
|
|
51
|
+
readonly ttlSeconds?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Builds a ring from PEM text, refusing loudly at boot what would fail
|
|
55
|
+
* quietly at the first request: a non-RSA or undersized key, an empty
|
|
56
|
+
* verification set, a non-positive TTL, and a private key whose public
|
|
57
|
+
* half is not among the verification keys (it would sign tokens nothing
|
|
58
|
+
* accepts).
|
|
59
|
+
*/
|
|
60
|
+
export declare function platformTokenKeyRingFromPem(material: PlatformTokenKeyMaterial): PlatformTokenKeyRing;
|
|
61
|
+
/**
|
|
62
|
+
* The ring a composition runs with, decided once at the keys stage:
|
|
63
|
+
* - no authentication posture: none — a server that trusts every
|
|
64
|
+
* request verifies nothing, so it signs nothing (the mint refuses);
|
|
65
|
+
* - a supplied ring: that ring;
|
|
66
|
+
* - none supplied, open source: open source's own ring on the ladder;
|
|
67
|
+
* - none supplied, any other edition: a boot throw, so a hosted edition
|
|
68
|
+
* can never sign with a key generated into a pod's home directory,
|
|
69
|
+
* which no other replica shares and no restart keeps.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolvePlatformTokenKeys(input: {
|
|
72
|
+
readonly requireAuthentication: boolean;
|
|
73
|
+
readonly supplied: PlatformTokenKeyRing | undefined;
|
|
74
|
+
readonly edition: ServerEdition;
|
|
75
|
+
readonly options?: KeyLoaderOptions;
|
|
76
|
+
}): PlatformTokenKeyRing | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Open source's ring: one RSA key on the key-manager ladder, signing and
|
|
79
|
+
* verifying, no audience, the default TTL. Throws when an explicitly
|
|
80
|
+
* configured STIGMER_PLATFORM_TOKEN_KEY is unusable (the ladder's rule).
|
|
81
|
+
*/
|
|
82
|
+
export declare function openSourcePlatformTokenKeyRing(options?: KeyLoaderOptions): SigningPlatformTokenKeyRing;
|
|
83
|
+
/** The ladder codec for an RSA private key stored as PKCS#8 PEM. */
|
|
84
|
+
export declare const RSA_PRIVATE_KEY: KeyCodec<KeyObject>;
|
|
85
|
+
//# sourceMappingURL=key-ring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-ring.d.ts","sourceRoot":"","sources":["../../src/platformtoken/key-ring.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAGtF,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAE/E,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AAEvE,4EAA4E;AAC5E,eAAO,MAAM,4BAA4B,uBAAuB,CAAC;AAEjE;;;;;;GAMG;AACH,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,gEAAgE;AAChE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IACtC,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACpD,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,6DAA6D;AAC7D,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,GAAG;IAC/D,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;CACtC,CAAC;AAEF,wBAAgB,OAAO,CACrB,IAAI,EAAE,oBAAoB,GACzB,IAAI,IAAI,2BAA2B,CAErC;AAED,4DAA4D;AAC5D,MAAM,WAAW,wBAAwB;IACvC,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,wBAAwB,GACjC,oBAAoB,CA2CtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;CACrC,GAAG,oBAAoB,GAAG,SAAS,CAanC;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,GAAE,gBAAqB,GAC7B,2BAA2B,CAa7B;AAED,oEAAoE;AACpE,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,SAAS,CAuB/C,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform-token key ring: the RS256 key material every token the
|
|
3
|
+
* server signs for itself rides (the envelope in envelope.ts). One ring
|
|
4
|
+
* per server, supplied once:
|
|
5
|
+
*
|
|
6
|
+
* - Open source composes `openSourcePlatformTokenKeyRing()`: one RSA key
|
|
7
|
+
* on the key-manager ladder (STIGMER_PLATFORM_TOKEN_KEY, else
|
|
8
|
+
* ~/.stigmer/platform-token.key, else generated and persisted — the
|
|
9
|
+
* runner-token key's convention, under the ladder's own rules). It
|
|
10
|
+
* signs and verifies with the same key; no audience; a kid derived
|
|
11
|
+
* from the key, so two installations never claim the same kid.
|
|
12
|
+
* - A composition supplies its own ring through the `platformTokenKeys`
|
|
13
|
+
* driver point, built from its configured PEMs with
|
|
14
|
+
* `platformTokenKeyRingFromPem`: the active public key first, then any
|
|
15
|
+
* still accepted (the rotation window), an optional private key (a
|
|
16
|
+
* ring without one verifies and never signs), its kid and audience.
|
|
17
|
+
*
|
|
18
|
+
* Only RSA keys of at least MIN_RSA_MODULUS_BITS are accepted: the
|
|
19
|
+
* envelope signs PKCS#1 v1.5 over SHA-256 (RS256) and asks the key for
|
|
20
|
+
* nothing else, so an EC key would silently change the algorithm.
|
|
21
|
+
*/
|
|
22
|
+
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, } from "node:crypto";
|
|
23
|
+
import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
|
|
24
|
+
import { getOrCreateKey } from "../encryption/key-manager.js";
|
|
25
|
+
/** Env var carrying open source's signing key: Base64 of a PKCS#8 PEM. */
|
|
26
|
+
export const PLATFORM_TOKEN_KEY_ENV_VAR = "STIGMER_PLATFORM_TOKEN_KEY";
|
|
27
|
+
/** Key file under ~/.stigmer for the generated signing key (PKCS#8 PEM). */
|
|
28
|
+
export const PLATFORM_TOKEN_KEY_FILE_NAME = "platform-token.key";
|
|
29
|
+
/**
|
|
30
|
+
* The default lifetime of a signed token: the cloud's user-token lifetime
|
|
31
|
+
* since the Java issuer (STIGMER_JWT_USER_TOKEN_TTL_SECONDS' default). It
|
|
32
|
+
* bounds how long a user token outlives a rotated secret or a lost key; a
|
|
33
|
+
* deleted client's tokens stop at the next request regardless (the
|
|
34
|
+
* verifier's liveness).
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_PLATFORM_TOKEN_TTL_SECONDS = 900;
|
|
37
|
+
/** The smallest RSA modulus the ring accepts (NIST SP 800-131A's floor). */
|
|
38
|
+
export const MIN_RSA_MODULUS_BITS = 2048;
|
|
39
|
+
export function canSign(ring) {
|
|
40
|
+
return ring.signer !== undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Builds a ring from PEM text, refusing loudly at boot what would fail
|
|
44
|
+
* quietly at the first request: a non-RSA or undersized key, an empty
|
|
45
|
+
* verification set, a non-positive TTL, and a private key whose public
|
|
46
|
+
* half is not among the verification keys (it would sign tokens nothing
|
|
47
|
+
* accepts).
|
|
48
|
+
*/
|
|
49
|
+
export function platformTokenKeyRingFromPem(material) {
|
|
50
|
+
if (material.publicKeyPems.length === 0) {
|
|
51
|
+
throw new Error("a platform-token key ring needs at least one public key to verify against");
|
|
52
|
+
}
|
|
53
|
+
const verificationKeys = material.publicKeyPems.map((pem, index) => requireRsa(parseKey(() => createPublicKey(pem), `public key ${index + 1}`), `public key ${index + 1}`));
|
|
54
|
+
const ttlSeconds = material.ttlSeconds ?? DEFAULT_PLATFORM_TOKEN_TTL_SECONDS;
|
|
55
|
+
if (!Number.isInteger(ttlSeconds) || ttlSeconds <= 0) {
|
|
56
|
+
throw new Error(`platform-token TTL must be a positive integer number of seconds, got ${ttlSeconds}`);
|
|
57
|
+
}
|
|
58
|
+
const base = {
|
|
59
|
+
verificationKeys,
|
|
60
|
+
audience: material.audience ?? "",
|
|
61
|
+
ttlSeconds,
|
|
62
|
+
};
|
|
63
|
+
if (material.privateKeyPem === undefined) {
|
|
64
|
+
return base;
|
|
65
|
+
}
|
|
66
|
+
const privateKey = requireRsa(parseKey(() => createPrivateKey(material.privateKeyPem ?? ""), "private key"), "private key");
|
|
67
|
+
const derivedPublic = spkiOf(createPublicKey(privateKey));
|
|
68
|
+
if (!verificationKeys.some((key) => spkiOf(key).equals(derivedPublic))) {
|
|
69
|
+
throw new Error("the platform-token private key's public half is not among the verification keys — tokens it signed would verify nowhere");
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
signer: { key: privateKey, kid: material.kid ?? kidOf(privateKey) },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The ring a composition runs with, decided once at the keys stage:
|
|
78
|
+
* - no authentication posture: none — a server that trusts every
|
|
79
|
+
* request verifies nothing, so it signs nothing (the mint refuses);
|
|
80
|
+
* - a supplied ring: that ring;
|
|
81
|
+
* - none supplied, open source: open source's own ring on the ladder;
|
|
82
|
+
* - none supplied, any other edition: a boot throw, so a hosted edition
|
|
83
|
+
* can never sign with a key generated into a pod's home directory,
|
|
84
|
+
* which no other replica shares and no restart keeps.
|
|
85
|
+
*/
|
|
86
|
+
export function resolvePlatformTokenKeys(input) {
|
|
87
|
+
if (!input.requireAuthentication) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
if (input.supplied !== undefined) {
|
|
91
|
+
return input.supplied;
|
|
92
|
+
}
|
|
93
|
+
if (input.edition !== ServerEdition.oss) {
|
|
94
|
+
throw new Error(`the ${ServerEdition[input.edition]} edition requires authentication but registers no platform-token key ring — register drivers.platformTokenKeys; only open source generates its own key`);
|
|
95
|
+
}
|
|
96
|
+
return openSourcePlatformTokenKeyRing(input.options);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Open source's ring: one RSA key on the key-manager ladder, signing and
|
|
100
|
+
* verifying, no audience, the default TTL. Throws when an explicitly
|
|
101
|
+
* configured STIGMER_PLATFORM_TOKEN_KEY is unusable (the ladder's rule).
|
|
102
|
+
*/
|
|
103
|
+
export function openSourcePlatformTokenKeyRing(options = {}) {
|
|
104
|
+
const privateKey = getOrCreateKey(RSA_PRIVATE_KEY, PLATFORM_TOKEN_KEY_ENV_VAR, PLATFORM_TOKEN_KEY_FILE_NAME, options);
|
|
105
|
+
return {
|
|
106
|
+
signer: { key: privateKey, kid: kidOf(privateKey) },
|
|
107
|
+
verificationKeys: [createPublicKey(privateKey)],
|
|
108
|
+
audience: "",
|
|
109
|
+
ttlSeconds: DEFAULT_PLATFORM_TOKEN_TTL_SECONDS,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** The ladder codec for an RSA private key stored as PKCS#8 PEM. */
|
|
113
|
+
export const RSA_PRIVATE_KEY = {
|
|
114
|
+
fromEnv(envVar, decoded) {
|
|
115
|
+
const key = parseKey(() => createPrivateKey(decoded.toString("utf8")), envVar);
|
|
116
|
+
return requireRsa(key, envVar);
|
|
117
|
+
},
|
|
118
|
+
fromFile(content) {
|
|
119
|
+
try {
|
|
120
|
+
const key = createPrivateKey(content.toString("utf8"));
|
|
121
|
+
return isUsableRsa(key) ? key : undefined;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
generate() {
|
|
128
|
+
return generateKeyPairSync("rsa", { modulusLength: MIN_RSA_MODULUS_BITS })
|
|
129
|
+
.privateKey;
|
|
130
|
+
},
|
|
131
|
+
toFile(key) {
|
|
132
|
+
return Buffer.from(key.export({ format: "pem", type: "pkcs8" }));
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
/** A key's kid: the first 16 characters of its public half's SHA-256. */
|
|
136
|
+
function kidOf(key) {
|
|
137
|
+
const publicKey = key.type === "private" ? createPublicKey(key) : key;
|
|
138
|
+
return createHash("sha256")
|
|
139
|
+
.update(spkiOf(publicKey))
|
|
140
|
+
.digest("base64url")
|
|
141
|
+
.slice(0, 16);
|
|
142
|
+
}
|
|
143
|
+
function spkiOf(publicKey) {
|
|
144
|
+
return publicKey.export({ format: "der", type: "spki" });
|
|
145
|
+
}
|
|
146
|
+
function parseKey(parse, what) {
|
|
147
|
+
try {
|
|
148
|
+
return parse();
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
throw new Error(`${what} is not a readable PEM key: ${error instanceof Error ? error.message : String(error)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function requireRsa(key, what) {
|
|
155
|
+
if (!isUsableRsa(key)) {
|
|
156
|
+
throw new Error(`${what} must be an RSA key of at least ${MIN_RSA_MODULUS_BITS} bits`);
|
|
157
|
+
}
|
|
158
|
+
return key;
|
|
159
|
+
}
|
|
160
|
+
function isUsableRsa(key) {
|
|
161
|
+
return (key.asymmetricKeyType === "rsa" &&
|
|
162
|
+
(key.asymmetricKeyDetails?.modulusLength ?? 0) >= MIN_RSA_MODULUS_BITS);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=key-ring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-ring.js","sourceRoot":"","sources":["../../src/platformtoken/key-ring.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stigmer/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.25.0",
|
|
4
4
|
"description": "The Stigmer OSS control plane as a composable library: composeServer, the extension registry and the driver seams a composition builds on. The self-contained deployable of the same server is @stigmer/server-slim.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
"@connectrpc/connect-node": "^2.0.0",
|
|
63
63
|
"@kubernetes/client-node": "2.0.0",
|
|
64
64
|
"@opentelemetry/api": "^1.9.0",
|
|
65
|
-
"@stigmer/outbound": "3.
|
|
66
|
-
"@stigmer/plugin-package": "3.
|
|
67
|
-
"@stigmer/protos": "3.
|
|
68
|
-
"@stigmer/temporal-codecs": "3.
|
|
69
|
-
"@stigmer/zip-structure": "3.
|
|
65
|
+
"@stigmer/outbound": "3.25.0",
|
|
66
|
+
"@stigmer/plugin-package": "3.25.0",
|
|
67
|
+
"@stigmer/protos": "3.25.0",
|
|
68
|
+
"@stigmer/temporal-codecs": "3.25.0",
|
|
69
|
+
"@stigmer/zip-structure": "3.25.0",
|
|
70
70
|
"@temporalio/activity": "1.16.2",
|
|
71
71
|
"@temporalio/client": "1.16.2",
|
|
72
72
|
"@temporalio/common": "1.16.2",
|
|
@@ -24,7 +24,7 @@ editions; the cloud stores what open source computes.
|
|
|
24
24
|
(`this` with its type restrictions, `computed`, `from`, `union`), the
|
|
25
25
|
builders, and `declareKind`, which refuses a transcript that cannot be right
|
|
26
26
|
at module load.
|
|
27
|
-
- `model/<kind>.ts` — one transcript per open-source kind (twenty-
|
|
27
|
+
- `model/<kind>.ts` — one transcript per open-source kind (twenty-four), the
|
|
28
28
|
`.fga` file's relations in the file's order, its path in the header.
|
|
29
29
|
`model/index.ts` registers them in `fga.mod` order and answers by kind or by
|
|
30
30
|
FGA type name. Two relations no row carries as a `kind_meta` fact have a
|
|
@@ -124,9 +124,13 @@ const SEEDED_KINDS: ReadonlyArray<ApiResourceKind> = [
|
|
|
124
124
|
afterAll(dropPostgresFixture);
|
|
125
125
|
|
|
126
126
|
describe("NOT_FOUND_EXEMPT_KINDS", () => {
|
|
127
|
-
it("is the cloud's probe exemption restricted to the kinds this edition serves — identity_account and
|
|
127
|
+
it("is the cloud's probe exemption restricted to the kinds this edition serves — identity_account, iam_policy and platform_client", () => {
|
|
128
128
|
expect([...NOT_FOUND_EXEMPT_KINDS].sort()).toEqual(
|
|
129
|
-
[
|
|
129
|
+
[
|
|
130
|
+
ApiResourceKind.identity_account,
|
|
131
|
+
ApiResourceKind.iam_policy,
|
|
132
|
+
ApiResourceKind.platform_client,
|
|
133
|
+
].sort(),
|
|
130
134
|
);
|
|
131
135
|
});
|
|
132
136
|
});
|
|
@@ -78,10 +78,16 @@ import { formatObjectRef } from "./tuples.js";
|
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Kinds whose missing row is a denial, never `not-found` (arm 4): the
|
|
81
|
-
* cloud's `PROBE_EXEMPT_KINDS`
|
|
82
|
-
*
|
|
83
|
-
* `invitation` are refused by
|
|
84
|
-
*
|
|
81
|
+
* cloud's `PROBE_EXEMPT_KINDS` (stigmer-cloud
|
|
82
|
+
* src/authorizer/fga-authorizer.ts) restricted to the kinds this edition
|
|
83
|
+
* serves — `platform`, `identity_provider` and `invitation` are refused by
|
|
84
|
+
* arm 2 before any row is asked about. The cloud exempts them because
|
|
85
|
+
* their rows live in its own tables, out of its probe's reach; matching
|
|
86
|
+
* the set here keeps the wire identical across editions (an unknown
|
|
87
|
+
* platform client's id is PERMISSION_DENIED with the annotation's copy in
|
|
88
|
+
* both). The cloud's model drift test pins every kind here inside its
|
|
89
|
+
* `PROBE_EXEMPT_KINDS`, which is why this set is exported; a kind that
|
|
90
|
+
* moves into open source joins this set in the same change.
|
|
85
91
|
*
|
|
86
92
|
* `iam_policy` is reachable as a target through the IamPolicy RPCs' own
|
|
87
93
|
* `resource.kind` (a caller may name it); the grant scope refuses such a
|
|
@@ -91,6 +97,7 @@ import { formatObjectRef } from "./tuples.js";
|
|
|
91
97
|
export const NOT_FOUND_EXEMPT_KINDS: ReadonlySet<ApiResourceKind> = new Set([
|
|
92
98
|
ApiResourceKind.identity_account,
|
|
93
99
|
ApiResourceKind.iam_policy,
|
|
100
|
+
ApiResourceKind.platform_client,
|
|
94
101
|
]);
|
|
95
102
|
|
|
96
103
|
/** The Java RequestAuthorizationService's pre-check copy, byte for byte (the cloud renders the same). */
|
|
@@ -34,6 +34,7 @@ const DECLARED_KINDS = [
|
|
|
34
34
|
ApiResourceKind.iam_policy,
|
|
35
35
|
ApiResourceKind.api_key,
|
|
36
36
|
ApiResourceKind.oauth_app,
|
|
37
|
+
ApiResourceKind.platform_client,
|
|
37
38
|
ApiResourceKind.organization,
|
|
38
39
|
ApiResourceKind.agent,
|
|
39
40
|
ApiResourceKind.agent_channel,
|
|
@@ -94,6 +95,13 @@ const WIRE_PERMISSIONS_BY_TYPE: Readonly<
|
|
|
94
95
|
"can_grant_access",
|
|
95
96
|
"can_view_access",
|
|
96
97
|
],
|
|
98
|
+
platform_client: [
|
|
99
|
+
"can_view",
|
|
100
|
+
"can_edit",
|
|
101
|
+
"can_delete",
|
|
102
|
+
"can_grant_access",
|
|
103
|
+
"can_view_access",
|
|
104
|
+
],
|
|
97
105
|
organization: [
|
|
98
106
|
"can_view",
|
|
99
107
|
"can_edit",
|
|
@@ -262,7 +270,6 @@ describe("the built-in model's registry", () => {
|
|
|
262
270
|
for (const unserved of [
|
|
263
271
|
ApiResourceKind.platform,
|
|
264
272
|
ApiResourceKind.identity_provider,
|
|
265
|
-
ApiResourceKind.platform_client,
|
|
266
273
|
ApiResourceKind.invitation,
|
|
267
274
|
]) {
|
|
268
275
|
expect(declarationFor(unserved), kindEnumName(unserved)).toBeUndefined();
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
* chain past the depth bound) and for the cloud's drift test, which
|
|
13
13
|
* builds a model from the live `.fga` files and compares.
|
|
14
14
|
*
|
|
15
|
-
* Registered: every kind of the open-source tier — the twenty-
|
|
16
|
-
* `kind_meta.tier: open_source` members — and nothing else. The
|
|
15
|
+
* Registered: every kind of the open-source tier — the twenty-four
|
|
16
|
+
* `kind_meta.tier: open_source` members — and nothing else. The three
|
|
17
17
|
* files `fga.mod` lists that have no declaration here (`platform`,
|
|
18
|
-
* `identity_provider`, `
|
|
19
|
-
*
|
|
18
|
+
* `identity_provider`, `invitation`) are kinds this edition does not
|
|
19
|
+
* serve: a check that targets one is refused by the
|
|
20
20
|
* driver, and a tuple that names one (`identity_provider#platform_user`)
|
|
21
21
|
* resolves to nobody. The registry test pins the list against the tier.
|
|
22
22
|
*/
|
|
@@ -38,6 +38,7 @@ import { mcpServerDeclaration } from "./mcp_server.js";
|
|
|
38
38
|
import { memoryDeclaration } from "./memory.js";
|
|
39
39
|
import { oauthAppDeclaration } from "./oauth_app.js";
|
|
40
40
|
import { organizationDeclaration } from "./organization.js";
|
|
41
|
+
import { platformClientDeclaration } from "./platform_client.js";
|
|
41
42
|
import { pluginDeclaration } from "./plugin.js";
|
|
42
43
|
import type { KindDeclaration } from "./rewrite.js";
|
|
43
44
|
import { scheduleDeclaration } from "./schedule.js";
|
|
@@ -77,6 +78,7 @@ export const builtInModel: Model = newModel([
|
|
|
77
78
|
iamPolicyDeclaration,
|
|
78
79
|
apiKeyDeclaration,
|
|
79
80
|
oauthAppDeclaration,
|
|
81
|
+
platformClientDeclaration,
|
|
80
82
|
organizationDeclaration,
|
|
81
83
|
agentDeclaration,
|
|
82
84
|
agentChannelDeclaration,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Transcript of fga/model/iam/oauth_app.fga —
|
|
3
|
-
*
|
|
2
|
+
* Transcript of fga/model/iam/oauth_app.fga — an organization-scoped kind
|
|
3
|
+
* whose `owner` has NO admin arm while its `viewer` does (platform_client
|
|
4
|
+
* shares the shape, for the same reason): the file's
|
|
4
5
|
* "administrative visibility" for the organization's admins over a
|
|
5
6
|
* registration that holds vendor client credentials, with change and
|
|
6
7
|
* deletion the creator's (or an explicit grant's) alone, and no public
|