@stigmer/server 3.24.0 → 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/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/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/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/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/interceptors/auth.ts +27 -0
- package/src/pipeline/steps/__tests__/references.test.ts +2 -0
- package/src/pipeline/steps/build-update-state.ts +5 -2
- 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,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the PlatformClient user-token verifier and the origin guard, the two
|
|
3
|
+
* halves of the minting client's contract on every request a token bears:
|
|
4
|
+
* - the verifier claims only an untyped platform token, passes a foreign
|
|
5
|
+
* or typed one, refuses a bad one with the envelope's copy, refuses a
|
|
6
|
+
* token naming no client, refuses a deleted client's token with the
|
|
7
|
+
* cloud's liveness copy, lets a store fault propagate (never a
|
|
8
|
+
* revocation), and stamps the account from `sub` as a `user`;
|
|
9
|
+
* - the guard reads no client for a request without an Origin, passes an
|
|
10
|
+
* open allowlist and a listed origin case-insensitively, refuses an
|
|
11
|
+
* unlisted origin and the opaque "null" with the cloud's copy, and
|
|
12
|
+
* passes every credential that is not an untyped platform user token.
|
|
13
|
+
*/
|
|
14
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
15
|
+
|
|
16
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
17
|
+
import { create } from "@bufbuild/protobuf";
|
|
18
|
+
import { describe, expect, it, vi } from "vitest";
|
|
19
|
+
|
|
20
|
+
import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
|
|
21
|
+
import { PlatformClientSchema } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
|
|
22
|
+
import { PlatformClientQueryController } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/query_pb";
|
|
23
|
+
|
|
24
|
+
import { silentLogger } from "../../../extensions/__tests__/composed-support.js";
|
|
25
|
+
import type { CallerIdentity } from "../../../extensions/identity.js";
|
|
26
|
+
import { signPlatformToken } from "../../../platformtoken/envelope.js";
|
|
27
|
+
import { platformTokenKeyRingFromPem } from "../../../platformtoken/key-ring.js";
|
|
28
|
+
import type { SigningPlatformTokenKeyRing } from "../../../platformtoken/key-ring.js";
|
|
29
|
+
import { DELETED_CLIENT_MESSAGE, originRefusalMessage } from "../constants.js";
|
|
30
|
+
import { newPlatformClientOriginGuard } from "../origin-guard.js";
|
|
31
|
+
import { newPlatformClientTokenVerifier } from "../verifier.js";
|
|
32
|
+
|
|
33
|
+
const { privateKey, publicKey } = generateKeyPairSync("rsa", {
|
|
34
|
+
modulusLength: 2048,
|
|
35
|
+
});
|
|
36
|
+
const built = platformTokenKeyRingFromPem({
|
|
37
|
+
privateKeyPem: privateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
|
38
|
+
publicKeyPems: [publicKey.export({ format: "pem", type: "spki" }).toString()],
|
|
39
|
+
});
|
|
40
|
+
if (built.signer === undefined) throw new Error("fixture ring must sign");
|
|
41
|
+
const ring: SigningPlatformTokenKeyRing = { ...built, signer: built.signer };
|
|
42
|
+
|
|
43
|
+
const silent = silentLogger;
|
|
44
|
+
|
|
45
|
+
function client(allowedOrigins: string[] = []): PlatformClient {
|
|
46
|
+
return create(PlatformClientSchema, {
|
|
47
|
+
metadata: { id: "pcl_dashboard", org: "acme", slug: "dashboard" },
|
|
48
|
+
spec: { clientId: "stgm_cid_x", allowedOrigins },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function storeOf(found: PlatformClient | undefined) {
|
|
53
|
+
return { findById: vi.fn(async () => found) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function userToken(claims: Record<string, string> = {}): string {
|
|
57
|
+
return signPlatformToken(ring, {
|
|
58
|
+
sub: "ida_pat",
|
|
59
|
+
email: "pat@example.com",
|
|
60
|
+
name: "Pat",
|
|
61
|
+
platform_client_id: "pcl_dashboard",
|
|
62
|
+
...claims,
|
|
63
|
+
}).token;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function callerWith(rawToken: string): CallerIdentity {
|
|
67
|
+
return {
|
|
68
|
+
identityId: "ida_pat",
|
|
69
|
+
callerClass: "user",
|
|
70
|
+
issuer: "stigmer",
|
|
71
|
+
rawToken,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function refusal(promise: Promise<unknown>): Promise<ConnectError> {
|
|
76
|
+
try {
|
|
77
|
+
await promise;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof ConnectError) return error;
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
throw new Error("expected a ConnectError refusal");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe("the PlatformClient user-token verifier", () => {
|
|
86
|
+
it("stamps the account the token was minted for as a user, with the asserted profile", async () => {
|
|
87
|
+
const token = userToken();
|
|
88
|
+
const identity = await newPlatformClientTokenVerifier({
|
|
89
|
+
keys: ring,
|
|
90
|
+
clients: storeOf(client()),
|
|
91
|
+
}).verify(token);
|
|
92
|
+
expect(identity).toEqual({
|
|
93
|
+
identityId: "ida_pat",
|
|
94
|
+
callerClass: "user",
|
|
95
|
+
issuer: "stigmer",
|
|
96
|
+
rawToken: token,
|
|
97
|
+
email: "pat@example.com",
|
|
98
|
+
displayName: "Pat",
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("passes a foreign token and a typed platform token to the next verifier", async () => {
|
|
103
|
+
const verifier = newPlatformClientTokenVerifier({
|
|
104
|
+
keys: ring,
|
|
105
|
+
clients: storeOf(client()),
|
|
106
|
+
});
|
|
107
|
+
expect(await verifier.verify("stk_api_key")).toBeNull();
|
|
108
|
+
expect(
|
|
109
|
+
await verifier.verify(userToken({ token_type: "guest" })),
|
|
110
|
+
).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("refuses a deleted client's token with the liveness copy, and a token naming no client", async () => {
|
|
114
|
+
const gone = await refusal(
|
|
115
|
+
newPlatformClientTokenVerifier({
|
|
116
|
+
keys: ring,
|
|
117
|
+
clients: storeOf(undefined),
|
|
118
|
+
}).verify(userToken()),
|
|
119
|
+
);
|
|
120
|
+
expect(gone.code).toBe(Code.Unauthenticated);
|
|
121
|
+
expect(gone.rawMessage).toBe(DELETED_CLIENT_MESSAGE);
|
|
122
|
+
|
|
123
|
+
const unnamed = await refusal(
|
|
124
|
+
newPlatformClientTokenVerifier({
|
|
125
|
+
keys: ring,
|
|
126
|
+
clients: storeOf(client()),
|
|
127
|
+
}).verify(signPlatformToken(ring, { sub: "ida_pat" }).token),
|
|
128
|
+
);
|
|
129
|
+
expect(unnamed.code).toBe(Code.Unauthenticated);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("lets a store fault propagate instead of reading it as a revocation", async () => {
|
|
133
|
+
const failing = {
|
|
134
|
+
findById: vi.fn(async () => {
|
|
135
|
+
throw new Error("database down");
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
await expect(
|
|
139
|
+
newPlatformClientTokenVerifier({ keys: ring, clients: failing }).verify(
|
|
140
|
+
userToken(),
|
|
141
|
+
),
|
|
142
|
+
).rejects.toThrow("database down");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("the PlatformClient origin guard", () => {
|
|
147
|
+
const method = PlatformClientQueryController.method.get;
|
|
148
|
+
|
|
149
|
+
it("reads no client for a request without an Origin header", async () => {
|
|
150
|
+
const clients = storeOf(client(["https://app.example"]));
|
|
151
|
+
const guard = newPlatformClientOriginGuard({ clients, logger: silent });
|
|
152
|
+
await guard.guard(callerWith(userToken()), method, new Headers());
|
|
153
|
+
await guard.guard(
|
|
154
|
+
callerWith(userToken()),
|
|
155
|
+
method,
|
|
156
|
+
new Headers({ origin: " " }),
|
|
157
|
+
);
|
|
158
|
+
expect(clients.findById).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("passes an open allowlist and a listed origin, compared case-insensitively", async () => {
|
|
162
|
+
await newPlatformClientOriginGuard({
|
|
163
|
+
clients: storeOf(client()),
|
|
164
|
+
logger: silent,
|
|
165
|
+
}).guard(
|
|
166
|
+
callerWith(userToken()),
|
|
167
|
+
method,
|
|
168
|
+
new Headers({ origin: "https://anywhere.example" }),
|
|
169
|
+
);
|
|
170
|
+
await newPlatformClientOriginGuard({
|
|
171
|
+
clients: storeOf(client(["https://app.example"])),
|
|
172
|
+
logger: silent,
|
|
173
|
+
}).guard(
|
|
174
|
+
callerWith(userToken()),
|
|
175
|
+
method,
|
|
176
|
+
new Headers({ origin: "https://APP.example" }),
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("refuses an unlisted origin and the opaque origin with the cloud's copy", async () => {
|
|
181
|
+
const guard = newPlatformClientOriginGuard({
|
|
182
|
+
clients: storeOf(client(["https://app.example"])),
|
|
183
|
+
logger: silent,
|
|
184
|
+
});
|
|
185
|
+
for (const origin of ["https://evil.example", "null"]) {
|
|
186
|
+
const denied = await refusal(
|
|
187
|
+
guard.guard(callerWith(userToken()), method, new Headers({ origin })),
|
|
188
|
+
);
|
|
189
|
+
expect(denied.code).toBe(Code.PermissionDenied);
|
|
190
|
+
expect(denied.rawMessage).toBe(originRefusalMessage(origin));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("passes every credential that is not an untyped platform user token", async () => {
|
|
195
|
+
const clients = storeOf(client(["https://app.example"]));
|
|
196
|
+
const guard = newPlatformClientOriginGuard({ clients, logger: silent });
|
|
197
|
+
const foreign = new Headers({ origin: "https://evil.example" });
|
|
198
|
+
await guard.guard(callerWith("stk_api_key"), method, foreign);
|
|
199
|
+
await guard.guard(callerWith(""), method, foreign);
|
|
200
|
+
await guard.guard(
|
|
201
|
+
callerWith(userToken({ token_type: "guest" })),
|
|
202
|
+
method,
|
|
203
|
+
foreign,
|
|
204
|
+
);
|
|
205
|
+
expect(clients.findById).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform-client domain's pinned vocabulary: reserved names, the
|
|
3
|
+
* user-token claim names and every refusal sentence.
|
|
4
|
+
*
|
|
5
|
+
* Most copy is wire contract inherited from Stigmer Cloud, which has
|
|
6
|
+
* answered integrators with these exact sentences since the Java service:
|
|
7
|
+
* the mint's credential, expiry, organization and provisioning refusals,
|
|
8
|
+
* the system-managed and reserved-slug refusals, and the minting client's
|
|
9
|
+
* liveness and origin refusals on every request its tokens bear (pinned
|
|
10
|
+
* cross-edition by platformclient-enforcement.conformance.test.ts). The
|
|
11
|
+
* sentences open source adds — a server that cannot mint, the guest
|
|
12
|
+
* method's edition refusal, the mismatched-account guard — follow the
|
|
13
|
+
* house style (single-quoted handles).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The kind's name as the shared error constructors render it. */
|
|
17
|
+
export const PLATFORM_CLIENT_KIND_NAME = "PlatformClient";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The slug of an organization's system-managed share client: the client
|
|
21
|
+
* an edition that hosts shared-agent pages names in the tokens it signs
|
|
22
|
+
* for guests and schedule fires, so every platform token names a real
|
|
23
|
+
* client. A user client holding it would be adopted by those lanes.
|
|
24
|
+
*/
|
|
25
|
+
export const SYSTEM_SHARE_CLIENT_SLUG = "system-share-client";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Slugs the platform keeps for its own clients: the create chain refuses
|
|
29
|
+
* them, and newSystemManagedPlatformClient builds only under them.
|
|
30
|
+
*/
|
|
31
|
+
export const RESERVED_PLATFORM_CLIENT_SLUGS: ReadonlySet<string> = new Set([
|
|
32
|
+
SYSTEM_SHARE_CLIENT_SLUG,
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/** The claims the PlatformClient user token carries beside the envelope's. */
|
|
36
|
+
export const USER_TOKEN_CLAIMS = {
|
|
37
|
+
subject: "sub",
|
|
38
|
+
externalUserId: "ext_user_id",
|
|
39
|
+
email: "email",
|
|
40
|
+
name: "name",
|
|
41
|
+
org: "org",
|
|
42
|
+
platformClientId: "platform_client_id",
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
/** The mint response's token_type. */
|
|
46
|
+
export const BEARER_TOKEN_TYPE = "Bearer";
|
|
47
|
+
|
|
48
|
+
export function reservedSlugMessage(slug: string): string {
|
|
49
|
+
return `The slug '${slug}' is reserved for the platform's system-managed resources and cannot be used. If you did not provide a slug explicitly, it was derived from the resource name — choose a different name or provide an explicit slug.`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The three mutations a system-managed client refuses. */
|
|
53
|
+
export type SystemManagedMutation = "updated" | "deleted" | "rotated";
|
|
54
|
+
|
|
55
|
+
export function systemManagedMessage(verb: SystemManagedMutation): string {
|
|
56
|
+
return `This platform client is system-managed and cannot be ${verb}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function referenceNotFoundMessage(org: string, slug: string): string {
|
|
60
|
+
return `PlatformClient '${org}/${slug}' not found`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const INVALID_CLIENT_CREDENTIALS_MESSAGE =
|
|
64
|
+
"Invalid client_id or client_secret";
|
|
65
|
+
|
|
66
|
+
export const EXPIRED_CLIENT_SECRET_MESSAGE =
|
|
67
|
+
"PlatformClient secret has expired. Use rotateSecret to generate a new one.";
|
|
68
|
+
|
|
69
|
+
export function organizationMismatchMessage(owningOrg: string): string {
|
|
70
|
+
return `org_id must be empty or the PlatformClient's owning organization ('${owningOrg}'); cross-organization minting is not supported`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function noAccountMessage(externalUserId: string, org: string): string {
|
|
74
|
+
return `User '${externalUserId}' has no Stigmer account in organization '${org}'. Enable auto_provision_accounts on the PlatformClient or create the account first.`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const OWNER_AUTO_GRANT_MESSAGE =
|
|
78
|
+
"auto_grant_role cannot be 'owner'; organization ownership must be assigned explicitly.";
|
|
79
|
+
|
|
80
|
+
export const PROVISIONING_FAILED_MESSAGE =
|
|
81
|
+
"Account provisioning failed. No partial account was left behind; the request is safe to retry.";
|
|
82
|
+
|
|
83
|
+
export function blankSubjectPartMessage(
|
|
84
|
+
part: "org" | "externalUserId",
|
|
85
|
+
): string {
|
|
86
|
+
return `${part} must not be null or blank`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function subjectSeparatorMessage(
|
|
90
|
+
part: "org" | "externalUserId",
|
|
91
|
+
value: string,
|
|
92
|
+
): string {
|
|
93
|
+
return `${part} must not contain the separator character '|': ${value}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function foreignAccountMessage(
|
|
97
|
+
externalUserId: string,
|
|
98
|
+
org: string,
|
|
99
|
+
): string {
|
|
100
|
+
return `User '${externalUserId}' in organization '${org}' resolves to an account no platform client provisioned — refusing to mint a token for it`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const MINTING_REQUIRES_AUTHENTICATION_MESSAGE =
|
|
104
|
+
"mintUserToken requires a server that authenticates its callers: this server trusts every request, so nothing would verify the token — configure an identity provider (STIGMER_OIDC_ISSUER) to enable PlatformClient token minting";
|
|
105
|
+
|
|
106
|
+
export const MINTING_DISABLED_MESSAGE =
|
|
107
|
+
"mintUserToken is disabled: this server's platform-token key ring holds no signing key";
|
|
108
|
+
|
|
109
|
+
export const GUEST_MINT_UNIMPLEMENTED_MESSAGE =
|
|
110
|
+
"PlatformClientTokenController.mintGuestToken is not implemented: guest tokens for shared agents are served by the Cloud edition";
|
|
111
|
+
|
|
112
|
+
export const TOKEN_NAMES_NO_CLIENT_MESSAGE =
|
|
113
|
+
"platform token names no platform client";
|
|
114
|
+
|
|
115
|
+
export const DELETED_CLIENT_MESSAGE =
|
|
116
|
+
"The platform client that minted this token has been deleted, so the token is no longer accepted. Mint a new user token from an active platform client.";
|
|
117
|
+
|
|
118
|
+
export function originRefusalMessage(origin: string): string {
|
|
119
|
+
return `Request origin '${origin}' is not in this platform client's allowed_origins. Add it to the PlatformClient's allowed_origins to permit browser requests from this origin.`;
|
|
120
|
+
}
|