simple-authz 1.0.5 → 1.2.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.md +152 -348
- package/bin/simple-authz/index.js +132 -0
- package/lib/cache.js +149 -0
- package/lib/core/authz.js +73 -5
- package/lib/core/middleware.js +44 -0
- package/lib/engine/conditionCompiler.js +122 -21
- package/lib/engine/conditionExecutor.js +30 -6
- package/lib/rules/parser.js +62 -12
- package/lib/rules/validator.js +16 -10
- package/package.json +52 -36
- package/types/index.d.ts +31 -0
- package/simple-authz-1.0.1.tgz +0 -0
- package/simple-authz-1.0.2.tgz +0 -0
- package/simple-authz-1.0.3.tgz +0 -0
- package/simple-authz-1.0.4.tgz +0 -0
- package/simple-authz-1.0.5.tgz +0 -0
- package/simple-authz-v1.0.1.tgz +0 -0
package/lib/rules/parser.js
CHANGED
|
@@ -1,38 +1,88 @@
|
|
|
1
1
|
const fs = require("fs")
|
|
2
2
|
|
|
3
|
+
const KNOWN_KEYS = ["role", "action", "resource", "condition"]
|
|
4
|
+
|
|
3
5
|
function parseRules(filePath) {
|
|
4
6
|
|
|
5
7
|
const content = fs.readFileSync(filePath, "utf8")
|
|
6
|
-
|
|
7
|
-
const lines = content.split("\n").map(l => l.trim())
|
|
8
|
+
const rawLines = content.split("\n")
|
|
8
9
|
|
|
9
10
|
const rules = []
|
|
10
11
|
let current = null
|
|
12
|
+
let ruleStartLine = null
|
|
13
|
+
|
|
14
|
+
rawLines.forEach((rawLine, idx) => {
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
const lineNumber = idx + 1
|
|
17
|
+
const line = rawLine.trim()
|
|
13
18
|
|
|
14
|
-
if (!line || line.startsWith("#"))
|
|
19
|
+
if (!line || line.startsWith("#")) return
|
|
15
20
|
|
|
16
21
|
if (line === "rule") {
|
|
22
|
+
|
|
23
|
+
// Previously this silently overwrote `current`, discarding an
|
|
24
|
+
// already-open rule block that was missing its "end".
|
|
25
|
+
if (current) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Policy parse error at line ${lineNumber}: found "rule" but the ` +
|
|
28
|
+
`rule opened at line ${ruleStartLine} is still unclosed (missing "end").`
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
current = {}
|
|
33
|
+
ruleStartLine = lineNumber
|
|
34
|
+
return
|
|
18
35
|
}
|
|
19
36
|
|
|
20
|
-
|
|
37
|
+
if (line === "end") {
|
|
38
|
+
|
|
39
|
+
// Previously this pushed `current` (= null for a stray "end")
|
|
40
|
+
// straight into `rules`, which crashed later in validateRules
|
|
41
|
+
// with an unhelpful "Cannot read properties of null".
|
|
42
|
+
if (!current) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Policy parse error at line ${lineNumber}: found "end" with no matching "rule".`
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
21
48
|
rules.push(current)
|
|
22
49
|
current = null
|
|
50
|
+
ruleStartLine = null
|
|
51
|
+
return
|
|
23
52
|
}
|
|
24
53
|
|
|
25
|
-
|
|
54
|
+
if (!current) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Policy parse error at line ${lineNumber}: "${line}" appears outside ` +
|
|
57
|
+
`of a rule/end block.`
|
|
58
|
+
)
|
|
59
|
+
}
|
|
26
60
|
|
|
27
|
-
|
|
28
|
-
|
|
61
|
+
const [key, ...rest] = line.split(" ")
|
|
62
|
+
const value = rest.join(" ")
|
|
29
63
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
// Previously an unrecognized key (e.g. "roel" typo'd for "role")
|
|
65
|
+
// was silently dropped, leaving the rule incomplete with no warning.
|
|
66
|
+
if (!KNOWN_KEYS.includes(key)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Policy parse error at line ${lineNumber}: unknown key "${key}" ` +
|
|
69
|
+
`(expected one of: ${KNOWN_KEYS.join(", ")}).`
|
|
70
|
+
)
|
|
71
|
+
}
|
|
34
72
|
|
|
73
|
+
if (!value) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Policy parse error at line ${lineNumber}: "${key}" has no value.`
|
|
76
|
+
)
|
|
35
77
|
}
|
|
78
|
+
|
|
79
|
+
current[key] = value
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (current) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Policy parse error: rule opened at line ${ruleStartLine} was never closed with "end".`
|
|
85
|
+
)
|
|
36
86
|
}
|
|
37
87
|
|
|
38
88
|
return rules
|
package/lib/rules/validator.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { compileCondition } = require("../engine/conditionCompiler")
|
|
2
|
+
|
|
1
3
|
function validateRules(rules) {
|
|
2
4
|
|
|
3
5
|
const errors = []
|
|
@@ -20,17 +22,21 @@ function validateRules(rules) {
|
|
|
20
22
|
|
|
21
23
|
if (rule.condition) {
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
// Previously this only checked whether an operator substring
|
|
26
|
+
// (e.g. "==") appeared ANYWHERE in the condition text — it never
|
|
27
|
+
// actually verified the condition parses. A condition like
|
|
28
|
+
// "doc.status ==" (missing right-hand side) or one with mismatched
|
|
29
|
+
// AND/OR usage would pass this check and then either crash later
|
|
30
|
+
// deep inside the compiler with no rule number attached, or (worse)
|
|
31
|
+
// silently compile into a broken/always-false comparison.
|
|
32
|
+
//
|
|
33
|
+
// Actually attempting to compile it here catches real structural
|
|
34
|
+
// problems immediately, at the rule they belong to.
|
|
35
|
+
try {
|
|
36
|
+
compileCondition(rule.condition)
|
|
37
|
+
} catch (err) {
|
|
32
38
|
errors.push(
|
|
33
|
-
`Rule ${ruleNumber}: invalid condition "${rule.condition}"`
|
|
39
|
+
`Rule ${ruleNumber}: invalid condition "${rule.condition}" — ${err.message}`
|
|
34
40
|
)
|
|
35
41
|
}
|
|
36
42
|
}
|
package/package.json
CHANGED
|
@@ -1,36 +1,52 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "simple-authz",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Lightweight policy-based authorization engine for Node.js using TOON policy language",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "
|
|
8
|
-
"dev": "node lib/core/authz.js"
|
|
9
|
-
},
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "simple-authz",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Lightweight policy-based authorization engine for Node.js using TOON policy language",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"dev": "node lib/core/authz.js"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"simple-authz": "bin/simple-authz/index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"chalk": "^4.1.2",
|
|
15
|
+
"commander": "^11.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"jest": "^30.3.0"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"authorization",
|
|
22
|
+
"access-control",
|
|
23
|
+
"auth",
|
|
24
|
+
"policy-engine",
|
|
25
|
+
"rbac",
|
|
26
|
+
"abac",
|
|
27
|
+
"permissions",
|
|
28
|
+
"nodejs-auth",
|
|
29
|
+
"authorization-library",
|
|
30
|
+
"security",
|
|
31
|
+
"policy-based-access",
|
|
32
|
+
"authz",
|
|
33
|
+
"role-based-access-control",
|
|
34
|
+
"authorization-engine"
|
|
35
|
+
],
|
|
36
|
+
"types": "types/index.d.ts",
|
|
37
|
+
"files": [
|
|
38
|
+
"lib/",
|
|
39
|
+
"types/",
|
|
40
|
+
"package.json"
|
|
41
|
+
],
|
|
42
|
+
"author": "Dhruvil",
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/dhruvil05/simple-authz"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/dhruvil05/simple-authz/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/dhruvil05/simple-authz#readme"
|
|
52
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare module "simple-authz" {
|
|
2
|
+
interface User {
|
|
3
|
+
role?: string;
|
|
4
|
+
roles?: string[];
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ExplainResult {
|
|
9
|
+
allowed: boolean;
|
|
10
|
+
role?: string;
|
|
11
|
+
resource?: string;
|
|
12
|
+
action?: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
condition?: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class Authz {
|
|
18
|
+
load(path: string): void;
|
|
19
|
+
can(user: User, action: string, resource: string, data?: any): boolean;
|
|
20
|
+
explain(
|
|
21
|
+
user: User,
|
|
22
|
+
action: string,
|
|
23
|
+
resource: string,
|
|
24
|
+
data?: any,
|
|
25
|
+
): ExplainResult;
|
|
26
|
+
middleware(action: string, resource: string): any;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const authz: Authz;
|
|
30
|
+
export = authz;
|
|
31
|
+
}
|
package/simple-authz-1.0.1.tgz
DELETED
|
Binary file
|
package/simple-authz-1.0.2.tgz
DELETED
|
Binary file
|
package/simple-authz-1.0.3.tgz
DELETED
|
Binary file
|
package/simple-authz-1.0.4.tgz
DELETED
|
Binary file
|
package/simple-authz-1.0.5.tgz
DELETED
|
Binary file
|
package/simple-authz-v1.0.1.tgz
DELETED
|
Binary file
|