@owlmeans/iam 0.1.18-rc.44 → 0.1.18-rc.46

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/README.md CHANGED
@@ -6,14 +6,16 @@ Provider-agnostic IAM abstraction for the OwlMeans framework — `IamService` in
6
6
 
7
7
  - `IamService` — unified interface for provisioning clients, realms, and permissions across IAM backends (e.g. Keycloak, built-in)
8
8
  - `IamClient` / `IamCredentialsPair` / `IamResourceSpec` — shared contract types used by server-side IAM implementations
9
- - `IamPermissionArgs` — describes a named permission including resource-scope and human-readable title
10
- - `hasPermission` — utility to check whether a set of grants covers a given permission/resource pair
11
- - Errors: `IamError` and subtypes for client-provisioning and permission failures
9
+ - `IamPermissionArgs` — describes a named permission: resource and organization binding (`resourceScoped`, `entityScoped`), title, area and `defaultClass`
10
+ - Tenancy facets on `IamService` — `organizations`, `groups`, `subjects` — and `unsupportedFacet` for a backend without them
11
+ - `makeIamRuntimeProtocols` — declarations of the provider's runtime IAM API (organizations, members, permissions, grants)
12
+ - `hasPermission` — utility to check whether a set of grants covers a given permission, resource and acting organization
13
+ - Errors: `IamError` and subtypes for client, permission, user, organization and group failures
12
14
 
13
15
  ## Installation
14
16
 
15
17
  ```bash
16
- bun add @owlmeans/iam@^0.1.18-rc.44
18
+ bun add @owlmeans/iam@^0.1.18-rc.46
17
19
  ```
18
20
 
19
21
  ## Usage
@@ -22,8 +24,8 @@ bun add @owlmeans/iam@^0.1.18-rc.44
22
24
  import type { IamService, IamClient } from '@owlmeans/iam'
23
25
  import { hasPermission } from '@owlmeans/iam'
24
26
 
25
- // Check whether an auth payload includes a required permission
26
- const allowed = hasPermission(auth, { permission: 'manage-users', resourceScoped: false })
27
+ // Check whether an auth payload grants a permission for one record, in the organization the request acts in
28
+ const allowed = hasPermission(auth, 'order--edit', { resourceId: 'ord-1', entitySlug: 'acme' })
27
29
  ```
28
30
 
29
31
  Typically consumed via `@owlmeans/server-iam` which wires the concrete OIDC-backed implementation.
@@ -36,7 +38,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
36
38
  your project's skill store (`.agents/skills/`):
37
39
 
38
40
  ```sh
