@stigmer/server 3.14.0 → 3.15.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/boot/compose.d.ts +8 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +98 -12
- package/dist/boot/compose.js.map +1 -1
- package/dist/domain/agentexecution/constants.d.ts +16 -0
- package/dist/domain/agentexecution/constants.d.ts.map +1 -1
- package/dist/domain/agentexecution/constants.js +22 -0
- package/dist/domain/agentexecution/constants.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +10 -3
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/run-target.d.ts +26 -0
- package/dist/domain/agentexecution/run-target.d.ts.map +1 -0
- package/dist/domain/agentexecution/run-target.js +31 -0
- package/dist/domain/agentexecution/run-target.js.map +1 -0
- package/dist/domain/apikey/verifier.d.ts +8 -1
- package/dist/domain/apikey/verifier.d.ts.map +1 -1
- package/dist/domain/apikey/verifier.js +14 -2
- package/dist/domain/apikey/verifier.js.map +1 -1
- package/dist/domain/identityaccount/constants.d.ts +66 -0
- package/dist/domain/identityaccount/constants.d.ts.map +1 -0
- package/dist/domain/identityaccount/constants.js +153 -0
- package/dist/domain/identityaccount/constants.js.map +1 -0
- package/dist/domain/identityaccount/controller.d.ts +34 -0
- package/dist/domain/identityaccount/controller.d.ts.map +1 -0
- package/dist/domain/identityaccount/controller.js +342 -0
- package/dist/domain/identityaccount/controller.js.map +1 -0
- package/dist/domain/identityaccount/operator.d.ts +14 -0
- package/dist/domain/identityaccount/operator.d.ts.map +1 -0
- package/dist/domain/identityaccount/operator.js +67 -0
- package/dist/domain/identityaccount/operator.js.map +1 -0
- package/dist/domain/identityaccount/provisioning.d.ts +52 -0
- package/dist/domain/identityaccount/provisioning.d.ts.map +1 -0
- package/dist/domain/identityaccount/provisioning.js +114 -0
- package/dist/domain/identityaccount/provisioning.js.map +1 -0
- package/dist/domain/identityaccount/resolve.d.ts +36 -0
- package/dist/domain/identityaccount/resolve.d.ts.map +1 -0
- package/dist/domain/identityaccount/resolve.js +12 -0
- package/dist/domain/identityaccount/resolve.js.map +1 -0
- package/dist/domain/identityaccount/resource-store.d.ts +4 -0
- package/dist/domain/identityaccount/resource-store.d.ts.map +1 -0
- package/dist/domain/identityaccount/resource-store.js +131 -0
- package/dist/domain/identityaccount/resource-store.js.map +1 -0
- package/dist/domain/identityaccount/steps.d.ts +79 -0
- package/dist/domain/identityaccount/steps.d.ts.map +1 -0
- package/dist/domain/identityaccount/steps.js +291 -0
- package/dist/domain/identityaccount/steps.js.map +1 -0
- package/dist/domain/identityaccount/store-contract.d.ts +22 -0
- package/dist/domain/identityaccount/store-contract.d.ts.map +1 -0
- package/dist/domain/identityaccount/store-contract.js +307 -0
- package/dist/domain/identityaccount/store-contract.js.map +1 -0
- package/dist/domain/identityaccount/store.d.ts +66 -0
- package/dist/domain/identityaccount/store.d.ts.map +1 -0
- package/dist/domain/identityaccount/store.js +13 -0
- package/dist/domain/identityaccount/store.js.map +1 -0
- package/dist/domain/session/constants.d.ts +15 -0
- package/dist/domain/session/constants.d.ts.map +1 -0
- package/dist/domain/session/constants.js +17 -0
- package/dist/domain/session/constants.js.map +1 -0
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +10 -0
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/session/run-target.d.ts +15 -0
- package/dist/domain/session/run-target.d.ts.map +1 -0
- package/dist/domain/session/run-target.js +14 -0
- package/dist/domain/session/run-target.js.map +1 -0
- package/dist/domain/workflowexecution/constants.d.ts +9 -0
- package/dist/domain/workflowexecution/constants.d.ts.map +1 -1
- package/dist/domain/workflowexecution/constants.js +13 -0
- package/dist/domain/workflowexecution/constants.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +8 -0
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowexecution/run-target.d.ts +18 -0
- package/dist/domain/workflowexecution/run-target.d.ts.map +1 -0
- package/dist/domain/workflowexecution/run-target.js +23 -0
- package/dist/domain/workflowexecution/run-target.js.map +1 -0
- package/dist/extensions/drivers.d.ts +26 -0
- package/dist/extensions/drivers.d.ts.map +1 -1
- package/dist/extensions/gate-slots.d.ts +16 -1
- package/dist/extensions/gate-slots.d.ts.map +1 -1
- package/dist/extensions/gate-slots.js +1 -0
- package/dist/extensions/gate-slots.js.map +1 -1
- package/dist/extensions/identity-federation.d.ts +36 -0
- package/dist/extensions/identity-federation.d.ts.map +1 -0
- package/dist/extensions/identity-federation.js +2 -0
- package/dist/extensions/identity-federation.js.map +1 -0
- package/dist/extensions/registry.d.ts +18 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/extensions/registry.js +20 -0
- package/dist/extensions/registry.js.map +1 -1
- package/dist/identity/oidc-discovery.d.ts +36 -0
- package/dist/identity/oidc-discovery.d.ts.map +1 -0
- package/dist/identity/oidc-discovery.js +60 -0
- package/dist/identity/oidc-discovery.js.map +1 -0
- package/dist/identity/oidc-userinfo.d.ts +30 -0
- package/dist/identity/oidc-userinfo.d.ts.map +1 -0
- package/dist/identity/oidc-userinfo.js +28 -0
- package/dist/identity/oidc-userinfo.js.map +1 -0
- package/dist/identity/oidc-verifier.d.ts +6 -0
- package/dist/identity/oidc-verifier.d.ts.map +1 -1
- package/dist/identity/oidc-verifier.js +35 -34
- package/dist/identity/oidc-verifier.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/interceptors/auth.d.ts +10 -0
- package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
- package/dist/pipeline/interceptors/auth.js +9 -1
- package/dist/pipeline/interceptors/auth.js.map +1 -1
- package/dist/pipeline/steps/authorize-run-target.d.ts +105 -0
- package/dist/pipeline/steps/authorize-run-target.d.ts.map +1 -0
- package/dist/pipeline/steps/authorize-run-target.js +61 -0
- package/dist/pipeline/steps/authorize-run-target.js.map +1 -0
- package/dist/query/search/criteria.d.ts +10 -3
- package/dist/query/search/criteria.d.ts.map +1 -1
- package/dist/query/search/criteria.js +11 -4
- package/dist/query/search/criteria.js.map +1 -1
- package/package.json +4 -4
- package/src/boot/__tests__/auth-enabled-composition.test.ts +226 -2
- package/src/boot/compose.ts +118 -13
- package/src/domain/agentexecution/__tests__/run-gate.test.ts +245 -0
- package/src/domain/agentexecution/__tests__/run-target.test.ts +117 -0
- package/src/domain/agentexecution/constants.ts +25 -0
- package/src/domain/agentexecution/controller.ts +10 -3
- package/src/domain/agentexecution/run-target.ts +64 -0
- package/src/domain/apikey/__tests__/verifier.test.ts +146 -22
- package/src/domain/apikey/verifier.ts +24 -2
- package/src/domain/identityaccount/__tests__/constants.test.ts +155 -0
- package/src/domain/identityaccount/__tests__/identityaccount.test.ts +484 -0
- package/src/domain/identityaccount/__tests__/operator.test.ts +180 -0
- package/src/domain/identityaccount/__tests__/provisioning.test.ts +281 -0
- package/src/domain/identityaccount/__tests__/resolve.test.ts +114 -0
- package/src/domain/identityaccount/__tests__/resource-store.test.ts +216 -0
- package/src/domain/identityaccount/__tests__/support.ts +58 -0
- package/src/domain/identityaccount/constants.ts +178 -0
- package/src/domain/identityaccount/controller.ts +667 -0
- package/src/domain/identityaccount/operator.ts +94 -0
- package/src/domain/identityaccount/provisioning.ts +182 -0
- package/src/domain/identityaccount/resolve.ts +52 -0
- package/src/domain/identityaccount/resource-store.ts +157 -0
- package/src/domain/identityaccount/steps.ts +377 -0
- package/src/domain/identityaccount/store-contract.ts +471 -0
- package/src/domain/identityaccount/store.ts +70 -0
- package/src/domain/session/__tests__/run-gate.test.ts +219 -0
- package/src/domain/session/__tests__/run-target.test.ts +43 -0
- package/src/domain/session/constants.ts +17 -0
- package/src/domain/session/controller.ts +10 -0
- package/src/domain/session/run-target.ts +30 -0
- package/src/domain/workflowexecution/__tests__/run-gate.test.ts +200 -0
- package/src/domain/workflowexecution/__tests__/run-target.test.ts +80 -0
- package/src/domain/workflowexecution/constants.ts +17 -0
- package/src/domain/workflowexecution/controller.ts +10 -0
- package/src/domain/workflowexecution/run-target.ts +47 -0
- package/src/extensions/__tests__/extension-composition.test.ts +49 -0
- package/src/extensions/__tests__/identity-account-composed.test.ts +803 -0
- package/src/extensions/__tests__/identity-account-points.test.ts +129 -0
- package/src/extensions/__tests__/registry.test.ts +4 -0
- package/src/extensions/__tests__/tier-truthfulness.test.ts +299 -0
- package/src/extensions/drivers.ts +26 -0
- package/src/extensions/gate-slots.ts +16 -0
- package/src/extensions/identity-federation.ts +58 -0
- package/src/extensions/registry.ts +44 -1
- package/src/identity/__tests__/oidc-discovery.test.ts +122 -0
- package/src/identity/__tests__/oidc-userinfo.test.ts +103 -0
- package/src/identity/__tests__/oidc-verifier.test.ts +173 -3
- package/src/identity/oidc-discovery.ts +84 -0
- package/src/identity/oidc-userinfo.ts +64 -0
- package/src/identity/oidc-verifier.ts +42 -45
- package/src/index.ts +26 -0
- package/src/pipeline/__tests__/support.ts +34 -0
- package/src/pipeline/interceptors/auth.ts +13 -1
- package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +184 -0
- package/src/pipeline/steps/__tests__/authorize-run-target.test.ts +261 -0
- package/src/pipeline/steps/authorize-run-target.ts +148 -0
- package/src/query/search/__tests__/criteria.test.ts +18 -1
- package/src/query/search/criteria.ts +11 -4
|
@@ -23,16 +23,28 @@
|
|
|
23
23
|
* seam wants. Expiry is "expires_at set and past"; never_expires is
|
|
24
24
|
* deliberately not read — Java parity (never-expiring keys leave
|
|
25
25
|
* expires_at unset).
|
|
26
|
+
*
|
|
27
|
+
* The stamp is then resolved the way the OIDC lane resolves `sub`
|
|
28
|
+
* (20260911.11, T01_1_review.md A6; domain/identityaccount/resolve.ts):
|
|
29
|
+
* a key minted before its owner was provisioned carries the raw subject
|
|
30
|
+
* and answers the owner's ACCOUNT id once one exists, so no write over a
|
|
31
|
+
* legacy key mints a raw-subject stamp again; a stamp that is already an
|
|
32
|
+
* account id is kept — one primary-key read either way, no cache. Only
|
|
33
|
+
* identityId changes on a hit: email/displayName stay the stamp's, the
|
|
34
|
+
* actor the key recorded at minting. The credential checks run first, so
|
|
35
|
+
* a revoked or expired key never reaches the account store.
|
|
26
36
|
*/
|
|
27
37
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
28
38
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
39
|
+
import { identityIdForSubject } from "../identityaccount/resolve.js";
|
|
29
40
|
import { hashApiKey, isApiKeyToken } from "./keymaterial.js";
|
|
30
41
|
import { findApiKeyByHash } from "./lookup.js";
|
|
31
42
|
/** Java classifyAuthError's fallback arm — the unknown/revoked-key copy. */
|
|
32
43
|
export const INVALID_TOKEN_MESSAGE = "invalid token";
|
|
33
44
|
/** Java classifyAuthError's "expired" arm. */
|
|
34
45
|
export const TOKEN_EXPIRED_MESSAGE = "token has expired";
|
|
35
|
-
export function newApiKeyIdentityVerifier(
|
|
46
|
+
export function newApiKeyIdentityVerifier(deps) {
|
|
47
|
+
const { store, accounts } = deps;
|
|
36
48
|
return {
|
|
37
49
|
name: "apikey",
|
|
38
50
|
async verify(token) {
|
|
@@ -55,7 +67,7 @@ export function newApiKeyIdentityVerifier(store) {
|
|
|
55
67
|
throw new ConnectError(INVALID_TOKEN_MESSAGE, Code.Unauthenticated);
|
|
56
68
|
}
|
|
57
69
|
return {
|
|
58
|
-
identityId: owner.id,
|
|
70
|
+
identityId: await identityIdForSubject(accounts, owner.id),
|
|
59
71
|
callerClass: "user",
|
|
60
72
|
issuer: "",
|
|
61
73
|
rawToken: token,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../../src/domain/apikey/verifier.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../../src/domain/apikey/verifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAOvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAC;AAErD,8CAA8C;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AASzD,MAAM,UAAU,yBAAyB,CACvC,IAAwB;IAExB,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACjC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,KAAK,CAAC,MAAM,CAAC,KAAa;YACxB,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1B,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7D,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YACtE,CAAC;YACD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC;YACtC,IAAI,SAAS,KAAK,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;gBACtE,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YACtE,CAAC;YACD,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC;YACtD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC3C,oEAAoE;gBACpE,mEAAmE;gBACnE,+DAA+D;gBAC/D,MAAM,IAAI,YAAY,CAAC,qBAAqB,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YACtE,CAAC;YACD,OAAO;gBACL,UAAU,EAAE,MAAM,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC1D,WAAW,EAAE,MAAM;gBACnB,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,KAAK;gBACf,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrD,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
2
|
+
/** The kind's id prefix (kind_meta `identity_account.id_prefix`). */
|
|
3
|
+
export declare const ACCOUNT_ID_PREFIX = "ida";
|
|
4
|
+
/**
|
|
5
|
+
* The derived account id of a direct account with subject `idpId`. Pure,
|
|
6
|
+
* total over non-empty input; an empty subject has no principal and so
|
|
7
|
+
* no address — refused, never hashed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function accountIdFor(idpId: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* The subject namespace of the trusted-local posture. An OIDC subject is
|
|
12
|
+
* `<connection>|<id>` by Auth0 convention; `local|` claims the same shape
|
|
13
|
+
* for the one principal no issuer vouches for, so the derived ids of a
|
|
14
|
+
* local operator and an OIDC user can never collide by construction.
|
|
15
|
+
*/
|
|
16
|
+
export declare const LOCAL_IDP_ID_PREFIX = "local|";
|
|
17
|
+
/** `local|<identityId>` — the trusted-local operator's subject. */
|
|
18
|
+
export declare function localIdpIdFor(identityId: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* The issuer subject a caller stands for:
|
|
21
|
+
* - a caller with no issuer and no token is the trusted-local operator
|
|
22
|
+
* (`local|<identityId>`; identityId is the operator's email or
|
|
23
|
+
* "system" — pipeline/interceptors/auth.ts trustedLocalIdentity);
|
|
24
|
+
* - a token-bearing caller is its JWT `sub`, read from the raw token
|
|
25
|
+
* the verifier already proved; `""` when the token carries no
|
|
26
|
+
* readable subject (an opaque credential under an issuer), which
|
|
27
|
+
* the provisionMyAccount handler maps to the UNAUTHENTICATED arm.
|
|
28
|
+
*/
|
|
29
|
+
export declare function idpIdOf(caller: CallerIdentity): string;
|
|
30
|
+
/**
|
|
31
|
+
* Auth0's client-credentials subject shape (`<client_id>@clients`) — the
|
|
32
|
+
* cloud's IsMachineAccountVerifier rule, the one input to
|
|
33
|
+
* `is_machine_account`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const MACHINE_ACCOUNT_SUFFIX = "@clients";
|
|
36
|
+
export declare function isMachineSubject(idpId: string): boolean;
|
|
37
|
+
/** whoAmI for a caller with no account (NOT_FOUND). */
|
|
38
|
+
export declare const ACCOUNT_NOT_FOUND_FOR_CALLER_MESSAGE = "Identity account not found for the authenticated user";
|
|
39
|
+
/** provisionMyAccount for a caller whose credential names no subject (UNAUTHENTICATED). */
|
|
40
|
+
export declare const NO_IDP_ID_MESSAGE = "Cannot provision account: no IDP ID in authentication context";
|
|
41
|
+
/** provisionMyAccount when the issuer's userinfo cannot be read (UNAVAILABLE); the cause follows. */
|
|
42
|
+
export declare const USERINFO_UNAVAILABLE_PREFIX = "Failed to fetch user profile from identity provider: ";
|
|
43
|
+
/** get / getByEmail / getByIdpId / getActorInfo / update / delete on a missing account (NOT_FOUND). */
|
|
44
|
+
export declare function accountNotFoundMessage(handle: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* The cloud#393 gate's refusal (PERMISSION_DENIED): the create RPC admits
|
|
47
|
+
* the platform's own pipelines only — machine-class callers and
|
|
48
|
+
* server-composed (in-process) requests (T01_1_review.md A7).
|
|
49
|
+
*/
|
|
50
|
+
export declare const CREATE_IS_INTERNAL_MESSAGE = "this RPC is internal to the platform";
|
|
51
|
+
/**
|
|
52
|
+
* The reason a composition with no IdentityFederation unit refuses the four
|
|
53
|
+
* federation RPCs (UNIMPLEMENTED). The sentence a person can act on; the
|
|
54
|
+
* method-naming prefix is the organization directory's absent-method shape
|
|
55
|
+
* so clients matching on "is not implemented" keep working.
|
|
56
|
+
*/
|
|
57
|
+
export declare const FEDERATION_UNIMPLEMENTED_REASON = "federated identity accounts are served by the Enterprise and Cloud editions";
|
|
58
|
+
export declare function federationUnimplementedMessage(method: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* update with a changed spec.idp_id (FAILED_PRECONDITION; the schedule
|
|
61
|
+
* domain's immutability shape, T01_1_review.md A1). The subject IS the
|
|
62
|
+
* identity; a client that sends a different one has a bug it should hear
|
|
63
|
+
* about, never a silent preserve.
|
|
64
|
+
*/
|
|
65
|
+
export declare function idpIdImmutableMessage(existingIdpId: string): string;
|
|
66
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/domain/identityaccount/constants.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAQvC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBlD;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAKtD;AAsBD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvD;AAMD,uDAAuD;AACvD,eAAO,MAAM,oCAAoC,0DACQ,CAAC;AAE1D,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,kEACmC,CAAC;AAElE,qGAAqG;AACrG,eAAO,MAAM,2BAA2B,0DACiB,CAAC;AAE1D,uGAAuG;AACvG,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,yCACC,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,gFACmC,CAAC;AAEhF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-account domain constants (20260911.11, T01_1_review.md A1, A2,
|
|
3
|
+
* A7): the derived account id, the one function that turns a caller into
|
|
4
|
+
* an issuer subject, and the byte-pinned wire copy.
|
|
5
|
+
*
|
|
6
|
+
* The derived id (A1). A direct account's id is a pure function of its
|
|
7
|
+
* subject: `ida_` + the top 130 bits of sha256(idp_id) as 26 lowercase
|
|
8
|
+
* Crockford-base32 characters. The shape is indistinguishable from a
|
|
9
|
+
* minted `{prefix}_{ulid}`, so nothing downstream learns a second id
|
|
10
|
+
* grammar, and the PRIMARY KEY becomes the one home of "one account per
|
|
11
|
+
* subject": the generic store has no secondary uniqueness and its save is
|
|
12
|
+
* an upsert (`store/sqlite/store.ts`), so a lookup-then-insert on a scan
|
|
13
|
+
* could never close the first-login race. Two replicas deriving the same
|
|
14
|
+
* id converge by primary key with no migration and no second copy of any
|
|
15
|
+
* field. The hash is one-way, so an issuer subject never leaks into the
|
|
16
|
+
* `created_by.id` stamps that ride on every resource. The golden vectors in
|
|
17
|
+
* __tests__/constants.test.ts are wire-adjacent contract: a change here
|
|
18
|
+
* re-addresses every direct account open source ever created.
|
|
19
|
+
*
|
|
20
|
+
* `idpIdOf` (A2) is the ONE place a CallerIdentity becomes a subject. An
|
|
21
|
+
* issuer-vouched token is read for its `sub` (already verified at
|
|
22
|
+
* position 1, so this is a read of trusted state, never a verification);
|
|
23
|
+
* a caller with no issuer and no token is the trusted-local operator,
|
|
24
|
+
* whose subject is `local|<identityId>`. It deliberately knows nothing
|
|
25
|
+
* about API keys: every verifier resolves its stamp to the account when
|
|
26
|
+
* one exists (A6), so a credential's shape is the verifier's business.
|
|
27
|
+
*
|
|
28
|
+
* The copy moved from the cloud's iam/account/handlers.ts as-is: the CLI,
|
|
29
|
+
* console and SDK show these sentences verbatim, so they are contract.
|
|
30
|
+
*/
|
|
31
|
+
import { createHash } from "node:crypto";
|
|
32
|
+
/** The kind's id prefix (kind_meta `identity_account.id_prefix`). */
|
|
33
|
+
export const ACCOUNT_ID_PREFIX = "ida";
|
|
34
|
+
/** Crockford base32, lowercased — the alphabet every minted ULID id uses. */
|
|
35
|
+
const CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
36
|
+
const DERIVED_ID_CHARS = 26;
|
|
37
|
+
const DERIVED_ID_BITS = BigInt(DERIVED_ID_CHARS * 5);
|
|
38
|
+
const SHA256_BITS = 256n;
|
|
39
|
+
/**
|
|
40
|
+
* The derived account id of a direct account with subject `idpId`. Pure,
|
|
41
|
+
* total over non-empty input; an empty subject has no principal and so
|
|
42
|
+
* no address — refused, never hashed.
|
|
43
|
+
*/
|
|
44
|
+
export function accountIdFor(idpId) {
|
|
45
|
+
if (idpId === "") {
|
|
46
|
+
throw new Error("idp_id must not be empty");
|
|
47
|
+
}
|
|
48
|
+
const digest = createHash("sha256").update(idpId, "utf8").digest();
|
|
49
|
+
let bits = 0n;
|
|
50
|
+
for (const byte of digest) {
|
|
51
|
+
bits = (bits << 8n) | BigInt(byte);
|
|
52
|
+
}
|
|
53
|
+
let top = bits >> (SHA256_BITS - DERIVED_ID_BITS);
|
|
54
|
+
let encoded = "";
|
|
55
|
+
for (let i = 0; i < DERIVED_ID_CHARS; i++) {
|
|
56
|
+
encoded = CROCKFORD_ALPHABET[Number(top & 31n)] + encoded;
|
|
57
|
+
top >>= 5n;
|
|
58
|
+
}
|
|
59
|
+
return `${ACCOUNT_ID_PREFIX}_${encoded}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The subject namespace of the trusted-local posture. An OIDC subject is
|
|
63
|
+
* `<connection>|<id>` by Auth0 convention; `local|` claims the same shape
|
|
64
|
+
* for the one principal no issuer vouches for, so the derived ids of a
|
|
65
|
+
* local operator and an OIDC user can never collide by construction.
|
|
66
|
+
*/
|
|
67
|
+
export const LOCAL_IDP_ID_PREFIX = "local|";
|
|
68
|
+
/** `local|<identityId>` — the trusted-local operator's subject. */
|
|
69
|
+
export function localIdpIdFor(identityId) {
|
|
70
|
+
return `${LOCAL_IDP_ID_PREFIX}${identityId}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The issuer subject a caller stands for:
|
|
74
|
+
* - a caller with no issuer and no token is the trusted-local operator
|
|
75
|
+
* (`local|<identityId>`; identityId is the operator's email or
|
|
76
|
+
* "system" — pipeline/interceptors/auth.ts trustedLocalIdentity);
|
|
77
|
+
* - a token-bearing caller is its JWT `sub`, read from the raw token
|
|
78
|
+
* the verifier already proved; `""` when the token carries no
|
|
79
|
+
* readable subject (an opaque credential under an issuer), which
|
|
80
|
+
* the provisionMyAccount handler maps to the UNAUTHENTICATED arm.
|
|
81
|
+
*/
|
|
82
|
+
export function idpIdOf(caller) {
|
|
83
|
+
if (caller.issuer === "" && caller.rawToken === "") {
|
|
84
|
+
return localIdpIdFor(caller.identityId);
|
|
85
|
+
}
|
|
86
|
+
return jwtSubjectOf(caller.rawToken);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The cloud's jwtSubjectOf verbatim: the payload segment decoded, `sub`
|
|
90
|
+
* when it is a string. Not a verification — the chain's position 1 did
|
|
91
|
+
* that; this only reads a claim from trusted state.
|
|
92
|
+
*/
|
|
93
|
+
function jwtSubjectOf(rawToken) {
|
|
94
|
+
const segments = rawToken.split(".");
|
|
95
|
+
if (segments.length !== 3) {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const payload = JSON.parse(Buffer.from(segments[1] ?? "", "base64url").toString("utf8"));
|
|
100
|
+
return typeof payload.sub === "string" ? payload.sub : "";
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Auth0's client-credentials subject shape (`<client_id>@clients`) — the
|
|
108
|
+
* cloud's IsMachineAccountVerifier rule, the one input to
|
|
109
|
+
* `is_machine_account`.
|
|
110
|
+
*/
|
|
111
|
+
export const MACHINE_ACCOUNT_SUFFIX = "@clients";
|
|
112
|
+
export function isMachineSubject(idpId) {
|
|
113
|
+
return idpId.endsWith(MACHINE_ACCOUNT_SUFFIX);
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Byte-pinned copy (the cloud handlers' sentences, moved as-is).
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
/** whoAmI for a caller with no account (NOT_FOUND). */
|
|
119
|
+
export const ACCOUNT_NOT_FOUND_FOR_CALLER_MESSAGE = "Identity account not found for the authenticated user";
|
|
120
|
+
/** provisionMyAccount for a caller whose credential names no subject (UNAUTHENTICATED). */
|
|
121
|
+
export const NO_IDP_ID_MESSAGE = "Cannot provision account: no IDP ID in authentication context";
|
|
122
|
+
/** provisionMyAccount when the issuer's userinfo cannot be read (UNAVAILABLE); the cause follows. */
|
|
123
|
+
export const USERINFO_UNAVAILABLE_PREFIX = "Failed to fetch user profile from identity provider: ";
|
|
124
|
+
/** get / getByEmail / getByIdpId / getActorInfo / update / delete on a missing account (NOT_FOUND). */
|
|
125
|
+
export function accountNotFoundMessage(handle) {
|
|
126
|
+
return `Identity account not found: ${handle}`;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The cloud#393 gate's refusal (PERMISSION_DENIED): the create RPC admits
|
|
130
|
+
* the platform's own pipelines only — machine-class callers and
|
|
131
|
+
* server-composed (in-process) requests (T01_1_review.md A7).
|
|
132
|
+
*/
|
|
133
|
+
export const CREATE_IS_INTERNAL_MESSAGE = "this RPC is internal to the platform";
|
|
134
|
+
/**
|
|
135
|
+
* The reason a composition with no IdentityFederation unit refuses the four
|
|
136
|
+
* federation RPCs (UNIMPLEMENTED). The sentence a person can act on; the
|
|
137
|
+
* method-naming prefix is the organization directory's absent-method shape
|
|
138
|
+
* so clients matching on "is not implemented" keep working.
|
|
139
|
+
*/
|
|
140
|
+
export const FEDERATION_UNIMPLEMENTED_REASON = "federated identity accounts are served by the Enterprise and Cloud editions";
|
|
141
|
+
export function federationUnimplementedMessage(method) {
|
|
142
|
+
return `${method} is not implemented: ${FEDERATION_UNIMPLEMENTED_REASON}`;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* update with a changed spec.idp_id (FAILED_PRECONDITION; the schedule
|
|
146
|
+
* domain's immutability shape, T01_1_review.md A1). The subject IS the
|
|
147
|
+
* identity; a client that sends a different one has a bug it should hear
|
|
148
|
+
* about, never a silent preserve.
|
|
149
|
+
*/
|
|
150
|
+
export function idpIdImmutableMessage(existingIdpId) {
|
|
151
|
+
return `spec.idp_id is immutable (account subject is '${existingIdpId}') — create a new account for a different subject`;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/constants.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ConnectRouter } from "@connectrpc/connect";
|
|
2
|
+
import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
3
|
+
import type { Logger } from "../../boot/logger.js";
|
|
4
|
+
import type { Authorizer } from "../../extensions/authorizer.js";
|
|
5
|
+
import type { ResolvedGateSteps } from "../../extensions/gate-slots.js";
|
|
6
|
+
import type { IdentityFederation } from "../../extensions/identity-federation.js";
|
|
7
|
+
import type { ResourceAuthorizationLifecycle } from "../../extensions/resource-authorization.js";
|
|
8
|
+
import type { CreateAccount, DirectAccountProvisioner } from "./provisioning.js";
|
|
9
|
+
import type { IdentityAccountStore } from "./store.js";
|
|
10
|
+
/** What the create path needs — the subset every caller of it composes. */
|
|
11
|
+
export interface CreateAccountPathDeps {
|
|
12
|
+
readonly accounts: IdentityAccountStore;
|
|
13
|
+
readonly logger: Logger;
|
|
14
|
+
/** The composed authorization seam — the Authorize step at position 1 of every chain calls it (DD-007 §3). */
|
|
15
|
+
readonly authorizer: Authorizer;
|
|
16
|
+
/** The composed tuple-lifecycle driver — undefined = the shared steps no-op. */
|
|
17
|
+
readonly authorizationLifecycle: ResourceAuthorizationLifecycle | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface IdentityAccountControllerDeps extends CreateAccountPathDeps {
|
|
20
|
+
readonly provisioner: DirectAccountProvisioner;
|
|
21
|
+
/** The composed federation capability — undefined = the four RPCs refuse UNIMPLEMENTED. */
|
|
22
|
+
readonly federation: IdentityFederation | undefined;
|
|
23
|
+
/** The composed slot registrations — this domain's provision slot (Q-IA-9, A10). */
|
|
24
|
+
readonly gateSteps: ResolvedGateSteps;
|
|
25
|
+
}
|
|
26
|
+
/** Registers both identityaccount services on the router (routes stage). */
|
|
27
|
+
export declare function registerIdentityAccountServices(router: ConnectRouter, deps: IdentityAccountControllerDeps): void;
|
|
28
|
+
/**
|
|
29
|
+
* The create path as its in-process callers see it (provisioning.ts
|
|
30
|
+
* `CreateAccount`): the input becomes the resource envelope the chain
|
|
31
|
+
* expects, tagged with the kind this controller serves.
|
|
32
|
+
*/
|
|
33
|
+
export declare function newCreateAccountPath(deps: CreateAccountPathDeps, kind: ApiResourceKind): CreateAccount;
|
|
34
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/domain/identityaccount/controller.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,qBAAqB,CAAC;AAKzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qFAAqF,CAAC;AAc3H,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAEjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAExE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAC;AA8BjG,OAAO,KAAK,EACV,aAAa,EAEb,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAiB3B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,2EAA2E;AAC3E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8GAA8G;IAC9G,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,sBAAsB,EAAE,8BAA8B,GAAG,SAAS,CAAC;CAC7E;AAED,MAAM,WAAW,6BAA8B,SAAQ,qBAAqB;IAC1E,QAAQ,CAAC,WAAW,EAAE,wBAAwB,CAAC;IAC/C,2FAA2F;IAC3F,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACpD,oFAAoF;IACpF,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;CACvC;AAED,4EAA4E;AAC5E,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,6BAA6B,GAClC,IAAI,CAkDN;AA0DD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,qBAAqB,EAC3B,IAAI,EAAE,eAAe,GACpB,aAAa,CAaf"}
|