@owlmeans/iam 0.1.18-rc.45 → 0.1.18-rc.47

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.45
18
+ bun add @owlmeans/iam@^0.1.18-rc.47
17
19
  ```
18
20
 
19
21
  ## Usage
@@ -22,8 +24,8 @@ bun add @owlmeans/iam@^0.1.18-rc.45
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.44
41
+ npx @owlmeans/agent-skills@^0.1.18-rc.46
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.45",
5
- "generatedAt": "2026-09-30T09:41:02.825Z",
4
+ "version": "0.1.18-rc.47",
5
+ "generatedAt": "2026-10-03T11:19:50.779Z",
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,57 +8,43 @@ metadata:
8
8
 
9
9
  # Using `@owlmeans/iam`
10
10
 
11
- **Install:** `"@owlmeans/iam": "^0.1.18-rc.45"` in `dependencies`
11
+ **Install:** `"@owlmeans/iam": "^0.1.18-rc.47"` 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
 
18
- ## The browser half — `@owlmeans/client-iam`
19
+ ## Where the halves live
19
20
 
20
- `@owlmeans/client-iam` has no skill of its own because it is a wiring package: it re-exports these
21
- types and `hasPermission`, and adds the two calls that turn "this application uses IAM" into two
22
- lines.
23
-
24
- | Export | Description |
25
- |--------|-------------|
26
- | `appendIam(context, opts?)` | Wires the OIDC guard onto a web context **and** installs the consent-before-sign-in precondition |
27
- | `withIamGuard(protocolTree, coguards?)` | Returns a decorated immutable protocol tree with IAM's OIDC guard applied |
28
- | `iamEntrypoints(dispatcherProps?)` | Browser-local OIDC bindings and dispatcher-screen binding |
29
- | `requireConsentForLogin(ctx, opts?)` | That precondition on its own, for a context wired some other way |
30
- | `CONSENT_LOGIN_PRECONDITION` / `CLIENT_IAM_SERVICE` | The precondition alias and the service alias |
31
- | `useLogin` / `useLogout`, `LoginOutcome` / `LoginIntent`, `LoginPlugin` / `LoginService` / … | Re-exported sign-in surface, so an app has one IAM import rather than three |
32
- | Everything from `@owlmeans/iam` | The types above, plus `hasPermission` |
33
-
34
- The protocol tree is immutable. Decorate it once with `withIamGuard(protocols)` before binding local
35
- screens and API callers, then spread `iamEntrypoints()` once into that browser binding list. The
36
- helper deliberately does not add declarations to a flattened entrypoint array.
37
-
38
- And the consent precondition sits on `LoginService.begin` rather than on a hook, a screen or a login
39
- plugin, because `begin` is the single funnel every sign-in mechanic passes through and the one place
40
- the user's gesture is still live — a refusal resolves as `LoginOutcome.Gesture` and opens the consent dialog in
41
- the same gesture. An application that genuinely sets no cookie passes `{ consent: { disabled: true } }`.
42
-
43
- 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.
21
+ This package is contracts only. The browser half — `appendIam`, the session's organizations and the
22
+ switch, `hasPermission` re-exported — is `@owlmeans/client-iam` (`client-iam` skill); the server half
23
+ — the IAM gate, the session's organizations on the server and the runtime IAM client — is
24
+ `@owlmeans/server-iam` (`server-iam` skill). Permission decisions are made on the server, never in
25
+ the browser.
45
26
 
46
27
  ## Public API surface
47
28
 
48
29
  | Symbol | Kind | Purpose |
49
30
  |--------|------|---------|
50
31
  | `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) |
32
+ | `IamClient` | type | Provisioned OIDC client `{ id?, clientId, secret?, name?, realm?, config? }` |
33
+ | `IamClientInfo` | type | `IamClient` without `secret` — what `getClient` answers |
34
+ | `IamClientConfig` | type | `{ operators?, users?, [key]: unknown }` — the two tenancy flags plus the client's arbitrary configuration |
35
+ | `IamClientOptions` | type | `{ redirectUris?, config? }` — explicit `ensureClient` hardening (omitting `redirectUris` is a keycloak-only legacy shape, see below); `config` merges shallowly |
53
36
  | `IamCredentialsPair` | type | `{ token: string; realm: string }` |
