@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
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Named-key loader — ports pkg/encryption/keymanager.go.
|
|
3
3
|
*
|
|
4
|
-
* One convention for every
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* One convention for every key the server holds: env var (Base64) →
|
|
5
|
+
* ~/.stigmer/<file> (0600) → auto-generate and persist. The
|
|
6
|
+
* loader is generalized over the env var, the file name and a KeyCodec so
|
|
7
|
+
* sibling key material rides the same ladder instead of growing a
|
|
8
|
+
* divergent loader: the runner-token signing key (oss#535) and the
|
|
9
|
+
* encryption key are 32 raw bytes (RAW_32_BYTE_KEY); the platform-token
|
|
10
|
+
* signing key is an RSA private key (platformtoken/key-ring.ts). The codec
|
|
11
|
+
* says what a key IS — how an env value and a file decode, how one is
|
|
12
|
+
* generated and written — and nothing about the ladder, which stays one.
|
|
9
13
|
*
|
|
10
|
-
* Ladder semantics, exactly Go's:
|
|
14
|
+
* Ladder semantics, exactly Go's, for every codec:
|
|
11
15
|
* - An EXPLICITLY configured env key that is unusable (bad Base64, wrong
|
|
12
|
-
* length) is an ERROR, never a
|
|
13
|
-
* configuration would be worse
|
|
16
|
+
* length, not the key type the codec holds) is an ERROR, never a
|
|
17
|
+
* degrade — silently ignoring deliberate configuration would be worse
|
|
18
|
+
* than refusing to boot.
|
|
14
19
|
* - A key file that fails its load checks (permissions other than 0600,
|
|
15
|
-
*
|
|
16
|
-
* auto-generate, which overwrites the file's content —
|
|
17
|
-
* behavior (loadKeyFromFile error → generate → save),
|
|
20
|
+
* content the codec does not accept) is never adopted; the ladder
|
|
21
|
+
* FALLS THROUGH to auto-generate, which overwrites the file's content —
|
|
22
|
+
* Go's shipped behavior (loadKeyFromFile error → generate → save),
|
|
23
|
+
* ported as-is.
|
|
18
24
|
* - Auto-generation persists for future boots; a persist FAILURE is a
|
|
19
25
|
* stderr warning only — the key is still usable for this process.
|
|
20
26
|
*
|
|
@@ -50,34 +56,81 @@ export interface KeyLoaderOptions {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
59
|
+
* What one kind of key is, for the ladder. `fromEnv` receives the env
|
|
60
|
+
* value already strictly Base64-decoded and THROWS when the bytes are not
|
|
61
|
+
* a usable key (explicit configuration is never silently ignored);
|
|
62
|
+
* `fromFile` answers `undefined` for content it does not accept, so the
|
|
63
|
+
* ladder falls through exactly as it does for a wrong-sized raw key.
|
|
64
|
+
*/
|
|
65
|
+
export interface KeyCodec<Key> {
|
|
66
|
+
fromEnv(envVar: string, decoded: Buffer): Key;
|
|
67
|
+
fromFile(content: Buffer): Key | undefined;
|
|
68
|
+
generate(): Key;
|
|
69
|
+
toFile(key: Key): Buffer;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The 32 raw bytes the encryption and runner-token keys are. */
|
|
73
|
+
export const RAW_32_BYTE_KEY: KeyCodec<Buffer> = {
|
|
74
|
+
fromEnv(envVar, decoded) {
|
|
75
|
+
if (decoded.length !== KEY_SIZE) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`${envVar} must be exactly 32 bytes (256 bits) when decoded, got ${decoded.length} bytes`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return decoded;
|
|
81
|
+
},
|
|
82
|
+
fromFile(content) {
|
|
83
|
+
return content.length === KEY_SIZE ? content : undefined;
|
|
84
|
+
},
|
|
85
|
+
generate() {
|
|
86
|
+
return randomBytes(KEY_SIZE);
|
|
87
|
+
},
|
|
88
|
+
toFile(key) {
|
|
89
|
+
return key;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Go GetOrCreateNamedKey: env var → key file → auto-generate, for a 32-byte
|
|
95
|
+
* raw key. Throws only on unusable EXPLICIT configuration (bad env value)
|
|
96
|
+
* or when no key can be produced at all.
|
|
56
97
|
*/
|
|
57
98
|
export function getOrCreateNamedKey(
|
|
58
99
|
envVar: string,
|
|
59
100
|
fileName: string,
|
|
60
101
|
options: KeyLoaderOptions = {},
|
|
61
102
|
): Buffer {
|
|
103
|
+
return getOrCreateKey(RAW_32_BYTE_KEY, envVar, fileName, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The ladder over any codec: env var → key file → auto-generate. */
|
|
107
|
+
export function getOrCreateKey<Key>(
|
|
108
|
+
codec: KeyCodec<Key>,
|
|
109
|
+
envVar: string,
|
|
110
|
+
fileName: string,
|
|
111
|
+
options: KeyLoaderOptions = {},
|
|
112
|
+
): Key {
|
|
62
113
|
const env = options.env ?? process.env;
|
|
63
114
|
|
|
64
|
-
// 1. Environment variable (highest priority) — Base64-encoded
|
|
115
|
+
// 1. Environment variable (highest priority) — Base64-encoded.
|
|
65
116
|
const envValue = env[envVar];
|
|
66
117
|
if (envValue !== undefined && envValue !== "") {
|
|
67
|
-
return
|
|
118
|
+
return codec.fromEnv(envVar, decodeEnvBase64(envVar, envValue));
|
|
68
119
|
}
|
|
69
120
|
|
|
70
|
-
// 2. Local key file —
|
|
121
|
+
// 2. Local key file — refused on insecure permissions or content the
|
|
122
|
+
// codec does not accept.
|
|
71
123
|
const keyPath = namedKeyFilePath(fileName, options);
|
|
72
|
-
const
|
|
124
|
+
const content = readKeyFile(keyPath);
|
|
125
|
+
const fromFile = content === undefined ? undefined : codec.fromFile(content);
|
|
73
126
|
if (fromFile !== undefined) {
|
|
74
127
|
return fromFile;
|
|
75
128
|
}
|
|
76
129
|
|
|
77
130
|
// 3. Auto-generate (local development); persist for future boots.
|
|
78
|
-
const key =
|
|
131
|
+
const key = codec.generate();
|
|
79
132
|
try {
|
|
80
|
-
saveKeyToFile(keyPath, key);
|
|
133
|
+
saveKeyToFile(keyPath, codec.toFile(key));
|
|
81
134
|
} catch (error) {
|
|
82
135
|
// Warn but don't fail — the key is still usable (Go's posture).
|
|
83
136
|
process.stderr.write(
|
|
@@ -87,22 +140,17 @@ export function getOrCreateNamedKey(
|
|
|
87
140
|
return key;
|
|
88
141
|
}
|
|
89
142
|
|
|
90
|
-
/** Strict Base64 decode
|
|
91
|
-
function
|
|
92
|
-
const
|
|
143
|
+
/** Strict Base64 decode for an env-configured key. */
|
|
144
|
+
function decodeEnvBase64(envVar: string, value: string): Buffer {
|
|
145
|
+
const decoded = Buffer.from(value, "base64");
|
|
93
146
|
// Node's base64 decoder is lenient (skips invalid characters, accepts
|
|
94
147
|
// missing padding) and never throws; Go's StdEncoding errors on any
|
|
95
148
|
// non-canonical input. Round-tripping — Buffer always re-emits the
|
|
96
149
|
// canonical padded form — restores Go's strictness.
|
|
97
|
-
if (
|
|
150
|
+
if (decoded.toString("base64") !== value) {
|
|
98
151
|
throw new Error(`invalid Base64 encoding in ${envVar}`);
|
|
99
152
|
}
|
|
100
|
-
|
|
101
|
-
throw new Error(
|
|
102
|
-
`${envVar} must be exactly 32 bytes (256 bits) when decoded, got ${key.length} bytes`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
return key;
|
|
153
|
+
return decoded;
|
|
106
154
|
}
|
|
107
155
|
|
|
108
156
|
/** Path to a named key file under <home>/.stigmer. */
|
|
@@ -115,13 +163,13 @@ export function namedKeyFilePath(
|
|
|
115
163
|
}
|
|
116
164
|
|
|
117
165
|
/**
|
|
118
|
-
* Reads
|
|
119
|
-
* undefined (fall through the ladder); present
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
166
|
+
* Reads a key file's bytes, mirroring Go loadKeyFromFile: missing file →
|
|
167
|
+
* undefined (fall through the ladder); present with insecure permissions
|
|
168
|
+
* also falls through — exactly Go's behavior, where any load error falls
|
|
169
|
+
* to auto-generate. The 0600 check is skipped on Windows, where POSIX
|
|
170
|
+
* modes are not meaningful.
|
|
123
171
|
*/
|
|
124
|
-
function
|
|
172
|
+
function readKeyFile(keyPath: string): Buffer | undefined {
|
|
125
173
|
let stat: fs.Stats;
|
|
126
174
|
try {
|
|
127
175
|
stat = fs.statSync(keyPath);
|
|
@@ -134,23 +182,18 @@ function loadKeyFromFile(keyPath: string): Buffer | undefined {
|
|
|
134
182
|
return undefined;
|
|
135
183
|
}
|
|
136
184
|
}
|
|
137
|
-
let key: Buffer;
|
|
138
185
|
try {
|
|
139
|
-
|
|
186
|
+
return fs.readFileSync(keyPath);
|
|
140
187
|
} catch {
|
|
141
188
|
return undefined;
|
|
142
189
|
}
|
|
143
|
-
if (key.length !== KEY_SIZE) {
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
return key;
|
|
147
190
|
}
|
|
148
191
|
|
|
149
192
|
/** Writes the key with secure permissions (dir 0700, file 0600). */
|
|
150
|
-
function saveKeyToFile(keyPath: string,
|
|
193
|
+
function saveKeyToFile(keyPath: string, content: Buffer): void {
|
|
151
194
|
fs.mkdirSync(path.dirname(keyPath), {
|
|
152
195
|
recursive: true,
|
|
153
196
|
mode: KEY_DIR_PERMISSIONS,
|
|
154
197
|
});
|
|
155
|
-
fs.writeFileSync(keyPath,
|
|
198
|
+
fs.writeFileSync(keyPath, content, { mode: KEY_FILE_PERMISSIONS });
|
|
156
199
|
}
|
|
@@ -549,8 +549,9 @@ describe("extension composition (require-authentication posture)", () => {
|
|
|
549
549
|
// composes its Authorizer, and with it the runner-subject verifier
|
|
550
550
|
// between the API-key lane and the unit's own (runnerauth/
|
|
551
551
|
// runner-subject-verifier.ts). A unit with its own Authorizer gets
|
|
552
|
-
// neither (runner-subject-composed.test.ts pins that chain).
|
|
553
|
-
|
|
552
|
+
// neither (runner-subject-composed.test.ts pins that chain). The
|
|
553
|
+
// PlatformClient lane rides the posture itself, ahead of both.
|
|
554
|
+
expect(parsed.verifiers).toBe("apikey, platform-client, runner, fake-verifier");
|
|
554
555
|
});
|
|
555
556
|
|
|
556
557
|
it("a declared posture whose unit registers no verifier of its own is a boot throw naming the unit — the API-key lane alone cannot bootstrap", async () => {
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The PlatformClient lane on a composed server in the authentication
|
|
3
|
+
* posture with the built-in Authorizer (a unit that vouches for tokens,
|
|
4
|
+
* declares the posture and registers no Authorizer — the open-source OIDC
|
|
5
|
+
* self-host's shape), with the key ring supplied so the run is hermetic:
|
|
6
|
+
* - the founder creates a client; an anonymous mint answers a token; the
|
|
7
|
+
* token authenticates as the end user's derived account, which holds the
|
|
8
|
+
* auto-grant role on the owning organization and sees that one alone;
|
|
9
|
+
* - the platform-client verifier sits ahead of the unit's verifier and
|
|
10
|
+
* claims only its lane: the founder's tokens still pass, and a TYPED
|
|
11
|
+
* platform token (a lane open source does not speak) is refused as
|
|
12
|
+
* unclaimed;
|
|
13
|
+
* - the origin guard refuses an unlisted Origin with the cloud's copy and
|
|
14
|
+
* passes a request without one;
|
|
15
|
+
* - deleting the client refuses its token on the next request with the
|
|
16
|
+
* cloud's liveness copy;
|
|
17
|
+
* - an outsider cannot read the client by reference.
|
|
18
|
+
*/
|
|
19
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
20
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
|
|
24
|
+
import { Code, ConnectError, createClient } from "@connectrpc/connect";
|
|
25
|
+
import type { Interceptor, Transport } from "@connectrpc/connect";
|
|
26
|
+
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
27
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
28
|
+
|
|
29
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
30
|
+
import { IdentityAccountCommandController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/command_pb";
|
|
31
|
+
import { IdentityAccountQueryController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/query_pb";
|
|
32
|
+
import { PlatformClientCommandController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/command_pb";
|
|
33
|
+
import { PlatformClientQueryController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/query_pb";
|
|
34
|
+
import { PlatformClientTokenController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/token_pb";
|
|
35
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
36
|
+
import { OrganizationCommandController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/command_pb";
|
|
37
|
+
import { OrganizationQueryController } from "@stigmer/protos/ai/stigmer/tenancy/organization/v1/query_pb";
|
|
38
|
+
|
|
39
|
+
import { loadConfig } from "../../boot/config.js";
|
|
40
|
+
import { composeServer } from "../../boot/compose.js";
|
|
41
|
+
import type { ComposedServer } from "../../boot/compose.js";
|
|
42
|
+
import { accountIdFor } from "../../domain/identityaccount/constants.js";
|
|
43
|
+
import {
|
|
44
|
+
DELETED_CLIENT_MESSAGE,
|
|
45
|
+
originRefusalMessage,
|
|
46
|
+
} from "../../domain/platformclient/constants.js";
|
|
47
|
+
import { signPlatformToken } from "../../platformtoken/envelope.js";
|
|
48
|
+
import { platformTokenKeyRingFromPem } from "../../platformtoken/key-ring.js";
|
|
49
|
+
import type { SigningPlatformTokenKeyRing } from "../../platformtoken/key-ring.js";
|
|
50
|
+
import type { ServerExtension } from "../registry.js";
|
|
51
|
+
import {
|
|
52
|
+
baseConfig,
|
|
53
|
+
bearer,
|
|
54
|
+
fakeJwt,
|
|
55
|
+
fakeVerifier,
|
|
56
|
+
silentLogger,
|
|
57
|
+
transportFor,
|
|
58
|
+
} from "./composed-support.js";
|
|
59
|
+
|
|
60
|
+
const FOUNDER = "fake|pc-founder";
|
|
61
|
+
const OUTSIDER = "fake|pc-outsider";
|
|
62
|
+
const ORG = "pc-org";
|
|
63
|
+
|
|
64
|
+
const { privateKey, publicKey } = generateKeyPairSync("rsa", {
|
|
65
|
+
modulusLength: 2048,
|
|
66
|
+
});
|
|
67
|
+
const built = platformTokenKeyRingFromPem({
|
|
68
|
+
privateKeyPem: privateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
|
69
|
+
publicKeyPems: [publicKey.export({ format: "pem", type: "spki" }).toString()],
|
|
70
|
+
});
|
|
71
|
+
if (built.signer === undefined) throw new Error("fixture ring must sign");
|
|
72
|
+
const RING: SigningPlatformTokenKeyRing = { ...built, signer: built.signer };
|
|
73
|
+
|
|
74
|
+
/** Stamps the lowercase `origin` header a browser attaches to a cross-origin call. */
|
|
75
|
+
function browserOrigin(origin: string): Interceptor {
|
|
76
|
+
return (next) => (request) => {
|
|
77
|
+
request.header.set("origin", origin);
|
|
78
|
+
return next(request);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function refusal(promise: Promise<unknown>): Promise<ConnectError> {
|
|
83
|
+
try {
|
|
84
|
+
await promise;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof ConnectError) return error;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
throw new Error("expected a ConnectError refusal");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe("PlatformClient on a composed server (authentication posture, built-in Authorizer)", () => {
|
|
93
|
+
let dir: string;
|
|
94
|
+
let server: ComposedServer;
|
|
95
|
+
let port: number;
|
|
96
|
+
|
|
97
|
+
const asFounder = (): Transport =>
|
|
98
|
+
transportFor(port, fakeJwt(FOUNDER, "founder@example.com"));
|
|
99
|
+
const asOutsider = (): Transport =>
|
|
100
|
+
transportFor(port, fakeJwt(OUTSIDER, "outsider@example.com"));
|
|
101
|
+
const presenting = (token: string, origin?: string): Transport =>
|
|
102
|
+
createGrpcTransport({
|
|
103
|
+
baseUrl: `http://127.0.0.1:${port}`,
|
|
104
|
+
interceptors: [
|
|
105
|
+
bearer(token),
|
|
106
|
+
...(origin !== undefined ? [browserOrigin(origin)] : []),
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
beforeAll(async () => {
|
|
111
|
+
dir = mkdtempSync(path.join(tmpdir(), "platform-client-composed-"));
|
|
112
|
+
const unit: ServerExtension = {
|
|
113
|
+
name: "fake-oidc-only",
|
|
114
|
+
requireAuthentication: true,
|
|
115
|
+
identityVerifiers: [fakeVerifier],
|
|
116
|
+
drivers: { platformTokenKeys: RING },
|
|
117
|
+
};
|
|
118
|
+
server = await composeServer({
|
|
119
|
+
config: loadConfig(baseConfig(dir)),
|
|
120
|
+
logger: silentLogger,
|
|
121
|
+
extensions: [unit],
|
|
122
|
+
portOverride: 0,
|
|
123
|
+
host: "127.0.0.1",
|
|
124
|
+
});
|
|
125
|
+
port = await server.start();
|
|
126
|
+
|
|
127
|
+
await createClient(
|
|
128
|
+
IdentityAccountCommandController,
|
|
129
|
+
asFounder(),
|
|
130
|
+
).provisionMyAccount({});
|
|
131
|
+
await createClient(OrganizationCommandController, asFounder()).create({
|
|
132
|
+
apiVersion: "tenancy.stigmer.ai/v1",
|
|
133
|
+
kind: "Organization",
|
|
134
|
+
metadata: { name: ORG, slug: ORG, org: "" },
|
|
135
|
+
spec: { description: ORG },
|
|
136
|
+
});
|
|
137
|
+
await createClient(
|
|
138
|
+
IdentityAccountCommandController,
|
|
139
|
+
asOutsider(),
|
|
140
|
+
).provisionMyAccount({});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
afterAll(async () => {
|
|
144
|
+
await server?.shutdown();
|
|
145
|
+
rmSync(dir, { recursive: true, force: true });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
async function createPlatformClient(allowedOrigins: string[] = []) {
|
|
149
|
+
const created = await createClient(
|
|
150
|
+
PlatformClientCommandController,
|
|
151
|
+
asFounder(),
|
|
152
|
+
).create({
|
|
153
|
+
apiVersion: "iam.stigmer.ai/v1",
|
|
154
|
+
kind: "PlatformClient",
|
|
155
|
+
metadata: {
|
|
156
|
+
name: `client-${Math.random().toString(36).slice(2, 8)}`,
|
|
157
|
+
org: ORG,
|
|
158
|
+
},
|
|
159
|
+
spec: {
|
|
160
|
+
autoProvisionAccounts: true,
|
|
161
|
+
autoGrantOnOrg: true,
|
|
162
|
+
autoGrantRole: IamRole.member,
|
|
163
|
+
allowedOrigins,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
id: created.platformClient?.metadata?.id ?? "",
|
|
168
|
+
slug: created.platformClient?.metadata?.slug ?? "",
|
|
169
|
+
clientId: created.platformClient?.spec?.clientId ?? "",
|
|
170
|
+
clientSecret: created.clientSecret,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function mint(
|
|
175
|
+
client: { clientId: string; clientSecret: string },
|
|
176
|
+
userId: string,
|
|
177
|
+
) {
|
|
178
|
+
const minted = await createClient(
|
|
179
|
+
PlatformClientTokenController,
|
|
180
|
+
transportFor(port),
|
|
181
|
+
).mintUserToken({
|
|
182
|
+
clientId: client.clientId,
|
|
183
|
+
clientSecret: client.clientSecret,
|
|
184
|
+
userId,
|
|
185
|
+
userEmail: `${userId}@product.example`,
|
|
186
|
+
userName: "Product User",
|
|
187
|
+
});
|
|
188
|
+
return minted.accessToken;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
it("mints for the end user's derived account, which sees its owning organization alone", async () => {
|
|
192
|
+
const client = await createPlatformClient();
|
|
193
|
+
const token = await mint(client, "user-1");
|
|
194
|
+
|
|
195
|
+
const me = await createClient(
|
|
196
|
+
IdentityAccountQueryController,
|
|
197
|
+
presenting(token),
|
|
198
|
+
).whoAmI({});
|
|
199
|
+
expect(me.metadata?.id).toBe(accountIdFor(`stgm_pc|${ORG}|user-1`));
|
|
200
|
+
|
|
201
|
+
const mine = await createClient(
|
|
202
|
+
OrganizationQueryController,
|
|
203
|
+
presenting(token),
|
|
204
|
+
).findMyOrganizations({});
|
|
205
|
+
expect(
|
|
206
|
+
mine.entries.map((organization) => organization.metadata?.slug),
|
|
207
|
+
).toEqual([ORG]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("claims only its lane: the unit's tokens still pass, and a typed platform token is refused as unclaimed", async () => {
|
|
211
|
+
await createClient(
|
|
212
|
+
OrganizationQueryController,
|
|
213
|
+
asFounder(),
|
|
214
|
+
).findMyOrganizations({});
|
|
215
|
+
|
|
216
|
+
const guest = signPlatformToken(RING, {
|
|
217
|
+
sub: "ida_guest",
|
|
218
|
+
token_type: "guest",
|
|
219
|
+
}).token;
|
|
220
|
+
const refused = await refusal(
|
|
221
|
+
createClient(
|
|
222
|
+
OrganizationQueryController,
|
|
223
|
+
presenting(guest),
|
|
224
|
+
).findMyOrganizations({}),
|
|
225
|
+
);
|
|
226
|
+
expect(refused.code).toBe(Code.Unauthenticated);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("refuses an unlisted Origin with the cloud's copy and passes a request without one", async () => {
|
|
230
|
+
const client = await createPlatformClient(["https://app.example"]);
|
|
231
|
+
const token = await mint(client, "user-2");
|
|
232
|
+
|
|
233
|
+
const denied = await refusal(
|
|
234
|
+
createClient(
|
|
235
|
+
OrganizationQueryController,
|
|
236
|
+
presenting(token, "https://evil.example"),
|
|
237
|
+
).findMyOrganizations({}),
|
|
238
|
+
);
|
|
239
|
+
expect(denied.code).toBe(Code.PermissionDenied);
|
|
240
|
+
expect(denied.rawMessage).toBe(
|
|
241
|
+
originRefusalMessage("https://evil.example"),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
await createClient(
|
|
245
|
+
OrganizationQueryController,
|
|
246
|
+
presenting(token),
|
|
247
|
+
).findMyOrganizations({});
|
|
248
|
+
await createClient(
|
|
249
|
+
OrganizationQueryController,
|
|
250
|
+
presenting(token, "https://app.example"),
|
|
251
|
+
).findMyOrganizations({});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("refuses a deleted client's token on the next request with the liveness copy", async () => {
|
|
255
|
+
const client = await createPlatformClient();
|
|
256
|
+
const token = await mint(client, "user-3");
|
|
257
|
+
await createClient(
|
|
258
|
+
OrganizationQueryController,
|
|
259
|
+
presenting(token),
|
|
260
|
+
).findMyOrganizations({});
|
|
261
|
+
|
|
262
|
+
await createClient(PlatformClientCommandController, asFounder()).delete({
|
|
263
|
+
resourceId: client.id,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const denied = await refusal(
|
|
267
|
+
createClient(
|
|
268
|
+
OrganizationQueryController,
|
|
269
|
+
presenting(token),
|
|
270
|
+
).findMyOrganizations({}),
|
|
271
|
+
);
|
|
272
|
+
expect(denied.code).toBe(Code.Unauthenticated);
|
|
273
|
+
expect(denied.rawMessage).toBe(DELETED_CLIENT_MESSAGE);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("an outsider cannot read the client by reference", async () => {
|
|
277
|
+
const client = await createPlatformClient();
|
|
278
|
+
const denied = await refusal(
|
|
279
|
+
createClient(PlatformClientQueryController, asOutsider()).getByReference({
|
|
280
|
+
org: ORG,
|
|
281
|
+
slug: client.slug,
|
|
282
|
+
kind: ApiResourceKind.platform_client,
|
|
283
|
+
}),
|
|
284
|
+
);
|
|
285
|
+
expect(denied.code).toBe(Code.PermissionDenied);
|
|
286
|
+
expect(denied.rawMessage).toBe("unauthorized to view platform client");
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -193,7 +193,10 @@ describe("resolveExtensions — merge semantics", () => {
|
|
|
193
193
|
"g2",
|
|
194
194
|
"g3",
|
|
195
195
|
]);
|
|
196
|
-
expect(resolved.services).toEqual([
|
|
196
|
+
expect(resolved.services).toEqual([
|
|
197
|
+
{ unit: "alpha", register: registerA },
|
|
198
|
+
{ unit: "beta", register: registerB },
|
|
199
|
+
]);
|
|
197
200
|
expect(resolved.workers).toEqual([workerFactory]);
|
|
198
201
|
expect(resolved.statusObservers).toEqual([observerA]);
|
|
199
202
|
expect(resolved.responseDecorators).toEqual([decoratorB]);
|
|
@@ -218,6 +218,14 @@ describe("tier truthfulness against the empty composition", () => {
|
|
|
218
218
|
);
|
|
219
219
|
expect(served.has("iampolicy")).toBe(true);
|
|
220
220
|
});
|
|
221
|
+
|
|
222
|
+
it("platform_client is open_source and served in the empty composition — the mint refuses without a posture, the kind does not", () => {
|
|
223
|
+
const served = servedKindSegments(server.routes);
|
|
224
|
+
expect(getKindMeta(ApiResourceKind.platform_client).tier).toBe(
|
|
225
|
+
ResourceTier.open_source,
|
|
226
|
+
);
|
|
227
|
+
expect(served.has("platformclient")).toBe(true);
|
|
228
|
+
});
|
|
221
229
|
});
|
|
222
230
|
|
|
223
231
|
describe("the check bites (mutation proofs over the same function)", () => {
|
|
@@ -10,9 +10,14 @@
|
|
|
10
10
|
* allowed_origins, oss#375). Java separates the two classes the same way.
|
|
11
11
|
*
|
|
12
12
|
* List point — guards concatenate in unit order and run in composed
|
|
13
|
-
* order; the first throw wins.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* order; the first throw wins. Open source's own guards run first (the
|
|
14
|
+
* verifier list's "OSS entries first" rule): today the PlatformClient
|
|
15
|
+
* origin guard (domain/platformclient/origin-guard.ts), composed only
|
|
16
|
+
* under an authentication posture, where PlatformClient tokens exist.
|
|
17
|
+
* Liveness is not a guard in open source: the PlatformClient verifier
|
|
18
|
+
* refuses a deleted client's tokens itself, so verifier-only lanes (the
|
|
19
|
+
* extension HTTP edges) refuse them too. With no posture and no unit
|
|
20
|
+
* guards, the serving chain runs no guard at all.
|
|
16
21
|
*
|
|
17
22
|
* The in-process exemption is STRUCTURAL, not policed: guards are a
|
|
18
23
|
* parameter of the serving chain's identity source only
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
* it reaches a URL a user supplied: the MCP endpoint it probes at save
|
|
36
36
|
* time and the login server it reaches on Sign in) — landed with the
|
|
37
37
|
* save-time OAuth completion for URL-only MCP servers, 2026-09-19
|
|
38
|
+
* - platform-client store + platform-token keys + guest-token minting
|
|
39
|
+
* (PlatformClient served in every edition: the port the cloud's
|
|
40
|
+
* `cloud.iam_platform_client` driver fills, the RS256 key ring every
|
|
41
|
+
* self-signed token rides, and the one token method only an edition
|
|
42
|
+
* that hosts shared-agent pages serves) — landed 2026-09-23
|
|
38
43
|
*
|
|
39
44
|
* Merge rules (enforced by resolveExtensions, DD-006 §2b): the two
|
|
40
45
|
* provider kinds are single-instance points — a second declaring unit is
|
|
@@ -50,12 +55,15 @@ import type { ArtifactStorageDriverFactory } from "../artifactstorage/artifact-s
|
|
|
50
55
|
import type { ChannelRuntime } from "../domain/agentchannel/channel-runtime.js";
|
|
51
56
|
import type { IdentityAccountStore } from "../domain/identityaccount/store.js";
|
|
52
57
|
import type { IamPolicyStore } from "../domain/iampolicy/store.js";
|
|
58
|
+
import type { PlatformClientStore } from "../domain/platformclient/store.js";
|
|
53
59
|
import type { SecretCodec } from "../encryption/codec.js";
|
|
54
60
|
import type { ModelCatalogProvider } from "../domain/workflow/registry/model-catalog-provider.js";
|
|
55
61
|
import type { VisitorErrorPolicy } from "../pipeline/interceptors/error-boundary.js";
|
|
62
|
+
import type { PlatformTokenKeyRing } from "../platformtoken/key-ring.js";
|
|
56
63
|
import type { RunnerCredentialProvider } from "../runnerauth/runner-credential-provider.js";
|
|
57
64
|
import type { SandboxProvisionerFactory } from "../sandbox/provisioner.js";
|
|
58
65
|
import type { AuthorizationQueryEngine } from "./authorization-queries.js";
|
|
66
|
+
import type { GuestTokenMinting } from "./guest-token-minting.js";
|
|
59
67
|
import type { IdentityFederation } from "./identity-federation.js";
|
|
60
68
|
import type { LicenseStatusProvider } from "./license-status.js";
|
|
61
69
|
import type { OutboundEgressPolicy } from "./outbound-egress.js";
|
|
@@ -247,4 +255,33 @@ export interface ExtensionDrivers {
|
|
|
247
255
|
* (extensions/outbound-egress.ts carries the contract).
|
|
248
256
|
*/
|
|
249
257
|
readonly outboundEgress?: OutboundEgressPolicy;
|
|
258
|
+
/**
|
|
259
|
+
* The platform-client store driver (single-instance point). The
|
|
260
|
+
* platform-client domain's persistence is a PORT like the
|
|
261
|
+
* identity-account domain's: when composed, every chain, the mint and
|
|
262
|
+
* the verifier's liveness read go through this driver (the cloud serves
|
|
263
|
+
* the domain over its own `cloud.iam_platform_client` table this way,
|
|
264
|
+
* legacy `pc_` ids included). When absent, the OSS adapter over the
|
|
265
|
+
* generic Store installs at the compose.ts consumption site.
|
|
266
|
+
*/
|
|
267
|
+
readonly platformClientStore?: PlatformClientStore;
|
|
268
|
+
/**
|
|
269
|
+
* The platform-token key ring (single-instance point): the RS256 keys
|
|
270
|
+
* every token the server signs for itself rides
|
|
271
|
+
* (platformtoken/key-ring.ts). Consumed only under an authentication
|
|
272
|
+
* posture — nothing verifies a token on a server that trusts every
|
|
273
|
+
* request. When absent there, open source composes its own ring on the
|
|
274
|
+
* key-manager ladder; a composition declaring an edition other than
|
|
275
|
+
* open source must supply one (compose.ts refuses to boot without it,
|
|
276
|
+
* so a hosted edition never signs with a generated key).
|
|
277
|
+
*/
|
|
278
|
+
readonly platformTokenKeys?: PlatformTokenKeyRing;
|
|
279
|
+
/**
|
|
280
|
+
* The guest-token capability (single-instance point): the one
|
|
281
|
+
* PlatformClientTokenController method only an edition hosting
|
|
282
|
+
* shared-agent pages serves. When absent, `mintGuestToken` refuses
|
|
283
|
+
* UNIMPLEMENTED with the edition sentence
|
|
284
|
+
* (extensions/guest-token-minting.ts carries the contract).
|
|
285
|
+
*/
|
|
286
|
+
readonly guestTokenMinting?: GuestTokenMinting;
|
|
250
287
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guest-token capability: `PlatformClientTokenController.mintGuestToken`,
|
|
3
|
+
* the credential-free mint for anonymous visitors of a shared agent's
|
|
4
|
+
* hosted page. Single instance, registered as `drivers.guestTokenMinting`
|
|
5
|
+
* (the identityFederation shape: a driver point whose absence is a
|
|
6
|
+
* refusal, never a silent no-op).
|
|
7
|
+
*
|
|
8
|
+
* Why a capability and not a second service: open source registers the
|
|
9
|
+
* whole PlatformClientTokenController, because `mintUserToken` is core,
|
|
10
|
+
* and the composition refuses an extension that registers a service the
|
|
11
|
+
* core already routes. Guest sharing — the hosted page, its rate limits,
|
|
12
|
+
* the share resolution, the org's system-managed client and guest
|
|
13
|
+
* account — is an edition's, so the one method that needs it is
|
|
14
|
+
* dispatched here.
|
|
15
|
+
*
|
|
16
|
+
* Absent, the controller answers UNIMPLEMENTED with the edition sentence
|
|
17
|
+
* (domain/platformclient/constants.ts). Present, the capability owns the
|
|
18
|
+
* whole arm: the request has passed the protovalidate interceptor, and
|
|
19
|
+
* `headers` carries the request's `origin` for the embed checks. It signs
|
|
20
|
+
* with the ring its own composition supplied (`signPlatformToken`).
|
|
21
|
+
*/
|
|
22
|
+
import type {
|
|
23
|
+
MintGuestTokenRequest,
|
|
24
|
+
MintGuestTokenResponse,
|
|
25
|
+
} from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/token_pb";
|
|
26
|
+
|
|
27
|
+
export interface GuestTokenMinting {
|
|
28
|
+
mintGuestToken(
|
|
29
|
+
request: MintGuestTokenRequest,
|
|
30
|
+
headers: Headers,
|
|
31
|
+
): Promise<MintGuestTokenResponse>;
|
|
32
|
+
}
|
|
@@ -23,10 +23,12 @@
|
|
|
23
23
|
* known classes from the type surface).
|
|
24
24
|
*
|
|
25
25
|
* `internal` is ratified contract with a structural guarantee: it is
|
|
26
|
-
* minted ONLY by
|
|
27
|
-
* (boot/inprocess.ts)
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* minted ONLY by server code, in pipeline/interceptors/auth.ts — the
|
|
27
|
+
* in-process chain's identity interceptor (boot/inprocess.ts) and
|
|
28
|
+
* `serverActingFor`, the server acting for a principal it authenticated
|
|
29
|
+
* itself (the PlatformClient mint). No IdentityVerifier may produce it —
|
|
30
|
+
* the serving chain always overwrites the position-1 identity from the
|
|
31
|
+
* wire, so a spoofed internal class cannot enter through a transport.
|
|
30
32
|
*/
|
|
31
33
|
export type CallerClass =
|
|
32
34
|
| "user"
|