39
- npx @owlmeans/agent-skills@^0.1.18-rc.42
41
+ npx @owlmeans/agent-skills@^0.1.18-rc.45
40
42
  ```
41
43
 
42
44
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/iam",
4
- "version": "0.1.18-rc.44",
5
- "generatedAt": "2026-09-29T08:01:58.122Z",
4
+ "version": "0.1.18-rc.46",
5
+ "generatedAt": "2026-10-01T23:27:48.730Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: iam
3
- description: "How to use @owlmeans/iam — the provider-agnostic IamService, the permission-definition and two-form grant model, client-id uniqueness, issuer and redirect-URI rules, and hasPermission. Load when wiring IAM operations, granting or revoking permissions, selecting the IAM backend, or implementing a new IAM provider. Applies to files matching **/services/iam*.ts, **/context.ts, **/types.ts."
3
+ description: "How to use @owlmeans/iam — the provider-agnostic IamService, the permission-definition model with its four kinds (unbound, entity-bound, resource-bound, both) and default classes, the organization/group/subject facets, the runtime IAM API declarations, client-id uniqueness, issuer and redirect-URI rules, and hasPermission. Load when wiring IAM operations, granting or revoking permissions, working with tenanted clients, selecting the IAM backend, or implementing a new IAM provider. Applies to files matching **/services/iam*.ts, **/context.ts, **/types.ts."
4
4
  metadata:
5
5
  applyTo: "**/services/iam*.ts, **/context.ts, **/types.ts"
6
6
  ---
@@ -8,10 +8,11 @@ metadata:
8
8
 
9
9
  # Using `@owlmeans/iam`
10
10
 
11
- **Install:** `"@owlmeans/iam": "^0.1.18-rc.44"` in `dependencies`
11
+ **Install:** `"@owlmeans/iam": "^0.1.18-rc.46"` in `dependencies`
12
12
 
13
13
  Provider-agnostic IAM abstraction: the `IamService` interface, the permission and grant shapes, the
14
- gate-param grammar, and `hasPermission`. It contains no implementation and talks to no provider —
14
+ gate-param grammar, `hasPermission`, and the declarations of the provider's runtime IAM API. It
15
+ contains no implementation and talks to no provider —
15
16
  concrete backends (a Keycloak proxy, an integrated backend built on the OwlMeans OIDC provider) are
16
17
  separate packages, and every consumer depends on this interface rather than on one of them.
17
18
 
@@ -29,6 +30,9 @@ lines.
29
30
  | `requireConsentForLogin(ctx, opts?)` | That precondition on its own, for a context wired some other way |
30
31
  | `CONSENT_LOGIN_PRECONDITION` / `CLIENT_IAM_SERVICE` | The precondition alias and the service alias |
31
32
  | `useLogin` / `useLogout`, `LoginOutcome` / `LoginIntent`, `LoginPlugin` / `LoginService` / … | Re-exported sign-in surface, so an app has one IAM import rather than three |
33
+ | `listOrganizations(ctx)` | The signed-in subject's organizations (`OidcOrganizationItem[]`, the acting one marked `acting`) over `oidcProtocols.organizations` |
34
+ | `switchOrganization(ctx, entitySlug)` | Moves the session into another of its organizations over `oidcProtocols.organization` and adopts the re-signed token with `adoptToken` |
35
+ | `ORGANIZATION_REFUSAL` / `ORGANIZATION_OWNER_REFUSAL` | Re-exported from `@owlmeans/oidc`: the `AuthForbidden` reasons a screen phrases (not a member; not the owner) |
32
36
  | Everything from `@owlmeans/iam` | The types above, plus `hasPermission` |
33
37
 
34
38
  The protocol tree is immutable. Decorate it once with `withIamGuard(protocols)` before binding local
@@ -40,25 +44,35 @@ plugin, because `begin` is the single funnel every sign-in mechanic passes throu
40
44
  the user's gesture is still live — a refusal resolves as `LoginOutcome.Gesture` and opens the consent dialog in
41
45
  the same gesture. An application that genuinely sets no cookie passes `{ consent: { disabled: true } }`.
42
46
 
47
+ Both organization calls need the bindings `iamEntrypoints()` spreads, and both are answered by the
48
+ application's own server from the session it holds — there is no per-request organization selector,
49
+ so a screen that offers "act in another organization" calls `switchOrganization` and re-reads `auth`.
50
+
43
51
  The server half of the same story is `@owlmeans/server-iam` — the IAM gate that asserts the grants
44
- this package's screens hand out. Permission decisions are made there, never in the browser.
52
+ this package's screens hand out, and the runtime IAM client. Permission decisions are made there,
53
+ never in the browser.
45
54
 
46
55
  ## Public API surface
47
56
 
48
57
  | Symbol | Kind | Purpose |
49
58
  |--------|------|---------|
50
59
  | `IamService` | type | Unified IAM interface — all provisioning + authorization operations |
51
- | `IamClient` | type | Provisioned OIDC client `{ id?, clientId, secret?, name?, realm? }` |
52
- | `IamClientOptions` | type | `{ redirectUris? }` — explicit `ensureClient` hardening; omitting is a keycloak-only legacy shape (see below) |
60
+ | `IamClient` | type | Provisioned OIDC client `{ id?, clientId, secret?, name?, realm?, config? }` |
61
+ | `IamClientInfo` | type | `IamClient` without `secret` — what `getClient` answers |
62
+ | `IamClientConfig` | type | `{ operators?, users?, [key]: unknown }` — the two tenancy flags plus the client's arbitrary configuration |
63
+ | `IamClientOptions` | type | `{ redirectUris?, config? }` — explicit `ensureClient` hardening (omitting `redirectUris` is a keycloak-only legacy shape, see below); `config` merges shallowly |
53
64
  | `IamCredentialsPair` | type | `{ token: string; realm: string }` |
54
- | `IamPermissionArgs` | type | `{ permission?, resourceScoped?, title?, area?, managed?, defaultEnabled? }` — `permission` absent means unscoped resource name |
65
+ | `IamPermissionArgs` | type | `{ permission?, resourceScoped?, entityScoped?, title?, area?, managed?, defaultClass? }` — `permission` absent means unscoped resource name; omitted flags are kept, passed ones (`false` / `None` included) written |
55
66
  | `IamResourceSpec` | type | `{ name: string; displayName?: string }` |
56
- | `IamPermissionDefinition` | type | Declared permission `{ name, resource, action?, resourceScoped?, title?, area?, managed?, defaultEnabled? }` |
57
- | `IamPermissionFilter` | type | `{ areas?, managed?, resourceScoped? }` — narrows `listPermissions`; an `areas` entry of `null` matches untagged definitions |
58
- | `IamGrantArgs` | type | `{ resources?: string[], mode? }` |
59
- | `IamGrant` | type | `{ profileId, clientId, permission, resources?, mode?, inherited? }` |
67
+ | `IamPermissionDefinition` | type | Declared permission `{ name, resource, action?, resourceScoped?, entityScoped?, title?, area?, managed?, defaultClass? }` |
68
+ | `IamPermissionFilter` | type | `{ areas?, managed?, resourceScoped?, entityScoped? }` — narrows `listPermissions`; an `areas` entry of `null` matches untagged definitions |
69
+ | `IamDefaultClass` | enum | `None` / `User` / `Member` / `Owner` — who holds a definition without a grant (see "Permission kinds") |
70
+ | `IamGrantArgs` | type | `{ resources?, mode?, entitySlug? }` — `entitySlug` binds an `entityScoped` grant |
71
+ | `IamGrant` | type | `{ profileId? \| group?, clientId, permission, resources?, mode?, entitySlug?, origin?, through? }` — exactly one subject |
72
+ | `IamGrantOrigin` | enum | `Direct` / `Default` / `Group` — why a listed grant is held; `through` names the group |
73
+ | `IamGroupRef` | type | `{ entitySlug, key }` — a group by its organization's slug and its key |
60
74
  | `IamGrantMode` | enum | `Blanket` / `Resources` / `All` — which FORM of a grant an operation addresses |
61
- | `IamGrantBundle` | type | `{ filter?, permissions?, mode?, resources? }` — what `grantBundle` hands out |
75
+ | `IamGrantBundle` | type | `{ filter?, permissions?, mode?, resources?, entitySlug? }` — what `grantBundle` hands out |
62
76
  | `IamRemovalPolicy` | enum | `Cascade` / `Refuse` — what to do about grants of a definition being deleted |
63
77
  | `IamPermissionDeleteArgs`, `IamPermissionRemoval` | type | `{ policy?, managed? }` and `{ permission, clientId, found, revoked }` |
64
78
  | `IamNormalizeArgs`, `IamNormalizationReport` | type | `{ dryRun? }` and the rename/merge plan `normalizePermissions` reports |
@@ -66,11 +80,24 @@ this package's screens hand out. Permission decisions are made there, never in t
66
80
  | `PERMISSION_ACTION_SEPARATOR` | const | `'--'` — TWO hyphens, never one |
67
81
  | `parsePermissionName` / `composePermissionName` / `isPermissionName` | fn | Read, build and validate a permission NAME |
68
82
  | `ParsedPermissionName` | type | `{ name, resource, action?, problem? }` |
69
- | `IamUser` | type | End-user of an entity `{ profileId, email?, name?, role, disabled?, grantCount? }` |
83
+ | `IamUser` | type | Subject of one client `{ profileId, email?, name?, role, disabled?, grantCount?, home?, memberships? }` |
84
+ | `IamMembershipInfo` | type | `{ entitySlug, title?, owner, groups, home? }` — one organization of an `IamUser` |
70
85
  | `IamUserInvite` | type | `{ email, name?, role? }` — find-or-create args |
71
- | `IamUserUpdate` | type | `{ name?, role?, disabled? }` |
72
- | `hasPermission` | fn | `(auth, permission, { scope?, resourceId? }?)` — checks `Authorization.permissions` PermissionSet[]; an unscoped set satisfies a resourceId check |
73
- | `HasPermissionOptions` | type | `{ scope?, resourceId? }` |
86
+ | `IamUserUpdate` | type | `{ name?, role?, disabled? }` — `disabled` lands on the primary row |
87
+ | `IamOrganization`, `IamOrganizationArgs`, `IamOrganizationUpdate` | type | `{ entitySlug, title?, members? }`; create `{ title?, profileId? }` (the new owner); update `{ title }` |
88
+ | `IamMember`, `IamMemberInvite`, `IamMemberUpdate` | type | `{ profileId, email?, name?, owner, groups, managed? }`; `{ email, name?, owner? }`; `{ owner?, groups? }` |
89
+ | `IamGroup`, `IamGroupArgs`, `IamGroupBundle` | type | `{ entitySlug, key, title?, managed?, members?, bundles }`; `{ title?, bundles? }`; `{ filter?, permissions? }` |
90
+ | `IamStaffSyncArgs`, `IamStaffSyncReport` | type | `{ bundles }` and `{ added, removed }` (profile ids) of `syncStaff` |
91
+ | `IamSubject`, `IamSignInArgs`, `IamSignInResult` | type | `{ profileId, email?, name?, organizations, permissions }` (claims input); `{ email, name? }`; `{ accountId, profileId }` |
92
+ | `IamOrganizationFacet`, `IamGroupFacet`, `IamSubjectFacet` | type | The three facets of `IamService` |
93
+ | `unsupportedFacet<T>(what)` | fn | A facet whose every method throws `IamUnsupported(what)` — synchronously |
94
+ | `IAM_MEMBERS_GROUP` | const | `'members'` — the managed staff group `syncStaff` keeps |
95
+ | `IAM_EMAIL_PROOF` | const | `'proof:email'` — the `profileId` of a credential that proves an address and identifies nobody yet |
96
+ | `hasPermission` | fn | `(auth, permission, { scope?, resourceId?, entitySlug? }?)` — checks `Authorization.permissions`; an unscoped set satisfies a resourceId check, an unbound set any organization |
97
+ | `HasPermissionOptions` | type | `{ scope?, resourceId?, entitySlug? }` |
98
+ | `makeIamRuntimeProtocols({ service, path? })` | fn | The runtime IAM API's protocol tree — see "Runtime IAM API" |
99
+ | `IAM_RUNTIME_GUARD`, `IAM_RUNTIME_PATH`, `IAM_RUNTIME_ROUTES` | const | Its guard alias, default base path (`'/runtime'`) and leaf-path table |
100
+ | `IamRuntime*` | type/schema | Its wire shapes and closed schemas (`IamRuntimeOrganization`, `IamRuntimeMember`, `IamRuntimePermission`, `IamRuntimeGrant`, `IamRuntimeGrantRequest`, … and `*Schema`) |
74
101
  | Gate grammar | fn/type | `parseGateParam`, `parseGateSelector`, `formatGateParam`, `resolveGateResource`, `validateGateParams`, `routeParamsOf`, plus `RESOURCE_*_SEPARATOR`, `GateParamSource`, `DEFAULT_GATE_PARAM_SOURCES`, `GateParamErrorCode`, `GateResolutionFailure` and the `Gate*` types. The syntax itself is documented in the `server-iam` skill |
75
102
  | `DEFAULT_ALIAS` | const | Default service alias `'iam-service'` |
76
103
  | `IAM_MODE_KEYCLOAK` | const | `'keycloak'` |
@@ -82,9 +109,11 @@ this package's screens hand out. Permission decisions are made there, never in t
82
109
  | `IamGrantError` | class | Grant subject missing or entity mismatch |
83
110
  | `IamPermissionError` | class | A refusal about a DEFINITION: `permission:held:<name>`, `permission:managed:<name>` |
84
111
  | `IamUserError` | class | An end-user operation was refused |
112
+ | `IamOrganizationError` | class | An organization or membership refusal — unknown organization, last owner, membership cap |
113
+ | `IamGroupError` | class | A group refusal — unknown key, a managed group written by an operator, another app's group |
85
114
  | `IamUnsupported` | class | Operation not supported by the active backend (e.g. `resource-scoped-grant`, `user-management`) |
86
115
 
87
- ## Permission model (two grant forms)
116
+ ## Permission model (kinds and grant forms)
88
117
 
89
118
  Permissions are declared per entity client (project) with `ensurePermission` and granted to end-user
90
119
  subjects with `grantPermission`:
@@ -113,12 +142,44 @@ nothing at request time. `managed: true` marks a definition the platform owns: a
113
142
  revoke it from an undifferentiated list, and a repair that deletes "definitions no declaration names"
114
143
  must not reach one.
115
144
 
116
- `defaultEnabled: true` makes an unmanaged `user`-area permission effective for every end user of
117
- that client. `mergePermissionDefaults` adds inherited blanket grants when reading grants or
118
- building OIDC claims; it never writes a grant to every profile. The first explicit blanket change
119
- stores `true` or `false` on that profile, and a stored `false` suppresses the inherited grant.
120
- Resource-specific grants remain independent. Integrated IAM supports this contract; Keycloak
121
- refuses a default-enabled definition with `IamUnsupported`.
145
+ ### Permission kinds
146
+
147
+ What a grant binds to is a property of the DEFINITION, and grants follow it:
148
+
149
+ | Kind | Definition flags | Grant stored on | Applies |
150
+ |---|---|---|---|
151
+ | unbound | none | the subject's primary row, or a group | everywhere |
152
+ | entity-bound | `entityScoped` | the subject's row of THAT organization, or a group of it | only while acting in that organization |
153
+ | resource-bound | `resourceScoped` | the primary row, a titled set with `resources` | to those resource ids |
154
+ | both | both | that organization's row, a titled set with `resources` | both checks |
155
+
156
+ - Granting an `entityScoped` definition REQUIRES `args.entitySlug`, which names the subject's row
157
+ there — the subject must be a member. Any other definition refuses `args.entitySlug`. Turning
158
+ `entityScoped` off while grants exist is refused: every bound grant would silently widen.
159
+ - Stored sets carry the organization's id; CLAIM sets (provider → relying party only) carry its
160
+ `entitySlug` instead (`OidcPermissionSetClaim`). The relying party keeps the unbound sets and the
161
+ acting organization's, strips the binding, and drops the rest — a browser token never carries a
162
+ bound set of another organization.
163
+ - `hasPermission(auth, name, { entitySlug })` applies a set without `entitySlug` everywhere and a
164
+ bound one only when `opts.entitySlug` names its organization — fail-closed, so a bound set that
165
+ leaked into a token never counts where the caller named no organization.
166
+
167
+ ### Default classes
168
+
169
+ `defaultClass` says who holds a definition without an explicit grant. It is evaluated when claims and
170
+ grant listings are built — never written onto a row:
171
+
172
+ - `User` — every signed-in subject of the client. Requires an unbound, unmanaged `user`-area definition.
173
+ - `Member` / `Owner` — every member / owner of the organization the subject acts in. Requires
174
+ `entityScoped`.
175
+ - `None` — nobody.
176
+
177
+ A class default is a BLANKET set, so `Owner` on a resource-scoped definition is its all-records form.
178
+ An explicit `false` on a row suppresses only the class default of that binding (an unbound `false`
179
+ suppresses `User`; a `false` on the row of an organization suppresses `Member`/`Owner` there).
180
+ Grants are additive — there is no deny rule over a group's grant. A listing marks each grant's
181
+ `origin`: `Direct`, `Default`, or `Group` with `through` naming the group. A backend that cannot
182
+ evaluate classes (Keycloak) refuses any `defaultClass` other than `None` with `IamUnsupported`.
122
183
 
123
184
  ## `IamService` interface
124
185
 
@@ -135,10 +196,14 @@ interface IamService extends InitializedService {
135
196
  // "A client id is a global name" for the two rules it has to obey.
136
197
  ensureClient: (entityId: string, clientId: string, options?: IamClientOptions) => Promise<IamClient>
137
198
 
199
+ // The entity's client without its secret, or null. Never provisions.
200
+ getClient: (entityId: string, clientId: string) => Promise<IamClientInfo | null>
201
+
138
202
  // Reserve a free client id without provisioning it; false when anyone else holds it.
139
203
  claimClient: (entityId: string, clientId: string) => Promise<boolean>
140
204
 
141
- // Release a client id and everything keyed by it (called when a project/slot is deleted).
205
+ // Release a client id and everything keyed by it — its subjects' rows, its groups, its
206
+ // definitions (called when a project/slot is deleted).
142
207
  deleteClient: (entityId: string, clientId: string) => Promise<void>
143
208
 
144
209
  // Declare a permission. Returns the canonical name. MERGES into an existing definition —
@@ -162,7 +227,8 @@ interface IamService extends InitializedService {
162
227
 
163
228
  listPermissions: (entityId: string, clientId: string, filter?: IamPermissionFilter) => Promise<IamPermissionDefinition[]>
164
229
 
165
- // args.resources selects the resource-scoped form; see "Permission model (two grant forms)"
230
+ // args.resources selects the resource-scoped form, args.entitySlug the binding of an entityScoped
231
+ // definition; see "Permission model (kinds and grant forms)"
166
232
  grantPermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<IamGrant>
167
233
  revokePermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<void>
168
234
 
@@ -171,30 +237,108 @@ interface IamService extends InitializedService {
171
237
  // grantPermission, which costs N·M writes and N·M lost-update windows on the same records.
172
238
  grantBundle: (entityId: string, clientId: string, profileIds: string[], bundle: IamGrantBundle) => Promise<IamGrant[]>
173
239
 
240
+ // Direct, default-class and group grants, each with its `origin`.
174
241
  listGrants: (entityId: string, clientId: string, profileId?: string) => Promise<IamGrant[]>
175
242
 
176
- // --- End-user management — see "End-users are customer-wide" ---
243
+ // --- Users of one client — see "Users are per client" ---
177
244
 
178
245
  listUsers: (entityId: string, clientId?: string) => Promise<IamUser[]>
179
- getUser: (entityId: string, profileId: string) => Promise<IamUser | null>
180
- inviteUser: (entityId: string, invite: IamUserInvite) => Promise<IamUser>
181
- updateUser: (entityId: string, profileId: string, update: IamUserUpdate) => Promise<IamUser>
182
- removeUser: (entityId: string, profileId: string) => Promise<void>
246
+ getUser: (entityId: string, clientId: string, profileId: string) => Promise<IamUser | null>
247
+ inviteUser: (entityId: string, clientId: string, invite: IamUserInvite) => Promise<IamUser>
248
+ updateUser: (entityId: string, clientId: string, profileId: string, update: IamUserUpdate) => Promise<IamUser>
249
+ removeUser: (entityId: string, clientId: string, profileId: string) => Promise<void>
250
+
251
+ // The owning organization's staff, admitted to the client through the managed `members` group.
252
+ syncStaff: (entityId: string, clientId: string, args: IamStaffSyncArgs) => Promise<IamStaffSyncReport>
253
+
254
+ // --- Tenancy facets — see "Organizations, groups and subjects" ---
255
+ organizations: IamOrganizationFacet
256
+ groups: IamGroupFacet
257
+ subjects: IamSubjectFacet
183
258
  }
184
259
  ```
