@owlmeans/iam 0.1.18-rc.45 → 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 +9 -7
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/iam/SKILL.md +193 -46
- package/build/consts.d.ts +57 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +59 -0
- package/build/consts.js.map +1 -1
- package/build/errors.d.ts +13 -0
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +21 -0
- package/build/errors.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/runtime.d.ts +187 -0
- package/build/runtime.d.ts.map +1 -0
- package/build/runtime.js +244 -0
- package/build/runtime.js.map +1 -0
- package/build/types.d.ts +251 -34
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +21 -4
- package/build/utils.d.ts.map +1 -1
- package/build/utils.js +26 -15
- package/build/utils.js.map +1 -1
- package/package.json +7 -3
- package/src/consts.ts +64 -0
- package/src/errors.ts +25 -0
- package/src/index.ts +1 -0
- package/src/runtime.ts +440 -0
- package/src/types.ts +289 -35
- package/src/utils.ts +35 -19
- package/tests/runtime.spec.ts +101 -0
- package/tests/tsconfig.json +12 -0
- package/tests/utils.spec.ts +47 -1
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
|
|
10
|
-
- `
|
|
11
|
-
-
|
|
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.
|
|
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.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
|
|
26
|
-
const allowed = hasPermission(auth, {
|
|
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.
|
|
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
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/iam",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
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
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
-
| `
|
|
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?,
|
|
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?,
|
|
57
|
-
| `IamPermissionFilter` | type | `{ areas?, managed?, resourceScoped? }` — narrows `listPermissions`; an `areas` entry of `null` matches untagged definitions |
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
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 |
|
|
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
|
-
| `
|
|
73
|
-
| `
|
|
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 (
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
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
|
|
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
|
-
// ---
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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>')
|
|
290
|
-
|
|
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
|
|
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
|
package/build/consts.d.ts.map
CHANGED
|
@@ -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
|
package/build/consts.js.map
CHANGED
|
@@ -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":"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;AAE/C;;;;;;;;GAQG;AACH,MAAM,CAAN,IAAY,eAKX;AALD,WAAY,eAAe;IACzB,gCAAa,CAAA;IACb,gCAAa,CAAA;IACb,oCAAiB,CAAA;IACjB,kCAAe,CAAA;AACjB,CAAC,EALW,eAAe,KAAf,eAAe,QAK1B;AAED,4DAA4D;AAC5D,MAAM,CAAN,IAAY,cAOX;AAPD,WAAY,cAAc;IACxB,wCAAwC;IACxC,mCAAiB,CAAA;IACjB,0DAA0D;IAC1D,qCAAmB,CAAA;IACnB,gFAAgF;IAChF,iCAAe,CAAA;AACjB,CAAC,EAPW,cAAc,KAAd,cAAc,QAOzB;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,SAAS,CAAA;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAA;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAA;AAEpD,oGAAoG;AACpG,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAA;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,aAAa,EAAE,gBAAgB;IAC/B,YAAY,EAAE,4BAA4B;IAC1C,OAAO,EAAE,oCAAoC;IAC7C,MAAM,EAAE,+CAA+C;IACvD,YAAY,EAAE,sDAAsD;IACpE,WAAW,EAAE,wCAAwC;IACrD,MAAM,EAAE,mCAAmC;IAC3C,YAAY,EAAE,0CAA0C;CAChD,CAAA"}
|
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
|
package/build/errors.d.ts.map
CHANGED
|
@@ -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
|