@owlmeans/server-iam 0.1.18-rc.53 → 0.1.18-rc.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ One-call OIDC RP wiring and IAM gate for OwlMeans servers — `appendIam()` and
14
14
  ## Installation
15
15
 
16
16
  ```bash
17
- bun add @owlmeans/server-iam@^0.1.18-rc.53
17
+ bun add @owlmeans/server-iam@^0.1.18-rc.55
18
18
  ```
19
19
 
20
20
  ## Usage
@@ -47,7 +47,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
47
47
  your project's skill store (`.agents/skills/`):
48
48
 
49
49
  ```sh
50
- npx @owlmeans/agent-skills@^0.1.18-rc.45
50
+ npx @owlmeans/agent-skills@^0.1.18-rc.47
51
51
  ```
52
52
 
53
53
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/server-iam",
4
- "version": "0.1.18-rc.53",
5
- "generatedAt": "2026-10-01T23:27:48.727Z",
4
+ "version": "0.1.18-rc.55",
5
+ "generatedAt": "2026-10-03T20:05:07.589Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ metadata:
8
8
 
9
9
  # Using `@owlmeans/server-iam`
10
10
 
11
- **Install:** `"@owlmeans/server-iam": "^0.1.18-rc.53"` in `dependencies`
11
+ **Install:** `"@owlmeans/server-iam": "^0.1.18-rc.55"` in `dependencies`
12
12
 
13
13
  Boilerplate-less server-side consumer of the OwlMeans IAM. One call wires the OIDC RP stack and the
14
14
  IAM gate; the consumer never knows which IAM backend (Keycloak or integrated) is active. For a
@@ -109,12 +109,9 @@ await ctx.service<GateService>(OIDC_GATE).assert(req, res, ['article--modify'])
109
109
  from `@owlmeans/server-oidc-rp` (`createGateModel().loadPermissions`) — exactly the check
110
110
  `makeOidcGate` performs, which is what a Keycloak-backed deployment gets.
111
111
 
112
- In claims mode the four permission kinds (`iam` skill, "Permission kinds") are all decided locally:
113
- an unbound set applies everywhere, a set bound to an organization (`entitySlug`) only in that one, a
114
- set with `resources` only for those ids. The relying party already strips the binding of the acting
115
- organization's sets and drops every other bound set, so a bound set reaching the gate is a leak —
116
- and `hasPermission` refuses it unless the request acts in exactly its organization, including when
117
- nothing names an organization at all.
112
+ In claims mode all four permission kinds (`iam` skill, "Permission kinds") are decided locally by
113
+ `hasPermission`; a bound set reaching the gate is a leak the relying party should have stripped, and
114
+ it counts only when the request acts in exactly its organization.
118
115
 
119
116
  Which branch runs is decided per request by the token alone: a conforming `permissions` claim selects
120
117
  claims mode, its absence selects the fallback. Nothing configures it, and there is no environment
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAqC,MAAM,sBAAsB,CAAA;AAY1F;;;;GAIG;AACH,OAAO,EACL,wBAAwB,EAAE,yBAAyB,EAAE,uBAAuB,EAC5E,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAC1D,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,kBAAkB,EAC5F,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE1F,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAiED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,WAAW,MAAM,SAAqB,cAAc,KAAG,WAiG9E,CAAA"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAqC,MAAM,sBAAsB,CAAA;AAa1F;;;;GAIG;AACH,OAAO,EACL,wBAAwB,EAAE,yBAAyB,EAAE,uBAAuB,EAC5E,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAC1D,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,kBAAkB,EAC5F,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE1F,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAgFD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,WAAW,MAAM,SAAqB,cAAc,KAAG,WAiG9E,CAAA"}
package/build/gate.js CHANGED
@@ -4,6 +4,7 @@ import { OIDC_GATE } from '@owlmeans/oidc';
4
4
  import { AuthForbidden } from '@owlmeans/auth';
5
5
  import { createGateModel, extractPermissionSets } from '@owlmeans/server-oidc-rp';
