@vantageos/cloud-identity 0.10.0 → 0.12.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/CHANGELOG.md +115 -0
- package/README.md +220 -1
- package/dist/identity-refusal.d.ts +3 -2
- package/dist/identity-refusal.d.ts.map +1 -1
- package/dist/identity-refusal.js +2 -1
- package/dist/identity-refusal.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/person-principal.d.ts +7 -0
- package/dist/person-principal.d.ts.map +1 -1
- package/dist/person-principal.js +7 -0
- package/dist/person-principal.js.map +1 -1
- package/dist/principal-by-id.d.ts +249 -0
- package/dist/principal-by-id.d.ts.map +1 -0
- package/dist/principal-by-id.js +388 -0
- package/dist/principal-by-id.js.map +1 -0
- package/dist/scope-filter.d.ts +19 -0
- package/dist/scope-filter.d.ts.map +1 -1
- package/dist/scope-filter.js +19 -0
- package/dist/scope-filter.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The acting principal by ID, and the target checked by ID.
|
|
3
|
+
*
|
|
4
|
+
* An actor and a target are identified by their stored IDs, never by a name.
|
|
5
|
+
* A name is a display label: it never selects a row and never authorises one.
|
|
6
|
+
*
|
|
7
|
+
* Motivating incident: an agent of organisation A named "eta", calling by name
|
|
8
|
+
* through a service account, completed, updated and deleted the task of
|
|
9
|
+
* organisation B's agent also named "eta" at ten task doors, because every door
|
|
10
|
+
* admitted the caller by comparing names. Two different agents shared a label;
|
|
11
|
+
* nothing compared their IDs.
|
|
12
|
+
*
|
|
13
|
+
* Two functions close that class:
|
|
14
|
+
*
|
|
15
|
+
* - `resolveActingPrincipal(credential, lookups)` turns a VERIFIED credential
|
|
16
|
+
* into `{ principalId, orgId, kind }` read from stored rows, or refuses.
|
|
17
|
+
* - `assertTargetBelongsTo(principal, target, lookups, opts)` admits a target
|
|
18
|
+
* only when its stored organisation ID (and owner ID, where the door is
|
|
19
|
+
* owner-only) equals the principal's resolved IDs.
|
|
20
|
+
*
|
|
21
|
+
* The fleet is the OPERATOR ORGANISATION: the organisation the consumer's
|
|
22
|
+
* `orgKindOf` adapter reports as `"operator"`, read from stored data at run
|
|
23
|
+
* time. No organisation ID is reserved or spelled in this package; an
|
|
24
|
+
* adapter that answers anything else, or nothing, means "not the fleet".
|
|
25
|
+
*
|
|
26
|
+
* RULING 5: membership of the operator org is ORDINARY membership. Its agents
|
|
27
|
+
* and persons resolve as `kind: "agent"` / `"person"` of that org and reach its
|
|
28
|
+
* rows only. Cross-org reach (`kind: "fleet"` + `fleetCrossOrg`) belongs to the
|
|
29
|
+
* operator org's SERVICE ACCOUNT alone; `reserved-fleet-scope` refuses a claim
|
|
30
|
+
* of that scope by any other credential, never plain membership.
|
|
31
|
+
*
|
|
32
|
+
* Pure functions over injected lookups: no database, no framework, no network.
|
|
33
|
+
* The consumer performs each indexed read by ID and passes the function in;
|
|
34
|
+
* every failure (throwing lookup, miss, malformed row, inactive row, wrong
|
|
35
|
+
* organisation) is a typed `IdentityRefusal`, never a default principal.
|
|
36
|
+
*
|
|
37
|
+
* @security This module verifies NOTHING about the credential itself. The IDs
|
|
38
|
+
* in a credential must come from the consumer's verification of what the
|
|
39
|
+
* caller presented (a bearer row, a person's token, the service secret). A
|
|
40
|
+
* client-supplied argument passed as a credential becomes the principal.
|
|
41
|
+
*/
|
|
42
|
+
import { z } from "zod";
|
|
43
|
+
import { type IdentityRefusal } from "./identity-refusal.js";
|
|
44
|
+
/**
|
|
45
|
+
* A verified credential, in the package's vocabulary. Every identity field is
|
|
46
|
+
* an ID. The schemas are STRICT: a credential carrying any other key (an
|
|
47
|
+
* `agentName`, a `callerOrchestrator`, a `name`) is refused whole, so a name
|
|
48
|
+
* cannot be smuggled in beside an ID.
|
|
49
|
+
*
|
|
50
|
+
* - `agent`: an agent's own bearer, verified by the consumer (for example by
|
|
51
|
+
* `validatePresentedBearer`): the agent ID and the organisation the bearer
|
|
52
|
+
* row is stamped with.
|
|
53
|
+
* - `person`: a person's token, verified by the consumer: the stored person
|
|
54
|
+
* ID (the verified subject) and the organisation the token is bound to.
|
|
55
|
+
* Optionally (0.12.0) the role the SAME token carries in that organisation
|
|
56
|
+
* (`verifiedOrgRole`, for example Clerk's `org_role`), read by
|
|
57
|
+
* `assertOrgAdmin`. Only a verified claim belongs here, never an argument.
|
|
58
|
+
* - `service`: the service account, verified by the consumer. When it acts
|
|
59
|
+
* for an agent it names that agent BY ID (`actingForAgentId`); the agent
|
|
60
|
+
* is resolved within the service account's own organisation.
|
|
61
|
+
*/
|
|
62
|
+
export declare const actingCredentialSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
63
|
+
kind: z.ZodLiteral<"agent">;
|
|
64
|
+
agentId: z.ZodString;
|
|
65
|
+
verifiedOrgId: z.ZodString;
|
|
66
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
67
|
+
kind: z.ZodLiteral<"person">;
|
|
68
|
+
personId: z.ZodString;
|
|
69
|
+
verifiedOrgId: z.ZodString;
|
|
70
|
+
verifiedOrgRole: z.ZodOptional<z.ZodString>;
|
|
71
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
72
|
+
kind: z.ZodLiteral<"service">;
|
|
73
|
+
serviceAccountId: z.ZodString;
|
|
74
|
+
actingForAgentId: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, z.core.$strict>], "kind">;
|
|
76
|
+
export type ActingCredential = z.input<typeof actingCredentialSchema>;
|
|
77
|
+
/**
|
|
78
|
+
* Each row repeats its own ID, so a loose lookup (prefix match, wrong index)
|
|
79
|
+
* returning another row cannot become a principal. `active` is REQUIRED and
|
|
80
|
+
* must be `true`: an absent flag grants nothing. Extra fields (a display name)
|
|
81
|
+
* are allowed and ignored.
|
|
82
|
+
*/
|
|
83
|
+
export declare const principalRowSchema: z.ZodObject<{
|
|
84
|
+
id: z.ZodString;
|
|
85
|
+
orgId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
86
|
+
active: z.ZodBoolean;
|
|
87
|
+
}, z.core.$strip>;
|
|
88
|
+
export type PrincipalRow = z.input<typeof principalRowSchema>;
|
|
89
|
+
export declare const organisationRowSchema: z.ZodObject<{
|
|
90
|
+
id: z.ZodString;
|
|
91
|
+
active: z.ZodBoolean;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
export type OrganisationRow = z.input<typeof organisationRowSchema>;
|
|
94
|
+
/**
|
|
95
|
+
* The kind of a stored organisation, as the consumer's mapping row records it.
|
|
96
|
+
* `"operator"` is the fleet: the operator's own organisation, whose rows are
|
|
97
|
+
* the master scope. Any other answer, `null` included, is not the fleet.
|
|
98
|
+
*/
|
|
99
|
+
export type OrgKind = "operator" | "client";
|
|
100
|
+
type Lookup<A extends unknown[], R> = (...args: A) => Promise<R | null | undefined> | R | null | undefined;
|
|
101
|
+
/** Indexed reads BY ID. Only the lookups of the presented path are called. */
|
|
102
|
+
export type PrincipalLookups = {
|
|
103
|
+
agentById?: Lookup<[agentId: string], PrincipalRow>;
|
|
104
|
+
/** A person's row IN one organisation (their membership there). */
|
|
105
|
+
personById?: Lookup<[personId: string, orgId: string], PrincipalRow>;
|
|
106
|
+
serviceAccountById?: Lookup<[serviceAccountId: string], PrincipalRow>;
|
|
107
|
+
/** The organisation row; the operator organisation is checked like any other. */
|
|
108
|
+
organisationById?: Lookup<[orgId: string], OrganisationRow>;
|
|
109
|
+
/**
|
|
110
|
+
* The stored kind of an organisation (for example `orgKind` on its mapping
|
|
111
|
+
* row). Decides, from data, whether an organisation is the operator org,
|
|
112
|
+
* that is the fleet. `null`, a miss, or any value other than exactly
|
|
113
|
+
* `"operator"` is NOT the fleet. A missing or throwing adapter refuses.
|
|
114
|
+
*/
|
|
115
|
+
orgKindOf?: Lookup<[orgId: string], OrgKind>;
|
|
116
|
+
};
|
|
117
|
+
/** The one lookup `assertTargetBelongsTo` reads. */
|
|
118
|
+
export type OrgKindLookups = Pick<PrincipalLookups, "orgKindOf">;
|
|
119
|
+
export type ActingPrincipalKind = "agent" | "person" | "service" | "fleet";
|
|
120
|
+
export type ActingPrincipal = {
|
|
121
|
+
/** The stored ID of the actor: agent ID, person ID or service-account ID. */
|
|
122
|
+
principalId: string;
|
|
123
|
+
/** The stored organisation ID. The operator org's ID for `fleet`. */
|
|
124
|
+
orgId: string;
|
|
125
|
+
kind: ActingPrincipalKind;
|
|
126
|
+
/** Set when a service account acts for an agent: the service account's ID. */
|
|
127
|
+
viaServiceAccountId?: string;
|
|
128
|
+
/**
|
|
129
|
+
* `kind: "person"` only, and only when the credential carried
|
|
130
|
+
* `verifiedOrgRole`: the verified role in `orgId`. Read by `assertOrgAdmin`.
|
|
131
|
+
*/
|
|
132
|
+
orgRole?: string;
|
|
133
|
+
};
|
|
134
|
+
export type ResolveActingPrincipalResult = {
|
|
135
|
+
ok: true;
|
|
136
|
+
principal: ActingPrincipal;
|
|
137
|
+
} | {
|
|
138
|
+
ok: false;
|
|
139
|
+
refusal: IdentityRefusal;
|
|
140
|
+
};
|
|
141
|
+
export type TargetIds = {
|
|
142
|
+
/** The target row's stored organisation ID. Absent: unstamped. */
|
|
143
|
+
orgId?: string | null;
|
|
144
|
+
/** The target row's stored owner ID (agent, person or service account). */
|
|
145
|
+
ownerId?: string | null;
|
|
146
|
+
};
|
|
147
|
+
export type AssertTargetOptions = {
|
|
148
|
+
/** The consumer's name for the door; carried on every refusal. */
|
|
149
|
+
door?: string;
|
|
150
|
+
/** Owner-only door: the target's `ownerId` must equal `principalId`. */
|
|
151
|
+
ownerOnly?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* A fleet principal may reach a row of a CLIENT organisation only when the
|
|
154
|
+
* door declares it (a master export), and only when `orgKindOf` reports that
|
|
155
|
+
* organisation as `"client"`. Default `false`: refused.
|
|
156
|
+
*/
|
|
157
|
+
fleetCrossOrg?: boolean;
|
|
158
|
+
};
|
|
159
|
+
export type AssertTargetResult = {
|
|
160
|
+
ok: true;
|
|
161
|
+
} | {
|
|
162
|
+
ok: false;
|
|
163
|
+
refusal: IdentityRefusal;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Resolves the acting principal from a VERIFIED credential to stored IDs, or
|
|
167
|
+
* refuses. Refusal by default: a credential claiming the fleet kind
|
|
168
|
+
* (`reserved-fleet-scope`), a credential that does not parse (including one
|
|
169
|
+
* carrying any name field), a missing or throwing lookup, a miss, a malformed
|
|
170
|
+
* or inactive row, an unstamped row and an inactive organisation are all
|
|
171
|
+
* refused. Nothing is ever resolved by a name, and nothing falls back to a
|
|
172
|
+
* master principal.
|
|
173
|
+
*
|
|
174
|
+
* - agent: the agent row by `agentId`; its stored org must equal the org
|
|
175
|
+
* the credential was verified for. An agent of the operator org resolves
|
|
176
|
+
* as an ordinary `kind: "agent"` of that org (RULING 5).
|
|
177
|
+
* - person: the person row by (`personId`, `verifiedOrgId`); it must be
|
|
178
|
+
* stamped with that org. A person of the operator org resolves as an
|
|
179
|
+
* ordinary `kind: "person"` of that org (RULING 5).
|
|
180
|
+
* - service: the service-account row by `serviceAccountId`. Alone, it acts
|
|
181
|
+
* as itself (`kind: "service"`, or `kind: "fleet"` when `orgKindOf` reports
|
|
182
|
+
* its stored org as `"operator"`). With `actingForAgentId`, the agent row is read by
|
|
183
|
+
* ID and must be stamped with the service account's own org; the principal
|
|
184
|
+
* is that agent, with `viaServiceAccountId` recording the carrier.
|
|
185
|
+
*/
|
|
186
|
+
export declare function resolveActingPrincipal(credential: ActingCredential | null | undefined, lookups: PrincipalLookups, door?: string): Promise<ResolveActingPrincipalResult>;
|
|
187
|
+
/**
|
|
188
|
+
* Admits a target only by comparing its STORED IDs with the principal's
|
|
189
|
+
* resolved IDs, byte for byte. No name field is read; there is none to read.
|
|
190
|
+
*
|
|
191
|
+
* Whether an organisation is the operator org (the fleet) is read from
|
|
192
|
+
* `lookups.orgKindOf`; a missing or throwing adapter refuses.
|
|
193
|
+
*
|
|
194
|
+
* - An absent principal, or one with an empty ID, is refused.
|
|
195
|
+
* - A `kind: "fleet"` principal outside the operator org is refused, and so
|
|
196
|
+
* is a `kind: "service"` principal inside it (the operator org's service
|
|
197
|
+
* account is the fleet). An agent or person of the operator org is an
|
|
198
|
+
* ordinary member of it (RULING 5).
|
|
199
|
+
* - An unstamped target (no `orgId`) is refused to EVERY principal, the
|
|
200
|
+
* fleet principal included. No right is inferred from an absence: a fleet
|
|
201
|
+
* row carries the operator org's ID explicitly, and a row with no
|
|
202
|
+
* organisation is never treated as a master row.
|
|
203
|
+
* - An operator-org row is refused to every client principal.
|
|
204
|
+
* - Otherwise the target's `orgId` must equal the principal's. A fleet
|
|
205
|
+
* principal reaches a client organisation's row only when the door sets
|
|
206
|
+
* `fleetCrossOrg` (a master export) AND `orgKindOf` reports that
|
|
207
|
+
* organisation as `"client"`; an unknown organisation is refused. Any
|
|
208
|
+
* other principal reaching across organisations under `fleetCrossOrg`
|
|
209
|
+
* claims the fleet scope and is refused `reserved-fleet-scope`.
|
|
210
|
+
* - `ownerOnly`: the target's `ownerId` must also equal `principalId`; an
|
|
211
|
+
* absent owner is refused.
|
|
212
|
+
*/
|
|
213
|
+
export declare function assertTargetBelongsTo(principal: ActingPrincipal | null | undefined, target: TargetIds | null | undefined, lookups: OrgKindLookups, opts?: AssertTargetOptions): Promise<AssertTargetResult>;
|
|
214
|
+
export type AssertOrgAdminOptions = {
|
|
215
|
+
/**
|
|
216
|
+
* The role values that count as organisation admin, exactly as the verified
|
|
217
|
+
* claim spells them (for Clerk: `["org:admin"]`). Data supplied by the
|
|
218
|
+
* consumer; an empty or malformed list admits nobody.
|
|
219
|
+
*/
|
|
220
|
+
adminRoles: readonly string[];
|
|
221
|
+
/** The consumer's name for the door; carried on every refusal. */
|
|
222
|
+
door?: string;
|
|
223
|
+
};
|
|
224
|
+
export type AssertOrgAdminResult = {
|
|
225
|
+
ok: true;
|
|
226
|
+
} | {
|
|
227
|
+
ok: false;
|
|
228
|
+
refusal: IdentityRefusal;
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* Admits a principal as ADMIN of the target organisation, deciding by ID only.
|
|
232
|
+
* The principal must come from `resolveActingPrincipal`; the role is the
|
|
233
|
+
* verified claim its person credential carried (`verifiedOrgRole`), so no host
|
|
234
|
+
* lookup is needed and none is read here.
|
|
235
|
+
*
|
|
236
|
+
* - An absent principal, or one with an empty ID, is refused.
|
|
237
|
+
* - Only a PERSON is an organisation admin. An agent, a service account and
|
|
238
|
+
* the fleet principal are refused `principal-not-a-person`, whatever they
|
|
239
|
+
* carry: the service account keeps its own rules and never becomes an
|
|
240
|
+
* admin through this check.
|
|
241
|
+
* - The target organisation must be present and byte-equal to the
|
|
242
|
+
* principal's stored organisation: an admin of A is not an admin of B.
|
|
243
|
+
* - The role must be byte-equal to one entry of `adminRoles`. An absent
|
|
244
|
+
* role, an unknown role, and an empty or malformed `adminRoles` refuse.
|
|
245
|
+
* No case folding, no prefix stripping, no default role.
|
|
246
|
+
*/
|
|
247
|
+
export declare function assertOrgAdmin(principal: ActingPrincipal | null | undefined, targetOrgId: string | null | undefined, opts: AssertOrgAdminOptions): AssertOrgAdminResult;
|
|
248
|
+
export {};
|
|
249
|
+
//# sourceMappingURL=principal-by-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"principal-by-id.d.ts","sourceRoot":"","sources":["../src/principal-by-id.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The acting principal by ID, and the target checked by ID.
|
|
3
|
+
*
|
|
4
|
+
* An actor and a target are identified by their stored IDs, never by a name.
|
|
5
|
+
* A name is a display label: it never selects a row and never authorises one.
|
|
6
|
+
*
|
|
7
|
+
* Motivating incident: an agent of organisation A named "eta", calling by name
|
|
8
|
+
* through a service account, completed, updated and deleted the task of
|
|
9
|
+
* organisation B's agent also named "eta" at ten task doors, because every door
|
|
10
|
+
* admitted the caller by comparing names. Two different agents shared a label;
|
|
11
|
+
* nothing compared their IDs.
|
|
12
|
+
*
|
|
13
|
+
* Two functions close that class:
|
|
14
|
+
*
|
|
15
|
+
* - `resolveActingPrincipal(credential, lookups)` turns a VERIFIED credential
|
|
16
|
+
* into `{ principalId, orgId, kind }` read from stored rows, or refuses.
|
|
17
|
+
* - `assertTargetBelongsTo(principal, target, lookups, opts)` admits a target
|
|
18
|
+
* only when its stored organisation ID (and owner ID, where the door is
|
|
19
|
+
* owner-only) equals the principal's resolved IDs.
|
|
20
|
+
*
|
|
21
|
+
* The fleet is the OPERATOR ORGANISATION: the organisation the consumer's
|
|
22
|
+
* `orgKindOf` adapter reports as `"operator"`, read from stored data at run
|
|
23
|
+
* time. No organisation ID is reserved or spelled in this package; an
|
|
24
|
+
* adapter that answers anything else, or nothing, means "not the fleet".
|
|
25
|
+
*
|
|
26
|
+
* RULING 5: membership of the operator org is ORDINARY membership. Its agents
|
|
27
|
+
* and persons resolve as `kind: "agent"` / `"person"` of that org and reach its
|
|
28
|
+
* rows only. Cross-org reach (`kind: "fleet"` + `fleetCrossOrg`) belongs to the
|
|
29
|
+
* operator org's SERVICE ACCOUNT alone; `reserved-fleet-scope` refuses a claim
|
|
30
|
+
* of that scope by any other credential, never plain membership.
|
|
31
|
+
*
|
|
32
|
+
* Pure functions over injected lookups: no database, no framework, no network.
|
|
33
|
+
* The consumer performs each indexed read by ID and passes the function in;
|
|
34
|
+
* every failure (throwing lookup, miss, malformed row, inactive row, wrong
|
|
35
|
+
* organisation) is a typed `IdentityRefusal`, never a default principal.
|
|
36
|
+
*
|
|
37
|
+
* @security This module verifies NOTHING about the credential itself. The IDs
|
|
38
|
+
* in a credential must come from the consumer's verification of what the
|
|
39
|
+
* caller presented (a bearer row, a person's token, the service secret). A
|
|
40
|
+
* client-supplied argument passed as a credential becomes the principal.
|
|
41
|
+
*/
|
|
42
|
+
import { z } from "zod";
|
|
43
|
+
import { refusal } from "./identity-refusal.js";
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// The credential: verified IDs only, no name field anywhere
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* A verified credential, in the package's vocabulary. Every identity field is
|
|
49
|
+
* an ID. The schemas are STRICT: a credential carrying any other key (an
|
|
50
|
+
* `agentName`, a `callerOrchestrator`, a `name`) is refused whole, so a name
|
|
51
|
+
* cannot be smuggled in beside an ID.
|
|
52
|
+
*
|
|
53
|
+
* - `agent`: an agent's own bearer, verified by the consumer (for example by
|
|
54
|
+
* `validatePresentedBearer`): the agent ID and the organisation the bearer
|
|
55
|
+
* row is stamped with.
|
|
56
|
+
* - `person`: a person's token, verified by the consumer: the stored person
|
|
57
|
+
* ID (the verified subject) and the organisation the token is bound to.
|
|
58
|
+
* Optionally (0.12.0) the role the SAME token carries in that organisation
|
|
59
|
+
* (`verifiedOrgRole`, for example Clerk's `org_role`), read by
|
|
60
|
+
* `assertOrgAdmin`. Only a verified claim belongs here, never an argument.
|
|
61
|
+
* - `service`: the service account, verified by the consumer. When it acts
|
|
62
|
+
* for an agent it names that agent BY ID (`actingForAgentId`); the agent
|
|
63
|
+
* is resolved within the service account's own organisation.
|
|
64
|
+
*/
|
|
65
|
+
export const actingCredentialSchema = z.discriminatedUnion("kind", [
|
|
66
|
+
z.strictObject({
|
|
67
|
+
kind: z.literal("agent"),
|
|
68
|
+
agentId: z.string().min(1),
|
|
69
|
+
verifiedOrgId: z.string().min(1),
|
|
70
|
+
}),
|
|
71
|
+
z.strictObject({
|
|
72
|
+
kind: z.literal("person"),
|
|
73
|
+
personId: z.string().min(1),
|
|
74
|
+
verifiedOrgId: z.string().min(1),
|
|
75
|
+
verifiedOrgRole: z.string().min(1).optional(),
|
|
76
|
+
}),
|
|
77
|
+
z.strictObject({
|
|
78
|
+
kind: z.literal("service"),
|
|
79
|
+
serviceAccountId: z.string().min(1),
|
|
80
|
+
actingForAgentId: z.string().min(1).optional(),
|
|
81
|
+
}),
|
|
82
|
+
]);
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// The stored rows the consumer's lookups return
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Each row repeats its own ID, so a loose lookup (prefix match, wrong index)
|
|
88
|
+
* returning another row cannot become a principal. `active` is REQUIRED and
|
|
89
|
+
* must be `true`: an absent flag grants nothing. Extra fields (a display name)
|
|
90
|
+
* are allowed and ignored.
|
|
91
|
+
*/
|
|
92
|
+
export const principalRowSchema = z.object({
|
|
93
|
+
id: z.string().min(1),
|
|
94
|
+
orgId: z.string().min(1).nullish(),
|
|
95
|
+
active: z.boolean(),
|
|
96
|
+
});
|
|
97
|
+
export const organisationRowSchema = z.object({
|
|
98
|
+
id: z.string().min(1),
|
|
99
|
+
active: z.boolean(),
|
|
100
|
+
});
|
|
101
|
+
function nonEmpty(value) {
|
|
102
|
+
return typeof value === "string" && value.length > 0;
|
|
103
|
+
}
|
|
104
|
+
function denied(reason, door, detail) {
|
|
105
|
+
return { ok: false, refusal: refusal("RBAC_DENIED", reason, door, detail) };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Reads one principal row BY ID and checks it: the lookup is supplied and does
|
|
109
|
+
* not throw, the row exists, is well formed, repeats the ID asked for, and is
|
|
110
|
+
* active. The lookup's own error text is never surfaced.
|
|
111
|
+
*/
|
|
112
|
+
async function loadPrincipalRow(lookup, args, expectedId, what, door) {
|
|
113
|
+
if (typeof lookup !== "function") {
|
|
114
|
+
return denied("principal-lookup-failed", door, `No lookup was supplied for the ${what}.`);
|
|
115
|
+
}
|
|
116
|
+
let raw;
|
|
117
|
+
try {
|
|
118
|
+
raw = await lookup(...args);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return denied("principal-lookup-failed", door, `The ${what} could not be read, so none is resolved.`);
|
|
122
|
+
}
|
|
123
|
+
if (raw === null || raw === undefined) {
|
|
124
|
+
return denied("principal-not-found", door, `No ${what} has this ID.`);
|
|
125
|
+
}
|
|
126
|
+
const parsed = principalRowSchema.safeParse(raw);
|
|
127
|
+
if (!parsed.success || parsed.data.id !== expectedId) {
|
|
128
|
+
return denied("principal-record-invalid", door, `The ${what} record is not well formed.`);
|
|
129
|
+
}
|
|
130
|
+
if (parsed.data.active !== true) {
|
|
131
|
+
return denied("principal-inactive", door, `The ${what} is not active.`);
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, row: parsed.data };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Reads the stored kind of an organisation. Refusal by default: a missing or
|
|
137
|
+
* throwing adapter refuses; a miss or any answer other than exactly
|
|
138
|
+
* `"operator"` / `"client"` is `null`, which is never the fleet.
|
|
139
|
+
*/
|
|
140
|
+
async function loadOrgKind(lookups, orgId, door) {
|
|
141
|
+
const lookup = lookups?.orgKindOf;
|
|
142
|
+
if (typeof lookup !== "function") {
|
|
143
|
+
return denied("principal-lookup-failed", door, "No lookup was supplied for the organisation kind.");
|
|
144
|
+
}
|
|
145
|
+
let raw;
|
|
146
|
+
try {
|
|
147
|
+
raw = await lookup(orgId);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return denied("principal-lookup-failed", door, "The organisation kind could not be read, so none is resolved.");
|
|
151
|
+
}
|
|
152
|
+
return { ok: true, kind: raw === "operator" || raw === "client" ? raw : null };
|
|
153
|
+
}
|
|
154
|
+
/** Every organisation, the operator org included, must exist and be active. */
|
|
155
|
+
async function checkOrganisation(orgId, lookups, door) {
|
|
156
|
+
const lookup = lookups.organisationById;
|
|
157
|
+
if (typeof lookup !== "function") {
|
|
158
|
+
return denied("principal-lookup-failed", door, "No lookup was supplied for the organisation.");
|
|
159
|
+
}
|
|
160
|
+
let raw;
|
|
161
|
+
try {
|
|
162
|
+
raw = await lookup(orgId);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return denied("principal-lookup-failed", door, "The organisation could not be read, so none is resolved.");
|
|
166
|
+
}
|
|
167
|
+
const parsed = organisationRowSchema.safeParse(raw);
|
|
168
|
+
if (!parsed.success || parsed.data.id !== orgId || parsed.data.active !== true) {
|
|
169
|
+
return denied("organisation-not-active", door, "The organisation is not mapped or not active.");
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
function served(principal) {
|
|
174
|
+
return { ok: true, principal };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolves the acting principal from a VERIFIED credential to stored IDs, or
|
|
178
|
+
* refuses. Refusal by default: a credential claiming the fleet kind
|
|
179
|
+
* (`reserved-fleet-scope`), a credential that does not parse (including one
|
|
180
|
+
* carrying any name field), a missing or throwing lookup, a miss, a malformed
|
|
181
|
+
* or inactive row, an unstamped row and an inactive organisation are all
|
|
182
|
+
* refused. Nothing is ever resolved by a name, and nothing falls back to a
|
|
183
|
+
* master principal.
|
|
184
|
+
*
|
|
185
|
+
* - agent: the agent row by `agentId`; its stored org must equal the org
|
|
186
|
+
* the credential was verified for. An agent of the operator org resolves
|
|
187
|
+
* as an ordinary `kind: "agent"` of that org (RULING 5).
|
|
188
|
+
* - person: the person row by (`personId`, `verifiedOrgId`); it must be
|
|
189
|
+
* stamped with that org. A person of the operator org resolves as an
|
|
190
|
+
* ordinary `kind: "person"` of that org (RULING 5).
|
|
191
|
+
* - service: the service-account row by `serviceAccountId`. Alone, it acts
|
|
192
|
+
* as itself (`kind: "service"`, or `kind: "fleet"` when `orgKindOf` reports
|
|
193
|
+
* its stored org as `"operator"`). With `actingForAgentId`, the agent row is read by
|
|
194
|
+
* ID and must be stamped with the service account's own org; the principal
|
|
195
|
+
* is that agent, with `viaServiceAccountId` recording the carrier.
|
|
196
|
+
*/
|
|
197
|
+
export async function resolveActingPrincipal(credential, lookups, door = "resolveActingPrincipal") {
|
|
198
|
+
if (credential !== null &&
|
|
199
|
+
typeof credential === "object" &&
|
|
200
|
+
credential.kind === "fleet") {
|
|
201
|
+
return denied("reserved-fleet-scope", door, "The fleet scope is held only by the operator organisation's service account; no credential claims it.");
|
|
202
|
+
}
|
|
203
|
+
const parsed = actingCredentialSchema.safeParse(credential);
|
|
204
|
+
if (!parsed.success) {
|
|
205
|
+
return denied("credential-invalid", door, "The credential does not identify a principal by ID.");
|
|
206
|
+
}
|
|
207
|
+
const cred = parsed.data;
|
|
208
|
+
const look = lookups ?? {};
|
|
209
|
+
if (cred.kind === "agent" || cred.kind === "person") {
|
|
210
|
+
// The org's kind is read so a missing or throwing adapter refuses here as
|
|
211
|
+
// on every path; an operator-org member is NOT refused (RULING 5).
|
|
212
|
+
const credKind = await loadOrgKind(look, cred.verifiedOrgId, door);
|
|
213
|
+
if (!credKind.ok)
|
|
214
|
+
return credKind;
|
|
215
|
+
const loaded = cred.kind === "agent"
|
|
216
|
+
? await loadPrincipalRow(look.agentById, [cred.agentId], cred.agentId, "agent", door)
|
|
217
|
+
: await loadPrincipalRow(look.personById, [cred.personId, cred.verifiedOrgId], cred.personId, "person", door);
|
|
218
|
+
if (!loaded.ok)
|
|
219
|
+
return loaded;
|
|
220
|
+
const rowOrg = loaded.row.orgId;
|
|
221
|
+
if (!nonEmpty(rowOrg)) {
|
|
222
|
+
return denied("no-verified-organisation", door, `The ${cred.kind} is stamped with no organisation.`);
|
|
223
|
+
}
|
|
224
|
+
if (rowOrg !== cred.verifiedOrgId) {
|
|
225
|
+
return denied("other-organisation", door, `The ${cred.kind} belongs to another organisation than its credential.`);
|
|
226
|
+
}
|
|
227
|
+
const orgRefusal = await checkOrganisation(rowOrg, look, door);
|
|
228
|
+
if (orgRefusal !== null)
|
|
229
|
+
return orgRefusal;
|
|
230
|
+
if (cred.kind === "person" && cred.verifiedOrgRole !== undefined) {
|
|
231
|
+
return served({
|
|
232
|
+
principalId: loaded.row.id,
|
|
233
|
+
orgId: rowOrg,
|
|
234
|
+
kind: cred.kind,
|
|
235
|
+
orgRole: cred.verifiedOrgRole,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return served({ principalId: loaded.row.id, orgId: rowOrg, kind: cred.kind });
|
|
239
|
+
}
|
|
240
|
+
// service account
|
|
241
|
+
const svc = await loadPrincipalRow(look.serviceAccountById, [cred.serviceAccountId], cred.serviceAccountId, "service account", door);
|
|
242
|
+
if (!svc.ok)
|
|
243
|
+
return svc;
|
|
244
|
+
const svcOrg = svc.row.orgId;
|
|
245
|
+
if (!nonEmpty(svcOrg)) {
|
|
246
|
+
return denied("no-verified-organisation", door, "The service account is stamped with no organisation.");
|
|
247
|
+
}
|
|
248
|
+
const svcKind = await loadOrgKind(look, svcOrg, door);
|
|
249
|
+
if (!svcKind.ok)
|
|
250
|
+
return svcKind;
|
|
251
|
+
const orgRefusal = await checkOrganisation(svcOrg, look, door);
|
|
252
|
+
if (orgRefusal !== null)
|
|
253
|
+
return orgRefusal;
|
|
254
|
+
if (cred.actingForAgentId === undefined) {
|
|
255
|
+
return served({
|
|
256
|
+
principalId: svc.row.id,
|
|
257
|
+
orgId: svcOrg,
|
|
258
|
+
kind: svcKind.kind === "operator" ? "fleet" : "service",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const agent = await loadPrincipalRow(look.agentById, [cred.actingForAgentId], cred.actingForAgentId, "agent", door);
|
|
262
|
+
if (!agent.ok)
|
|
263
|
+
return agent;
|
|
264
|
+
if (agent.row.orgId !== svcOrg) {
|
|
265
|
+
return denied("acting-agent-other-organisation", door, "The service account may act only for an agent of its own organisation.");
|
|
266
|
+
}
|
|
267
|
+
return served({
|
|
268
|
+
principalId: agent.row.id,
|
|
269
|
+
orgId: svcOrg,
|
|
270
|
+
kind: "agent",
|
|
271
|
+
viaServiceAccountId: svc.row.id,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// assertTargetBelongsTo
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
/**
|
|
278
|
+
* Admits a target only by comparing its STORED IDs with the principal's
|
|
279
|
+
* resolved IDs, byte for byte. No name field is read; there is none to read.
|
|
280
|
+
*
|
|
281
|
+
* Whether an organisation is the operator org (the fleet) is read from
|
|
282
|
+
* `lookups.orgKindOf`; a missing or throwing adapter refuses.
|
|
283
|
+
*
|
|
284
|
+
* - An absent principal, or one with an empty ID, is refused.
|
|
285
|
+
* - A `kind: "fleet"` principal outside the operator org is refused, and so
|
|
286
|
+
* is a `kind: "service"` principal inside it (the operator org's service
|
|
287
|
+
* account is the fleet). An agent or person of the operator org is an
|
|
288
|
+
* ordinary member of it (RULING 5).
|
|
289
|
+
* - An unstamped target (no `orgId`) is refused to EVERY principal, the
|
|
290
|
+
* fleet principal included. No right is inferred from an absence: a fleet
|
|
291
|
+
* row carries the operator org's ID explicitly, and a row with no
|
|
292
|
+
* organisation is never treated as a master row.
|
|
293
|
+
* - An operator-org row is refused to every client principal.
|
|
294
|
+
* - Otherwise the target's `orgId` must equal the principal's. A fleet
|
|
295
|
+
* principal reaches a client organisation's row only when the door sets
|
|
296
|
+
* `fleetCrossOrg` (a master export) AND `orgKindOf` reports that
|
|
297
|
+
* organisation as `"client"`; an unknown organisation is refused. Any
|
|
298
|
+
* other principal reaching across organisations under `fleetCrossOrg`
|
|
299
|
+
* claims the fleet scope and is refused `reserved-fleet-scope`.
|
|
300
|
+
* - `ownerOnly`: the target's `ownerId` must also equal `principalId`; an
|
|
301
|
+
* absent owner is refused.
|
|
302
|
+
*/
|
|
303
|
+
export async function assertTargetBelongsTo(principal, target, lookups, opts = {}) {
|
|
304
|
+
const door = opts.door ?? "assertTargetBelongsTo";
|
|
305
|
+
if (principal === null ||
|
|
306
|
+
typeof principal !== "object" ||
|
|
307
|
+
!nonEmpty(principal.principalId) ||
|
|
308
|
+
!nonEmpty(principal.orgId)) {
|
|
309
|
+
return denied("credential-invalid", door, "No resolved principal stands behind this call.");
|
|
310
|
+
}
|
|
311
|
+
const principalKind = await loadOrgKind(lookups, principal.orgId, door);
|
|
312
|
+
if (!principalKind.ok)
|
|
313
|
+
return principalKind;
|
|
314
|
+
const inFleet = principalKind.kind === "operator";
|
|
315
|
+
const isFleet = principal.kind === "fleet";
|
|
316
|
+
if ((isFleet && !inFleet) || (inFleet && principal.kind === "service")) {
|
|
317
|
+
return denied("reserved-fleet-scope", door, "The principal does not hold the fleet scope.");
|
|
318
|
+
}
|
|
319
|
+
const targetOrg = target?.orgId;
|
|
320
|
+
if (!nonEmpty(targetOrg)) {
|
|
321
|
+
return denied("target-unstamped", door, "The target carries no organisation, so it is not reachable.");
|
|
322
|
+
}
|
|
323
|
+
if (targetOrg !== principal.orgId) {
|
|
324
|
+
const targetKind = await loadOrgKind(lookups, targetOrg, door);
|
|
325
|
+
if (!targetKind.ok)
|
|
326
|
+
return targetKind;
|
|
327
|
+
if (targetKind.kind === "operator") {
|
|
328
|
+
return denied("reserved-fleet-scope", door, "The target is in the fleet scope.");
|
|
329
|
+
}
|
|
330
|
+
if (!isFleet && opts.fleetCrossOrg === true) {
|
|
331
|
+
return denied("reserved-fleet-scope", door, "Cross-organisation reach is held only by the operator organisation's service account.");
|
|
332
|
+
}
|
|
333
|
+
const masterExport = isFleet && opts.fleetCrossOrg === true && targetKind.kind === "client";
|
|
334
|
+
if (!masterExport) {
|
|
335
|
+
return denied("target-other-organisation", door, "The target belongs to another organisation.");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (opts.ownerOnly === true) {
|
|
339
|
+
const owner = target?.ownerId;
|
|
340
|
+
if (!nonEmpty(owner) || owner !== principal.principalId) {
|
|
341
|
+
return denied("target-owner-mismatch", door, "Only the target's owner may act on it.");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return { ok: true };
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Admits a principal as ADMIN of the target organisation, deciding by ID only.
|
|
348
|
+
* The principal must come from `resolveActingPrincipal`; the role is the
|
|
349
|
+
* verified claim its person credential carried (`verifiedOrgRole`), so no host
|
|
350
|
+
* lookup is needed and none is read here.
|
|
351
|
+
*
|
|
352
|
+
* - An absent principal, or one with an empty ID, is refused.
|
|
353
|
+
* - Only a PERSON is an organisation admin. An agent, a service account and
|
|
354
|
+
* the fleet principal are refused `principal-not-a-person`, whatever they
|
|
355
|
+
* carry: the service account keeps its own rules and never becomes an
|
|
356
|
+
* admin through this check.
|
|
357
|
+
* - The target organisation must be present and byte-equal to the
|
|
358
|
+
* principal's stored organisation: an admin of A is not an admin of B.
|
|
359
|
+
* - The role must be byte-equal to one entry of `adminRoles`. An absent
|
|
360
|
+
* role, an unknown role, and an empty or malformed `adminRoles` refuse.
|
|
361
|
+
* No case folding, no prefix stripping, no default role.
|
|
362
|
+
*/
|
|
363
|
+
export function assertOrgAdmin(principal, targetOrgId, opts) {
|
|
364
|
+
const door = opts?.door ?? "assertOrgAdmin";
|
|
365
|
+
if (principal === null ||
|
|
366
|
+
typeof principal !== "object" ||
|
|
367
|
+
!nonEmpty(principal.principalId) ||
|
|
368
|
+
!nonEmpty(principal.orgId)) {
|
|
369
|
+
return denied("credential-invalid", door, "No resolved principal stands behind this call.");
|
|
370
|
+
}
|
|
371
|
+
if (principal.kind !== "person") {
|
|
372
|
+
return denied("principal-not-a-person", door, "Only a person is an organisation admin; this principal is not a person.");
|
|
373
|
+
}
|
|
374
|
+
if (!nonEmpty(targetOrgId)) {
|
|
375
|
+
return denied("target-unstamped", door, "No target organisation was named, so none is administered.");
|
|
376
|
+
}
|
|
377
|
+
if (targetOrgId !== principal.orgId) {
|
|
378
|
+
return denied("target-other-organisation", door, "The target organisation is not the principal's organisation.");
|
|
379
|
+
}
|
|
380
|
+
const adminRoles = opts?.adminRoles;
|
|
381
|
+
const rolesValid = Array.isArray(adminRoles) && adminRoles.length > 0 && adminRoles.every((r) => nonEmpty(r));
|
|
382
|
+
const role = principal.orgRole;
|
|
383
|
+
if (!rolesValid || !nonEmpty(role) || !adminRoles.includes(role)) {
|
|
384
|
+
return denied("role-not-admin", door, "The principal's role is not an organisation-admin role.");
|
|
385
|
+
}
|
|
386
|
+
return { ok: true };
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=principal-by-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"principal-by-id.js","sourceRoot":"","sources":["../src/principal-by-id.ts"],"names":[],"mappings":"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"}
|