185
260
 
186
- ## End-users are customer-wide
187
-
188
- An end-user belongs to the **entity**, not to a project: one customer's users are shared across every
189
- project they own. `listUsers` therefore returns the entity's whole set and `clientId` only scopes the
190
- reported `grantCount` to one project's client. Do not reintroduce a per-client filter — a person who
191
- has authenticated against a project but holds no grant there is still that project's user, and the
192
- screen that hands out grants is exactly where they have to be visible.
193
-
194
- `inviteUser` is idempotent by email, which is what lets an invitation and a first sign-in converge on
195
- one record instead of accumulating twins. A backend with no user store of its own — one where the
196
- customer manages users in the provider's own console — throws `IamUnsupported('user-management')`
197
- from all five methods.
261
+ The first parameter of every method is `entityId` — the organization that OWNS the client — and
262
+ every `(entityId, clientId)` method verifies that ownership before it reads or writes anything. The
263
+ organizations a client's subjects act in are a different thing, named by `entitySlug` inside the
264
+ facets.
265
+
266
+ ## Users are per client
267
+
268
+ A person is ONE account (one per e-mail address); what a client sees of it are its rows of that app —
269
+ one per (account, app, organization), the PRIMARY one in the account's own organization. The
270
+ `profileId` a client knows is the pairwise subject, the same on every row of one (account, app) and
271
+ different for every app, so two apps of one customer cannot correlate their users by it.
272
+
273
+ - `listUsers(entityId, clientId)` lists every account holding a row of the client, in whichever
274
+ organization. Without `clientId` it is a read-only aggregate over the entity's clients, for a
275
+ console — nothing is written through it.
276
+ - `getUser` / `inviteUser` / `updateUser` / `removeUser` take the client. `removeUser` deletes this
277
+ account's rows of THAT app only — the account, and its rows of other apps, stay. `updateUser`
278
+ writes `disabled` on the primary row, which is what refuses the subject at the provider.
279
+ - `inviteUser` is idempotent by email, which is what lets an invitation and a first sign-in converge on
280
+ one record instead of accumulating twins.
281
+
282
+ A backend with no user store of its own — one where the customer manages users in the provider's own
283
+ console — throws `IamUnsupported('user-management')` from all five methods.
284
+
285
+ ## Organizations, groups and subjects
286
+
287
+ The three facets of `IamService` carry tenancy. A backend that cannot provide one answers
288
+ `unsupportedFacet<IamOrganizationFacet>('organizations')` (and so on): every method of it throws
289
+ `IamUnsupported(what)` SYNCHRONOUSLY, because `subjects.identify` is synchronous and a rejected
290
+ promise would hand it the wrong type — an awaited call rejects either way.
291
+
292
+ - **Tenancy is the client's configuration.** `IamClientConfig.users` / `.operators` turn it on: with
293
+ neither flag, every subject acts in the client's owning organization (a single-tenant app); with
294
+ either, each subject acts in organizations of its own and the provider claims them under the
295
+ `organizations` scope (`@owlmeans/oidc`). The flags also gate the runtime API's writes by area.
296
+ - **`organizations`** — `list`, `get`, `create({ title?, profileId? })` (that subject becomes the
297
+ owner), `update({ title })`, `members`, `addMember` (find-or-create by e-mail, idempotent, refused
298
+ past the membership cap), `updateMember`, `removeMember`. The last owner cannot be demoted or
299
+ removed (`IamOrganizationError`).
300
+ - **`groups`** — a group is (organization, client, key), kept inside the organization record; one
301
+ client never reads or writes another's. `ensure`, `remove`, `members`, `addMembers`,
302
+ `removeMembers`, `grant` / `revoke` (bound to the group's organization), `grants`, and
303
+ `setBundles` — a bundle (`{ filter?, permissions? }`) grants every definition it selects to the
304
+ group's members at claim time. `IAM_MEMBERS_GROUP` is the managed group `syncStaff` keeps in the
305
+ owning organization; it is read-only to an operator (`IamGroupError`).
306
+ - **`subjects`** — the provider's half, keyed by the ACCOUNT, which never leaves the provider.
307
+ `signIn(clientId, { email })` finds or creates the account and its rows for the client;
308
+ `resolve(clientId, accountId)` builds the `IamSubject` the claims come from (`null` = not admitted:
309
+ no row, or a disabled primary row); `identify(clientId, accountId)` is the pairwise `sub`.
310
+
311
+ ## Runtime IAM API
312
+
313
+ `makeIamRuntimeProtocols({ service, path? })` declares the provider-side API a tenanted client's
314
+ server calls on behalf of its signed-in subject. It is declarations only: the provider binds it, and
315
+ a relying party calls it through `iamRuntime` (`@owlmeans/server-iam`) over plain `fetch`.
316
+
317
+ | Route (under `path`, default `IAM_RUNTIME_PATH`) | Tree | Who |
318
+ |---|---|---|
319
+ | `GET /organizations` | `organizations.list` | any subject — its own |
320
+ | `POST /organizations` `{ title? }` | `organizations.create` | any subject of a tenanted client; it becomes owner |
321
+ | `POST /organizations/:entitySlug` `{ title }` | `organizations.update` | owner |
322
+ | `GET /organizations/:entitySlug/members` | `members.list` | member |
323
+ | `POST /organizations/:entitySlug/members` `{ email, name?, owner? }` | `members.add` | owner; idempotent by e-mail |
324
+ | `POST …/members/:profileId` `{ owner?, groups? }` | `members.update` | owner |
325
+ | `POST …/members/:profileId/remove` | `members.remove` | owner, or the member itself |
326
+ | `GET …/permissions` | `permissions.list` | member |
327
+ | `GET …/grants` `?profileId` | `grants.list` | owner |
328
+ | `POST …/grants`, `POST …/grants/revoke` `{ profileId, permission, resources?, mode? }` | `grants.assign` / `grants.revoke` | owner |
329
+
330
+ - **Every route, the base included, is pinned to `service`** — a route's service is never inherited
331
+ from its parent — and every leaf hangs under ONE base carrying `IAM_RUNTIME_GUARD`. The serving
332
+ process registers that guard: it admits only a bearer access token the provider issued, and takes
333
+ the client and the account from it, never from the request.
334
+ - **The wire names organizations by `entitySlug` and subjects by the calling client's pairwise
335
+ `profileId`** — no organization or account id appears in any shape. Every schema is closed, since
336
+ the server's AJV strips an undeclared key silently. `IAM_RUNTIME_ROUTES` is the one leaf-path table
337
+ the declarations and the fetch client share.
338
+ - The serving process enforces the write rules: only `entityScoped`, unmanaged definitions of the
339
+ CALLING client, binding forced to the path's organization, the last owner protected, the
340
+ membership cap, and only for an area the client's tenancy config opens (`users` → `user`,
341
+ `operators` → `operator`). A non-owner write is `AuthForbidden(ORGANIZATION_OWNER_REFUSAL)`.
198
342
 