54
- | `IamPermissionArgs` | type | `{ permission?, resourceScoped?, title?, area?, managed?, defaultEnabled? }` — `permission` absent means unscoped resource name |
37
+ | `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
38
  | `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? }` |
39
+ | `IamPermissionDefinition` | type | Declared permission `{ name, resource, action?, resourceScoped?, entityScoped?, title?, area?, managed?, defaultClass? }` |
40
+ | `IamPermissionFilter` | type | `{ areas?, managed?, resourceScoped?, entityScoped? }` — narrows `listPermissions`; an `areas` entry of `null` matches untagged definitions |
41
+ | `IamDefaultClass` | enum | `None` / `User` / `Member` / `Owner` — who holds a definition without a grant (see "Permission kinds") |
42
+ | `IamGrantArgs` | type | `{ resources?, mode?, entitySlug? }` — `entitySlug` binds an `entityScoped` grant |
43
+ | `IamGrant` | type | `{ profileId? \| group?, clientId, permission, resources?, mode?, entitySlug?, origin?, through? }` — exactly one subject |
44
+ | `IamGrantOrigin` | enum | `Direct` / `Default` / `Group` — why a listed grant is held; `through` names the group |
45
+ | `IamGroupRef` | type | `{ entitySlug, key }` — a group by its organization's slug and its key |
60
46
  | `IamGrantMode` | enum | `Blanket` / `Resources` / `All` — which FORM of a grant an operation addresses |
61
- | `IamGrantBundle` | type | `{ filter?, permissions?, mode?, resources? }` — what `grantBundle` hands out |
47
+ | `IamGrantBundle` | type | `{ filter?, permissions?, mode?, resources?, entitySlug? }` — what `grantBundle` hands out |
62
48
  | `IamRemovalPolicy` | enum | `Cascade` / `Refuse` — what to do about grants of a definition being deleted |
63
49
  | `IamPermissionDeleteArgs`, `IamPermissionRemoval` | type | `{ policy?, managed? }` and `{ permission, clientId, found, revoked }` |
64
50
  | `IamNormalizeArgs`, `IamNormalizationReport` | type | `{ dryRun? }` and the rename/merge plan `normalizePermissions` reports |
@@ -66,11 +52,24 @@ this package's screens hand out. Permission decisions are made there, never in t
66
52
  | `PERMISSION_ACTION_SEPARATOR` | const | `'--'` — TWO hyphens, never one |
67
53
  | `parsePermissionName` / `composePermissionName` / `isPermissionName` | fn | Read, build and validate a permission NAME |
68
54
  | `ParsedPermissionName` | type | `{ name, resource, action?, problem? }` |
69
- | `IamUser` | type | End-user of an entity `{ profileId, email?, name?, role, disabled?, grantCount? }` |
55
+ | `IamUser` | type | Subject of one client `{ profileId, email?, name?, role, disabled?, grantCount?, home?, memberships? }` |
56
+ | `IamMembershipInfo` | type | `{ entitySlug, title?, owner, groups, home? }` — one organization of an `IamUser` |
70
57
  | `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? }` |
58
+ | `IamUserUpdate` | type | `{ name?, role?, disabled? }` — `disabled` lands on the primary row |
59
+ | `IamOrganization`, `IamOrganizationArgs`, `IamOrganizationUpdate` | type | `{ entitySlug, title?, members? }`; create `{ title?, profileId? }` (the new owner); update `{ title }` |
60
+ | `IamMember`, `IamMemberInvite`, `IamMemberUpdate` | type | `{ profileId, email?, name?, owner, groups, managed? }`; `{ email, name?, owner? }`; `{ owner?, groups? }` |
61
+ | `IamGroup`, `IamGroupArgs`, `IamGroupBundle` | type | `{ entitySlug, key, title?, managed?, members?, bundles }`; `{ title?, bundles? }`; `{ filter?, permissions? }` |
62
+ | `IamStaffSyncArgs`, `IamStaffSyncReport` | type | `{ bundles }` and `{ added, removed }` (profile ids) of `syncStaff` |
63
+ | `IamSubject`, `IamSignInArgs`, `IamSignInResult` | type | `{ profileId, email?, name?, organizations, permissions }` (claims input); `{ email, name? }`; `{ accountId, profileId }` |
64
+ | `IamOrganizationFacet`, `IamGroupFacet`, `IamSubjectFacet` | type | The three facets of `IamService` |
65
+ | `unsupportedFacet<T>(what)` | fn | A facet whose every method throws `IamUnsupported(what)` — synchronously |
66
+ | `IAM_MEMBERS_GROUP` | const | `'members'` — the managed staff group `syncStaff` keeps |
67
+ | `IAM_EMAIL_PROOF` | const | `'proof:email'` — the `profileId` of a credential that proves an address and identifies nobody yet |
68
+ | `hasPermission` | fn | `(auth, permission, { scope?, resourceId?, entitySlug? }?)` — checks `Authorization.permissions`; an unscoped set satisfies a resourceId check, an unbound set any organization |
69
+ | `HasPermissionOptions` | type | `{ scope?, resourceId?, entitySlug? }` |
70
+ | `makeIamRuntimeProtocols({ service, path? })` | fn | The runtime IAM API's protocol tree — see "Runtime IAM API" |
71
+ | `IAM_RUNTIME_GUARD`, `IAM_RUNTIME_PATH`, `IAM_RUNTIME_ROUTES` | const | Its guard alias, default base path (`'/runtime'`) and leaf-path table |
72
+ | `IamRuntime*` | type/schema | Its wire shapes and closed schemas (`IamRuntimeOrganization`, `IamRuntimeMember`, `IamRuntimePermission`, `IamRuntimeGrant`, `IamRuntimeGrantRequest`, … and `*Schema`) |
74
73
  | 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
74
  | `DEFAULT_ALIAS` | const | Default service alias `'iam-service'` |
76
75
  | `IAM_MODE_KEYCLOAK` | const | `'keycloak'` |
@@ -82,9 +81,11 @@ this package's screens hand out. Permission decisions are made there, never in t
82
81
  | `IamGrantError` | class | Grant subject missing or entity mismatch |
83
82
  | `IamPermissionError` | class | A refusal about a DEFINITION: `permission:held:<name>`, `permission:managed:<name>` |
84
83
  | `IamUserError` | class | An end-user operation was refused |
84
+ | `IamOrganizationError` | class | An organization or membership refusal — unknown organization, last owner, membership cap |
85
+ | `IamGroupError` | class | A group refusal — unknown key, a managed group written by an operator, another app's group |
85
86
  | `IamUnsupported` | class | Operation not supported by the active backend (e.g. `resource-scoped-grant`, `user-management`) |
86
87
 
87
- ## Permission model (two grant forms)
88
+ ## Permission model (kinds and grant forms)
88
89
 
89
90
  Permissions are declared per entity client (project) with `ensurePermission` and granted to end-user
90
91
  subjects with `grantPermission`:
@@ -113,12 +114,44 @@ nothing at request time. `managed: true` marks a definition the platform owns: a
113
114
  revoke it from an undifferentiated list, and a repair that deletes "definitions no declaration names"
114
115
  must not reach one.
115
116
 
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`.
117
+ ### Permission kinds
118
+
119
+ What a grant binds to is a property of the DEFINITION, and grants follow it:
120
+
121
+ | Kind | Definition flags | Grant stored on | Applies |
122
+ |---|---|---|---|
123
+ | unbound | none | the subject's primary row, or a group | everywhere |
124
+ | entity-bound | `entityScoped` | the subject's row of THAT organization, or a group of it | only while acting in that organization |
125
+ | resource-bound | `resourceScoped` | the primary row, a titled set with `resources` | to those resource ids |
126
+ | both | both | that organization's row, a titled set with `resources` | both checks |
127
+
128
+ - Granting an `entityScoped` definition REQUIRES `args.entitySlug`, which names the subject's row
129
+ there — the subject must be a member. Any other definition refuses `args.entitySlug`. Turning
130
+ `entityScoped` off while grants exist is refused: every bound grant would silently widen.
131
+ - Stored sets carry the organization's id; CLAIM sets (provider → relying party only) carry its
132
+ `entitySlug` instead (`OidcPermissionSetClaim`). The relying party keeps the unbound sets and the
133
+ acting organization's, strips the binding, and drops the rest — a browser token never carries a
134
+ bound set of another organization.
135
+ - `hasPermission(auth, name, { entitySlug })` applies a set without `entitySlug` everywhere and a
136
+ bound one only when `opts.entitySlug` names its organization — fail-closed, so a bound set that
137
+ leaked into a token never counts where the caller named no organization.
138
+
139
+ ### Default classes
140
+
141
+ `defaultClass` says who holds a definition without an explicit grant. It is evaluated when claims and
142
+ grant listings are built — never written onto a row:
143
+
144
+ - `User` — every signed-in subject of the client. Requires an unbound, unmanaged `user`-area definition.
145
+ - `Member` / `Owner` — every member / owner of the organization the subject acts in. Requires
146
+ `entityScoped`.
147
+ - `None` — nobody.
148
+
149
+ A class default is a BLANKET set, so `Owner` on a resource-scoped definition is its all-records form.
150
+ An explicit `false` on a row suppresses only the class default of that binding (an unbound `false`
151
+ suppresses `User`; a `false` on the row of an organization suppresses `Member`/`Owner` there).
152
+ Grants are additive — there is no deny rule over a group's grant. A listing marks each grant's
153
+ `origin`: `Direct`, `Default`, or `Group` with `through` naming the group. A backend that cannot
154
+ evaluate classes (Keycloak) refuses any `defaultClass` other than `None` with `IamUnsupported`.
122
155
 
