@owlmeans/iam 0.1.11 → 0.1.15
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/agent-meta/instructions/iam.instructions.md +39 -3
- package/agent-meta/manifest.json +2 -2
- package/build/types.d.ts +22 -4
- package/build/types.d.ts.map +1 -1
- package/package.json +8 -9
- package/src/types.ts +23 -4
|
@@ -14,7 +14,7 @@ Provider-agnostic IAM abstraction. Defines `IamService` interface and related ty
|
|
|
14
14
|
|--------|------|---------|
|
|
15
15
|
| `IamService` | type | Unified IAM interface — all provisioning + authorization operations |
|
|
16
16
|
| `IamClient` | type | Provisioned OIDC client `{ id?, clientId, secret?, name?, realm? }` |
|
|
17
|
-
| `IamClientOptions` | type | `{ redirectUris? }` —
|
|
17
|
+
| `IamClientOptions` | type | `{ redirectUris? }` — explicit `ensureClient` hardening; omitting is a keycloak-only legacy shape (see below) |
|
|
18
18
|
| `IamCredentialsPair` | type | `{ token: string; realm: string }` |
|
|
19
19
|
| `IamPermissionArgs` | type | `{ permission?, resourceScoped?, title? }` — `permission` absent means unscoped resource name |
|
|
20
20
|
| `IamResourceSpec` | type | `{ name: string; displayName?: string }` |
|
|
@@ -54,8 +54,12 @@ interface IamService extends InitializedService {
|
|
|
54
54
|
// Get an admin { token, realm } pair for raw low-level calls (avoid unless necessary)
|
|
55
55
|
getCredentialsPair: (entityId: string) => Promise<IamCredentialsPair>
|
|
56
56
|
|
|
57
|
+
// The tenant's public, fully-qualified OIDC issuer URL — the single value a relying party needs.
|
|
58
|
+
// Config-only (no remote call); throws IamClientError when the provider route is unconfigured.
|
|
59
|
+
getIssuerUrl: (entityId: string) => Promise<string>
|
|
60
|
+
|
|
57
61
|
// Provision an OIDC client for a tenant and return its credentials.
|
|
58
|
-
// options.redirectUris hardens the client (
|
|
62
|
+
// options.redirectUris hardens the client; always pass them (see "Issuer & redirect URIs").
|
|
59
63
|
ensureClient: (entityId: string, clientId: string, options?: IamClientOptions) => Promise<IamClient>
|
|
60
64
|
|
|
61
65
|
// Provision a named permission on a client resource; returns the resource name
|
|
@@ -92,6 +96,30 @@ await meta.update('oidcRealm', client.realm ?? fallbackEntityId)
|
|
|
92
96
|
await meta.update('oidcRealm', (client as any)._realm)
|
|
93
97
|
```
|
|
94
98
|
|
|
99
|
+
## Issuer & redirect URIs
|
|
100
|
+
|
|
101
|
+
**`getIssuerUrl(entityId)` is the only place an issuer URL may be composed.** Consumers pass the
|
|
102
|
+
result straight through as `OidcProviderDescriptor.discoveryUrl`; they must never rebuild it from a
|
|
103
|
+
host plus a base path. Each backend owns its own shape (keycloak `{iam-host}/realms/{entityId}`,
|
|
104
|
+
integrated `{provider-host}/{basePath}`), and an implementation must:
|
|
105
|
+
|
|
106
|
+
- resolve it from configuration only — no admin round-trip, so a consumer that just needs the issuer
|
|
107
|
+
never pays for or fails on a token grant;
|
|
108
|
+
- return exactly what the provider advertises as `issuer` (`openid-client` compares the two and fails
|
|
109
|
+
discovery on any difference — build it with the same `makeUrl(..., { base: true })` call
|
|
110
|
+
`@owlmeans/server-oidc-provider` uses);
|
|
111
|
+
- throw `IamClientError` when the provider's service route is missing, never fall back silently.
|
|
112
|
+
|
|
113
|
+
`getEntityAdminConfig` additionally carries the same value as `discoveryUrl`, so the admin provider
|
|
114
|
+
config is self-sufficient too.
|
|
115
|
+
|
|
116
|
+
**Always pass `redirectUris`.** Omitting them is a keycloak-only legacy default (`['*']`, which
|
|
117
|
+
Keycloak expands per-origin). The integrated provider does exact `redirect_uri` matching and
|
|
118
|
+
`oidc-provider` refuses to load a client whose `redirect_uris` are not absolute URIs — so
|
|
119
|
+
`iam-integrated.ensureClient` throws `IamClientError('redirect-uris')` on creation rather than
|
|
120
|
+
registering a client that can never complete a callback. An omitted list never widens an existing
|
|
121
|
+
hardened client.
|
|
122
|
+
|
|
95
123
|
## Selecting the IAM provider
|
|
96
124
|
|
|
97
125
|
The platform wires the correct implementation via the `IAM_MODE` environment variable. The default is **`integrated`** (the internal IAM); `IAM_MODE=keycloak` is opt-in, used mainly for custom / standalone customer production setups. Do not read `IAM_MODE` directly from feature code — it belongs in the service-factory only (the platform exposes `resolveIamMode()` / `isIntegratedIam()` from `viable-common`):
|
|
@@ -99,10 +127,18 @@ The platform wires the correct implementation via the `IAM_MODE` environment var
|
|
|
99
127
|
```ts
|
|
100
128
|
export const makeIamService = (mode = resolveIamMode()) => {
|
|
101
129
|
if (mode === IAM_MODE_KEYCLOAK) return makeIamKeycloakService(VIABLE_IAM, { oidcProductAlias: OIDC_PRODUCT })
|
|
102
|
-
|
|
130
|
+
// Pass the consumer's OWN service alias + base path: the package defaults ('manager-api',
|
|
131
|
+
// '/oidc') are placeholders, and a mismatch makes every cfg.services[...] lookup miss.
|
|
132
|
+
return makeIamIntegratedService(VIABLE_IAM, {
|
|
133
|
+
providerServiceAlias: MANAGER_API, providerBasePath: IAM_PROVIDER_BASE_PATH,
|
|
134
|
+
})
|
|
103
135
|
}
|
|
104
136
|
```
|
|
105
137
|
|
|
138
|
+
When a context hosts **more than one** `IamService` instance (the viable agent registers both
|
|
139
|
+
`viable-backend`'s and the agent library's), every instance must be constructed with the same
|
|
140
|
+
options — otherwise they disagree about the issuer.
|
|
141
|
+
|
|
106
142
|
## Rules
|
|
107
143
|
|
|
108
144
|
- Always depend on `IamService` from `@owlmeans/iam`, not on `KeycloakApiService` directly.
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": "@owlmeans/iam",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.15",
|
|
5
|
+
"generatedAt": "2026-08-07T17:06:32.920Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
package/build/types.d.ts
CHANGED
|
@@ -14,10 +14,15 @@ export interface IamCredentialsPair {
|
|
|
14
14
|
}
|
|
15
15
|
export interface IamClientOptions {
|
|
16
16
|
/**
|
|
17
|
-
* Explicit allowed redirect URIs for the client.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Explicit allowed redirect URIs for the client. Pass the concrete callbacks a deployment
|
|
18
|
+
* may return to — the generated host, an attached custom domain, and owner-registered
|
|
19
|
+
* self-host origins.
|
|
20
|
+
*
|
|
21
|
+
* Omitting them is a **legacy keycloak-only** shape: Keycloak accepts a `*` wildcard, while
|
|
22
|
+
* the integrated provider does exact `redirect_uri` matching and rejects a wildcard outright
|
|
23
|
+
* (`oidc-provider` refuses a client whose `redirect_uris` are not absolute URIs). Backends
|
|
24
|
+
* that cannot honour a wildcard MUST throw `IamClientError` instead of registering an
|
|
25
|
+
* unusable client.
|
|
21
26
|
*/
|
|
22
27
|
redirectUris?: string[];
|
|
23
28
|
}
|
|
@@ -86,6 +91,19 @@ export interface IamUserUpdate {
|
|
|
86
91
|
export interface IamService extends InitializedService {
|
|
87
92
|
getEntityAdminConfig: (entityId: string) => Promise<OidcProviderConfig>;
|
|
88
93
|
getCredentialsPair: (entityId: string) => Promise<IamCredentialsPair>;
|
|
94
|
+
/**
|
|
95
|
+
* The public, fully-qualified OIDC **issuer** URL a relying party of this entity must use for
|
|
96
|
+
* discovery — the single value a consumer needs (`OidcProviderDescriptor.discoveryUrl`). Each
|
|
97
|
+
* backend owns its own URL shape: keycloak `https://{iam-host}/realms/{entityId}`, integrated
|
|
98
|
+
* `https://{provider-host}/{basePath}`. Nothing outside an adapter may reassemble it.
|
|
99
|
+
*
|
|
100
|
+
* The returned string MUST equal, byte for byte, what the provider advertises as `issuer` in
|
|
101
|
+
* its discovery document: `openid-client` compares the two and fails the whole discovery when
|
|
102
|
+
* they differ. Implementations resolve it from configuration only — no remote admin call — and
|
|
103
|
+
* throw `IamClientError` when the provider's service route is not configured, so a
|
|
104
|
+
* misconfiguration is loud instead of yielding a silently wrong issuer.
|
|
105
|
+
*/
|
|
106
|
+
getIssuerUrl: (entityId: string) => Promise<string>;
|
|
89
107
|
ensureClient: (entityId: string, clientId: string, options?: IamClientOptions) => Promise<IamClient>;
|
|
90
108
|
/**
|
|
91
109
|
* Ensures a permission/resource exists in the entity's client.
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAExD,MAAM,WAAW,SAAS;IACxB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAExD,MAAM,WAAW,SAAS;IACxB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qFAAqF;IACrF,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,uBAAuB;IACtC,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,QAAQ;IACvB,kGAAkG;IAClG,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,sFAAsF;AACtF,MAAM,WAAW,OAAO;IACtB,kGAAkG;IAClG,SAAS,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iGAAiG;IACjG,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,yGAAyG;AACzG,MAAM,WAAW,UAAW,SAAQ,kBAAkB;IAEpD,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACvE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAErE;;;;;;;;;;;OAWG;IACH,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAGnD,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,SAAS,CAAC,CAAA;IAEpG;;;OAGG;IACH,gBAAgB,EAAE,CAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,iBAAiB,KACrB,OAAO,CAAC,MAAM,CAAC,CAAA;IAEpB;;;OAGG;IACH,uBAAuB,EAAE,CACvB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,eAAe,KACtB,OAAO,CAAC,IAAI,CAAC,CAAA;IAIlB,uEAAuE;IACvE,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAA;IAE3F;;;OAGG;IACH,eAAe,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,YAAY,KAChB,OAAO,CAAC,QAAQ,CAAC,CAAA;IAEtB;;;OAGG;IACH,gBAAgB,EAAE,CAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,YAAY,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;IAElB,6EAA6E;IAC7E,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;IAI3F;;;OAGG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAEtE,kEAAkE;IAClE,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IAEzE,iFAAiF;IACjF,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAEzE,mEAAmE;IACnE,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAE5F,uEAAuE;IACvE,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACnE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/iam",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -22,17 +22,16 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@owlmeans/auth": "^0.1.
|
|
26
|
-
"@owlmeans/context": "^0.1.
|
|
27
|
-
"@owlmeans/error": "^0.1.
|
|
28
|
-
"@owlmeans/oidc": "^0.1.
|
|
25
|
+
"@owlmeans/auth": "^0.1.15",
|
|
26
|
+
"@owlmeans/context": "^0.1.15",
|
|
27
|
+
"@owlmeans/error": "^0.1.15",
|
|
28
|
+
"@owlmeans/oidc": "^0.1.15"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@owlmeans/dep-config": "workspace:*",
|
|
32
|
-
"@types/bun": "^1.3.
|
|
33
|
-
"nodemon": "^3.1.
|
|
34
|
-
"
|
|
35
|
-
"typescript": "^6.0.2"
|
|
32
|
+
"@types/bun": "^1.3.14",
|
|
33
|
+
"nodemon": "^3.1.14",
|
|
34
|
+
"typescript": "^6.0.3"
|
|
36
35
|
},
|
|
37
36
|
"publishConfig": {
|
|
38
37
|
"access": "public"
|
package/src/types.ts
CHANGED
|
@@ -17,10 +17,15 @@ export interface IamCredentialsPair {
|
|
|
17
17
|
|
|
18
18
|
export interface IamClientOptions {
|
|
19
19
|
/**
|
|
20
|
-
* Explicit allowed redirect URIs for the client.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* Explicit allowed redirect URIs for the client. Pass the concrete callbacks a deployment
|
|
21
|
+
* may return to — the generated host, an attached custom domain, and owner-registered
|
|
22
|
+
* self-host origins.
|
|
23
|
+
*
|
|
24
|
+
* Omitting them is a **legacy keycloak-only** shape: Keycloak accepts a `*` wildcard, while
|
|
25
|
+
* the integrated provider does exact `redirect_uri` matching and rejects a wildcard outright
|
|
26
|
+
* (`oidc-provider` refuses a client whose `redirect_uris` are not absolute URIs). Backends
|
|
27
|
+
* that cannot honour a wildcard MUST throw `IamClientError` instead of registering an
|
|
28
|
+
* unusable client.
|
|
24
29
|
*/
|
|
25
30
|
redirectUris?: string[]
|
|
26
31
|
}
|
|
@@ -100,6 +105,20 @@ export interface IamService extends InitializedService {
|
|
|
100
105
|
getEntityAdminConfig: (entityId: string) => Promise<OidcProviderConfig>
|
|
101
106
|
getCredentialsPair: (entityId: string) => Promise<IamCredentialsPair>
|
|
102
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The public, fully-qualified OIDC **issuer** URL a relying party of this entity must use for
|
|
110
|
+
* discovery — the single value a consumer needs (`OidcProviderDescriptor.discoveryUrl`). Each
|
|
111
|
+
* backend owns its own URL shape: keycloak `https://{iam-host}/realms/{entityId}`, integrated
|
|
112
|
+
* `https://{provider-host}/{basePath}`. Nothing outside an adapter may reassemble it.
|
|
113
|
+
*
|
|
114
|
+
* The returned string MUST equal, byte for byte, what the provider advertises as `issuer` in
|
|
115
|
+
* its discovery document: `openid-client` compares the two and fails the whole discovery when
|
|
116
|
+
* they differ. Implementations resolve it from configuration only — no remote admin call — and
|
|
117
|
+
* throw `IamClientError` when the provider's service route is not configured, so a
|
|
118
|
+
* misconfiguration is loud instead of yielding a silently wrong issuer.
|
|
119
|
+
*/
|
|
120
|
+
getIssuerUrl: (entityId: string) => Promise<string>
|
|
121
|
+
|
|
103
122
|
// --- Provisioning (agent → story development) ---
|
|
104
123
|
ensureClient: (entityId: string, clientId: string, options?: IamClientOptions) => Promise<IamClient>
|
|
105
124
|
|