199
343
  ## A client id is a global name
200
344
 
@@ -286,13 +430,16 @@ the same options — otherwise they disagree about the issuer, and only one of t
286
430
  - `IamClientError` means the provisioned client was invalid (missing secret, null token). The adapter
287
431
  already throws it; callers handle it rather than re-throwing a wrapped error.
288
432
  - `IamClient.realm` is set by every concrete implementation.
289
- - An operation a backend cannot support throws `IamUnsupported('<what>')`. Callers turn that into a
290
- fallback — an "external console" link, a disabled control — rather than an error screen.
433
+ - An operation a backend cannot support throws `IamUnsupported('<what>')`; a whole facet it cannot
434
+ support is `unsupportedFacet(what)`. Callers turn that into a fallback — an "external console"
435
+ link, a disabled control — rather than an error screen.
436
+ - A wire value names an organization by `entitySlug` only; the `entityId` parameter is the client's
437
+ OWNER, resolved server-side, never taken from a token or a request.
291
438
 
292
439
  ## Related
293
440
 
294
- - `@owlmeans/server-iam` — the IAM gate that asserts both grant forms, and the gate-param grammar;
295
- see the `server-iam` skill
441
+ - `@owlmeans/server-iam` — the IAM gate that asserts every permission kind, the gate-param grammar,
442
+ the session's organizations and the runtime IAM client; see the `server-iam` skill
296
443
  - `@owlmeans/oidc` — provider descriptors and the `iamMode` config seam; see the `oidc` skill
