@o3co/auth.policy-verifier.builtins 0.3.1 → 0.5.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/README.ja.md +70 -14
- package/README.md +75 -15
- package/dist/collectors/PayloadScopeCollector.d.mts +5 -0
- package/dist/collectors/PayloadScopeCollector.d.mts.map +1 -1
- package/dist/collectors/PayloadScopeCollector.mjs +8 -1
- package/dist/collectors/PayloadSubjectIdCollector.d.mts +9 -0
- package/dist/collectors/PayloadSubjectIdCollector.d.mts.map +1 -1
- package/dist/collectors/PayloadSubjectIdCollector.mjs +16 -4
- package/dist/collectors/RequestContextAttributeCollector.d.mts +47 -0
- package/dist/collectors/RequestContextAttributeCollector.d.mts.map +1 -0
- package/dist/collectors/RequestContextAttributeCollector.mjs +100 -0
- package/dist/collectors/StaticPermissionCollector.d.mts +5 -0
- package/dist/collectors/StaticPermissionCollector.d.mts.map +1 -1
- package/dist/collectors/StaticPermissionCollector.mjs +7 -0
- package/dist/collectors/StaticRoleCollector.d.mts +5 -0
- package/dist/collectors/StaticRoleCollector.d.mts.map +1 -1
- package/dist/collectors/StaticRoleCollector.mjs +7 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -1
- package/dist/module.d.mts +5 -0
- package/dist/module.d.mts.map +1 -1
- package/dist/module.mjs +10 -1
- package/dist/resource/DotNotationResourceParser.d.mts +48 -0
- package/dist/resource/DotNotationResourceParser.d.mts.map +1 -1
- package/dist/resource/DotNotationResourceParser.mjs +94 -10
- package/dist/rules/AttrLiteralCompare.d.mts +2 -2
- package/dist/rules/AttrLiteralCompare.d.mts.map +1 -1
- package/dist/rules/AttrLiteralCompare.mjs +2 -0
- package/dist/rules/AttrLiteralEqual.d.mts +2 -2
- package/dist/rules/AttrLiteralEqual.d.mts.map +1 -1
- package/dist/rules/AttrLiteralEqual.mjs +2 -0
- package/dist/rules/AttrLiteralIn.d.mts +2 -2
- package/dist/rules/AttrLiteralIn.d.mts.map +1 -1
- package/dist/rules/AttrLiteralIn.mjs +2 -0
- package/dist/rules/AttrLiteralNotEqual.d.mts +2 -2
- package/dist/rules/AttrLiteralNotEqual.d.mts.map +1 -1
- package/dist/rules/AttrLiteralNotEqual.mjs +2 -0
- package/dist/rules/AttrLiteralNotIn.d.mts +2 -2
- package/dist/rules/AttrLiteralNotIn.d.mts.map +1 -1
- package/dist/rules/AttrLiteralNotIn.mjs +2 -0
- package/dist/rules/AttrMatchRule.d.mts.map +1 -1
- package/dist/rules/AttrMatchRule.mjs +2 -0
- package/dist/rules/AttrPairCompare.d.mts +2 -2
- package/dist/rules/AttrPairCompare.d.mts.map +1 -1
- package/dist/rules/AttrPairCompare.mjs +2 -0
- package/dist/rules/AttrPairEqual.d.mts +2 -2
- package/dist/rules/AttrPairEqual.d.mts.map +1 -1
- package/dist/rules/AttrPairEqual.mjs +2 -0
- package/dist/rules/AttrPairNotEqual.d.mts +2 -2
- package/dist/rules/AttrPairNotEqual.d.mts.map +1 -1
- package/dist/rules/AttrPairNotEqual.mjs +2 -0
- package/dist/rules/HasPermission.d.mts +36 -2
- package/dist/rules/HasPermission.d.mts.map +1 -1
- package/dist/rules/HasPermission.mjs +57 -6
- package/dist/rules/HasScope.d.mts +45 -4
- package/dist/rules/HasScope.d.mts.map +1 -1
- package/dist/rules/HasScope.mjs +46 -9
- package/dist/rules/_sharedValidation.d.mts +12 -9
- package/dist/rules/_sharedValidation.d.mts.map +1 -1
- package/dist/rules/_sharedValidation.mjs +46 -9
- package/dist/rules/collectors/ResourceActionPermissionRuleCollector.d.mts +6 -0
- package/dist/rules/collectors/ResourceActionPermissionRuleCollector.d.mts.map +1 -1
- package/dist/rules/collectors/ResourceActionPermissionRuleCollector.mjs +8 -0
- package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts +52 -4
- package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts.map +1 -1
- package/dist/rules/collectors/ResourceActionScopeRuleCollector.mjs +54 -6
- package/package.json +8 -5
- package/dist/__tests__/collectors/PayloadScopeCollector.test.d.mts +0 -2
- package/dist/__tests__/collectors/PayloadScopeCollector.test.d.mts.map +0 -1
- package/dist/__tests__/collectors/PayloadScopeCollector.test.mjs +0 -24
- package/dist/__tests__/collectors/PayloadSubjectIdCollector.test.d.mts +0 -2
- package/dist/__tests__/collectors/PayloadSubjectIdCollector.test.d.mts.map +0 -1
- package/dist/__tests__/collectors/PayloadSubjectIdCollector.test.mjs +0 -33
- package/dist/__tests__/collectors/StaticPermissionCollector.test.d.mts +0 -2
- package/dist/__tests__/collectors/StaticPermissionCollector.test.d.mts.map +0 -1
- package/dist/__tests__/collectors/StaticPermissionCollector.test.mjs +0 -22
- package/dist/__tests__/collectors/StaticRoleCollector.test.d.mts +0 -2
- package/dist/__tests__/collectors/StaticRoleCollector.test.d.mts.map +0 -1
- package/dist/__tests__/collectors/StaticRoleCollector.test.mjs +0 -24
- package/dist/__tests__/module.test.d.mts +0 -2
- package/dist/__tests__/module.test.d.mts.map +0 -1
- package/dist/__tests__/module.test.mjs +0 -71
- package/dist/__tests__/resource/DotNotationResourceParser.test.d.mts +0 -2
- package/dist/__tests__/resource/DotNotationResourceParser.test.d.mts.map +0 -1
- package/dist/__tests__/resource/DotNotationResourceParser.test.mjs +0 -34
- package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrLiteralCompare.test.mjs +0 -253
- package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrLiteralEqual.test.mjs +0 -138
- package/dist/__tests__/rules/AttrLiteralIn.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrLiteralIn.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrLiteralIn.test.mjs +0 -160
- package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrLiteralNotEqual.test.mjs +0 -157
- package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrLiteralNotIn.test.mjs +0 -168
- package/dist/__tests__/rules/AttrMatchRule.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrMatchRule.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrMatchRule.test.mjs +0 -79
- package/dist/__tests__/rules/AttrPairCompare.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrPairCompare.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrPairCompare.test.mjs +0 -319
- package/dist/__tests__/rules/AttrPairEqual.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrPairEqual.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrPairEqual.test.mjs +0 -155
- package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts +0 -2
- package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts.map +0 -1
- package/dist/__tests__/rules/AttrPairNotEqual.test.mjs +0 -155
- package/dist/__tests__/rules/HasPermission.test.d.mts +0 -2
- package/dist/__tests__/rules/HasPermission.test.d.mts.map +0 -1
- package/dist/__tests__/rules/HasPermission.test.mjs +0 -86
- package/dist/__tests__/rules/HasScope.test.d.mts +0 -2
- package/dist/__tests__/rules/HasScope.test.d.mts.map +0 -1
- package/dist/__tests__/rules/HasScope.test.mjs +0 -42
- package/dist/__tests__/rules/_sharedValidation.test.d.mts +0 -2
- package/dist/__tests__/rules/_sharedValidation.test.d.mts.map +0 -1
- package/dist/__tests__/rules/_sharedValidation.test.mjs +0 -325
- package/dist/__tests__/rules/collectors/ResourceActionPermissionRuleCollector.test.d.mts +0 -2
- package/dist/__tests__/rules/collectors/ResourceActionPermissionRuleCollector.test.d.mts.map +0 -1
- package/dist/__tests__/rules/collectors/ResourceActionPermissionRuleCollector.test.mjs +0 -27
- package/dist/__tests__/rules/collectors/ResourceActionScopeRuleCollector.test.d.mts +0 -2
- package/dist/__tests__/rules/collectors/ResourceActionScopeRuleCollector.test.d.mts.map +0 -1
- package/dist/__tests__/rules/collectors/ResourceActionScopeRuleCollector.test.mjs +0 -48
|
@@ -1,11 +1,52 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
|
|
2
|
+
/** Options accepted by `HasScope`. */
|
|
3
|
+
export interface HasScopeOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Opt in to treating a bare granted scope (one containing no `:`) as
|
|
6
|
+
* `read:<scope>` in addition to its literal value. Defaults to `false`.
|
|
7
|
+
*
|
|
8
|
+
* This exists only for deployments whose issuer emits bare resource names
|
|
9
|
+
* and that relied on the rewrite before it became opt-in. It is off by
|
|
10
|
+
* default because the rewrite invents an action the issuer never wrote:
|
|
11
|
+
* a token granted `project` is silently promoted to `read:project`.
|
|
12
|
+
*/
|
|
13
|
+
allowBareScopeRewrite?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Rule that passes when the token carries the required scope.
|
|
17
|
+
*
|
|
18
|
+
* ## Matching
|
|
19
|
+
*
|
|
20
|
+
* Comparison is an **exact, case-sensitive string equality** against each value
|
|
21
|
+
* in `ATTR_SCOPES`. OAuth 2.0 scope values are case-sensitive opaque strings
|
|
22
|
+
* (RFC 6749 §3.3), so the verifier compares what the issuer wrote rather than a
|
|
23
|
+
* normalized form of it:
|
|
24
|
+
*
|
|
25
|
+
* - `read:PROJECT` does **not** satisfy a `read:project` requirement.
|
|
26
|
+
* - `read:project:restricted` is a value in its own right. It does not satisfy
|
|
27
|
+
* `read:project`, and `read:project` does not satisfy it. Nothing is split
|
|
28
|
+
* off at the second `:`; a scope the issuer deliberately narrowed must not
|
|
29
|
+
* collapse into the broader one.
|
|
30
|
+
*
|
|
31
|
+
* ## Bare-scope rewrite
|
|
32
|
+
*
|
|
33
|
+
* A granted scope carrying no `:` is compared literally unless
|
|
34
|
+
* `{ allowBareScopeRewrite: true }` is passed, in which case it also matches
|
|
35
|
+
* `read:<scope>`. Even then, only a scope with **no** `:` is ever rewritten —
|
|
36
|
+
* a value such as `project:restricted` is left alone, because which of its
|
|
37
|
+
* segments is the action is unknowable and guessing would over-grant.
|
|
38
|
+
*
|
|
39
|
+
* Non-string entries in `ATTR_SCOPES` never match and never throw: the map is
|
|
40
|
+
* untyped, and a malformed value must produce a denial, not a crash.
|
|
41
|
+
*/
|
|
2
42
|
export declare class HasScope implements Rule {
|
|
3
43
|
private scope;
|
|
4
44
|
readonly ruleType = "scope";
|
|
5
45
|
readonly code = "invalid_scope";
|
|
6
46
|
readonly message: string;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
47
|
+
private readonly allowBareScopeRewrite;
|
|
48
|
+
constructor(scope: string, options?: HasScopeOptions);
|
|
49
|
+
verify(attrs: ReadonlyAttributes): boolean;
|
|
50
|
+
private matchScope;
|
|
10
51
|
}
|
|
11
52
|
//# sourceMappingURL=HasScope.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HasScope.d.mts","sourceRoot":"","sources":["../../src/rules/HasScope.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HasScope.d.mts","sourceRoot":"","sources":["../../src/rules/HasScope.mts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC/B;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,QAAS,YAAW,IAAI;IAQnC,OAAO,CAAC,KAAK;IAPd,QAAQ,CAAC,QAAQ,WAAW;IAC5B,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;gBAGvC,KAAK,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,eAAe;IAM1B,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAM1C,OAAO,CAAC,UAAU;CAYlB"}
|
package/dist/rules/HasScope.mjs
CHANGED
|
@@ -1,22 +1,59 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
import { ATTR_SCOPES } from "@o3co/auth.policy-verifier.core";
|
|
4
|
+
/**
|
|
5
|
+
* Rule that passes when the token carries the required scope.
|
|
6
|
+
*
|
|
7
|
+
* ## Matching
|
|
8
|
+
*
|
|
9
|
+
* Comparison is an **exact, case-sensitive string equality** against each value
|
|
10
|
+
* in `ATTR_SCOPES`. OAuth 2.0 scope values are case-sensitive opaque strings
|
|
11
|
+
* (RFC 6749 §3.3), so the verifier compares what the issuer wrote rather than a
|
|
12
|
+
* normalized form of it:
|
|
13
|
+
*
|
|
14
|
+
* - `read:PROJECT` does **not** satisfy a `read:project` requirement.
|
|
15
|
+
* - `read:project:restricted` is a value in its own right. It does not satisfy
|
|
16
|
+
* `read:project`, and `read:project` does not satisfy it. Nothing is split
|
|
17
|
+
* off at the second `:`; a scope the issuer deliberately narrowed must not
|
|
18
|
+
* collapse into the broader one.
|
|
19
|
+
*
|
|
20
|
+
* ## Bare-scope rewrite
|
|
21
|
+
*
|
|
22
|
+
* A granted scope carrying no `:` is compared literally unless
|
|
23
|
+
* `{ allowBareScopeRewrite: true }` is passed, in which case it also matches
|
|
24
|
+
* `read:<scope>`. Even then, only a scope with **no** `:` is ever rewritten —
|
|
25
|
+
* a value such as `project:restricted` is left alone, because which of its
|
|
26
|
+
* segments is the action is unknowable and guessing would over-grant.
|
|
27
|
+
*
|
|
28
|
+
* Non-string entries in `ATTR_SCOPES` never match and never throw: the map is
|
|
29
|
+
* untyped, and a malformed value must produce a denial, not a crash.
|
|
30
|
+
*/
|
|
2
31
|
export class HasScope {
|
|
3
32
|
scope;
|
|
4
33
|
ruleType = "scope";
|
|
5
34
|
code = "invalid_scope";
|
|
6
35
|
message;
|
|
7
|
-
|
|
36
|
+
allowBareScopeRewrite;
|
|
37
|
+
constructor(scope, options) {
|
|
8
38
|
this.scope = scope;
|
|
9
39
|
this.message = `Token does not have required scope: ${scope}`;
|
|
40
|
+
this.allowBareScopeRewrite = options?.allowBareScopeRewrite ?? false;
|
|
10
41
|
}
|
|
11
42
|
verify(attrs) {
|
|
12
|
-
const scopes = attrs.get(ATTR_SCOPES)
|
|
13
|
-
|
|
43
|
+
const scopes = attrs.get(ATTR_SCOPES);
|
|
44
|
+
if (!Array.isArray(scopes))
|
|
45
|
+
return false;
|
|
46
|
+
return scopes.some((s) => typeof s === "string" && this.matchScope(s, this.scope));
|
|
14
47
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
48
|
+
matchScope(granted, required) {
|
|
49
|
+
if (granted === required)
|
|
50
|
+
return true;
|
|
51
|
+
// The rewrite applies to bare scopes only. A granted scope that already
|
|
52
|
+
// contains ":" is never re-interpreted: splitting it to guess an action
|
|
53
|
+
// is what let "read:project:restricted" pass as "read:project".
|
|
54
|
+
if (this.allowBareScopeRewrite && !granted.includes(":")) {
|
|
55
|
+
return `read:${granted}` === required;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
21
58
|
}
|
|
22
59
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Shared validation helpers for attribute-based rule constructors.
|
|
3
|
-
*/
|
|
1
|
+
export declare function fnv1a64(s: string): string;
|
|
4
2
|
export type LiteralValue = string | number | boolean;
|
|
5
3
|
export type CompareOp = "lt" | "le" | "gt" | "ge";
|
|
6
4
|
export declare const COMPARE_OPS: readonly CompareOp[];
|
|
@@ -69,15 +67,20 @@ export declare function requireCompareOp(className: string, value: unknown): Com
|
|
|
69
67
|
export declare function requireOptionalGroup(className: string, value: unknown): string | undefined;
|
|
70
68
|
/**
|
|
71
69
|
* Computes a stable cache/dedup key for an array of LiteralValues.
|
|
72
|
-
* Format: `{type}:{count}:{hashPrefix}` —
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
70
|
+
* Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is a 16-hex-character
|
|
71
|
+
* FNV-1a 64-bit hash over a canonical serialization of `values`. The canonical
|
|
72
|
+
* form first deduplicates `values` (to mirror the Set-based semantics of
|
|
73
|
+
* AttrLiteralIn / AttrLiteralNotIn — duplicates do not change the rule's
|
|
74
|
+
* behavior, so they must not change its `ruleType`), then applies
|
|
75
|
+
* `JSON.stringify(String(v))` to each remaining element before sorting and
|
|
76
|
+
* joining with `,`. The per-element quoting prevents separator collisions
|
|
78
77
|
* across different arrays (e.g. `["a,b", "c"]` vs `["a", "b,c"]`).
|
|
79
78
|
* The `{count}` segment reflects the post-dedup element count.
|
|
80
79
|
* Caller must guarantee values is a non-empty homogeneous LiteralValue[].
|
|
80
|
+
*
|
|
81
|
+
* The hash is non-cryptographic by design, but uses 64-bit output to keep
|
|
82
|
+
* the birthday-collision bound (~2^32) far above any realistic policy size
|
|
83
|
+
* and to resist deliberate collision construction.
|
|
81
84
|
*/
|
|
82
85
|
export declare function computeValuesKey(values: LiteralValue[]): string;
|
|
83
86
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_sharedValidation.d.mts","sourceRoot":"","sources":["../../src/rules/_sharedValidation.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_sharedValidation.d.mts","sourceRoot":"","sources":["../../src/rules/_sharedValidation.mts"],"names":[],"mappings":"AA4BA,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOzC;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAarD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAA6B,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAe/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,OAAO,GACZ,YAAY,CAad;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAQvF;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CAkChG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAU7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAe1F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAQ/D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAWxF"}
|
|
@@ -1,4 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Shared validation helpers for attribute-based rule constructors.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* FNV-1a 64-bit non-cryptographic hash, computed with BigInt. Sync,
|
|
8
|
+
* runtime-agnostic (no `node:*` imports), deterministic. Returns 16
|
|
9
|
+
* lowercase hex characters, zero-padded.
|
|
10
|
+
*
|
|
11
|
+
* Used by `computeValuesKey` to build a stable grouping suffix for `ruleType`.
|
|
12
|
+
* The 64-bit output gives ~2^32 birthday-collision bound, which is
|
|
13
|
+
* effectively non-colliding for any realistic policy size and resists
|
|
14
|
+
* deliberate collision construction by a hostile policy author or supply
|
|
15
|
+
* chain. (A 32-bit variant was rejected during review because random short
|
|
16
|
+
* strings can be made to collide within seconds, weakening evaluator
|
|
17
|
+
* grouping when two distinct rules on the same attribute share a `ruleType`.)
|
|
18
|
+
*
|
|
19
|
+
* Iterates UTF-16 code units via `charCodeAt`. BigInt is used because
|
|
20
|
+
* 64-bit multiplication overflows IEEE-754 doubles; `Math.imul` only
|
|
21
|
+
* supports 32-bit. BigInt is available in every modern JavaScript runtime
|
|
22
|
+
* (Node 10.4+, all evergreen browsers, Cloudflare Workers, Deno, Bun).
|
|
23
|
+
*/
|
|
24
|
+
const FNV_OFFSET_BASIS_64 = 0xcbf29ce484222325n;
|
|
25
|
+
const FNV_PRIME_64 = 0x100000001b3n;
|
|
26
|
+
const FNV_MASK_64 = 0xffffffffffffffffn;
|
|
27
|
+
export function fnv1a64(s) {
|
|
28
|
+
let hash = FNV_OFFSET_BASIS_64;
|
|
29
|
+
for (let i = 0; i < s.length; i++) {
|
|
30
|
+
hash ^= BigInt(s.charCodeAt(i));
|
|
31
|
+
hash = (hash * FNV_PRIME_64) & FNV_MASK_64;
|
|
32
|
+
}
|
|
33
|
+
return hash.toString(16).padStart(16, "0");
|
|
34
|
+
}
|
|
2
35
|
/**
|
|
3
36
|
* Returns a human-readable type label for use in error messages.
|
|
4
37
|
* Distinguishes null and undefined from other types instead of returning
|
|
@@ -152,15 +185,20 @@ export function requireOptionalGroup(className, value) {
|
|
|
152
185
|
}
|
|
153
186
|
/**
|
|
154
187
|
* Computes a stable cache/dedup key for an array of LiteralValues.
|
|
155
|
-
* Format: `{type}:{count}:{hashPrefix}` —
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
188
|
+
* Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is a 16-hex-character
|
|
189
|
+
* FNV-1a 64-bit hash over a canonical serialization of `values`. The canonical
|
|
190
|
+
* form first deduplicates `values` (to mirror the Set-based semantics of
|
|
191
|
+
* AttrLiteralIn / AttrLiteralNotIn — duplicates do not change the rule's
|
|
192
|
+
* behavior, so they must not change its `ruleType`), then applies
|
|
193
|
+
* `JSON.stringify(String(v))` to each remaining element before sorting and
|
|
194
|
+
* joining with `,`. The per-element quoting prevents separator collisions
|
|
161
195
|
* across different arrays (e.g. `["a,b", "c"]` vs `["a", "b,c"]`).
|
|
162
196
|
* The `{count}` segment reflects the post-dedup element count.
|
|
163
197
|
* Caller must guarantee values is a non-empty homogeneous LiteralValue[].
|
|
198
|
+
*
|
|
199
|
+
* The hash is non-cryptographic by design, but uses 64-bit output to keep
|
|
200
|
+
* the birthday-collision bound (~2^32) far above any realistic policy size
|
|
201
|
+
* and to resist deliberate collision construction.
|
|
164
202
|
*/
|
|
165
203
|
export function computeValuesKey(values) {
|
|
166
204
|
const elementType = typeof values[0];
|
|
@@ -169,8 +207,7 @@ export function computeValuesKey(values) {
|
|
|
169
207
|
.map((v) => JSON.stringify(String(v)))
|
|
170
208
|
.sort()
|
|
171
209
|
.join(",");
|
|
172
|
-
|
|
173
|
-
return `${elementType}:${unique.length}:${hash}`;
|
|
210
|
+
return `${elementType}:${unique.length}:${fnv1a64(joined)}`;
|
|
174
211
|
}
|
|
175
212
|
/**
|
|
176
213
|
* Applies a numeric comparison operator between left and right.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { CollectorContext, Rule, RuleCollector } from "@o3co/auth.policy-verifier.core";
|
|
2
|
+
/**
|
|
3
|
+
* Rule collector that emits a single `HasPermission` rule for the
|
|
4
|
+
* `{resource}.perm:{action}` permission string. Use when the effective
|
|
5
|
+
* permission is encoded directly into the token's permission attribute rather
|
|
6
|
+
* than derived from scopes.
|
|
7
|
+
*/
|
|
2
8
|
export declare class ResourceActionPermissionRuleCollector implements RuleCollector {
|
|
3
9
|
collect(context: CollectorContext): Promise<Rule[]>;
|
|
4
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceActionPermissionRuleCollector.d.mts","sourceRoot":"","sources":["../../../src/rules/collectors/ResourceActionPermissionRuleCollector.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResourceActionPermissionRuleCollector.d.mts","sourceRoot":"","sources":["../../../src/rules/collectors/ResourceActionPermissionRuleCollector.mts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAG7F;;;;;GAKG;AACH,qBAAa,qCAAsC,YAAW,aAAa;IACpE,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;CAIzD"}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
import { HasPermission } from "../HasPermission.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Rule collector that emits a single `HasPermission` rule for the
|
|
6
|
+
* `{resource}.perm:{action}` permission string. Use when the effective
|
|
7
|
+
* permission is encoded directly into the token's permission attribute rather
|
|
8
|
+
* than derived from scopes.
|
|
9
|
+
*/
|
|
2
10
|
export class ResourceActionPermissionRuleCollector {
|
|
3
11
|
async collect(context) {
|
|
4
12
|
const permission = `${context.resource.raw}.perm:${context.action}`;
|
|
@@ -1,14 +1,62 @@
|
|
|
1
1
|
import type { CollectorContext, Rule, RuleCollector } from "@o3co/auth.policy-verifier.core";
|
|
2
|
+
/** How the collector treats a token that carries no `scope` claim. */
|
|
3
|
+
export type ScopelessPolicy = "deny" | "skip";
|
|
4
|
+
/** Config entry accepted by `ResourceActionScopeRuleCollector`. */
|
|
5
|
+
export interface ResourceActionScopeRuleCollectorConfig {
|
|
6
|
+
/**
|
|
7
|
+
* `"deny"` (default) emits the scope rule for every request, so a scopeless
|
|
8
|
+
* token fails it. `"skip"` emits no rule for a scopeless token, leaving the
|
|
9
|
+
* scope group out of AND-evaluation.
|
|
10
|
+
*/
|
|
11
|
+
scopeless?: ScopelessPolicy;
|
|
12
|
+
/**
|
|
13
|
+
* Forwarded to `HasScope`. `false` (default) compares granted scopes
|
|
14
|
+
* literally; `true` additionally treats a bare granted scope `x` as
|
|
15
|
+
* `read:x`. See `HasScopeOptions.allowBareScopeRewrite`.
|
|
16
|
+
*/
|
|
17
|
+
allowBareScopeRewrite?: boolean;
|
|
18
|
+
}
|
|
2
19
|
/**
|
|
3
20
|
* Generates a HasScope rule derived from the request action and resource type.
|
|
4
21
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
22
|
+
* ## Scope as capability ceiling
|
|
23
|
+
*
|
|
24
|
+
* The JWT `scope` claim represents what the session **can request** — a
|
|
25
|
+
* capability ceiling — not what the session **has been granted**. Whether the
|
|
26
|
+
* operation is ultimately permitted is determined by the full rule pipeline
|
|
27
|
+
* (other collectors, resource-owner policy, etc.). This collector enforces the
|
|
28
|
+
* ceiling only: it produces a `HasScope` rule for the requested
|
|
29
|
+
* `{action}:{resourceType}`, which must be satisfied by the token's scopes.
|
|
30
|
+
*
|
|
31
|
+
* ## Matching
|
|
32
|
+
*
|
|
33
|
+
* The emitted `HasScope` compares scopes exactly and case-sensitively. An
|
|
34
|
+
* issuer that emits bare resource names (`project` rather than `read:project`)
|
|
35
|
+
* must opt in with `{ allowBareScopeRewrite: true }`; without it a bare scope
|
|
36
|
+
* satisfies nothing of the `{action}:{resourceType}` form.
|
|
37
|
+
*
|
|
38
|
+
* ## Behavior for scopeless tokens
|
|
39
|
+
*
|
|
40
|
+
* By default the rule is emitted regardless of whether the token carries a
|
|
41
|
+
* `scope` claim, so a scopeless token fails it. Dropping the rule instead would
|
|
42
|
+
* remove the scope group from AND-evaluation, and in a scope-only pipeline that
|
|
43
|
+
* turns "the token asserts no capability" into "every capability is allowed".
|
|
44
|
+
*
|
|
45
|
+
* Flows where the IdP does not issue a `scope` claim (e.g. DID-grant tokens)
|
|
46
|
+
* must opt out explicitly with `{ scopeless: "skip" }`, and only in a pipeline
|
|
47
|
+
* where another rule group authorizes the request — otherwise the request is
|
|
48
|
+
* left with no applicable rule, which the evaluator denies.
|
|
49
|
+
*
|
|
50
|
+
* For pipelines that exclusively serve scopeless flows, prefer collectors that
|
|
51
|
+
* derive rules from identity claims (e.g. DID, `sub`, role) instead.
|
|
8
52
|
*
|
|
9
|
-
*
|
|
53
|
+
* See https://github.com/o3co/auth.provider/issues/56 for background on why
|
|
54
|
+
* the IdP asserts identity, not permissions.
|
|
10
55
|
*/
|
|
11
56
|
export declare class ResourceActionScopeRuleCollector implements RuleCollector {
|
|
57
|
+
private readonly scopeless;
|
|
58
|
+
private readonly allowBareScopeRewrite;
|
|
59
|
+
constructor(config?: ResourceActionScopeRuleCollectorConfig);
|
|
12
60
|
collect(context: CollectorContext): Promise<Rule[]>;
|
|
13
61
|
}
|
|
14
62
|
//# sourceMappingURL=ResourceActionScopeRuleCollector.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceActionScopeRuleCollector.d.mts","sourceRoot":"","sources":["../../../src/rules/collectors/ResourceActionScopeRuleCollector.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResourceActionScopeRuleCollector.d.mts","sourceRoot":"","sources":["../../../src/rules/collectors/ResourceActionScopeRuleCollector.mts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAG7F,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAI9C,mEAAmE;AACnE,MAAM,WAAW,sCAAsC;IACtD;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,gCAAiC,YAAW,aAAa;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;gBAEpC,MAAM,CAAC,EAAE,sCAAsC;IAqBrD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;CAOzD"}
|
|
@@ -1,19 +1,67 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
import { HasScope } from "../HasScope.mjs";
|
|
4
|
+
const SCOPELESS_POLICIES = ["deny", "skip"];
|
|
2
5
|
/**
|
|
3
6
|
* Generates a HasScope rule derived from the request action and resource type.
|
|
4
7
|
*
|
|
5
|
-
*
|
|
6
|
-
* are generated. The scope rule group is therefore absent from AND-evaluation,
|
|
7
|
-
* allowing other rule groups to decide independently.
|
|
8
|
+
* ## Scope as capability ceiling
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* The JWT `scope` claim represents what the session **can request** — a
|
|
11
|
+
* capability ceiling — not what the session **has been granted**. Whether the
|
|
12
|
+
* operation is ultimately permitted is determined by the full rule pipeline
|
|
13
|
+
* (other collectors, resource-owner policy, etc.). This collector enforces the
|
|
14
|
+
* ceiling only: it produces a `HasScope` rule for the requested
|
|
15
|
+
* `{action}:{resourceType}`, which must be satisfied by the token's scopes.
|
|
16
|
+
*
|
|
17
|
+
* ## Matching
|
|
18
|
+
*
|
|
19
|
+
* The emitted `HasScope` compares scopes exactly and case-sensitively. An
|
|
20
|
+
* issuer that emits bare resource names (`project` rather than `read:project`)
|
|
21
|
+
* must opt in with `{ allowBareScopeRewrite: true }`; without it a bare scope
|
|
22
|
+
* satisfies nothing of the `{action}:{resourceType}` form.
|
|
23
|
+
*
|
|
24
|
+
* ## Behavior for scopeless tokens
|
|
25
|
+
*
|
|
26
|
+
* By default the rule is emitted regardless of whether the token carries a
|
|
27
|
+
* `scope` claim, so a scopeless token fails it. Dropping the rule instead would
|
|
28
|
+
* remove the scope group from AND-evaluation, and in a scope-only pipeline that
|
|
29
|
+
* turns "the token asserts no capability" into "every capability is allowed".
|
|
30
|
+
*
|
|
31
|
+
* Flows where the IdP does not issue a `scope` claim (e.g. DID-grant tokens)
|
|
32
|
+
* must opt out explicitly with `{ scopeless: "skip" }`, and only in a pipeline
|
|
33
|
+
* where another rule group authorizes the request — otherwise the request is
|
|
34
|
+
* left with no applicable rule, which the evaluator denies.
|
|
35
|
+
*
|
|
36
|
+
* For pipelines that exclusively serve scopeless flows, prefer collectors that
|
|
37
|
+
* derive rules from identity claims (e.g. DID, `sub`, role) instead.
|
|
38
|
+
*
|
|
39
|
+
* See https://github.com/o3co/auth.provider/issues/56 for background on why
|
|
40
|
+
* the IdP asserts identity, not permissions.
|
|
10
41
|
*/
|
|
11
42
|
export class ResourceActionScopeRuleCollector {
|
|
43
|
+
scopeless;
|
|
44
|
+
allowBareScopeRewrite;
|
|
45
|
+
constructor(config) {
|
|
46
|
+
const scopeless = config?.scopeless ?? "deny";
|
|
47
|
+
if (!SCOPELESS_POLICIES.includes(scopeless)) {
|
|
48
|
+
throw new Error(`ResourceActionScopeRuleCollector: scopeless must be one of ${SCOPELESS_POLICIES.join(", ")}, got "${scopeless}"`);
|
|
49
|
+
}
|
|
50
|
+
this.scopeless = scopeless;
|
|
51
|
+
// Validate the raw value before defaulting: `?? false` alone would treat an
|
|
52
|
+
// explicit `null` as "unset" and silently accept a misconfiguration. Only
|
|
53
|
+
// an absent key may fall back to the default.
|
|
54
|
+
const rawRewrite = config?.allowBareScopeRewrite;
|
|
55
|
+
if (rawRewrite !== undefined && typeof rawRewrite !== "boolean") {
|
|
56
|
+
throw new Error(`ResourceActionScopeRuleCollector: allowBareScopeRewrite must be a boolean, got "${rawRewrite}"`);
|
|
57
|
+
}
|
|
58
|
+
this.allowBareScopeRewrite = rawRewrite ?? false;
|
|
59
|
+
}
|
|
12
60
|
async collect(context) {
|
|
13
|
-
if (context.
|
|
61
|
+
if (this.scopeless === "skip" && context.subject.scope === undefined) {
|
|
14
62
|
return [];
|
|
15
63
|
}
|
|
16
64
|
const scope = `${context.action}:${context.resource.resourceType}`;
|
|
17
|
-
return [new HasScope(scope)];
|
|
65
|
+
return [new HasScope(scope, { allowBareScopeRewrite: this.allowBareScopeRewrite })];
|
|
18
66
|
}
|
|
19
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@o3co/auth.policy-verifier.builtins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -31,15 +31,18 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@o3co/auth.policy-verifier.core": "0.
|
|
34
|
+
"@o3co/auth.policy-verifier.core": "0.5.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@types/node": "^
|
|
38
|
-
"vitest": "^4.1.
|
|
37
|
+
"@types/node": "^26.4.0",
|
|
38
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
39
|
+
"vitest": "^4.1.11"
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
41
42
|
"prebuild": "rimraf dist",
|
|
42
43
|
"build": "tsc",
|
|
43
|
-
"
|
|
44
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:coverage": "vitest run --coverage"
|
|
44
47
|
}
|
|
45
48
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PayloadScopeCollector.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/collectors/PayloadScopeCollector.test.mts"],"names":[],"mappings":""}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { ATTR_SCOPES } from "@o3co/auth.policy-verifier.core";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { PayloadScopeCollector } from "#/collectors/PayloadScopeCollector.mjs";
|
|
4
|
-
const makeContext = (scope) => ({
|
|
5
|
-
payload: { scope },
|
|
6
|
-
resource: { raw: "test:1", resourceType: "test", resourceId: "1" },
|
|
7
|
-
action: "read",
|
|
8
|
-
});
|
|
9
|
-
describe("PayloadScopeCollector", () => {
|
|
10
|
-
const collector = new PayloadScopeCollector();
|
|
11
|
-
it("extracts scopes from payload", async () => {
|
|
12
|
-
const attrs = await collector.collect(makeContext("read:user write:doc"));
|
|
13
|
-
expect(attrs.get(ATTR_SCOPES)).toEqual(["read:user", "write:doc"]);
|
|
14
|
-
});
|
|
15
|
-
it("returns empty array when scope missing", async () => {
|
|
16
|
-
const ctx = {
|
|
17
|
-
payload: {},
|
|
18
|
-
resource: { raw: "test:1", resourceType: "test", resourceId: "1" },
|
|
19
|
-
action: "read",
|
|
20
|
-
};
|
|
21
|
-
const attrs = await collector.collect(ctx);
|
|
22
|
-
expect(attrs.get(ATTR_SCOPES)).toEqual([]);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PayloadSubjectIdCollector.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/collectors/PayloadSubjectIdCollector.test.mts"],"names":[],"mappings":""}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { ATTR_CLIENT_ID, ATTR_USER_ID } from "@o3co/auth.policy-verifier.core";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { PayloadSubjectIdCollector } from "#/collectors/PayloadSubjectIdCollector.mjs";
|
|
4
|
-
describe("PayloadSubjectIdCollector", () => {
|
|
5
|
-
const collector = new PayloadSubjectIdCollector();
|
|
6
|
-
it("extracts userId from payload.sub", async () => {
|
|
7
|
-
const ctx = {
|
|
8
|
-
payload: { sub: "u1" },
|
|
9
|
-
resource: { raw: "test:1", resourceType: "test", resourceId: "1" },
|
|
10
|
-
action: "read",
|
|
11
|
-
};
|
|
12
|
-
const attrs = await collector.collect(ctx);
|
|
13
|
-
expect(attrs.get(ATTR_USER_ID)).toBe("u1");
|
|
14
|
-
});
|
|
15
|
-
it("extracts clientId from payload.azp", async () => {
|
|
16
|
-
const ctx = {
|
|
17
|
-
payload: { azp: "c1" },
|
|
18
|
-
resource: { raw: "test:1", resourceType: "test", resourceId: "1" },
|
|
19
|
-
action: "read",
|
|
20
|
-
};
|
|
21
|
-
const attrs = await collector.collect(ctx);
|
|
22
|
-
expect(attrs.get(ATTR_CLIENT_ID)).toBe("c1");
|
|
23
|
-
});
|
|
24
|
-
it("returns empty map when neither sub nor azp present", async () => {
|
|
25
|
-
const ctx = {
|
|
26
|
-
payload: {},
|
|
27
|
-
resource: { raw: "test:1", resourceType: "test", resourceId: "1" },
|
|
28
|
-
action: "read",
|
|
29
|
-
};
|
|
30
|
-
const attrs = await collector.collect(ctx);
|
|
31
|
-
expect(attrs.size).toBe(0);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StaticPermissionCollector.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/collectors/StaticPermissionCollector.test.mts"],"names":[],"mappings":""}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ATTR_PERMISSIONS } from "@o3co/auth.policy-verifier.core";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { StaticPermissionCollector } from "#/collectors/StaticPermissionCollector.mjs";
|
|
4
|
-
const stubContext = {
|
|
5
|
-
payload: {},
|
|
6
|
-
resource: { raw: "test:1", resourceType: "test", resourceId: "1" },
|
|
7
|
-
action: "read",
|
|
8
|
-
};
|
|
9
|
-
describe("StaticPermissionCollector", () => {
|
|
10
|
-
it("returns configured permissions", async () => {
|
|
11
|
-
const collector = new StaticPermissionCollector({
|
|
12
|
-
permissions: ["project:*.perm:read", "document:*.perm:write"],
|
|
13
|
-
});
|
|
14
|
-
const attrs = await collector.collect(stubContext);
|
|
15
|
-
expect(attrs.get(ATTR_PERMISSIONS)).toEqual(["project:*.perm:read", "document:*.perm:write"]);
|
|
16
|
-
});
|
|
17
|
-
it("returns empty array when no permissions configured", async () => {
|
|
18
|
-
const collector = new StaticPermissionCollector({ permissions: [] });
|
|
19
|
-
const attrs = await collector.collect(stubContext);
|
|
20
|
-
expect(attrs.get(ATTR_PERMISSIONS)).toEqual([]);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StaticRoleCollector.test.d.mts","sourceRoot":"","sources":["../../../src/__tests__/collectors/StaticRoleCollector.test.mts"],"names":[],"mappings":""}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { StaticRoleCollector } from "#/collectors/StaticRoleCollector.mjs";
|
|
4
|
-
const stubContext = {
|
|
5
|
-
payload: {},
|
|
6
|
-
resource: { raw: "test:1", resourceType: "test", resourceId: "1" },
|
|
7
|
-
action: "read",
|
|
8
|
-
};
|
|
9
|
-
describe("StaticRoleCollector", () => {
|
|
10
|
-
it("returns configured roles", async () => {
|
|
11
|
-
const roles = [
|
|
12
|
-
{ name: "admin", permissions: ["*"] },
|
|
13
|
-
{ name: "viewer", permissions: ["project:*.perm:read"] },
|
|
14
|
-
];
|
|
15
|
-
const collector = new StaticRoleCollector({ roles });
|
|
16
|
-
const attrs = await collector.collect(stubContext);
|
|
17
|
-
expect(attrs.get(ATTR_ROLES)).toEqual(roles);
|
|
18
|
-
});
|
|
19
|
-
it("returns empty array when no roles configured", async () => {
|
|
20
|
-
const collector = new StaticRoleCollector({ roles: [] });
|
|
21
|
-
const attrs = await collector.collect(stubContext);
|
|
22
|
-
expect(attrs.get(ATTR_ROLES)).toEqual([]);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"module.test.d.mts","sourceRoot":"","sources":["../../src/__tests__/module.test.mts"],"names":[],"mappings":""}
|