@astram/guard 0.20.2 → 0.21.0
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/dist/authz/operation-marker-check.d.ts +6 -0
- package/dist/authz/operation-marker-check.d.ts.map +1 -1
- package/dist/authz/operation-marker-check.js +18 -10
- package/dist/identity/module-registry.bridge.d.ts.map +1 -1
- package/dist/identity/module-registry.bridge.js +10 -2
- package/package.json +5 -5
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
* (`decide`) passes the attribute through ONLY when `operationOf(principal)` exists and its
|
|
9
9
|
* `operationKey` and `stepKey` equal the marker's; in every other case it is removed before the
|
|
10
10
|
* inner `decide()`, so the baseline decision stands (Shield #1, #9).
|
|
11
|
+
*
|
|
12
|
+
* G-025 (ADR-0048): the same seal for the separation-of-duties facts. Gate 6 reads
|
|
13
|
+
* `shield.separationOfDuties`; only a facts object the engine built
|
|
14
|
+
* (`isEngineSeparationOfDutiesFacts`, a module-private WeakSet) passes. Any other value is
|
|
15
|
+
* removed, so a bound action then fails closed (`separation_of_duties_unverified`) instead of a
|
|
16
|
+
* forged empty set skipping the rule.
|
|
11
17
|
*/
|
|
12
18
|
import type { DecisionAuthority } from '@astram/core';
|
|
13
19
|
export declare function withOperationMarkerCheck(inner: DecisionAuthority): DecisionAuthority;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-marker-check.d.ts","sourceRoot":"","sources":["../../src/authz/operation-marker-check.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"operation-marker-check.d.ts","sourceRoot":"","sources":["../../src/authz/operation-marker-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAMtD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,CA2BpF"}
|
|
@@ -3,22 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.withOperationMarkerCheck = withOperationMarkerCheck;
|
|
4
4
|
const engine_1 = require("@astram/engine");
|
|
5
5
|
const MARKER = 'shield.delegation';
|
|
6
|
+
const SOD_FACTS = 'shield.separationOfDuties';
|
|
6
7
|
function withOperationMarkerCheck(inner) {
|
|
7
8
|
return {
|
|
8
9
|
decide: (principal, action, resource, context) => {
|
|
9
10
|
const attributes = context.attributes;
|
|
10
|
-
if (attributes === undefined || !(MARKER in attributes)) {
|
|
11
|
+
if (attributes === undefined || !(MARKER in attributes || SOD_FACTS in attributes)) {
|
|
11
12
|
return inner.decide(principal, action, resource, context);
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
binding
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
// Either key is present (own or inherited): forward an own-property copy, so an inherited
|
|
15
|
+
// key never reaches core (review R3 #3), and only genuine own values survive.
|
|
16
|
+
const rest = { ...attributes };
|
|
17
|
+
if (Object.hasOwn(rest, MARKER)) {
|
|
18
|
+
const raw = rest[MARKER];
|
|
19
|
+
const binding = (0, engine_1.operationOf)(principal);
|
|
20
|
+
const { operationKey, stepKey } = typeof raw === 'object' && raw !== null ? raw : {};
|
|
21
|
+
const genuine = binding !== undefined &&
|
|
22
|
+
binding.operationKey === operationKey &&
|
|
23
|
+
binding.stepKey === stepKey;
|
|
24
|
+
if (!genuine)
|
|
25
|
+
delete rest[MARKER];
|
|
26
|
+
}
|
|
27
|
+
if (Object.hasOwn(rest, SOD_FACTS) && !(0, engine_1.isEngineSeparationOfDutiesFacts)(rest[SOD_FACTS])) {
|
|
28
|
+
delete rest[SOD_FACTS];
|
|
29
|
+
}
|
|
22
30
|
return inner.decide(principal, action, resource, { ...context, attributes: rest });
|
|
23
31
|
},
|
|
24
32
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-registry.bridge.d.ts","sourceRoot":"","sources":["../../src/identity/module-registry.bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module-registry.bridge.d.ts","sourceRoot":"","sources":["../../src/identity/module-registry.bridge.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,gBAAgB,CAAC;AASlE,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,cAAc,CA+B9F"}
|
|
@@ -8,7 +8,8 @@ exports.buildIdentityModuleRegistry = buildIdentityModuleRegistry;
|
|
|
8
8
|
* are its declared vocabulary (`actionsOf` = engine `effectiveActions`, ADR-0038): CRUD plus
|
|
9
9
|
* extras, or exactly the declared list for `crudActions: false`; an invalid declaration throws
|
|
10
10
|
* `RegistrationError` here. Every action is `gated` with the type's `access.plane` — nothing
|
|
11
|
-
* open unless reclassified.
|
|
11
|
+
* open unless reclassified. G-025: field descriptors carry `referencesTypeKey` (engine
|
|
12
|
+
* `referencedTypeOf`) and types carry their separation-of-duties action lists. `register()` throws on duplicate typeKey/permissionKey.
|
|
12
13
|
*/
|
|
13
14
|
const core_1 = require("@astram/core");
|
|
14
15
|
const engine_1 = require("@astram/engine");
|
|
@@ -25,8 +26,15 @@ function buildIdentityModuleRegistry(inputs) {
|
|
|
25
26
|
return {
|
|
26
27
|
typeKey: r.typeKey,
|
|
27
28
|
label: r.typeKey,
|
|
28
|
-
fields: r.fields.map((f) =>
|
|
29
|
+
fields: r.fields.map((f) => {
|
|
30
|
+
const ref = (0, engine_1.referencedTypeOf)(f);
|
|
31
|
+
return { key: f.key, label: f.key, ...(ref ? { referencesTypeKey: ref } : {}) };
|
|
32
|
+
}),
|
|
29
33
|
...(Object.keys(access).length > 0 ? { access } : {}),
|
|
34
|
+
// Separation of duties (G-025): only the action lists reach core.
|
|
35
|
+
...(r.separationOfDuties?.length
|
|
36
|
+
? { separationOfDuties: r.separationOfDuties.map((d) => ({ actions: [...d.actions] })) }
|
|
37
|
+
: {}),
|
|
30
38
|
};
|
|
31
39
|
});
|
|
32
40
|
const actions = regs.flatMap((r) => (0, identity_actions_js_1.actionsOf)(r).map((actionKey) => ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Astram framework — the enforcement guard + real-world adapters (the 'hands'). One global deny-by-default gate every request passes through (ADR-0002), classification decorators, boot-pool assertion (ADR-0018), the forRoot() registration seam, and the DB/Redis/crypto adapters that connect the sealed core to a real backend. Depends on @astram/core (ADR-0003, ADR-0023).",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"argon2": "^0.41.1",
|
|
37
37
|
"ioredis": "^5.4.1",
|
|
38
38
|
"mariadb": "^3.5.4",
|
|
39
|
-
"@astram/core": "1.
|
|
40
|
-
"@astram/engine": "0.
|
|
39
|
+
"@astram/core": "1.12.0",
|
|
40
|
+
"@astram/engine": "0.14.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@nestjs/common": "^11",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@nestjs/platform-express": "^11",
|
|
46
46
|
"reflect-metadata": "^0.2",
|
|
47
47
|
"rxjs": "^7",
|
|
48
|
-
"@astram/messaging": "^0.1.
|
|
48
|
+
"@astram/messaging": "^0.1.6"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"@astram/messaging": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"typescript": "^5.7.0",
|
|
69
69
|
"unplugin-swc": "^1.5.1",
|
|
70
70
|
"vitest": "^2.1.8",
|
|
71
|
-
"@astram/messaging": "0.1.
|
|
71
|
+
"@astram/messaging": "0.1.6"
|
|
72
72
|
},
|
|
73
73
|
"repository": {
|
|
74
74
|
"type": "git",
|