297
444
  - The concrete adapters (a Keycloak proxy, and the integrated backend built on
298
445
  `@owlmeans/server-oidc-provider` plus `@owlmeans/server-auth-identity`) ship separately and are
package/build/consts.d.ts CHANGED
@@ -93,4 +93,61 @@ export declare const IAM_AREAS: readonly ['user', 'operator', 'admin'];
93
93
  export type IamArea = typeof IAM_AREAS[number];
94
94
  /** Separates the resource from the action in a permission name. TWO hyphens, never one. */
95
95
  export declare const PERMISSION_ACTION_SEPARATOR = "--";
96
+ /**
97
+ * Who holds a permission without an explicit grant — a property of the DEFINITION, evaluated when
98
+ * claims are built and never written onto a profile.
99
+ *
100
+ * `User` is every signed-in subject of the client (an unbound, unmanaged `user`-area definition
101
+ * only); `Member` and `Owner` are every member / owner of the organization the subject acts in, so
102
+ * they require an `entityScoped` definition. A class default is a BLANKET set: `Owner` on a
103
+ * resource-scoped definition is its all-records form.
104
+ */
105
+ export declare enum IamDefaultClass {
106
+ None = "none",
107
+ User = "user",
108
+ Member = "member",
109
+ Owner = "owner"
110
+ }
111
+ /** Why a subject holds a grant, as a listing reports it. */
112
+ export declare enum IamGrantOrigin {
113
+ /** Written on the subject's own row. */
114
+ Direct = "direct",
115
+ /** The definition's `defaultClass` covers the subject. */
116
+ Default = "default",
117
+ /** Held by a group the subject is a member of — `IamGrant.through` names it. */
118
+ Group = "group"
119
+ }
120
+ /**
121
+ * The managed group `syncStaff` keeps in a client's owning organization: the owner's own staff,
122
+ * admitted to the client with the bundles the sync hands it. Read-only to an operator.
123
+ */
124
+ export declare const IAM_MEMBERS_GROUP = "members";
125
+ /**
126
+ * The `profileId` of a credential that proves an e-mail address and identifies nobody yet: what the
127
+ * end-user sign-in hands the provider's finalizer, which then resolves the account itself. It carries
128
+ * no `entitySlug` — a proof bound to an organization is something else.
129
+ */
130
+ export declare const IAM_EMAIL_PROOF = "proof:email";
131
+ /**
132
+ * The guard of the runtime IAM API (`makeIamRuntimeProtocols`). The serving process registers it: it
133
+ * admits a bearer access token the provider itself issued, and takes the client and the account from
134
+ * that token, never from the request.
135
+ */
136
+ export declare const IAM_RUNTIME_GUARD = "iam-runtime-guard";
137
+ /** Where the runtime IAM API mounts under its service unless the declaration names another path. */
138
+ export declare const IAM_RUNTIME_PATH = "/runtime";
139
+ /**
140
+ * The runtime API's leaf paths under its base. One table so the declarations and a fetch-only client
141
+ * (a target's server, which binds no entrypoint of the provider's tree) cannot disagree on a segment.
142
+ */
143
+ export declare const IAM_RUNTIME_ROUTES: {
144
+ readonly organizations: '/organizations';
145
+ readonly organization: '/organizations/:entitySlug';
146
+ readonly members: '/organizations/:entitySlug/members';
147
+ readonly member: '/organizations/:entitySlug/members/:profileId';
148
+ readonly memberRemove: '/organizations/:entitySlug/members/:profileId/remove';
149
+ readonly permissions: '/organizations/:entitySlug/permissions';
150
+ readonly grants: '/organizations/:entitySlug/grants';
151
+ readonly grantsRevoke: '/organizations/:entitySlug/grants/revoke';
152
+ };
96
153
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAE1C,eAAO,MAAM,iBAAiB,aAAa,CAAA;AAC3C,eAAO,MAAM,mBAAmB,eAAe,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG,OAAO,iBAAiB,GAAG,OAAO,mBAAmB,CAAA;AAE3E;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAA;AAE3C;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB,MAAM,CAAA;AAE1C,8DAA8D;AAC9D,oBAAY,eAAe;IACzB,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,IAAI,SAAS;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,eAAe,EAEvD,CAAA;AAED,+FAA+F;AAC/F,oBAAY,kBAAkB;IAC5B,aAAa,8BAA8B;IAC3C,aAAa,8BAA8B;IAC3C,YAAY,6BAA6B;IACzC,oGAAoG;IACpG,cAAc,+BAA+B;IAC7C,oEAAoE;IACpE,iBAAiB,mCAAmC;CACrD;AAED,8CAA8C;AAC9C,oBAAY,qBAAqB;IAC/B,4CAA4C;IAC5C,WAAW,iBAAiB;IAC5B,iDAAiD;IACjD,aAAa,mBAAmB;IAChC,wFAAwF;IACxF,SAAS,eAAe;IACxB,yCAAyC;IACzC,SAAS,cAAc;CACxB;AAED;;;;;;;GAOG;AACH,oBAAY,YAAY;IACtB,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,mDAAmD;IACnD,GAAG,QAAQ;CACZ;AAED,+EAA+E;AAC/E,oBAAY,gBAAgB;IAC1B,uFAAuF;IACvF,OAAO,YAAY;IACnB,gFAAgF;IAChF,MAAM,WAAW;CAClB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,YAAI,MAAM,EAAE,UAAU,EAAE,OAAO,CAAU,CAAA;AAE/D,MAAM,MAAM,OAAO,GAAG,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;AAE9C,2FAA2F;AAC3F,eAAO,MAAM,2BAA2B,OAAO,CAAA"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAE1C,eAAO,MAAM,iBAAiB,aAAa,CAAA;AAC3C,eAAO,MAAM,mBAAmB,eAAe,CAAA;AAE/C,MAAM,MAAM,OAAO,GAAG,OAAO,iBAAiB,GAAG,OAAO,mBAAmB,CAAA;AAE3E;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAA;AAE3C;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB,MAAM,CAAA;AAE1C,8DAA8D;AAC9D,oBAAY,eAAe;IACzB,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,IAAI,SAAS;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,EAAE,eAAe,EAEvD,CAAA;AAED,+FAA+F;AAC/F,oBAAY,kBAAkB;IAC5B,aAAa,8BAA8B;IAC3C,aAAa,8BAA8B;IAC3C,YAAY,6BAA6B;IACzC,oGAAoG;IACpG,cAAc,+BAA+B;IAC7C,oEAAoE;IACpE,iBAAiB,mCAAmC;CACrD;AAED,8CAA8C;AAC9C,oBAAY,qBAAqB;IAC/B,4CAA4C;IAC5C,WAAW,iBAAiB;IAC5B,iDAAiD;IACjD,aAAa,mBAAmB;IAChC,wFAAwF;IACxF,SAAS,eAAe;IACxB,yCAAyC;IACzC,SAAS,cAAc;CACxB;AAED;;;;;;;GAOG;AACH,oBAAY,YAAY;IACtB,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,mDAAmD;IACnD,GAAG,QAAQ;CACZ;AAED,+EAA+E;AAC/E,oBAAY,gBAAgB;IAC1B,uFAAuF;IACvF,OAAO,YAAY;IACnB,gFAAgF;IAChF,MAAM,WAAW;CAClB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,YAAI,MAAM,EAAE,UAAU,EAAE,OAAO,CAAU,CAAA;AAE/D,MAAM,MAAM,OAAO,GAAG,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;AAE9C,2FAA2F;AAC3F,eAAO,MAAM,2BAA2B,OAAO,CAAA;AAE/C;;;;;;;;GAQG;AACH,oBAAY,eAAe;IACzB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,KAAK,UAAU;CAChB;AAED,4DAA4D;AAC5D,oBAAY,cAAc;IACxB,wCAAwC;IACxC,MAAM,WAAW;IACjB,0DAA0D;IAC1D,OAAO,YAAY;IACnB,gFAAgF;IAChF,KAAK,UAAU;CAChB;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,YAAY,CAAA;AAE1C;;;;GAIG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAA;AAE5C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD,oGAAoG;AACpG,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAE1C;;;GAGG;AACH,eAAO,MAAM,kBAAkB;aAC7B,aAAa,EAAE,gBAAgB;aAC/B,YAAY,EAAE,4BAA4B;aAC1C,OAAO,EAAE,oCAAoC;aAC7C,MAAM,EAAE,+CAA+C;aACvD,YAAY,EAAE,sDAAsD;aACpE,WAAW,EAAE,wCAAwC;aACrD,MAAM,EAAE,mCAAmC;aAC3C,YAAY,EAAE,0CAA0C;CAChD,CAAA"}
package/build/consts.js CHANGED
@@ -98,4 +98,63 @@ export var IamRemovalPolicy;
98
98
  export const IAM_AREAS = ['user', 'operator', 'admin'];
