@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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-iam/SKILL.md +4 -7
- package/build/gate.d.ts.map +1 -1
- package/build/gate.js +27 -11
- package/build/gate.js.map +1 -1
- package/package.json +8 -7
- package/src/gate.ts +29 -13
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.
|
|
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.
|
|
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
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-iam",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
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.
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
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
|
package/build/gate.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
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}`,
|
|
128
|
-
throw
|
|
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}`,
|
|
137
|
-
+ '
|
|
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
|
|
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
|
|
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":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAA;AAEjF,OAAO,EACL,aAAa,EAAE,cAAc,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,qBAAqB,EAC9F,MAAM,eAAe,CAAA;
|
|
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.
|
|
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.
|
|
25
|
+
"@owlmeans/auth": "^0.1.18-rc.38",
|
|
26
26
|
"@owlmeans/context": "^0.1.18-rc.32",
|
|
27
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
27
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.39",
|
|
28
28
|
"@owlmeans/error": "^0.1.18-rc.36",
|
|
29
|
-
"@owlmeans/iam": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/
|
|
31
|
-
"@owlmeans/
|
|
32
|
-
"@owlmeans/server-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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
|
-
|
|
192
|
+
'Gate refused a request it could not evaluate', { alias: req.alias, detail }
|
|
177
193
|
))
|
|
178
194
|
|
|
179
|
-
throw
|
|
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
|
-
|
|
192
|
-
+ '
|
|
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
|
|
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
|
|
224
|
+
throw forbidden(req, 'permission', params)
|
|
209
225
|
}
|
|
210
226
|
}
|
|
211
227
|
})
|