6
6
  import { hasPermission, parseGateParam, resolveGateResource, validateGateParams, GateResolutionFailure } from '@owlmeans/iam';
7
+ import { logger, logThrottle } from '@owlmeans/log';
7
8
  /**
8
9
  * The gate-param grammar lives in `@owlmeans/iam` so the browser, the adapters and code-generation
9
10
  * tooling can all read it. Re-exported here because this module was its home and is the documented
@@ -17,12 +18,26 @@ export { RESOURCE_PARAM_SEPARATOR, RESOURCE_SOURCE_SEPARATOR, RESOURCE_PATH_SEPA
17
18
  * so an un-deduplicated log turns one typo into the incident.
18
19
  */
19
20
  const reported = new Set();
20
- const reportOnce = (key, message) => {
21
+ const log = logger('server-iam:gate');
22
+ const reportOnce = (key, message, data) => {
21
23
  if (reported.has(key)) {
22
24
  return;
23
25
  }
24
26
  reported.add(key);
25
- console.error(message);
27
+ log.error(message, data);
28
+ };
29
+ /**
30
+ * A refusal of the gate. Logged once a minute per route, reason and subject — a hot endpoint
31
+ * refuses on every request, and one line per window says the same thing.
32
+ */
33
+ const forbidden = (req, reason, params) => {
34
+ const auth = req.auth;
35
+ if (logThrottle(`access.forbidden\0${req.alias ?? ''}\0${reason}\0${auth?.userId ?? ''}`, 60_000)) {
36
+ log.warn('Access forbidden', {
37
+ alias: req.alias, reason, params, userId: auth?.userId, entitySlug: req.entity?.slug ?? (auth != null ? entitySlugOf(auth) : undefined),
38
+ }, { event: 'access.forbidden' });
39
+ }
40
+ return new AuthForbidden(reason);
26
41
  };
27
42
  /** Aliases whose declarations have already been checked. */
28
43
  const audited = new Set();
@@ -57,8 +72,9 @@ const auditEntrypoint = (ctx, req, params) => {
57
72
  ...(entrypoint.filter != null ? { filter: entrypoint.filter } : {})
58
73
  };
59
74
  for (const issue of validateGateParams(params, audit)) {
60
- console.error(`Gate param "${issue.param}" on entrypoint "${req.alias}" is misconfigured`
61
- + ` [${issue.code}]: ${issue.detail}`);
75
+ log.error('Gate param is misconfigured', {
76
+ param: issue.param, alias: req.alias, code: issue.code, detail: issue.detail,
77
+ });
62
78
  }
63
79
  }
64
80
  catch {
@@ -86,7 +102,7 @@ export const makeIamGate = (alias = OIDC_GATE, opts) => {
86
102
  await service.ready();
87
103
  const ctx = service.assertCtx();
88
104
  if (req.auth == null) {
89
- throw new AuthForbidden('auth');
105
+ throw forbidden(req, 'auth', params);
90
106
  }
91
107
  const auth = req.auth;
92
108
  const sets = extractPermissionSets(auth.permissions);
@@ -124,8 +140,8 @@ export const makeIamGate = (alias = OIDC_GATE, opts) => {
124
140
  return hasPermission(auth, fixed, { entitySlug });
125
141
  });
126
142
  if (!granted) {
127
- structural.forEach(detail => reportOnce(`${req.alias}\0${detail}`, `Gate on entrypoint "${req.alias}" refused a request it could not evaluate: ${detail}`));
128
- throw new AuthForbidden('permission');
143
+ structural.forEach(detail => reportOnce(`${req.alias}\0${detail}`, 'Gate refused a request it could not evaluate', { alias: req.alias, detail }));
144
+ throw forbidden(req, 'permission', params);
129
145
  }
130
146
  return;
131
147
  }