99
99
  /** Separates the resource from the action in a permission name. TWO hyphens, never one. */
100
100
  export const PERMISSION_ACTION_SEPARATOR = '--';
101
+ /**
102
+ * Who holds a permission without an explicit grant — a property of the DEFINITION, evaluated when
103
+ * claims are built and never written onto a profile.
104
+ *
105
+ * `User` is every signed-in subject of the client (an unbound, unmanaged `user`-area definition
106
+ * only); `Member` and `Owner` are every member / owner of the organization the subject acts in, so
107
+ * they require an `entityScoped` definition. A class default is a BLANKET set: `Owner` on a
108
+ * resource-scoped definition is its all-records form.
109
+ */
110
+ export var IamDefaultClass;
111
+ (function (IamDefaultClass) {
112
+ IamDefaultClass["None"] = "none";
113
+ IamDefaultClass["User"] = "user";
114
+ IamDefaultClass["Member"] = "member";
115
+ IamDefaultClass["Owner"] = "owner";
116
+ })(IamDefaultClass || (IamDefaultClass = {}));
117
+ /** Why a subject holds a grant, as a listing reports it. */
118
+ export var IamGrantOrigin;
119
+ (function (IamGrantOrigin) {
120
+ /** Written on the subject's own row. */
121
+ IamGrantOrigin["Direct"] = "direct";
122
+ /** The definition's `defaultClass` covers the subject. */
123
+ IamGrantOrigin["Default"] = "default";
124
+ /** Held by a group the subject is a member of — `IamGrant.through` names it. */
125
+ IamGrantOrigin["Group"] = "group";
126
+ })(IamGrantOrigin || (IamGrantOrigin = {}));
127
+ /**
128
+ * The managed group `syncStaff` keeps in a client's owning organization: the owner's own staff,
129
+ * admitted to the client with the bundles the sync hands it. Read-only to an operator.
130
+ */
131
+ export const IAM_MEMBERS_GROUP = 'members';
132
+ /**
133
+ * The `profileId` of a credential that proves an e-mail address and identifies nobody yet: what the
134
+ * end-user sign-in hands the provider's finalizer, which then resolves the account itself. It carries
135
+ * no `entitySlug` — a proof bound to an organization is something else.
136
+ */
137
+ export const IAM_EMAIL_PROOF = 'proof:email';
138
+ /**
139
+ * The guard of the runtime IAM API (`makeIamRuntimeProtocols`). The serving process registers it: it
140
+ * admits a bearer access token the provider itself issued, and takes the client and the account from
141
+ * that token, never from the request.
142
+ */
143
+ export const IAM_RUNTIME_GUARD = 'iam-runtime-guard';
144
+ /** Where the runtime IAM API mounts under its service unless the declaration names another path. */
145
+ export const IAM_RUNTIME_PATH = '/runtime';
146
+ /**
147
+ * The runtime API's leaf paths under its base. One table so the declarations and a fetch-only client
148
+ * (a target's server, which binds no entrypoint of the provider's tree) cannot disagree on a segment.
149
+ */
150
+ export const IAM_RUNTIME_ROUTES = {
151
+ organizations: '/organizations',
152
+ organization: '/organizations/:entitySlug',
153
+ members: '/organizations/:entitySlug/members',
154
+ member: '/organizations/:entitySlug/members/:profileId',
155
+ memberRemove: '/organizations/:entitySlug/members/:profileId/remove',
156
+ permissions: '/organizations/:entitySlug/permissions',
157
+ grants: '/organizations/:entitySlug/grants',
158
+ grantsRevoke: '/organizations/:entitySlug/grants/revoke',
159
+ };
101
160
  //# sourceMappingURL=consts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAA;AAE1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAA;AAC3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAA;AAI/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA;AAE3C;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAE5C,6EAA6E;AAC7E,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AAE1C,8DAA8D;AAC9D,MAAM,CAAN,IAAY,eAMX;AAND,WAAY,eAAe;IACzB,oCAAiB,CAAA;IACjB,kCAAe,CAAA;IACf,gCAAa,CAAA;IACb,sCAAmB,CAAA;IACnB,gCAAa,CAAA;AACf,CAAC,EANW,eAAe,KAAf,eAAe,QAM1B;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,KAAK;CAC9C,CAAA;AAED,+FAA+F;AAC/F,MAAM,CAAN,IAAY,kBAQX;AARD,WAAY,kBAAkB;IAC5B,iEAA2C,CAAA;IAC3C,iEAA2C,CAAA;IAC3C,+DAAyC,CAAA;IACzC,oGAAoG;IACpG,mEAA6C,CAAA;IAC7C,oEAAoE;IACpE,0EAAoD,CAAA;AACtD,CAAC,EARW,kBAAkB,KAAlB,kBAAkB,QAQ7B;AAED,8CAA8C;AAC9C,MAAM,CAAN,IAAY,qBASX;AATD,WAAY,qBAAqB;IAC/B,4CAA4C;IAC5C,qDAA4B,CAAA;IAC5B,iDAAiD;IACjD,yDAAgC,CAAA;IAChC,wFAAwF;IACxF,iDAAwB,CAAA;IACxB,yCAAyC;IACzC,gDAAuB,CAAA;AACzB,CAAC,EATW,qBAAqB,KAArB,qBAAqB,QAShC;AAED;;;;;;;GAOG;AACH,MAAM,CAAN,IAAY,YAKX;AALD,WAAY,YAAY;IACtB,mCAAmB,CAAA;IACnB,uCAAuB,CAAA;IACvB,mDAAmD;IACnD,2BAAW,CAAA;AACb,CAAC,EALW,YAAY,KAAZ,YAAY,QAKvB;AAED,+EAA+E;AAC/E,MAAM,CAAN,IAAY,gBAKX;AALD,WAAY,gBAAgB;IAC1B,uFAAuF;IACvF,uCAAmB,CAAA;IACnB,gFAAgF;IAChF,qCAAiB,CAAA;AACnB,CAAC,EALW,gBAAgB,KAAhB,gBAAgB,QAK3B;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAU,CAAA;AAI/D,2FAA2F;AAC3F,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAA"}