123
156
  ## `IamService` interface
124
157
 
@@ -135,10 +168,14 @@ interface IamService extends InitializedService {
135
168
  // "A client id is a global name" for the two rules it has to obey.
136
169
  ensureClient: (entityId: string, clientId: string, options?: IamClientOptions) => Promise<IamClient>
137
170
 
171
+ // The entity's client without its secret, or null. Never provisions.
172
+ getClient: (entityId: string, clientId: string) => Promise<IamClientInfo | null>
173
+
138
174
  // Reserve a free client id without provisioning it; false when anyone else holds it.
139
175
  claimClient: (entityId: string, clientId: string) => Promise<boolean>
140
176
 
141
- // Release a client id and everything keyed by it (called when a project/slot is deleted).
177
+ // Release a client id and everything keyed by it — its subjects' rows, its groups, its
178
+ // definitions (called when a project/slot is deleted).
142
179
  deleteClient: (entityId: string, clientId: string) => Promise<void>
143
180
 
144
181
  // Declare a permission. Returns the canonical name. MERGES into an existing definition —
@@ -155,14 +192,16 @@ interface IamService extends InitializedService {
155
192
  // A rename and a merge — never a delete, never an invention.
156
193
  normalizePermissions: (entityId: string, clientId: string, args?: IamNormalizeArgs) => Promise<IamNormalizationReport>
157
194
 
158
- // Provision a resource and assign it to the tenant's owner role
195
+ // Provision a resource and assign it to the tenant's owner role (keycloak). A no-op on the
196
+ // integrated backend, which has no owner role to assign to.
159
197
  ensureResourceOwnership: (entityId: string, clientId: string, resource: IamResourceSpec) => Promise<void>
160
198
 
161
199
  // --- Authorization (permission definitions & grants) ---
162
200
 
163
201
  listPermissions: (entityId: string, clientId: string, filter?: IamPermissionFilter) => Promise<IamPermissionDefinition[]>
164
202
 
165
- // args.resources selects the resource-scoped form; see "Permission model (two grant forms)"
203
+ // args.resources selects the resource-scoped form, args.entitySlug the binding of an entityScoped
204
+ // definition; see "Permission model (kinds and grant forms)"
166
205
  grantPermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<IamGrant>
167
206
  revokePermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<void>
168
207
 
@@ -171,30 +210,108 @@ interface IamService extends InitializedService {
171
210
  // grantPermission, which costs N·M writes and N·M lost-update windows on the same records.
172
211
  grantBundle: (entityId: string, clientId: string, profileIds: string[], bundle: IamGrantBundle) => Promise<IamGrant[]>
173
212
 
213
+ // Direct, default-class and group grants, each with its `origin`.
174
214
  listGrants: (entityId: string, clientId: string, profileId?: string) => Promise<IamGrant[]>
175
215
 
176
- // --- End-user management — see "End-users are customer-wide" ---
216
+ // --- Users of one client — see "Users are per client" ---
177
217
 
178
218
  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>
219
+ getUser: (entityId: string, clientId: string, profileId: string) => Promise<IamUser | null>
220
+ inviteUser: (entityId: string, clientId: string, invite: IamUserInvite) => Promise<IamUser>
221
+ updateUser: (entityId: string, clientId: string, profileId: string, update: IamUserUpdate) => Promise<IamUser>
222
+ removeUser: (entityId: string, clientId: string, profileId: string) => Promise<void>
223
+
224
+ // The owning organization's staff, admitted to the client through the managed `members` group.
225
+ syncStaff: (entityId: string, clientId: string, args: IamStaffSyncArgs) => Promise<IamStaffSyncReport>
226
+
227
+ // --- Tenancy facets — see "Organizations, groups and subjects" ---
228
+ organizations: IamOrganizationFacet
229
+ groups: IamGroupFacet
230
+ subjects: IamSubjectFacet
183
231
  }
184
232
  ```
185
233
 
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.
234
+ The first parameter of every method is `entityId` — the organization that OWNS the client — and
235
+ every `(entityId, clientId)` method verifies that ownership before it reads or writes anything. The
236
+ organizations a client's subjects act in are a different thing, named by `entitySlug` inside the
237
+ facets.
238
+
239
+ ## Users are per client
240
+
241
+ A person is ONE account (one per e-mail address); what a client sees of it are its rows of that app —
242
+ one per (account, app, organization), the PRIMARY one in the account's own organization. The
243
+ `profileId` a client knows is the pairwise subject, the same on every row of one (account, app) and
244
+ different for every app, so two apps of one customer cannot correlate their users by it.
245
+
246
+ - `listUsers(entityId, clientId)` lists every account holding a row of the client, in whichever
247
+ organization. Without `clientId` it is a read-only aggregate over the entity's clients, for a
248
+ console — nothing is written through it.
249
+ - `getUser` / `inviteUser` / `updateUser` / `removeUser` take the client. `removeUser` deletes this
250
+ account's rows of THAT app only — the account, and its rows of other apps, stay. `updateUser`
251
+ writes `disabled` on the primary row, which is what refuses the subject at the provider.
252
+ - `inviteUser` is idempotent by email, which is what lets an invitation and a first sign-in converge on
253
+ one record instead of accumulating twins.
254
+
255
+ A backend with no user store of its own — one where the customer manages users in the provider's own
256
+ console — throws `IamUnsupported('user-management')` from all five methods.
257
+
258
+ ## Organizations, groups and subjects
259
+
260
+ The three facets of `IamService` carry tenancy. A backend that cannot provide one answers
261
+ `unsupportedFacet<IamOrganizationFacet>('organizations')` (and so on): every method of it throws
262
+ `IamUnsupported(what)` SYNCHRONOUSLY, because `subjects.identify` is synchronous and a rejected
263
+ promise would hand it the wrong type — an awaited call rejects either way.
264
+
265
+ - **Tenancy is the client's configuration.** `IamClientConfig.users` / `.operators` turn it on: with
266
+ neither flag, every subject acts in the client's owning organization (a single-tenant app); with
267
+ either, each subject acts in organizations of its own and the provider claims them under the
268
+ `organizations` scope (`@owlmeans/oidc`). The flags also gate the runtime API's writes by area.
269
+ - **`organizations`** — `list`, `get`, `create({ title?, profileId? })` (that subject becomes the
270
+ owner), `update({ title })`, `members`, `addMember` (find-or-create by e-mail, idempotent, refused
271
+ past the membership cap), `updateMember`, `removeMember`. The last owner cannot be demoted or
272
+ removed (`IamOrganizationError`).
273
+ - **`groups`** — a group is (organization, client, key), kept inside the organization record; one
274
+ client never reads or writes another's. `list`, `ensure`, `remove`, `members`, `addMembers`,
275
+ `removeMembers`, `grant` / `revoke` (bound to the group's organization), `grants`, and
276
+ `setBundles` — a bundle (`{ filter?, permissions? }`) grants every definition it selects to the
277
+ group's members at claim time. `IAM_MEMBERS_GROUP` is the managed group `syncStaff` keeps in the
278
+ owning organization; it is read-only to an operator (`IamGroupError`).
279
+ - **`subjects`** — the provider's half, keyed by the ACCOUNT, which never leaves the provider.
280
+ `signIn(clientId, { email })` finds or creates the account and its rows for the client;
281
+ `resolve(clientId, accountId)` builds the `IamSubject` the claims come from (`null` = not admitted:
282
+ no row, or a disabled primary row); `identify(clientId, accountId)` is the pairwise `sub`.
283
+
284
+ ## Runtime IAM API
285
+
286
+ `makeIamRuntimeProtocols({ service, path? })` declares the provider-side API a tenanted client's
287
+ server calls on behalf of its signed-in subject. It is declarations only: the provider binds it, and
288
+ a relying party calls it through `iamRuntime` (`@owlmeans/server-iam`) over plain `fetch`.
289
+
290
+ | Route (under `path`, default `IAM_RUNTIME_PATH`) | Tree | Who |
291
+ |---|---|---|
292
+ | `GET /organizations` | `organizations.list` | any subject — its own |
293
+ | `POST /organizations` `{ title? }` | `organizations.create` | any subject of a tenanted client; it becomes owner |
294
+ | `POST /organizations/:entitySlug` `{ title }` | `organizations.update` | owner |
295
+ | `GET /organizations/:entitySlug/members` | `members.list` | member |
296
+ | `POST /organizations/:entitySlug/members` `{ email, name?, owner? }` | `members.add` | owner; idempotent by e-mail |
297
+ | `POST …/members/:profileId` `{ owner?, groups? }` | `members.update` | owner |
298
+ | `POST …/members/:profileId/remove` | `members.remove` | owner, or the member itself |
299
+ | `GET …/permissions` | `permissions.list` | member |
300
+ | `GET …/grants` `?profileId` | `grants.list` | owner |
301
+ | `POST …/grants`, `POST …/grants/revoke` `{ profileId, permission, resources?, mode? }` | `grants.assign` / `grants.revoke` | owner |
302
+
303
+ - **Every route, the base included, is pinned to `service`** — a route's service is never inherited
304
+ from its parent — and every leaf hangs under ONE base carrying `IAM_RUNTIME_GUARD`. The serving
305
+ process registers that guard: it admits only a bearer access token the provider issued, and takes
306
+ the client and the account from it, never from the request.
307
+ - **The wire names organizations by `entitySlug` and subjects by the calling client's pairwise
308
+ `profileId`** — no organization or account id appears in any shape. Every schema is closed, since
309
+ the server's AJV strips an undeclared key silently. `IAM_RUNTIME_ROUTES` is the one leaf-path table
310
+ the declarations and the fetch client share.
311
+ - The serving process enforces the write rules: only `entityScoped`, unmanaged definitions of the
312
+ CALLING client, binding forced to the path's organization, the last owner protected, the
313
+ membership cap, and only for an area the client's tenancy config opens (`users` → `user`,
314
+ `operators` → `operator`). A non-owner write is `AuthForbidden(ORGANIZATION_OWNER_REFUSAL)`.
198
315
 
199
316
  ## A client id is a global name
200
317
 
@@ -215,8 +332,8 @@ cannot namespace it on a consumer's behalf. Uniqueness has to be *in the id*.
215
332
 
216
333
  ## `IamClient.realm` field
217
334
 
218
- `realm` is set by the provider to the entity id, so read `client.realm ?? fallbackEntityId` and never
219
- reach into an adapter's own internals (`(client as any)._realm`) for the same value.
335
+ Every implementation sets `realm` to the entity id, so read `client.realm ?? fallbackEntityId` and
336
+ never reach into an adapter's own internals (`(client as any)._realm`) for the same value.
220
337
 
221
338
  ## Issuer & redirect URIs
222
339
 
@@ -285,14 +402,18 @@ the same options — otherwise they disagree about the issuer, and only one of t
285
402
  - Do not expose `getCredentialsPair` to request handlers — it returns a raw admin token.
286
403
  - `IamClientError` means the provisioned client was invalid (missing secret, null token). The adapter
287
404
  already throws it; callers handle it rather than re-throwing a wrapped error.
288
- - `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.
405
+ - An operation a backend cannot support throws `IamUnsupported('<what>')`; a whole facet it cannot
406
+ support is `unsupportedFacet(what)`. Callers turn that into a fallback — an "external console"
407
+ link, a disabled control — rather than an error screen.
408
+ - A wire value names an organization by `entitySlug` only; the `entityId` parameter is the client's
409
+ OWNER, resolved server-side, never taken from a token or a request.
291
410
 
292
411
  ## Related
293
412
 
294
- - `@owlmeans/server-iam` — the IAM gate that asserts both grant forms, and the gate-param grammar;
295
- see the `server-iam` skill
413
+ - `@owlmeans/server-iam` — the IAM gate that asserts every permission kind, the gate-param grammar,
414
+ the session's organizations and the runtime IAM client; see the `server-iam` skill
415
+ - `@owlmeans/client-iam` — browser wiring, the organization switch and `hasPermission`; see the
416
+ `client-iam` skill
296
417
  - `@owlmeans/oidc` — provider descriptors and the `iamMode` config seam; see the `oidc` skill
297
418
  - The concrete adapters (a Keycloak proxy, and the integrated backend built on
298
419
  `@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"}