@@ -133,19 +149,19 @@ export const makeIamGate = (alias = OIDC_GATE, opts) => {
133
149
  // store a resource-scoped grant, so a scoped param widens to a project-wide check and the same
134
150
  // declaration means two different things depending on which backend is active.
135
151
  const scoped = params.filter(param => parseGateParam(param).resource != null);
136
- scoped.forEach(param => reportOnce(`uma2\0${req.alias}\0${param}`, `Gate param "${param}" on entrypoint "${req.alias}" is resource-scoped, but resource scoping`
137
- + ' is not enforced in UMA2 mode — it is checked as a project-wide permission'));
152
+ scoped.forEach(param => reportOnce(`uma2\0${req.alias}\0${param}`, 'Gate param is resource-scoped, but resource scoping is not enforced in UMA2 mode — it is checked as a '
153
+ + 'project-wide permission', { param, alias: req.alias }));
138
154
  const usable = opts?.strictResourceScope === true
139
155
  ? params.filter(param => parseGateParam(param).resource == null)
140
156
  : params;
141
157
  if (usable.length < 1) {
142
- throw new AuthForbidden('permission');
158
+ throw forbidden(req, 'permission', params);
143
159
  }
144
160
  const stripped = usable.map(param => parseGateParam(param).permission);
145
161
  const model = createGateModel(ctx);
146
162
  const permissions = await model.loadPermissions(auth, stripped);
147
163
  if (permissions.length < 1) {
148
- throw new AuthForbidden('permission');
164
+ throw forbidden(req, 'permission', params);
149
165
  }
150
166
  }
151
167
  });
package/build/gate.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-iam",
3
- "version": "0.1.18-rc.53",
3
+ "version": "0.1.18-rc.55",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -22,14 +22,15 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@owlmeans/auth": "^0.1.18-rc.37",
25
+ "@owlmeans/auth": "^0.1.18-rc.38",
26
26
  "@owlmeans/context": "^0.1.18-rc.32",
27
- "@owlmeans/entrypoint": "^0.1.18-rc.38",
27
+ "@owlmeans/entrypoint": "^0.1.18-rc.39",
28
28
  "@owlmeans/error": "^0.1.18-rc.36",
29
- "@owlmeans/iam": "^0.1.18-rc.46",
30
- "@owlmeans/oidc": "^0.1.18-rc.51",
31
- "@owlmeans/server-context": "^0.1.18-rc.39",
32
- "@owlmeans/server-oidc-rp": "^0.1.18-rc.51"
29
+ "@owlmeans/iam": "^0.1.18-rc.48",
30
+ "@owlmeans/log": "^0.1.18-rc.0",
31
+ "@owlmeans/oidc": "^0.1.18-rc.53",
32
+ "@owlmeans/server-context": "^0.1.18-rc.41",
33
+ "@owlmeans/server-oidc-rp": "^0.1.18-rc.53"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@owlmeans/dep-config": "workspace:*",
package/src/gate.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  hasPermission, parseGateParam, resolveGateResource, validateGateParams, GateResolutionFailure
11
11
  } from '@owlmeans/iam'
12
12
  import type { GateParamAudit } from '@owlmeans/iam'
13
+ import { logger, logThrottle } from '@owlmeans/log'
13
14
 
14
15
  /**
15
16
  * The gate-param grammar lives in `@owlmeans/iam` so the browser, the adapters and code-generation
@@ -44,12 +45,28 @@ export interface IamGateOptions {
44
45
  */
45
46
  const reported = new Set<string>()
46
47
 