1
+ {"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"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"}
package/build/errors.d.ts CHANGED
@@ -36,4 +36,17 @@ export declare class IamUserError extends IamError {
36
36
  static typeName: string;
37
37
  constructor(message?: string);
38
38
  }
39
+ /**
40
+ * A refusal about an organization or a membership in it — an unknown organization, the last owner,
41
+ * the membership cap.
42
+ */
43
+ export declare class IamOrganizationError extends IamError {
44
+ static typeName: string;
45
+ constructor(message?: string);
46
+ }
47
+ /** A refusal about a group: an unknown key, a managed group written by an operator, another app's group. */
48
+ export declare class IamGroupError extends IamError {
49
+ static typeName: string;
50
+ constructor(message?: string);
51
+ }
39
52
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAkC;IAEjE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAwC;IAEvE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,gBAAiB,SAAQ,QAAQ;IAC5C,OAAuB,QAAQ,SAA0C;IAEzE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,aAAc,SAAQ,QAAQ;IACzC,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;IAC9C,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAA6C;IAE5E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,YAAa,SAAQ,QAAQ;IACxC,OAAuB,QAAQ,SAAsC;IAErE,YAAY,OAAO,SAAU,EAG5B;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,QAAS,SAAQ,cAAc;IAC1C,OAAuB,QAAQ,SAAkC;IAEjE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAAwC;IAEvE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,gBAAiB,SAAQ,QAAQ;IAC5C,OAAuB,QAAQ,SAA0C;IAEzE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,aAAc,SAAQ,QAAQ;IACzC,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;IAC9C,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,OAAuB,QAAQ,SAA6C;IAE5E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,qBAAa,YAAa,SAAQ,QAAQ;IACxC,OAAuB,QAAQ,SAAsC;IAErE,YAAY,OAAO,SAAU,EAG5B;CACF;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ;IAChD,OAAuB,QAAQ,SAA8C;IAE7E,YAAY,OAAO,SAAU,EAG5B;CACF;AAED,4GAA4G;AAC5G,qBAAa,aAAc,SAAQ,QAAQ;IACzC,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,OAAO,SAAU,EAG5B;CACF"}
package/build/errors.js CHANGED
@@ -57,6 +57,25 @@ export class IamUserError extends IamError {
57
57
  this.type = IamUserError.typeName;
58
58
  }
59
59
  }
