@o3co/auth.policy-verifier.builtins 0.4.0 → 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.
|
@@ -26,6 +26,12 @@ import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
|
|
|
26
26
|
* literal halves around the `*` still compare exactly and case-sensitively.
|
|
27
27
|
* Multiple wildcards are rejected outright because the two-part split would
|
|
28
28
|
* silently drop segments and over-grant.
|
|
29
|
+
*
|
|
30
|
+
* The two halves must not overlap in the required permission (#180): a grant
|
|
31
|
+
* of `"posts.*.read"` does not match `"posts.read"`, where the single `.`
|
|
32
|
+
* would satisfy `startsWith` and `endsWith` at once. The wildcard may match
|
|
33
|
+
* the empty string, but each character of the required permission counts
|
|
34
|
+
* toward at most one half.
|
|
29
35
|
*/
|
|
30
36
|
export declare class HasPermission implements Rule {
|
|
31
37
|
private permission;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HasPermission.d.mts","sourceRoot":"","sources":["../../src/rules/HasPermission.mts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAQ,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGtF
|
|
1
|
+
{"version":3,"file":"HasPermission.d.mts","sourceRoot":"","sources":["../../src/rules/HasPermission.mts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAQ,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,aAAc,YAAW,IAAI;IAK7B,OAAO,CAAC,UAAU;IAJ9B,QAAQ,CAAC,QAAQ,gBAAgB;IACjC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEL,UAAU,EAAE,MAAM;IAItC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAoB1C,OAAO,CAAC,KAAK;CA0Bb"}
|
|
@@ -28,6 +28,12 @@ import { ATTR_PERMISSIONS, ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
|
|
|
28
28
|
* literal halves around the `*` still compare exactly and case-sensitively.
|
|
29
29
|
* Multiple wildcards are rejected outright because the two-part split would
|
|
30
30
|
* silently drop segments and over-grant.
|
|
31
|
+
*
|
|
32
|
+
* The two halves must not overlap in the required permission (#180): a grant
|
|
33
|
+
* of `"posts.*.read"` does not match `"posts.read"`, where the single `.`
|
|
34
|
+
* would satisfy `startsWith` and `endsWith` at once. The wildcard may match
|
|
35
|
+
* the empty string, but each character of the required permission counts
|
|
36
|
+
* toward at most one half.
|
|
31
37
|
*/
|
|
32
38
|
export class HasPermission {
|
|
33
39
|
permission;
|
|
@@ -39,10 +45,20 @@ export class HasPermission {
|
|
|
39
45
|
this.message = `User does not have required permission: ${permission}`;
|
|
40
46
|
}
|
|
41
47
|
verify(attrs) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
// Nothing about these values is taken on the cast's word (#180): both
|
|
49
|
+
// arrive from collectors, and a store-backed one can hand back anything
|
|
50
|
+
// — a non-array under either key, a role whose `permissions` is missing
|
|
51
|
+
// or not an array, non-string entries. Every malformed shape is ignored
|
|
52
|
+
// rather than half-honoured: an entry-level guard alone still throws on
|
|
53
|
+
// a non-array container, and spreading a bare string under
|
|
54
|
+
// `permissions` would splay it into characters, letting a
|
|
55
|
+
// one-character requirement match a value that was never a grant.
|
|
56
|
+
const direct = attrs.get(ATTR_PERMISSIONS);
|
|
57
|
+
const roles = attrs.get(ATTR_ROLES);
|
|
58
|
+
const fromRoles = (Array.isArray(roles) ? roles : []).flatMap((role) => Array.isArray(role?.permissions) ? role.permissions : []);
|
|
59
|
+
const all = [...(Array.isArray(direct) ? direct : []), ...fromRoles];
|
|
60
|
+
// Non-string entries never match and never throw, as in HasScope (#116).
|
|
61
|
+
return all.some((p) => typeof p === "string" && this.match(p, this.permission));
|
|
46
62
|
}
|
|
47
63
|
match(permission, required) {
|
|
48
64
|
if (permission === "*")
|
|
@@ -60,6 +76,13 @@ export class HasPermission {
|
|
|
60
76
|
if ((permission.match(/\*/g) ?? []).length > 1)
|
|
61
77
|
return false;
|
|
62
78
|
const [prefix, suffix] = permission.split("*");
|
|
79
|
+
// The halves must not overlap in `required` (#180): "posts.*.read"
|
|
80
|
+
// must not match "posts.read", where the single "." satisfies both
|
|
81
|
+
// startsWith and endsWith. Length is the whole check — the wildcard
|
|
82
|
+
// may match the empty string, but a character may only count toward
|
|
83
|
+
// one half.
|
|
84
|
+
if (required.length < prefix.length + suffix.length)
|
|
85
|
+
return false;
|
|
63
86
|
return (!prefix || required.startsWith(prefix)) && (!suffix || required.endsWith(suffix));
|
|
64
87
|
}
|
|
65
88
|
return false;
|
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,12 +31,12 @@
|
|
|
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": "^26.
|
|
38
|
-
"@vitest/coverage-v8": "^4.1.
|
|
39
|
-
"vitest": "^4.1.
|
|
37
|
+
"@types/node": "^26.4.0",
|
|
38
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
39
|
+
"vitest": "^4.1.11"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"prebuild": "rimraf dist",
|