47
- const reportOnce = (key: string, message: string): void => {
48
+ const log = logger('server-iam:gate')
49
+
50
+ const reportOnce = (key: string, message: string, data: Record<string, unknown>): void => {
48
51
  if (reported.has(key)) {
49
52
  return
50
53
  }
51
54
  reported.add(key)
52
- console.error(message)
55
+ log.error(message, data)
56
+ }
57
+
58
+ /**
59
+ * A refusal of the gate. Logged once a minute per route, reason and subject — a hot endpoint
60
+ * refuses on every request, and one line per window says the same thing.
61
+ */
62
+ const forbidden = (req: AbstractRequest, reason: string, params: string[]): AuthForbidden => {
63
+ const auth = req.auth as Auth | undefined
64
+ if (logThrottle(`access.forbidden\0${req.alias ?? ''}\0${reason}\0${auth?.userId ?? ''}`, 60_000)) {
65
+ log.warn('Access forbidden', {
66
+ alias: req.alias, reason, params, userId: auth?.userId, entitySlug: req.entity?.slug ?? (auth != null ? entitySlugOf(auth) : undefined),
67
+ }, { event: 'access.forbidden' })
68
+ }
69
+ return new AuthForbidden(reason)
53
70
  }
54
71
 
55
72
  /** Aliases whose declarations have already been checked. */
@@ -89,10 +106,9 @@ const auditEntrypoint = (ctx: Context, req: AbstractRequest, params: string[]):
89
106
  }
90
107
 
91
108
  for (const issue of validateGateParams(params, audit)) {
92
- console.error(
93
- `Gate param "${issue.param}" on entrypoint "${req.alias}" is misconfigured`
94
- + ` [${issue.code}]: ${issue.detail}`
95
- )
109
+ log.error('Gate param is misconfigured', {
110
+ param: issue.param, alias: req.alias, code: issue.code, detail: issue.detail,
111
+ })
96
112
  }
97
113
  } catch {
98
114
  // An entrypoint that cannot be looked up is not this gate's problem to report.
@@ -121,7 +137,7 @@ export const makeIamGate = (alias: string = OIDC_GATE, opts?: IamGateOptions): G
121
137
  const ctx = service.assertCtx<Config, Context>()
122
138
 
123
139
  if (req.auth == null) {
124
- throw new AuthForbidden('auth')
140
+ throw forbidden(req, 'auth', params)
125
141
  }
126
142
 
127
143
  const auth = req.auth as Auth
@@ -173,10 +189,10 @@ export const makeIamGate = (alias: string = OIDC_GATE, opts?: IamGateOptions): G
173
189
  if (!granted) {
174
190
  structural.forEach(detail => reportOnce(
175
191
  `${req.alias}\0${detail}`,
176
- `Gate on entrypoint "${req.alias}" refused a request it could not evaluate: ${detail}`
192
+ 'Gate refused a request it could not evaluate', { alias: req.alias, detail }
177
193
  ))
178
194
 
179
- throw new AuthForbidden('permission')
195
+ throw forbidden(req, 'permission', params)
180
196
  }
181
197
 
182
198
  return
@@ -188,8 +204,8 @@ export const makeIamGate = (alias: string = OIDC_GATE, opts?: IamGateOptions): G
188
204
  const scoped = params.filter(param => parseGateParam(param).resource != null)
189
205
  scoped.forEach(param => reportOnce(
190
206
  `uma2\0${req.alias}\0${param}`,
191
- `Gate param "${param}" on entrypoint "${req.alias}" is resource-scoped, but resource scoping`
192
- + ' is not enforced in UMA2 mode — it is checked as a project-wide permission'
207
+ 'Gate param is resource-scoped, but resource scoping is not enforced in UMA2 mode — it is checked as a '
208
+ + 'project-wide permission', { param, alias: req.alias }
193
209
  ))
194
210
 
195
211
  const usable = opts?.strictResourceScope === true
@@ -197,7 +213,7 @@ export const makeIamGate = (alias: string = OIDC_GATE, opts?: IamGateOptions): G
197
213
  : params
198
214
 
199
215
  if (usable.length < 1) {
200
- throw new AuthForbidden('permission')
216
+ throw forbidden(req, 'permission', params)
201
217
  }
202
218
 
203
219
  const stripped = usable.map(param => parseGateParam(param).permission)
@@ -205,7 +221,7 @@ export const makeIamGate = (alias: string = OIDC_GATE, opts?: IamGateOptions): G
205
221
  const permissions = await model.loadPermissions(auth, stripped)
206
222
 
207
223
  if (permissions.length < 1) {
208
- throw new AuthForbidden('permission')
224
+ throw forbidden(req, 'permission', params)
209
225
  }
210
226
  }
211
227
  })