60
+ /**
61
+ * A refusal about an organization or a membership in it — an unknown organization, the last owner,
62
+ * the membership cap.
63
+ */
64
+ export class IamOrganizationError extends IamError {
65
+ static typeName = `IamOrganization${ResilientError.typeName}`;
66
+ constructor(message = 'error') {
67
+ super(`organization:${message}`);
68
+ this.type = IamOrganizationError.typeName;
69
+ }
70
+ }
71
+ /** A refusal about a group: an unknown key, a managed group written by an operator, another app's group. */
72
+ export class IamGroupError extends IamError {
73
+ static typeName = `IamGroup${ResilientError.typeName}`;
74
+ constructor(message = 'error') {
75
+ super(`group:${message}`);
76
+ this.type = IamGroupError.typeName;
77
+ }
78
+ }
60
79
  ResilientError.registerErrorClass(IamError);
61
80
  ResilientError.registerErrorClass(IamClientError);
62
81
  ResilientError.registerErrorClass(IamResourceError);
@@ -64,4 +83,6 @@ ResilientError.registerErrorClass(IamGrantError);
64
83
  ResilientError.registerErrorClass(IamPermissionError);
65
84
  ResilientError.registerErrorClass(IamUnsupported);
66
85
  ResilientError.registerErrorClass(IamUserError);
86
+ ResilientError.registerErrorClass(IamOrganizationError);
87
+ ResilientError.registerErrorClass(IamGroupError);
67
88
  //# sourceMappingURL=errors.js.map