@owlmeans/iam 0.1.18-rc.4 → 0.1.18-rc.41
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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/iam/SKILL.md +193 -56
- package/build/consts.d.ts +91 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +97 -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 +17 -0
- package/build/errors.js.map +1 -1
- package/build/gate/index.d.ts +4 -0
- package/build/gate/index.d.ts.map +1 -0
- package/build/gate/index.js +4 -0
- package/build/gate/index.js.map +1 -0
- package/build/gate/param.d.ts +31 -0
- package/build/gate/param.d.ts.map +1 -0
- package/build/gate/param.js +99 -0
- package/build/gate/param.js.map +1 -0
- package/build/gate/resolve.d.ts +10 -0
- package/build/gate/resolve.d.ts.map +1 -0
- package/build/gate/resolve.js +93 -0
- package/build/gate/resolve.js.map +1 -0
- package/build/gate/validate.d.ts +15 -0
- package/build/gate/validate.d.ts.map +1 -0
- package/build/gate/validate.js +107 -0
- package/build/gate/validate.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/permission.d.ts +21 -0
- package/build/permission.d.ts.map +1 -0
- package/build/permission.js +39 -0
- package/build/permission.js.map +1 -0
- package/build/types.d.ts +251 -6
- package/build/types.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/consts.ts +105 -0
- package/src/errors.ts +19 -0
- package/src/gate/index.ts +3 -0
- package/src/gate/param.ts +122 -0
- package/src/gate/resolve.ts +114 -0
- package/src/gate/validate.ts +132 -0
- package/src/index.ts +2 -0
- package/src/permission.ts +48 -0
- package/src/types.ts +283 -6
- package/tests/gate-param.spec.ts +234 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Provider-agnostic IAM abstraction for the OwlMeans framework — `IamService` in
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
bun add @owlmeans/iam
|
|
16
|
+
bun add @owlmeans/iam@^0.1.18-rc.41
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
@@ -36,7 +36,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
36
36
|
your project's skill store (`.agents/skills/`):
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
npx @owlmeans/agent-skills
|
|
39
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.39
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
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.41",
|
|
5
|
+
"generatedAt": "2026-09-24T14:00:35.030Z",
|
|
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: "
|
|
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."
|
|
4
4
|
metadata:
|
|
5
5
|
applyTo: "**/services/iam*.ts, **/context.ts, **/types.ts"
|
|
6
6
|
---
|
|
@@ -8,7 +8,40 @@ metadata:
|
|
|
8
8
|
|
|
9
9
|
# Using `@owlmeans/iam`
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Install:** `"@owlmeans/iam": "^0.1.18-rc.41"` in `dependencies`
|
|
12
|
+
|
|
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 —
|
|
15
|
+
concrete backends (a Keycloak proxy, an integrated backend built on the OwlMeans OIDC provider) are
|
|
16
|
+
separate packages, and every consumer depends on this interface rather than on one of them.
|
|
17
|
+
|
|
18
|
+
## The browser half — `@owlmeans/client-iam`
|
|
19
|
+
|
|
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.
|
|
12
45
|
|
|
13
46
|
## Public API surface
|
|
14
47
|
|
|
@@ -18,21 +51,38 @@ Provider-agnostic IAM abstraction. Defines `IamService` interface and related ty
|
|
|
18
51
|
| `IamClient` | type | Provisioned OIDC client `{ id?, clientId, secret?, name?, realm? }` |
|
|
19
52
|
| `IamClientOptions` | type | `{ redirectUris? }` — explicit `ensureClient` hardening; omitting is a keycloak-only legacy shape (see below) |
|
|
20
53
|
| `IamCredentialsPair` | type | `{ token: string; realm: string }` |
|
|
21
|
-
| `IamPermissionArgs` | type | `{ permission?, resourceScoped?, title? }` — `permission` absent means unscoped resource name |
|
|
54
|
+
| `IamPermissionArgs` | type | `{ permission?, resourceScoped?, title?, area?, managed? }` — `permission` absent means unscoped resource name |
|
|
22
55
|
| `IamResourceSpec` | type | `{ name: string; displayName?: string }` |
|
|
23
|
-
| `IamPermissionDefinition` | type | Declared permission `{ name, resource, action?, resourceScoped?, title? }` |
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
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
|
+
| `IamGrantArgs` | type | `{ resources?: string[], mode? }` |
|
|
59
|
+
| `IamGrant` | type | `{ profileId, clientId, permission, resources?, mode? }` |
|
|
60
|
+
| `IamGrantMode` | enum | `Blanket` / `Resources` / `All` — which FORM of a grant an operation addresses |
|
|
61
|
+
| `IamGrantBundle` | type | `{ filter?, permissions?, mode?, resources? }` — what `grantBundle` hands out |
|
|
62
|
+
| `IamRemovalPolicy` | enum | `Cascade` / `Refuse` — what to do about grants of a definition being deleted |
|
|
63
|
+
| `IamPermissionDeleteArgs`, `IamPermissionRemoval` | type | `{ policy?, managed? }` and `{ permission, clientId, found, revoked }` |
|
|
64
|
+
| `IamNormalizeArgs`, `IamNormalizationReport` | type | `{ dryRun? }` and the rename/merge plan `normalizePermissions` reports |
|
|
65
|
+
| `IAM_AREAS`, `IamArea` | const/type | `['user', 'operator', 'admin']` — presentational grouping tags, read by no gate |
|
|
66
|
+
| `PERMISSION_ACTION_SEPARATOR` | const | `'--'` — TWO hyphens, never one |
|
|
67
|
+
| `parsePermissionName` / `composePermissionName` / `isPermissionName` | fn | Read, build and validate a permission NAME |
|
|
68
|
+
| `ParsedPermissionName` | type | `{ name, resource, action?, problem? }` |
|
|
69
|
+
| `IamUser` | type | End-user of an entity `{ profileId, email?, name?, role, disabled?, grantCount? }` |
|
|
70
|
+
| `IamUserInvite` | type | `{ email, name?, role? }` — find-or-create args |
|
|
71
|
+
| `IamUserUpdate` | type | `{ name?, role?, disabled? }` |
|
|
26
72
|
| `hasPermission` | fn | `(auth, permission, { scope?, resourceId? }?)` — checks `Authorization.permissions` PermissionSet[]; an unscoped set satisfies a resourceId check |
|
|
73
|
+
| `HasPermissionOptions` | type | `{ scope?, resourceId? }` |
|
|
74
|
+
| 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 |
|
|
27
75
|
| `DEFAULT_ALIAS` | const | Default service alias `'iam-service'` |
|
|
28
76
|
| `IAM_MODE_KEYCLOAK` | const | `'keycloak'` |
|
|
29
77
|
| `IAM_MODE_INTEGRATED` | const | `'integrated'` |
|
|
30
78
|
| `IamMode` | type | `'keycloak' \| 'integrated'` |
|
|
31
79
|
| `IamError` | class | Base IAM error |
|
|
32
|
-
| `IamClientError` | class |
|
|
33
|
-
| `IamResourceError` | class |
|
|
80
|
+
| `IamClientError` | class | An OIDC client is invalid or unusable — missing `secret`, unusable `redirect_uris`, `client:entity-mismatch`, an unconfigured provider route |
|
|
81
|
+
| `IamResourceError` | class | A provider resource returned with no name |
|
|
34
82
|
| `IamGrantError` | class | Grant subject missing or entity mismatch |
|
|
35
|
-
| `
|
|
83
|
+
| `IamPermissionError` | class | A refusal about a DEFINITION: `permission:held:<name>`, `permission:managed:<name>` |
|
|
84
|
+
| `IamUserError` | class | An end-user operation was refused |
|
|
85
|
+
| `IamUnsupported` | class | Operation not supported by the active backend (e.g. `resource-scoped-grant`, `user-management`) |
|
|
36
86
|
|
|
37
87
|
## Permission model (two grant forms)
|
|
38
88
|
|
|
@@ -46,57 +96,120 @@ subjects with `grantPermission`:
|
|
|
46
96
|
Grants materialize as OwlMeans `PermissionSet[]` (`scope` = clientId; resource-scoped grants live in a
|
|
47
97
|
dedicated set per permission carrying `resources[]`, because `resources` applies to all keys of a set).
|
|
48
98
|
|
|
99
|
+
A **name** is `<resource>--<action>`, with two hyphens. One hyphen is not a separator: `enquiry-view`
|
|
100
|
+
is a resource named `enquiry-view` with no action, and it must keep parsing that way, because a name
|
|
101
|
+
already granted somewhere cannot be reinterpreted without orphaning the grant. Build and read names
|
|
102
|
+
through `composePermissionName` / `parsePermissionName` rather than splitting strings.
|
|
103
|
+
|
|
104
|
+
`resourceScoped: true` says a permission *may* be bound to resource ids, not that it must be. The
|
|
105
|
+
same name is grantable in both forms and they are stored separately, which is what `IamGrantMode`
|
|
106
|
+
addresses: `Blanket` is the set carrying no `resources` (and `hasPermission` reads it as covering
|
|
107
|
+
every id), `Resources` is an explicit id list, and `All` is revoke-only. The defaults are asymmetric
|
|
108
|
+
on purpose — `grantPermission` defaults to `Blanket`, `revokePermission` to `All`, because a bare
|
|
109
|
+
revoke has always meant "remove it everywhere". Pass `mode` explicitly rather than relying on either.
|
|
110
|
+
|
|
111
|
+
`area` is a presentational grouping tag (`IAM_AREAS`, or any string a deployment defines) and decides
|
|
112
|
+
nothing at request time. `managed: true` marks a definition the platform owns: an operator cannot
|
|
113
|
+
revoke it from an undifferentiated list, and a repair that deletes "definitions no declaration names"
|
|
114
|
+
must not reach one.
|
|
115
|
+
|
|
49
116
|
## `IamService` interface
|
|
50
117
|
|
|
51
118
|
```ts
|
|
52
119
|
interface IamService extends InitializedService {
|
|
53
|
-
//
|
|
120
|
+
// The tenant's admin OIDC provider config, and a raw admin { token, realm } pair.
|
|
54
121
|
getEntityAdminConfig: (entityId: string) => Promise<OidcProviderConfig>
|
|
55
|
-
|
|
56
|
-
// Get an admin { token, realm } pair for raw low-level calls (avoid unless necessary)
|
|
57
122
|
getCredentialsPair: (entityId: string) => Promise<IamCredentialsPair>
|
|
58
123
|
|
|
59
|
-
// The tenant's public
|
|
60
|
-
// Config-only (no remote call); throws IamClientError when the provider route is unconfigured.
|
|
124
|
+
// The tenant's public issuer URL — see "Issuer & redirect URIs".
|
|
61
125
|
getIssuerUrl: (entityId: string) => Promise<string>
|
|
62
126
|
|
|
63
|
-
// Provision an OIDC client for a tenant
|
|
64
|
-
//
|
|
127
|
+
// Provision an OIDC client for a tenant — see "Issuer & redirect URIs" and
|
|
128
|
+
// "A client id is a global name" for the two rules it has to obey.
|
|
65
129
|
ensureClient: (entityId: string, clientId: string, options?: IamClientOptions) => Promise<IamClient>
|
|
66
130
|
|
|
67
|
-
//
|
|
131
|
+
// Reserve a free client id without provisioning it; false when anyone else holds it.
|
|
132
|
+
claimClient: (entityId: string, clientId: string) => Promise<boolean>
|
|
133
|
+
|
|
134
|
+
// Release a client id and everything keyed by it (called when a project/slot is deleted).
|
|
135
|
+
deleteClient: (entityId: string, clientId: string) => Promise<void>
|
|
136
|
+
|
|
137
|
+
// Declare a permission. Returns the canonical name. MERGES into an existing definition —
|
|
138
|
+
// resource/action re-derived, omitted flags kept — and NEVER rewrites the name. Refuses a
|
|
139
|
+
// `resource` carrying an `@` selector.
|
|
68
140
|
ensurePermission: (entityId: string, clientId: string, resource?: string, args?: IamPermissionArgs) => Promise<string>
|
|
69
141
|
|
|
70
|
-
//
|
|
142
|
+
// Remove definitions BY NAME, and by default their grants. Idempotent ({ found: false } when
|
|
143
|
+
// absent). Grants go first: a crash between the stores must leave a definition with no grants,
|
|
144
|
+
// never grants with no definition. A `managed` definition needs args.managed === true.
|
|
145
|
+
deletePermission: (entityId: string, clientId: string, permission: string | string[], args?: IamPermissionDeleteArgs) => Promise<IamPermissionRemoval[]>
|
|
146
|
+
|
|
147
|
+
// Repair definitions whose stored name leaked a gate selector, moving grants in the same write.
|
|
148
|
+
// A rename and a merge — never a delete, never an invention.
|
|
149
|
+
normalizePermissions: (entityId: string, clientId: string, args?: IamNormalizeArgs) => Promise<IamNormalizationReport>
|
|
150
|
+
|
|
151
|
+
// Provision a resource and assign it to the tenant's owner role
|
|
71
152
|
ensureResourceOwnership: (entityId: string, clientId: string, resource: IamResourceSpec) => Promise<void>
|
|
72
153
|
|
|
73
154
|
// --- Authorization (permission definitions & grants) ---
|
|
74
155
|
|
|
75
|
-
|
|
76
|
-
listPermissions: (entityId: string, clientId: string) => Promise<IamPermissionDefinition[]>
|
|
156
|
+
listPermissions: (entityId: string, clientId: string, filter?: IamPermissionFilter) => Promise<IamPermissionDefinition[]>
|
|
77
157
|
|
|
78
|
-
//
|
|
158
|
+
// args.resources selects the resource-scoped form; see "Permission model (two grant forms)"
|
|
79
159
|
grantPermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<IamGrant>
|
|
80
|
-
|
|
81
|
-
// Revoke; with args.resources only those ids are removed, else the whole grant
|
|
82
160
|
revokePermission: (entityId: string, clientId: string, profileId: string, permission: string, args?: IamGrantArgs) => Promise<void>
|
|
83
161
|
|
|
84
|
-
//
|
|
162
|
+
// Grant everything a bundle selects to each subject, idempotently, in ONE read and write per
|
|
163
|
+
// subject. Returns what each subject now holds, not what changed. Use it instead of looping over
|
|
164
|
+
// grantPermission, which costs N·M writes and N·M lost-update windows on the same records.
|
|
165
|
+
grantBundle: (entityId: string, clientId: string, profileIds: string[], bundle: IamGrantBundle) => Promise<IamGrant[]>
|
|
166
|
+
|
|
85
167
|
listGrants: (entityId: string, clientId: string, profileId?: string) => Promise<IamGrant[]>
|
|
168
|
+
|
|
169
|
+
// --- End-user management — see "End-users are customer-wide" ---
|
|
170
|
+
|
|
171
|
+
listUsers: (entityId: string, clientId?: string) => Promise<IamUser[]>
|
|
172
|
+
getUser: (entityId: string, profileId: string) => Promise<IamUser | null>
|
|
173
|
+
inviteUser: (entityId: string, invite: IamUserInvite) => Promise<IamUser>
|
|
174
|
+
updateUser: (entityId: string, profileId: string, update: IamUserUpdate) => Promise<IamUser>
|
|
175
|
+
removeUser: (entityId: string, profileId: string) => Promise<void>
|
|
86
176
|
}
|
|
87
177
|
```
|
|
88
178
|
|
|
89
|
-
##
|
|
179
|
+
## End-users are customer-wide
|
|
90
180
|
|
|
91
|
-
|
|
181
|
+
An end-user belongs to the **entity**, not to a project: one customer's users are shared across every
|
|
182
|
+
project they own. `listUsers` therefore returns the entity's whole set and `clientId` only scopes the
|
|
183
|
+
reported `grantCount` to one project's client. Do not reintroduce a per-client filter — a person who
|
|
184
|
+
has authenticated against a project but holds no grant there is still that project's user, and the
|
|
185
|
+
screen that hands out grants is exactly where they have to be visible.
|
|
92
186
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
187
|
+
`inviteUser` is idempotent by email, which is what lets an invitation and a first sign-in converge on
|
|
188
|
+
one record instead of accumulating twins. A backend with no user store of its own — one where the
|
|
189
|
+
customer manages users in the provider's own console — throws `IamUnsupported('user-management')`
|
|
190
|
+
from all five methods.
|
|
96
191
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
192
|
+
## A client id is a global name
|
|
193
|
+
|
|
194
|
+
A provider resolves a client from the bare `client_id` a relying party sends — the adapter gets no
|
|
195
|
+
tenant context — so the id is unique across the whole deployment, not per entity, and the store
|
|
196
|
+
cannot namespace it on a consumer's behalf. Uniqueness has to be *in the id*.
|
|
197
|
+
|
|
198
|
+
- Compose it with the owning entity in it. A name derived from a project's own name alone lets two
|
|
199
|
+
organizations share one registration: one secret, one redirect-URI list, one permission-definition
|
|
200
|
+
set, and one grant namespace, because `PermissionSet.scope` is that same string.
|
|
201
|
+
- `ensureClient` refuses a record belonging to another entity rather than returning it. The failure
|
|
202
|
+
is deliberate — a loud provisioning error is the only alternative to a silent cross-tenant handover.
|
|
203
|
+
- Assign an id **once** and reserve it with `claimClient`. Whatever a consumer derives ids from
|
|
204
|
+
(a project alias, a slug) may later be released and re-used by a sibling, so only the registry can
|
|
205
|
+
say whether an id is free. `deleteClient` gives it back, or a deleted project's name is burned.
|
|
206
|
+
- Backends whose clients are already per-tenant (keycloak realms) satisfy this by construction:
|
|
207
|
+
`claimClient` returns true and `deleteClient` may be `IamUnsupported`.
|
|
208
|
+
|
|
209
|
+
## `IamClient.realm` field
|
|
210
|
+
|
|
211
|
+
`realm` is set by the provider to the entity id, so read `client.realm ?? fallbackEntityId` and never
|
|
212
|
+
reach into an adapter's own internals (`(client as any)._realm`) for the same value.
|
|
100
213
|
|
|
101
214
|
## Issuer & redirect URIs
|
|
102
215
|
|
|
@@ -117,39 +230,63 @@ config is self-sufficient too.
|
|
|
117
230
|
|
|
118
231
|
**Always pass `redirectUris`.** Omitting them is a keycloak-only legacy default (`['*']`, which
|
|
119
232
|
Keycloak expands per-origin). The integrated provider does exact `redirect_uri` matching and
|
|
120
|
-
`oidc-provider` refuses to load a client whose `redirect_uris` are not absolute URIs — so
|
|
121
|
-
`
|
|
233
|
+
`oidc-provider` refuses to load a client whose `redirect_uris` are not absolute URIs — so an
|
|
234
|
+
integrated backend's `ensureClient` throws `IamClientError('redirect-uris')` on creation rather than
|
|
122
235
|
registering a client that can never complete a callback. An omitted list never widens an existing
|
|
123
236
|
hardened client.
|
|
124
237
|
|
|
125
|
-
## Selecting the IAM
|
|
238
|
+
## Selecting the IAM backend
|
|
126
239
|
|
|
127
|
-
The
|
|
240
|
+
The choice is made **once, in a service factory**, and nowhere else. Feature code depends on
|
|
241
|
+
`IamService` and never learns which implementation answers it. `IAM_MODE_INTEGRATED` /
|
|
242
|
+
`IAM_MODE_KEYCLOAK` and `OidcIamConfig.iamMode` (`@owlmeans/oidc`) exist so a deployment can carry
|
|
243
|
+
that choice as ordinary configuration rather than as a branch scattered through handlers. No OwlMeans
|
|
244
|
+
package reads `iamMode`; it is declared for the consumer's own factory to switch on, which is the
|
|
245
|
+
only thing that may branch.
|
|
128
246
|
|
|
129
247
|
```ts
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
248
|
+
import { DEFAULT_ALIAS, IAM_MODE_INTEGRATED, IAM_MODE_KEYCLOAK } from '@owlmeans/iam'
|
|
249
|
+
import type { IamMode, IamService } from '@owlmeans/iam'
|
|
250
|
+
|
|
251
|
+
// makeKeycloakAdapter / makeIntegratedAdapter are PLACEHOLDER names standing in for whichever
|
|
252
|
+
// adapter packages the deployment installs — this factory is the one place either is named, and
|
|
253
|
+
// every consumer resolves IamService by alias instead. An adapter exposes a
|
|
254
|
+
// `(alias?: string, options?: object) => IamService` factory whose alias defaults to DEFAULT_ALIAS.
|
|
255
|
+
export const makeIamService = (mode: IamMode = IAM_MODE_INTEGRATED): IamService =>
|
|
256
|
+
mode === IAM_MODE_KEYCLOAK
|
|
257
|
+
? makeKeycloakAdapter(DEFAULT_ALIAS, /* adapter options */)
|
|
258
|
+
: makeIntegratedAdapter(DEFAULT_ALIAS, /* adapter options */)
|
|
259
|
+
|
|
260
|
+
// cfg.oidc typed as OidcSharedConfig & OidcIamConfig is what carries the mode
|
|
261
|
+
context.registerService(makeIamService(cfg.oidc?.iamMode))
|
|
138
262
|
```
|
|
139
263
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
264
|
+
An adapter that reassembles the issuer from a registered service alias plus a base path must be given
|
|
265
|
+
the **consumer's own** alias and path. Whatever an adapter defaults to is a placeholder, and a
|
|
266
|
+
mismatch makes every service lookup behind `getIssuerUrl` miss.
|
|
143
267
|
|
|
144
|
-
|
|
268
|
+
An integrated backend — one built on `@owlmeans/server-oidc-provider`, so the deployment hosts its
|
|
269
|
+
own provider — is the default choice; a Keycloak-backed adapter exists for deployments that already
|
|
270
|
+
run one.
|
|
145
271
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
- `IamClientError` means the provisioned client was invalid (missing secret, null token). The proxy already throws it; callers only need to handle it, not re-throw wrapped errors.
|
|
149
|
-
- The `realm` field on `IamClient` is guaranteed to be set by all concrete implementations.
|
|
272
|
+
When a context hosts **more than one** `IamService` instance, every instance must be constructed with
|
|
273
|
+
the same options — otherwise they disagree about the issuer, and only one of them can be right.
|
|
150
274
|
|
|
151
|
-
##
|
|
275
|
+
## Rules
|
|
152
276
|
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
277
|
+
- Always depend on `IamService`, never on a backend's own admin client.
|
|
278
|
+
- Do not expose `getCredentialsPair` to request handlers — it returns a raw admin token.
|
|
279
|
+
- `IamClientError` means the provisioned client was invalid (missing secret, null token). The adapter
|
|
280
|
+
already throws it; callers handle it rather than re-throwing a wrapped error.
|
|
281
|
+
- `IamClient.realm` is set by every concrete implementation.
|
|
282
|
+
- An operation a backend cannot support throws `IamUnsupported('<what>')`. Callers turn that into a
|
|
283
|
+
fallback — an "external console" link, a disabled control — rather than an error screen.
|
|
284
|
+
|
|
285
|
+
## Related
|
|
286
|
+
|
|
287
|
+
- `@owlmeans/server-iam` — the IAM gate that asserts both grant forms, and the gate-param grammar;
|
|
288
|
+
see the `server-iam` skill
|
|
289
|
+
- `@owlmeans/oidc` — provider descriptors and the `iamMode` config seam; see the `oidc` skill
|
|
290
|
+
- The concrete adapters (a Keycloak proxy, and the integrated backend built on
|
|
291
|
+
`@owlmeans/server-oidc-provider` plus `@owlmeans/server-auth-identity`) ship separately and are
|
|
292
|
+
reached only through `IamService`.
|
package/build/consts.d.ts
CHANGED
|
@@ -2,4 +2,95 @@ export declare const DEFAULT_ALIAS = "iam-service";
|
|
|
2
2
|
export declare const IAM_MODE_KEYCLOAK = "keycloak";
|
|
3
3
|
export declare const IAM_MODE_INTEGRATED = "integrated";
|
|
4
4
|
export type IamMode = typeof IAM_MODE_KEYCLOAK | typeof IAM_MODE_INTEGRATED;
|
|
5
|
+
/**
|
|
6
|
+
* Gate-param syntax: `<permission>[@<selector>]`.
|
|
7
|
+
*
|
|
8
|
+
* The `@` belongs to the GATE and to nothing else. It says where a resource id is read from at
|
|
9
|
+
* request time; it is never part of a permission's stored NAME. A definition or a grant whose name
|
|
10
|
+
* carries one is a key nothing ever looks up, so every grant against it is a silent no-op.
|
|
11
|
+
*/
|
|
12
|
+
export declare const RESOURCE_PARAM_SEPARATOR = "@";
|
|
13
|
+
/**
|
|
14
|
+
* Separates an explicit source from its path: `@body:order.id`.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately `:` rather than a dot. Under a dotted-only form the meaning of a selector changes the
|
|
17
|
+
* moment a route gains a param named like a source keyword — `/report/:query` with `@query.id` flips
|
|
18
|
+
* from "the route param literally named `query.id`" to "the query string's `id`", redirecting an
|
|
19
|
+
* authorization lookup from a router-matched value to attacker-supplied input, caused by an
|
|
20
|
+
* unrelated route rename. `:` is decidable in one character, and no gate-param string written before
|
|
21
|
+
* this existed can contain one.
|
|
22
|
+
*/
|
|
23
|
+
export declare const RESOURCE_SOURCE_SEPARATOR = ":";
|
|
24
|
+
/** Separates path segments inside a qualified selector: `@body:order.id`. */
|
|
25
|
+
export declare const RESOURCE_PATH_SEPARATOR = ".";
|
|
26
|
+
/** Where a qualified selector may read a resource id from. */
|
|
27
|
+
export declare enum GateParamSource {
|
|
28
|
+
Params = "params",
|
|
29
|
+
Query = "query",
|
|
30
|
+
Body = "body",
|
|
31
|
+
Headers = "headers",
|
|
32
|
+
Auth = "auth"
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* What the bare form (`@enquiryId`) searches, in order.
|
|
36
|
+
*
|
|
37
|
+
* Params first, then query. The query fallback is legacy and deliberately kept: removing it would
|
|
38
|
+
* silently deny already-deployed applications whose selector happens to name a query key, and a
|
|
39
|
+
* silent denial inside a customer's running app is the worst available outcome.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_GATE_PARAM_SOURCES: GateParamSource[];
|
|
42
|
+
/** Why a gate param is structurally wrong — a misconfiguration, not a failed authorization. */
|
|
43
|
+
export declare enum GateParamErrorCode {
|
|
44
|
+
UnknownSource = "gate-param:unknown-source",
|
|
45
|
+
EmptySelector = "gate-param:empty-selector",
|
|
46
|
+
EmptySegment = "gate-param:empty-segment",
|
|
47
|
+
/** Named a request key the entrypoint's filter does not declare — AJV strips it before the gate. */
|
|
48
|
+
UnreachableKey = "gate-param:unreachable-key",
|
|
49
|
+
/** Named a route param the entrypoint's own path does not carry. */
|
|
50
|
+
UnknownRouteParam = "gate-param:unknown-route-param"
|
|
51
|
+
}
|
|
52
|
+
/** Why a selector produced no resource id. */
|
|
53
|
+
export declare enum GateResolutionFailure {
|
|
54
|
+
/** The source exists, the path does not. */
|
|
55
|
+
NotProvided = "not-provided",
|
|
56
|
+
/** The request carries no such source at all. */
|
|
57
|
+
SourceMissing = "source-missing",
|
|
58
|
+
/** Resolved to an object or a multi-element array — refused rather than stringified. */
|
|
59
|
+
NotScalar = "not-scalar",
|
|
60
|
+
/** The selector itself did not parse. */
|
|
61
|
+
Malformed = "malformed"
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Which FORM of a grant an operation addresses.
|
|
65
|
+
*
|
|
66
|
+
* A resource-scoped permission is grantable two ways, under one name: `Blanket` (a PermissionSet
|
|
67
|
+
* carrying no `resources`, which `hasPermission` reads as covering every id) and `Resources` (an
|
|
68
|
+
* explicit id list). They are stored separately and must be revoked separately, or a UI offering
|
|
69
|
+
* both controls would have one silently wipe the other.
|
|
70
|
+
*/
|
|
71
|
+
export declare enum IamGrantMode {
|
|
72
|
+
Blanket = "blanket",
|
|
73
|
+
Resources = "resources",
|
|
74
|
+
/** Revoke-only: every form. Illegal on a grant. */
|
|
75
|
+
All = "all"
|
|
76
|
+
}
|
|
77
|
+
/** What to do about grants that still reference a definition being removed. */
|
|
78
|
+
export declare enum IamRemovalPolicy {
|
|
79
|
+
/** Strip the permission from every subject of the entity, then drop the definition. */
|
|
80
|
+
Cascade = "cascade",
|
|
81
|
+
/** Refuse while any subject still holds it; the holders travel in the error. */
|
|
82
|
+
Refuse = "refuse"
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The grouping tiers a permission definition may be tagged with.
|
|
86
|
+
*
|
|
87
|
+
* A tag decides NOTHING at request time — `hasPermission` and every gate treat a permission name as
|
|
88
|
+
* one opaque string — and only groups the permission in an administrator's screen. `guest` is
|
|
89
|
+
* deliberately absent: a public surface declares no permissions, and adding an area later would
|
|
90
|
+
* re-classify names already tagged.
|
|
91
|
+
*/
|
|
92
|
+
export declare const IAM_AREAS: readonly ['user', 'operator', 'admin'];
|
|
93
|
+
export type IamArea = typeof IAM_AREAS[number];
|
|
94
|
+
/** Separates the resource from the action in a permission name. TWO hyphens, never one. */
|
|
95
|
+
export declare const PERMISSION_ACTION_SEPARATOR = "--";
|
|
5
96
|
//# 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"}
|
|
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"}
|
package/build/consts.js
CHANGED
|
@@ -1,4 +1,101 @@
|
|
|
1
1
|
export const DEFAULT_ALIAS = 'iam-service';
|
|
2
2
|
export const IAM_MODE_KEYCLOAK = 'keycloak';
|
|
3
3
|
export const IAM_MODE_INTEGRATED = 'integrated';
|
|
4
|
+
/**
|
|
5
|
+
* Gate-param syntax: `<permission>[@<selector>]`.
|
|
6
|
+
*
|
|
7
|
+
* The `@` belongs to the GATE and to nothing else. It says where a resource id is read from at
|
|
8
|
+
* request time; it is never part of a permission's stored NAME. A definition or a grant whose name
|
|
9
|
+
* carries one is a key nothing ever looks up, so every grant against it is a silent no-op.
|
|
10
|
+
*/
|
|
11
|
+
export const RESOURCE_PARAM_SEPARATOR = '@';
|
|
12
|
+
/**
|
|
13
|
+
* Separates an explicit source from its path: `@body:order.id`.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately `:` rather than a dot. Under a dotted-only form the meaning of a selector changes the
|
|
16
|
+
* moment a route gains a param named like a source keyword — `/report/:query` with `@query.id` flips
|
|
17
|
+
* from "the route param literally named `query.id`" to "the query string's `id`", redirecting an
|
|
18
|
+
* authorization lookup from a router-matched value to attacker-supplied input, caused by an
|
|
19
|
+
* unrelated route rename. `:` is decidable in one character, and no gate-param string written before
|
|
20
|
+
* this existed can contain one.
|
|
21
|
+
*/
|
|
22
|
+
export const RESOURCE_SOURCE_SEPARATOR = ':';
|
|
23
|
+
/** Separates path segments inside a qualified selector: `@body:order.id`. */
|
|
24
|
+
export const RESOURCE_PATH_SEPARATOR = '.';
|
|
25
|
+
/** Where a qualified selector may read a resource id from. */
|
|
26
|
+
export var GateParamSource;
|
|
27
|
+
(function (GateParamSource) {
|
|
28
|
+
GateParamSource["Params"] = "params";
|
|
29
|
+
GateParamSource["Query"] = "query";
|
|
30
|
+
GateParamSource["Body"] = "body";
|
|
31
|
+
GateParamSource["Headers"] = "headers";
|
|
32
|
+
GateParamSource["Auth"] = "auth";
|
|
33
|
+
})(GateParamSource || (GateParamSource = {}));
|
|
34
|
+
/**
|
|
35
|
+
* What the bare form (`@enquiryId`) searches, in order.
|
|
36
|
+
*
|
|
37
|
+
* Params first, then query. The query fallback is legacy and deliberately kept: removing it would
|
|
38
|
+
* silently deny already-deployed applications whose selector happens to name a query key, and a
|
|
39
|
+
* silent denial inside a customer's running app is the worst available outcome.
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_GATE_PARAM_SOURCES = [
|
|
42
|
+
GateParamSource.Params, GateParamSource.Query
|
|
43
|
+
];
|
|
44
|
+
/** Why a gate param is structurally wrong — a misconfiguration, not a failed authorization. */
|
|
45
|
+
export var GateParamErrorCode;
|
|
46
|
+
(function (GateParamErrorCode) {
|
|
47
|
+
GateParamErrorCode["UnknownSource"] = "gate-param:unknown-source";
|
|
48
|
+
GateParamErrorCode["EmptySelector"] = "gate-param:empty-selector";
|
|
49
|
+
GateParamErrorCode["EmptySegment"] = "gate-param:empty-segment";
|
|
50
|
+
/** Named a request key the entrypoint's filter does not declare — AJV strips it before the gate. */
|
|
51
|
+
GateParamErrorCode["UnreachableKey"] = "gate-param:unreachable-key";
|
|
52
|
+
/** Named a route param the entrypoint's own path does not carry. */
|
|
53
|
+
GateParamErrorCode["UnknownRouteParam"] = "gate-param:unknown-route-param";
|
|
54
|
+
})(GateParamErrorCode || (GateParamErrorCode = {}));
|
|
55
|
+
/** Why a selector produced no resource id. */
|
|
56
|
+
export var GateResolutionFailure;
|
|
57
|
+
(function (GateResolutionFailure) {
|
|
58
|
+
/** The source exists, the path does not. */
|
|
59
|
+
GateResolutionFailure["NotProvided"] = "not-provided";
|
|
60
|
+
/** The request carries no such source at all. */
|
|
61
|
+
GateResolutionFailure["SourceMissing"] = "source-missing";
|
|
62
|
+
/** Resolved to an object or a multi-element array — refused rather than stringified. */
|
|
63
|
+
GateResolutionFailure["NotScalar"] = "not-scalar";
|
|
64
|
+
/** The selector itself did not parse. */
|
|
65
|
+
GateResolutionFailure["Malformed"] = "malformed";
|
|
66
|
+
})(GateResolutionFailure || (GateResolutionFailure = {}));
|
|
67
|
+
/**
|
|
68
|
+
* Which FORM of a grant an operation addresses.
|
|
69
|
+
*
|
|
70
|
+
* A resource-scoped permission is grantable two ways, under one name: `Blanket` (a PermissionSet
|
|
71
|
+
* carrying no `resources`, which `hasPermission` reads as covering every id) and `Resources` (an
|
|
72
|
+
* explicit id list). They are stored separately and must be revoked separately, or a UI offering
|
|
73
|
+
* both controls would have one silently wipe the other.
|
|
74
|
+
*/
|
|
75
|
+
export var IamGrantMode;
|
|
76
|
+
(function (IamGrantMode) {
|
|
77
|
+
IamGrantMode["Blanket"] = "blanket";
|
|
78
|
+
IamGrantMode["Resources"] = "resources";
|
|
79
|
+
/** Revoke-only: every form. Illegal on a grant. */
|
|
80
|
+
IamGrantMode["All"] = "all";
|
|
81
|
+
})(IamGrantMode || (IamGrantMode = {}));
|
|
82
|
+
/** What to do about grants that still reference a definition being removed. */
|
|
83
|
+
export var IamRemovalPolicy;
|
|
84
|
+
(function (IamRemovalPolicy) {
|
|
85
|
+
/** Strip the permission from every subject of the entity, then drop the definition. */
|
|
86
|
+
IamRemovalPolicy["Cascade"] = "cascade";
|
|
87
|
+
/** Refuse while any subject still holds it; the holders travel in the error. */
|
|
88
|
+
IamRemovalPolicy["Refuse"] = "refuse";
|
|
89
|
+
})(IamRemovalPolicy || (IamRemovalPolicy = {}));
|
|
90
|
+
/**
|
|
91
|
+
* The grouping tiers a permission definition may be tagged with.
|
|
92
|
+
*
|
|
93
|
+
* A tag decides NOTHING at request time — `hasPermission` and every gate treat a permission name as
|
|
94
|
+
* one opaque string — and only groups the permission in an administrator's screen. `guest` is
|
|
95
|
+
* deliberately absent: a public surface declares no permissions, and adding an area later would
|
|
96
|
+
* re-classify names already tagged.
|
|
97
|
+
*/
|
|
98
|
+
export const IAM_AREAS = ['user', 'operator', 'admin'];
|
|
99
|
+
/** Separates the resource from the action in a permission name. TWO hyphens, never one. */
|
|
100
|
+
export const PERMISSION_ACTION_SEPARATOR = '--';
|
|
4
101
|
//# 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"}
|
|
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"}
|
package/build/errors.d.ts
CHANGED
|
@@ -15,6 +15,19 @@ export declare class IamGrantError extends IamError {
|
|
|
15
15
|
static typeName: string;
|
|
16
16
|
constructor(message?: string);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A refusal about a permission DEFINITION, as opposed to a grant.
|
|
20
|
+
*
|
|
21
|
+
* Separate from `IamGrantError`, which means a grant's subject is missing or its entity does not
|
|
22
|
+
* match — reusing it for a definition-level refusal is a category error the next reader has to unpick.
|
|
23
|
+
*
|
|
24
|
+
* Codes: `permission:held:<name>` (still granted to someone, under a policy that refuses),
|
|
25
|
+
* `permission:managed:<name>` (platform-owned, deleted only with an explicit second key).
|
|
26
|
+
*/
|
|
27
|
+
export declare class IamPermissionError extends IamError {
|
|
28
|
+
static typeName: string;
|
|
29
|
+
constructor(message?: string);
|
|
30
|
+
}
|
|
18
31
|
export declare class IamUnsupported extends IamError {
|
|
19
32
|
static typeName: string;
|
|
20
33
|
constructor(message?: string);
|
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,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"}
|