@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
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IdentityAccount controller (20260911.11) — the identity-account domain
|
|
3
|
+
* served ONCE by @stigmer/server in every edition (the editions program's
|
|
4
|
+
* tier truth: `identity_account` is open-source tier). The behavioural
|
|
5
|
+
* reference is the cloud's iam/account/handlers.ts, whose twelve direct
|
|
6
|
+
* handlers retire against this module; the byte-pinned copy moved with it
|
|
7
|
+
* (constants.ts). The cloud keeps only what differs per edition — its row
|
|
8
|
+
* store as `drivers.identityAccountStore`, the federated arms as
|
|
9
|
+
* `drivers.identityFederation`, the personal organization as a gate on
|
|
10
|
+
* `identity-account-provision:post-persist`.
|
|
11
|
+
*
|
|
12
|
+
* Persistence is the IdentityAccountStore PORT, never the generic Store
|
|
13
|
+
* (steps.ts explains); the storage-free shared steps are reused as they
|
|
14
|
+
* are, so every chain reads onto the inventory unchanged.
|
|
15
|
+
*
|
|
16
|
+
* The ONE create path (`newCreateAccountPath`): the create RPC, the
|
|
17
|
+
* provisioner and the trusted-local operator ensure all run the same
|
|
18
|
+
* chain — DeriveAccountId, CheckDuplicate by subject, BuildNewState,
|
|
19
|
+
* AssignBackendFields, Persist through the port, CreateAuthorizationTuples
|
|
20
|
+
* (the lifecycle driver's SELF arm gives the account its self-owner grant
|
|
21
|
+
* in the cloud) — so no account exists that skipped a step. The create
|
|
22
|
+
* RPC is the cloud#393 gate in front of that path (T01_1_review.md A7):
|
|
23
|
+
* machine / internal / in-process callers only, in every edition. The
|
|
24
|
+
* gate is the RPC's admission rule and deliberately NOT a step in the
|
|
25
|
+
* path, because the provisioner runs the path as the idp-shaped wire user
|
|
26
|
+
* (the account is created by the subject it is for) and the operator
|
|
27
|
+
* ensure runs it as the trusted-local user.
|
|
28
|
+
*
|
|
29
|
+
* Direct handlers (no pipeline): whoAmI (the cloud's two primary-key
|
|
30
|
+
* reads through idpIdOf; NOT_FOUND for the unprovisioned), getByEmail and
|
|
31
|
+
* getByIdpId (lookup FIRST, then authorizeDirect on the FOUND id — the
|
|
32
|
+
* cloud's order; the annotation's `field_path = "value"` would otherwise
|
|
33
|
+
* hand the Authorizer an email as a resource id), getActorInfo, and
|
|
34
|
+
* provisionMyAccount (the provisioner, then the composed post-persist
|
|
35
|
+
* gates; UNAUTHENTICATED with the cloud's copy for a credential naming no
|
|
36
|
+
* subject; UNAVAILABLE with the cloud's copy when userinfo fails).
|
|
37
|
+
*
|
|
38
|
+
* The four federation RPCs refuse UNIMPLEMENTED with the edition reason
|
|
39
|
+
* when no unit composes the capability (never INTERNAL; the organization
|
|
40
|
+
* directory's absent-method shape); present, the controller owns the
|
|
41
|
+
* shared precondition (authorize on the org, ref-with-slug, org match,
|
|
42
|
+
* IdP exists) and dispatches to the arm with the resolved ref.
|
|
43
|
+
*
|
|
44
|
+
* Proven by __tests__/identityaccount.test.ts (the composed trusted-local
|
|
45
|
+
* server), boot/__tests__/auth-enabled-composition.test.ts (the OIDC
|
|
46
|
+
* posture end to end) and identityaccount.conformance.test.ts.
|
|
47
|
+
*/
|
|
48
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
49
|
+
import { create } from "@bufbuild/protobuf";
|
|
50
|
+
import { ApiResourceAuditActorSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
|
|
51
|
+
import { IdentityAccountSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
52
|
+
import { IdentityAccountCommandController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/command_pb";
|
|
53
|
+
import { IdentityAccountQueryController } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/query_pb";
|
|
54
|
+
import { stepsForSlot } from "../../extensions/gate-slots.js";
|
|
55
|
+
import { internalError, invalidArgumentError } from "../../pipeline/errors.js";
|
|
56
|
+
import { apiResourceKindKey } from "../../pipeline/interceptors/apiresource.js";
|
|
57
|
+
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
58
|
+
import { newPipeline } from "../../pipeline/pipeline.js";
|
|
59
|
+
import { RequestContext } from "../../pipeline/request-context.js";
|
|
60
|
+
import { newCleanupIamPoliciesStep, newCreateAuthorizationTuplesStep, } from "../../pipeline/steps/authorization-tuples.js";
|
|
61
|
+
import { authorizeDirect, newAuthorizeStep, } from "../../pipeline/steps/authorize.js";
|
|
62
|
+
import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
|
|
63
|
+
import { newBuildNewStateStep } from "../../pipeline/steps/defaults.js";
|
|
64
|
+
import { newExtractResourceIdStep } from "../../pipeline/steps/delete.js";
|
|
65
|
+
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
66
|
+
import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
|
|
67
|
+
import { TARGET_RESOURCE_KEY } from "../../pipeline/steps/load-target.js";
|
|
68
|
+
import { newResolveSlugStep } from "../../pipeline/steps/slug.js";
|
|
69
|
+
import { newValidateProtoStep } from "../../pipeline/steps/validation.js";
|
|
70
|
+
import { ACCOUNT_NOT_FOUND_FOR_CALLER_MESSAGE, CREATE_IS_INTERNAL_MESSAGE, NO_IDP_ID_MESSAGE, USERINFO_UNAVAILABLE_PREFIX, federationUnimplementedMessage, idpIdOf, } from "./constants.js";
|
|
71
|
+
import { UserInfoFetchError } from "./provisioning.js";
|
|
72
|
+
import { accountNotFoundError, newAssignBackendFieldsStep, newCheckDuplicateStep, newDefaultAccountNameStep, newDeleteAccountStep, newDeriveAccountIdStep, newGuardImmutableSubjectStep, newLoadExistingAccountForDeleteStep, newLoadExistingAccountStep, newLoadTargetAccountStep, newPersistNewAccountStep, newPersistUpdatedAccountStep, newPreserveBackendFieldsStep, } from "./steps.js";
|
|
73
|
+
/** Registers both identityaccount services on the router (routes stage). */
|
|
74
|
+
export function registerIdentityAccountServices(router, deps) {
|
|
75
|
+
router.service(IdentityAccountCommandController, {
|
|
76
|
+
create: (account, ctx) => createRpc(deps, account, ctx),
|
|
77
|
+
update: (account, ctx) => update(deps, account, ctx),
|
|
78
|
+
delete: (id, ctx) => deleteAccount(deps, id, ctx),
|
|
79
|
+
createFederatedAccount: (input, ctx) => federated(deps, ctx, IdentityAccountCommandController.method.createFederatedAccount, input, (federation, ref, caller) => federation.createFederatedAccount(input, ref, caller)),
|
|
80
|
+
updateFederatedAccount: (input, ctx) => federated(deps, ctx, IdentityAccountCommandController.method.updateFederatedAccount, input, (federation, ref, caller) => federation.updateFederatedAccount(input, ref, caller)),
|
|
81
|
+
deprovisionFederatedAccount: (input, ctx) => federated(deps, ctx, IdentityAccountCommandController.method.deprovisionFederatedAccount, input, (federation, ref, caller) => federation.deprovisionFederatedAccount(input, ref, caller)),
|
|
82
|
+
provisionMyAccount: (empty, ctx) => provisionMyAccount(deps, empty, ctx),
|
|
83
|
+
});
|
|
84
|
+
router.service(IdentityAccountQueryController, {
|
|
85
|
+
get: (id, ctx) => get(deps, id, ctx),
|
|
86
|
+
whoAmI: (empty, ctx) => whoAmI(deps, empty, ctx),
|
|
87
|
+
getByEmail: (email, ctx) => getByEmail(deps, email, ctx),
|
|
88
|
+
getByIdpId: (idpId, ctx) => getByIdpId(deps, idpId, ctx),
|
|
89
|
+
getByExternalSub: (lookup, ctx) => federated(deps, ctx, IdentityAccountQueryController.method.getByExternalSub, lookup, (federation, ref, caller) => federation.getByExternalSub(lookup, ref, caller)),
|
|
90
|
+
getActorInfo: (id, ctx) => getActorInfo(deps, id, ctx),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function kindOf(ctx) {
|
|
94
|
+
return ctx.values.get(apiResourceKindKey);
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// The one create path
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Runs the create chain AS `caller` and answers the persisted account.
|
|
101
|
+
* `kind` is the service's kind tag for wire calls; the path callers with
|
|
102
|
+
* no HandlerContext (the provisioner, the operator ensure) pass the kind
|
|
103
|
+
* the controller serves.
|
|
104
|
+
*/
|
|
105
|
+
async function runCreateChain(deps, account, caller, kind) {
|
|
106
|
+
const reqCtx = new RequestContext(IdentityAccountSchema, account, caller, kind);
|
|
107
|
+
await newPipeline("identityaccount-create", deps.logger)
|
|
108
|
+
.addStep(newAuthorizeStep(IdentityAccountCommandController.method.create, deps.authorizer))
|
|
109
|
+
.addStep(newValidateProtoStep())
|
|
110
|
+
.addStep(newDefaultAccountNameStep())
|
|
111
|
+
.addStep(newResolveSlugStep())
|
|
112
|
+
.addStep(newDeriveAccountIdStep())
|
|
113
|
+
.addStep(newCheckDuplicateStep(deps.accounts))
|
|
114
|
+
.addStep(newBuildNewStateStep())
|
|
115
|
+
.addStep(newGuardReservedLabelsStep(deps.authorizer))
|
|
116
|
+
.addStep(newAssignBackendFieldsStep())
|
|
117
|
+
.addStep(newPersistNewAccountStep(deps.accounts))
|
|
118
|
+
.addStep(newCreateAuthorizationTuplesStep(deps.authorizationLifecycle, deps.logger))
|
|
119
|
+
.build()
|
|
120
|
+
.execute(reqCtx);
|
|
121
|
+
return reqCtx.newState;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The create path as its in-process callers see it (provisioning.ts
|
|
125
|
+
* `CreateAccount`): the input becomes the resource envelope the chain
|
|
126
|
+
* expects, tagged with the kind this controller serves.
|
|
127
|
+
*/
|
|
128
|
+
export function newCreateAccountPath(deps, kind) {
|
|
129
|
+
return (input, caller) => runCreateChain(deps, create(IdentityAccountSchema, {
|
|
130
|
+
apiVersion: "iam.stigmer.ai/v1",
|
|
131
|
+
kind: "IdentityAccount",
|
|
132
|
+
metadata: { name: input.name },
|
|
133
|
+
spec: input.spec,
|
|
134
|
+
}), caller, kind);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The create RPC: the cloud#393 gate, then the path AS the caller. FGA
|
|
138
|
+
* cannot gate this RPC (the account being created IS the principal — the
|
|
139
|
+
* bootstrap problem), so the gate is caller class: the platform's own
|
|
140
|
+
* pipelines only.
|
|
141
|
+
*/
|
|
142
|
+
function createRpc(deps, account, ctx) {
|
|
143
|
+
const caller = callerIdentityOf(ctx);
|
|
144
|
+
guardInternalRpc(caller);
|
|
145
|
+
return runCreateChain(deps, account, caller, kindOf(ctx));
|
|
146
|
+
}
|
|
147
|
+
function guardInternalRpc(caller) {
|
|
148
|
+
if (caller.callerClass === "machine" ||
|
|
149
|
+
caller.callerClass === "internal" ||
|
|
150
|
+
caller.origin === "in-process") {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
throw new ConnectError(CREATE_IS_INTERNAL_MESSAGE, Code.PermissionDenied);
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Update / Delete
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
/**
|
|
159
|
+
* Update: full-envelope replace with the cloud's writable surface (A9) —
|
|
160
|
+
* BuildUpdateState preserves id/slug/org/visibility, GuardImmutableSubject
|
|
161
|
+
* refuses a changed subject, AssignBackendFields preserves the rest of
|
|
162
|
+
* what the backend owns from the existing row.
|
|
163
|
+
*/
|
|
164
|
+
async function update(deps, account, ctx) {
|
|
165
|
+
const reqCtx = new RequestContext(IdentityAccountSchema, account, callerIdentityOf(ctx), kindOf(ctx));
|
|
166
|
+
await newPipeline("identityaccount-update", deps.logger)
|
|
167
|
+
.addStep(newAuthorizeStep(IdentityAccountCommandController.method.update, deps.authorizer))
|
|
168
|
+
.addStep(newValidateProtoStep())
|
|
169
|
+
.addStep(newLoadExistingAccountStep(deps.accounts))
|
|
170
|
+
.addStep(newGuardImmutableSubjectStep())
|
|
171
|
+
.addStep(newBuildUpdateStateStep())
|
|
172
|
+
.addStep(newPreserveBackendFieldsStep())
|
|
173
|
+
.addStep(newPersistUpdatedAccountStep(deps.accounts))
|
|
174
|
+
.build()
|
|
175
|
+
.execute(reqCtx);
|
|
176
|
+
return reqCtx.newState;
|
|
177
|
+
}
|
|
178
|
+
/** Delete — answers the pre-delete account (the canonical delete chain over the port). */
|
|
179
|
+
async function deleteAccount(deps, id, ctx) {
|
|
180
|
+
const reqCtx = new RequestContext(IdentityAccountCommandController.method.delete.input, id, callerIdentityOf(ctx), kindOf(ctx));
|
|
181
|
+
await newPipeline("identityaccount-delete", deps.logger)
|
|
182
|
+
.addStep(newAuthorizeStep(IdentityAccountCommandController.method.delete, deps.authorizer))
|
|
183
|
+
.addStep(newValidateProtoStep())
|
|
184
|
+
.addStep(newExtractResourceIdStep())
|
|
185
|
+
.addStep(newLoadExistingAccountForDeleteStep(deps.accounts))
|
|
186
|
+
.addStep(newDeleteAccountStep(deps.accounts))
|
|
187
|
+
.addStep(newCleanupIamPoliciesStep(deps.authorizationLifecycle, deps.logger))
|
|
188
|
+
.build()
|
|
189
|
+
.execute(reqCtx);
|
|
190
|
+
const deleted = reqCtx.get(EXISTING_RESOURCE_KEY);
|
|
191
|
+
if (deleted === undefined) {
|
|
192
|
+
throw internalError(new Error("delete pipeline completed without a loaded resource"), "deleted identity account not found in context");
|
|
193
|
+
}
|
|
194
|
+
return deleted;
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Reads
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
/** Get — LoadTarget by id through the port; the domain's NOT_FOUND copy. */
|
|
200
|
+
async function get(deps, id, ctx) {
|
|
201
|
+
return loadTarget(deps, IdentityAccountQueryController.method.get, id, ctx);
|
|
202
|
+
}
|
|
203
|
+
/** getActorInfo — the contract-shaped actor: id, avatar, "First Last", email. */
|
|
204
|
+
async function getActorInfo(deps, id, ctx) {
|
|
205
|
+
const account = await loadTarget(deps, IdentityAccountQueryController.method.getActorInfo, id, ctx);
|
|
206
|
+
const firstName = account.spec?.firstName ?? "";
|
|
207
|
+
const lastName = account.spec?.lastName ?? "";
|
|
208
|
+
return create(ApiResourceAuditActorSchema, {
|
|
209
|
+
id: account.metadata?.id ?? "",
|
|
210
|
+
avatar: account.spec?.pictureUrl ?? "",
|
|
211
|
+
displayName: `${firstName} ${lastName}`.trim(),
|
|
212
|
+
email: account.spec?.email ?? "",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async function loadTarget(deps, method, id, ctx) {
|
|
216
|
+
const reqCtx = new RequestContext(method.input, id, callerIdentityOf(ctx), kindOf(ctx));
|
|
217
|
+
await newPipeline(`identityaccount-${method.localName}`, deps.logger)
|
|
218
|
+
.addStep(newAuthorizeStep(method, deps.authorizer))
|
|
219
|
+
.addStep(newValidateProtoStep())
|
|
220
|
+
.addStep(newLoadTargetAccountStep(deps.accounts))
|
|
221
|
+
.build()
|
|
222
|
+
.execute(reqCtx);
|
|
223
|
+
return reqCtx.get(TARGET_RESOURCE_KEY);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* whoAmI — the cloud's lookup verbatim: by the resolved identity, then by
|
|
227
|
+
* the derived id of the caller's subject for a caller whose identity is
|
|
228
|
+
* still idp-shaped (an unprovisioned OIDC subject), then NOT_FOUND with
|
|
229
|
+
* the cloud's copy. Two primary-key reads at most; no scan.
|
|
230
|
+
*/
|
|
231
|
+
async function whoAmI(deps, _empty, ctx) {
|
|
232
|
+
const caller = callerIdentityOf(ctx);
|
|
233
|
+
let account = await read(() => deps.accounts.findById(caller.identityId), "failed to load identity account");
|
|
234
|
+
if (account === undefined) {
|
|
235
|
+
account = await read(() => deps.accounts.findDirectByIdpId(idpIdOf(caller)), "failed to load identity account");
|
|
236
|
+
}
|
|
237
|
+
if (account === undefined) {
|
|
238
|
+
throw new ConnectError(ACCOUNT_NOT_FOUND_FOR_CALLER_MESSAGE, Code.NotFound);
|
|
239
|
+
}
|
|
240
|
+
return account;
|
|
241
|
+
}
|
|
242
|
+
/** getByEmail — direct accounts only; lookup first, authorize on the FOUND id. */
|
|
243
|
+
async function getByEmail(deps, email, ctx) {
|
|
244
|
+
return lookupThenAuthorize(deps, IdentityAccountQueryController.method.getByEmail, email, ctx, () => deps.accounts.findDirectByEmail(email.value), email.value);
|
|
245
|
+
}
|
|
246
|
+
/** getByIdpId — any provisioning mode; lookup first, authorize on the FOUND id. */
|
|
247
|
+
async function getByIdpId(deps, idpId, ctx) {
|
|
248
|
+
return lookupThenAuthorize(deps, IdentityAccountQueryController.method.getByIdpId, idpId, ctx, () => deps.accounts.findByIdpId(idpId.value), idpId.value);
|
|
249
|
+
}
|
|
250
|
+
async function lookupThenAuthorize(deps, method, input, ctx, lookup, handle) {
|
|
251
|
+
const account = await read(lookup, "failed to look up identity account");
|
|
252
|
+
if (account === undefined) {
|
|
253
|
+
throw accountNotFoundError(handle);
|
|
254
|
+
}
|
|
255
|
+
await authorizeDirect(method, deps.authorizer, callerIdentityOf(ctx), input, {
|
|
256
|
+
resourceId: account.metadata?.id ?? "",
|
|
257
|
+
});
|
|
258
|
+
return account;
|
|
259
|
+
}
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Provisioning
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
/**
|
|
264
|
+
* provisionMyAccount — the subject from the caller's credential, the
|
|
265
|
+
* provisioner's flow, then the composed post-persist gates (the cloud's
|
|
266
|
+
* personal organization) with the caller RE-STAMPED as the account: the
|
|
267
|
+
* position-1 identity was idp-shaped because no row existed when the
|
|
268
|
+
* verifier ran, and a gate that attributes ownership must name the
|
|
269
|
+
* account, never a principal no later request carries. The gates run on
|
|
270
|
+
* EVERY call, the idempotent early return included — the cloud's step
|
|
271
|
+
* backfills accounts that predate personal organizations there, which is
|
|
272
|
+
* why Q-IA-9 chose a slot over onResourceCreated. Non-transactional in
|
|
273
|
+
* the `org-create:post-persist` sense: a gate failure fails the request,
|
|
274
|
+
* the row survives, the next call heals it.
|
|
275
|
+
*/
|
|
276
|
+
async function provisionMyAccount(deps, _empty, ctx) {
|
|
277
|
+
const caller = callerIdentityOf(ctx);
|
|
278
|
+
const idpId = idpIdOf(caller);
|
|
279
|
+
if (idpId === "") {
|
|
280
|
+
throw new ConnectError(NO_IDP_ID_MESSAGE, Code.Unauthenticated);
|
|
281
|
+
}
|
|
282
|
+
let account;
|
|
283
|
+
try {
|
|
284
|
+
account = await deps.provisioner.provisionDirectAccount(idpId, caller);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
if (error instanceof UserInfoFetchError) {
|
|
288
|
+
throw new ConnectError(`${USERINFO_UNAVAILABLE_PREFIX}${error.message}`, Code.Unavailable);
|
|
289
|
+
}
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
// The ratified provision slot (Q-IA-9; the gate-slots.ts header carries
|
|
293
|
+
// its semantics). Empty in OSS — no pipeline is built for zero steps.
|
|
294
|
+
const gates = stepsForSlot(deps.gateSteps, "identity-account-provision:post-persist");
|
|
295
|
+
if (gates.length > 0) {
|
|
296
|
+
const asAccount = {
|
|
297
|
+
...caller,
|
|
298
|
+
identityId: account.metadata?.id ?? "",
|
|
299
|
+
};
|
|
300
|
+
const reqCtx = new RequestContext(IdentityAccountSchema, account, asAccount, kindOf(ctx));
|
|
301
|
+
const pipeline = newPipeline("identityaccount-provision-post-persist", deps.logger);
|
|
302
|
+
for (const step of gates) {
|
|
303
|
+
pipeline.addStep(step);
|
|
304
|
+
}
|
|
305
|
+
await pipeline.build().execute(reqCtx);
|
|
306
|
+
}
|
|
307
|
+
return account;
|
|
308
|
+
}
|
|
309
|
+
async function federated(deps, ctx, method, input, arm) {
|
|
310
|
+
const federation = deps.federation;
|
|
311
|
+
if (federation === undefined) {
|
|
312
|
+
throw new ConnectError(federationUnimplementedMessage(`${method.parent.typeName}.${method.localName}`), Code.Unimplemented);
|
|
313
|
+
}
|
|
314
|
+
const caller = callerIdentityOf(ctx);
|
|
315
|
+
const org = input.org;
|
|
316
|
+
const ref = input.identityProviderRef;
|
|
317
|
+
if (ref === undefined || ref.slug === "") {
|
|
318
|
+
throw invalidArgumentError("identity_provider_ref with a valid slug is required");
|
|
319
|
+
}
|
|
320
|
+
if (ref.org !== "" && ref.org !== org) {
|
|
321
|
+
throw invalidArgumentError(`identity_provider_ref.org '${ref.org}' does not match input org '${org}'`);
|
|
322
|
+
}
|
|
323
|
+
// The annotation's target is the org (`field_path = "org"`); resolved
|
|
324
|
+
// from the input exactly as the pipeline step would.
|
|
325
|
+
await authorizeDirect(method, deps.authorizer, caller, input);
|
|
326
|
+
const refOrg = ref.org !== "" ? ref.org : org;
|
|
327
|
+
if (!(await federation.providerExists(refOrg, ref.slug))) {
|
|
328
|
+
throw new ConnectError(`IdentityProvider '${refOrg}/${ref.slug}' not found`, Code.NotFound);
|
|
329
|
+
}
|
|
330
|
+
ref.org = refOrg;
|
|
331
|
+
return arm(federation, ref, caller);
|
|
332
|
+
}
|
|
333
|
+
/** A direct handler's store read: a fault is INTERNAL with a static message, never "not found". */
|
|
334
|
+
async function read(operation, message) {
|
|
335
|
+
try {
|
|
336
|
+
return await operation();
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
throw internalError(error, message);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
2
|
+
import type { CreateAccount } from "./provisioning.js";
|
|
3
|
+
import type { IdentityAccountStore } from "./store.js";
|
|
4
|
+
/** The operator as boot/config.ts loads it; empty email = the "system" placeholder. */
|
|
5
|
+
export interface OperatorIdentity {
|
|
6
|
+
readonly email: string;
|
|
7
|
+
readonly displayName: string;
|
|
8
|
+
}
|
|
9
|
+
export interface OperatorAccountDeps {
|
|
10
|
+
readonly accounts: IdentityAccountStore;
|
|
11
|
+
readonly createAccount: CreateAccount;
|
|
12
|
+
}
|
|
13
|
+
export declare function ensureOperatorAccount(deps: OperatorAccountDeps, operator: OperatorIdentity): Promise<IdentityAccount>;
|
|
14
|
+
//# sourceMappingURL=operator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../../../src/domain/identityaccount/operator.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAKhG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;CACvC;AAKD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,mBAAmB,EACzB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC,CAiC1B"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The trusted-local operator account (20260911.11, T01_1_review.md A2,
|
|
3
|
+
* A5): under the posture with no authentication, the server knows its one
|
|
4
|
+
* principal from config, so the server states the fact at boot instead of
|
|
5
|
+
* asking three clients to provision it (the console's gate is off on the
|
|
6
|
+
* laptop and the desktop has none).
|
|
7
|
+
*
|
|
8
|
+
* `ensureOperatorAccount` is create-if-absent under the derived id of
|
|
9
|
+
* `local|<operator email, or "system">`, through the domain's ONE create
|
|
10
|
+
* path AS the trusted-local identity, so the row is stamped, derived and
|
|
11
|
+
* tuple-lifecycled exactly like every other account. Create-if-absent
|
|
12
|
+
* means a later boot never touches the row: preferences set through
|
|
13
|
+
* `update` survive a reboot, and a changed display name is edited like
|
|
14
|
+
* any profile, never re-synced from config. A changed operator email is a
|
|
15
|
+
* different subject and therefore a different account — the platform
|
|
16
|
+
* already treats the email as the identity (boot/config.ts
|
|
17
|
+
* loadOperatorIdentity). Two replicas ensuring at once converge by
|
|
18
|
+
* primary key: a lost race reads the winner back.
|
|
19
|
+
*
|
|
20
|
+
* The operator arrives from the SAME seam the interceptor stamps the
|
|
21
|
+
* caller from (pipeline/interceptors/auth.ts operatorIdentitySnapshot,
|
|
22
|
+
* read by the composition root), and the account is created AS the
|
|
23
|
+
* identity that seam yields for it (trustedLocalIdentityFor — the one
|
|
24
|
+
* construction of that principal), never from config directly: one
|
|
25
|
+
* source, so the row, its audit stamp and the per-request caller cannot
|
|
26
|
+
* disagree (A5).
|
|
27
|
+
*
|
|
28
|
+
* Whether this runs at all — only when no authentication posture is on —
|
|
29
|
+
* is the composition root's decision (boot/compose.ts); the cloud and an
|
|
30
|
+
* OIDC self-host never create one.
|
|
31
|
+
*/
|
|
32
|
+
import { create } from "@bufbuild/protobuf";
|
|
33
|
+
import { IdentityAccountSpecSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/spec_pb";
|
|
34
|
+
import { trustedLocalIdentityFor } from "../../pipeline/interceptors/auth.js";
|
|
35
|
+
import { accountIdFor, localIdpIdFor } from "./constants.js";
|
|
36
|
+
import { resolveCreateRace } from "./provisioning.js";
|
|
37
|
+
import { assignDirectBackendFields } from "./steps.js";
|
|
38
|
+
/** The unconfigured laptop's operator: what the interceptor stamps as identityId. */
|
|
39
|
+
const SYSTEM_OPERATOR = "system";
|
|
40
|
+
export async function ensureOperatorAccount(deps, operator) {
|
|
41
|
+
// The caller the interceptor would stamp for this operator; its
|
|
42
|
+
// identityId (the email, or "system") is the subject's local principal.
|
|
43
|
+
const caller = trustedLocalIdentityFor(operator);
|
|
44
|
+
const idpId = localIdpIdFor(caller.identityId);
|
|
45
|
+
const existing = await deps.accounts.findById(accountIdFor(idpId));
|
|
46
|
+
if (existing !== undefined) {
|
|
47
|
+
return existing;
|
|
48
|
+
}
|
|
49
|
+
const name = operator.displayName !== ""
|
|
50
|
+
? operator.displayName
|
|
51
|
+
: operator.email !== ""
|
|
52
|
+
? operator.email
|
|
53
|
+
: SYSTEM_OPERATOR;
|
|
54
|
+
try {
|
|
55
|
+
return await deps.createAccount({
|
|
56
|
+
name,
|
|
57
|
+
spec: assignDirectBackendFields(create(IdentityAccountSpecSchema, {
|
|
58
|
+
idpId,
|
|
59
|
+
email: operator.email,
|
|
60
|
+
})),
|
|
61
|
+
}, caller);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return resolveCreateRace(error, () => deps.accounts.findById(accountIdFor(idpId)));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=operator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/operator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,2DAA2D,CAAC;AAEtG,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAcvD,qFAAqF;AACrF,MAAM,eAAe,GAAG,QAAQ,CAAC;AAEjC,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAyB,EACzB,QAA0B;IAE1B,gEAAgE;IAChE,wEAAwE;IACxE,MAAM,MAAM,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,IAAI,GACR,QAAQ,CAAC,WAAW,KAAK,EAAE;QACzB,CAAC,CAAC,QAAQ,CAAC,WAAW;QACtB,CAAC,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE;YACrB,CAAC,CAAC,QAAQ,CAAC,KAAK;YAChB,CAAC,CAAC,eAAe,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,aAAa,CAC7B;YACE,IAAI;YACJ,IAAI,EAAE,yBAAyB,CAC7B,MAAM,CAAC,yBAAyB,EAAE;gBAChC,KAAK;gBACL,KAAK,EAAE,QAAQ,CAAC,KAAK;aACtB,CAAC,CACH;SACF,EACD,MAAM,CACP,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,CACnC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAC5C,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
2
|
+
import type { IdentityAccountSpec } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/spec_pb";
|
|
3
|
+
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
4
|
+
import type { IdentityAccountStore } from "./store.js";
|
|
5
|
+
/** The profile an identity provider's userinfo endpoint states. */
|
|
6
|
+
export interface UserProfile {
|
|
7
|
+
readonly email: string;
|
|
8
|
+
readonly firstName: string;
|
|
9
|
+
readonly lastName: string;
|
|
10
|
+
readonly pictureUrl: string;
|
|
11
|
+
}
|
|
12
|
+
/** The userinfo port: `issuer` is the one that vouched for `accessToken`. */
|
|
13
|
+
export interface UserInfoClient {
|
|
14
|
+
/** Throws on any failure to obtain a profile (the handler maps UNAVAILABLE). */
|
|
15
|
+
fetchUserProfile(issuer: string, accessToken: string): Promise<UserProfile>;
|
|
16
|
+
}
|
|
17
|
+
/** Thrown when the profile cannot be fetched; the handler answers UNAVAILABLE. */
|
|
18
|
+
export declare class UserInfoFetchError extends Error {
|
|
19
|
+
constructor(cause: unknown);
|
|
20
|
+
}
|
|
21
|
+
/** What a caller of the create path supplies: the rest is the chain's. */
|
|
22
|
+
export interface CreateAccountInput {
|
|
23
|
+
/** `metadata.name`; the chain defaults an empty one. */
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly spec: IdentityAccountSpec;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The domain's one create path, as its callers see it: runs the create
|
|
29
|
+
* chain AS `caller` and answers the persisted account. Built by the
|
|
30
|
+
* controller (newCreateAccountPath); the provisioner and the operator
|
|
31
|
+
* ensure call it, nothing else builds an account.
|
|
32
|
+
*/
|
|
33
|
+
export type CreateAccount = (input: CreateAccountInput, caller: CallerIdentity) => Promise<IdentityAccount>;
|
|
34
|
+
export interface DirectAccountProvisionerDeps {
|
|
35
|
+
readonly accounts: IdentityAccountStore;
|
|
36
|
+
readonly createAccount: CreateAccount;
|
|
37
|
+
readonly userInfo: UserInfoClient;
|
|
38
|
+
}
|
|
39
|
+
export interface DirectAccountProvisioner {
|
|
40
|
+
/** provisionMyAccount's domain flow; `idpId` is the caller's verified subject. */
|
|
41
|
+
provisionDirectAccount(idpId: string, caller: CallerIdentity): Promise<IdentityAccount>;
|
|
42
|
+
}
|
|
43
|
+
export declare function newDirectAccountProvisioner(deps: DirectAccountProvisionerDeps): DirectAccountProvisioner;
|
|
44
|
+
/**
|
|
45
|
+
* The concurrent-create resolution every lane uses: on a failed create,
|
|
46
|
+
* read the winner back by the lane's natural key; a winner is the answer,
|
|
47
|
+
* whatever the failure's shape (the chain's ALREADY_EXISTS, a driver's
|
|
48
|
+
* DuplicateAccountError, a transport fault after the row landed). No
|
|
49
|
+
* winner means the failure was real — it propagates untouched.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveCreateRace(error: unknown, findWinner: () => Promise<IdentityAccount | undefined>): Promise<IdentityAccount>;
|
|
52
|
+
//# sourceMappingURL=provisioning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../../../src/domain/identityaccount/provisioning.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAChG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2DAA2D,CAAC;AAGrG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7E;AAED,kFAAkF;AAClF,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,KAAK,EAAE,OAAO;CAI3B;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,eAAe,CAAC,CAAC;AAE9B,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;CACnC;AAED,MAAM,WAAW,wBAAwB;IACvC,kFAAkF;IAClF,sBAAsB,CACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,eAAe,CAAC,CAAC;CAC7B;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,4BAA4B,GACjC,wBAAwB,CA+B1B;AAkCD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,MAAM,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,GACrD,OAAO,CAAC,eAAe,CAAC,CAM1B"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct-account self-provisioning (20260911.11, Q-IA-2; the cloud's
|
|
3
|
+
* iam/account/provisioning.ts steps 1 to 3 as OSS core): the console, the
|
|
4
|
+
* CLI and the React hook call provisionMyAccount when whoAmI answers
|
|
5
|
+
* NOT_FOUND on a first sign-in, and the caller's OWN authenticated request
|
|
6
|
+
* drives provisioning synchronously — no webhook, no external trigger.
|
|
7
|
+
*
|
|
8
|
+
* The flow:
|
|
9
|
+
* 1. Idempotent early return when the subject already has an account
|
|
10
|
+
* (a primary-key read of the derived id; no userinfo call).
|
|
11
|
+
* 2. The profile. A token-bearing caller's comes from the userinfo
|
|
12
|
+
* endpoint of the issuer that vouched for the token, fetched with the
|
|
13
|
+
* caller's own access token — the token goes only where it came from
|
|
14
|
+
* (T01_1_review.md A8). One path for every issuer: `given_name` and
|
|
15
|
+
* `family_name` as the IdP states them, where splitting a `name`
|
|
16
|
+
* claim would be guesswork. A caller with no issuer (the trusted-local
|
|
17
|
+
* operator) never dials out: its profile is what the CallerIdentity
|
|
18
|
+
* carries.
|
|
19
|
+
* 3. Create through the ONE create path the domain owns — the same chain
|
|
20
|
+
* the create RPC runs, so the tuple lifecycle fires for both — AS the
|
|
21
|
+
* caller: the account is created by the subject it is for, and its
|
|
22
|
+
* audit stamp says so. A lost first-login race resolves the winner by
|
|
23
|
+
* subject whatever the failure's shape (the cloud's idempotency arm):
|
|
24
|
+
* the primary key guarantees one row, so a failed create with a row
|
|
25
|
+
* underneath it is a win, not an error.
|
|
26
|
+
*
|
|
27
|
+
* Step 4 of the cloud's flow (the personal organization) is not core: it
|
|
28
|
+
* fires on the `identity-account-provision:post-persist` slot in the
|
|
29
|
+
* composition (A10), wired by the controller.
|
|
30
|
+
*
|
|
31
|
+
* This module holds the domain flow over two PORTS — the store and the
|
|
32
|
+
* userinfo client — and no network code; identity/oidc-userinfo.ts is the
|
|
33
|
+
* OIDC implementation the composition root wires in.
|
|
34
|
+
*/
|
|
35
|
+
import { create } from "@bufbuild/protobuf";
|
|
36
|
+
import { IdentityAccountSpecSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/spec_pb";
|
|
37
|
+
import { assignDirectBackendFields } from "./steps.js";
|
|
38
|
+
/** Thrown when the profile cannot be fetched; the handler answers UNAVAILABLE. */
|
|
39
|
+
export class UserInfoFetchError extends Error {
|
|
40
|
+
constructor(cause) {
|
|
41
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
42
|
+
this.name = "UserInfoFetchError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function newDirectAccountProvisioner(deps) {
|
|
46
|
+
return {
|
|
47
|
+
async provisionDirectAccount(idpId, caller) {
|
|
48
|
+
const existing = await deps.accounts.findDirectByIdpId(idpId);
|
|
49
|
+
if (existing !== undefined) {
|
|
50
|
+
return existing;
|
|
51
|
+
}
|
|
52
|
+
const profile = await profileOf(deps.userInfo, caller);
|
|
53
|
+
const input = {
|
|
54
|
+
name: displayNameOf(caller, profile),
|
|
55
|
+
spec: assignDirectBackendFields(create(IdentityAccountSpecSchema, {
|
|
56
|
+
idpId,
|
|
57
|
+
email: profile.email,
|
|
58
|
+
firstName: profile.firstName,
|
|
59
|
+
lastName: profile.lastName,
|
|
60
|
+
pictureUrl: profile.pictureUrl,
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
return await deps.createAccount(input, caller);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return resolveCreateRace(error, () => deps.accounts.findDirectByIdpId(idpId));
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Userinfo for a token-bearing caller; the identity's own claims otherwise. */
|
|
73
|
+
async function profileOf(userInfo, caller) {
|
|
74
|
+
if (caller.issuer === "") {
|
|
75
|
+
return {
|
|
76
|
+
email: caller.email ?? "",
|
|
77
|
+
firstName: "",
|
|
78
|
+
lastName: "",
|
|
79
|
+
pictureUrl: "",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return await userInfo.fetchUserProfile(caller.issuer, caller.rawToken);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw new UserInfoFetchError(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The cloud names a provisioned account by its email; the trusted-local
|
|
91
|
+
* operator, who has no userinfo, by the display name the identity
|
|
92
|
+
* carries. An empty result is the chain's to default (DefaultAccountName).
|
|
93
|
+
*/
|
|
94
|
+
function displayNameOf(caller, profile) {
|
|
95
|
+
if (caller.issuer === "") {
|
|
96
|
+
return caller.displayName ?? profile.email;
|
|
97
|
+
}
|
|
98
|
+
return profile.email;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The concurrent-create resolution every lane uses: on a failed create,
|
|
102
|
+
* read the winner back by the lane's natural key; a winner is the answer,
|
|
103
|
+
* whatever the failure's shape (the chain's ALREADY_EXISTS, a driver's
|
|
104
|
+
* DuplicateAccountError, a transport fault after the row landed). No
|
|
105
|
+
* winner means the failure was real — it propagates untouched.
|
|
106
|
+
*/
|
|
107
|
+
export async function resolveCreateRace(error, findWinner) {
|
|
108
|
+
const winner = await findWinner();
|
|
109
|
+
if (winner === undefined) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
return winner;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=provisioning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../../../src/domain/identityaccount/provisioning.ts"],"names":[],"mappings":"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"}
|