@nmnmcc/ability 0.3.0 → 1.0.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 +169 -14
- package/dist/Authorizer.d.ts +64 -39
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js +291 -6
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +444 -65
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +1007 -25
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +27 -17
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +52 -10
- package/dist/Domain.js.map +1 -1
- package/dist/HttpApi.d.ts +84 -40
- package/dist/HttpApi.d.ts.map +1 -1
- package/dist/HttpApi.js +101 -11
- package/dist/HttpApi.js.map +1 -1
- package/dist/Policy.d.ts +311 -53
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +1212 -35
- package/dist/Policy.js.map +1 -1
- package/dist/Resource.d.ts +167 -16
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +784 -11
- package/dist/Resource.js.map +1 -1
- package/dist/Rule.d.ts +113 -27
- package/dist/Rule.d.ts.map +1 -1
- package/dist/Rule.js +217 -10
- package/dist/Rule.js.map +1 -1
- package/dist/index.js +4 -4
- package/package.json +1 -1
- package/src/Authorizer.ts +623 -59
- package/src/Condition.ts +2403 -112
- package/src/Domain.ts +79 -22
- package/src/HttpApi.ts +223 -23
- package/src/Policy.ts +2833 -93
- package/src/Resource.ts +1537 -22
- package/src/Rule.ts +461 -43
- package/dist/internal/authorizer.d.ts +0 -28
- package/dist/internal/authorizer.d.ts.map +0 -1
- package/dist/internal/authorizer.js +0 -153
- package/dist/internal/authorizer.js.map +0 -1
- package/dist/internal/condition.d.ts +0 -11
- package/dist/internal/condition.d.ts.map +0 -1
- package/dist/internal/condition.js +0 -393
- package/dist/internal/condition.js.map +0 -1
- package/dist/internal/domain.js +0 -34
- package/dist/internal/domain.js.map +0 -1
- package/dist/internal/httpApi.js +0 -41
- package/dist/internal/httpApi.js.map +0 -1
- package/dist/internal/policy.js +0 -524
- package/dist/internal/policy.js.map +0 -1
- package/dist/internal/resource.js +0 -66
- package/dist/internal/resource.js.map +0 -1
- package/dist/internal/rule.js +0 -131
- package/dist/internal/rule.js.map +0 -1
- package/src/internal/authorizer.ts +0 -271
- package/src/internal/condition.ts +0 -607
- package/src/internal/domain.ts +0 -56
- package/src/internal/httpApi.ts +0 -84
- package/src/internal/policy.ts +0 -815
- package/src/internal/resource.ts +0 -98
- package/src/internal/rule.ts +0 -203
package/src/internal/resource.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import * as Predicate from "effect/Predicate"
|
|
2
|
-
import * as Schema from "effect/Schema"
|
|
3
|
-
import type * as Resource from "../Resource.ts"
|
|
4
|
-
|
|
5
|
-
/** @internal */
|
|
6
|
-
export const TypeId = "~@nmnmcc/ability/Resource" as const
|
|
7
|
-
|
|
8
|
-
const fieldRefs = (fields: Schema.Struct.Fields) => {
|
|
9
|
-
const refs: Record<string, Resource.FieldRef<string>> = {}
|
|
10
|
-
for (const key of Object.keys(fields)) {
|
|
11
|
-
refs[key] = Object.freeze({ _tag: "Field", field: key })
|
|
12
|
-
}
|
|
13
|
-
return Object.freeze(refs)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const hasFields = (
|
|
17
|
-
schema: unknown,
|
|
18
|
-
): schema is { readonly fields: Schema.Struct.Fields } =>
|
|
19
|
-
Predicate.hasProperty(schema, "fields") && Predicate.isObject(schema.fields)
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Gives the paths of all data fields. A nested struct gives one path for each
|
|
23
|
-
* of its own fields, with a dot between the names.
|
|
24
|
-
*/
|
|
25
|
-
const collectFieldPaths = (
|
|
26
|
-
fields: Schema.Struct.Fields,
|
|
27
|
-
prefix: string,
|
|
28
|
-
output: Array<string>,
|
|
29
|
-
): void => {
|
|
30
|
-
for (const key of Object.keys(fields)) {
|
|
31
|
-
const schema = fields[key]
|
|
32
|
-
const path = prefix === "" ? key : `${prefix}.${key}`
|
|
33
|
-
if (hasFields(schema)) {
|
|
34
|
-
collectFieldPaths(schema.fields, path, output)
|
|
35
|
-
} else {
|
|
36
|
-
output.push(path)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const makeDescriptor = (
|
|
42
|
-
kind: string,
|
|
43
|
-
options: Resource.Options<ReadonlyArray<string>, Schema.Struct.Fields>,
|
|
44
|
-
schema: Schema.Top,
|
|
45
|
-
): Resource.Descriptor => {
|
|
46
|
-
const fieldPaths: Array<string> = []
|
|
47
|
-
collectFieldPaths(options.fields, "", fieldPaths)
|
|
48
|
-
return Object.freeze({
|
|
49
|
-
kind,
|
|
50
|
-
actions: Object.freeze([...options.actions]),
|
|
51
|
-
path:
|
|
52
|
-
options.path === undefined
|
|
53
|
-
? undefined
|
|
54
|
-
: Object.freeze([...options.path(fieldRefs(options.fields))]),
|
|
55
|
-
fieldPaths: Object.freeze(fieldPaths),
|
|
56
|
-
schema,
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** @internal */
|
|
61
|
-
export const Class =
|
|
62
|
-
(identifier?: string) =>
|
|
63
|
-
(
|
|
64
|
-
kind: string,
|
|
65
|
-
options: Resource.Options<ReadonlyArray<string>, Schema.Struct.Fields>,
|
|
66
|
-
) => {
|
|
67
|
-
const Base = Schema.TaggedClass<any>(identifier)(kind, options.fields)
|
|
68
|
-
const descriptor = makeDescriptor(kind, options, Base)
|
|
69
|
-
return class extends Base {
|
|
70
|
-
static readonly [TypeId] = descriptor
|
|
71
|
-
static readonly kind = kind
|
|
72
|
-
static readonly actions = descriptor.actions
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** @internal */
|
|
77
|
-
export const make = (
|
|
78
|
-
kind: string,
|
|
79
|
-
options: Resource.Options<ReadonlyArray<string>, Schema.Struct.Fields>,
|
|
80
|
-
) => {
|
|
81
|
-
const schema = Schema.TaggedStruct(kind, options.fields)
|
|
82
|
-
const descriptor = makeDescriptor(kind, options, schema)
|
|
83
|
-
return Object.freeze({
|
|
84
|
-
[TypeId]: descriptor,
|
|
85
|
-
kind,
|
|
86
|
-
actions: descriptor.actions,
|
|
87
|
-
schema,
|
|
88
|
-
make: (input: object) => schema.make(input as never),
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** @internal */
|
|
93
|
-
export const isResource = (u: unknown): u is Resource.Any =>
|
|
94
|
-
Predicate.hasProperty(u, TypeId)
|
|
95
|
-
|
|
96
|
-
/** @internal */
|
|
97
|
-
export const descriptor = (resource: Resource.Any): Resource.Descriptor =>
|
|
98
|
-
resource[TypeId]
|
package/src/internal/rule.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import * as Equal from "effect/Equal"
|
|
2
|
-
import * as Formatter from "effect/Formatter"
|
|
3
|
-
import * as Hash from "effect/Hash"
|
|
4
|
-
import * as Inspectable from "effect/Inspectable"
|
|
5
|
-
import * as Pipeable from "effect/Pipeable"
|
|
6
|
-
import * as Predicate from "effect/Predicate"
|
|
7
|
-
import type * as Condition from "../Condition.ts"
|
|
8
|
-
import type * as Domain from "../Domain.ts"
|
|
9
|
-
import type * as Rule from "../Rule.ts"
|
|
10
|
-
import { builder as conditionBuilder } from "./condition.ts"
|
|
11
|
-
import { isResource, descriptor } from "./resource.ts"
|
|
12
|
-
|
|
13
|
-
/** @internal */
|
|
14
|
-
export const TypeId = "~@nmnmcc/ability/Rule" as const
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The plain data of a rule.
|
|
18
|
-
*
|
|
19
|
-
* @internal
|
|
20
|
-
*/
|
|
21
|
-
export interface RuleData {
|
|
22
|
-
readonly _tag: "Allow" | "Deny"
|
|
23
|
-
readonly actions: ReadonlyArray<string>
|
|
24
|
-
readonly resources: ReadonlyArray<string>
|
|
25
|
-
readonly when?: Condition.Ast
|
|
26
|
-
readonly fields?: ReadonlyArray<string>
|
|
27
|
-
readonly reason?: string
|
|
28
|
-
readonly id?: string
|
|
29
|
-
readonly meta?: unknown
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** @internal */
|
|
33
|
-
export const toData = (rule: RuleData): RuleData => {
|
|
34
|
-
const data: {
|
|
35
|
-
-readonly [K in keyof RuleData]: RuleData[K]
|
|
36
|
-
} = {
|
|
37
|
-
_tag: rule._tag,
|
|
38
|
-
actions: rule.actions,
|
|
39
|
-
resources: rule.resources,
|
|
40
|
-
}
|
|
41
|
-
if (rule.when !== undefined) data.when = rule.when
|
|
42
|
-
if (rule.fields !== undefined) data.fields = rule.fields
|
|
43
|
-
if (rule.reason !== undefined) data.reason = rule.reason
|
|
44
|
-
if (rule.id !== undefined) data.id = rule.id
|
|
45
|
-
if (rule.meta !== undefined) data.meta = rule.meta
|
|
46
|
-
return data
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function* ruleIterator<A>(rule: A): Generator<A, A, unknown> {
|
|
50
|
-
yield rule
|
|
51
|
-
return rule
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const Proto = {
|
|
55
|
-
...Pipeable.Prototype,
|
|
56
|
-
[TypeId]: TypeId,
|
|
57
|
-
[Symbol.iterator](this: Rule.Any) {
|
|
58
|
-
return ruleIterator(this)
|
|
59
|
-
},
|
|
60
|
-
[Equal.symbol](this: RuleData, that: unknown): boolean {
|
|
61
|
-
return isRule(that) && Equal.equals(toData(this), toData(that))
|
|
62
|
-
},
|
|
63
|
-
[Hash.symbol](this: RuleData): number {
|
|
64
|
-
return Hash.structure(toData(this))
|
|
65
|
-
},
|
|
66
|
-
toJSON(this: RuleData) {
|
|
67
|
-
return toData(this)
|
|
68
|
-
},
|
|
69
|
-
toString(this: RuleData) {
|
|
70
|
-
return Formatter.format(toData(this), { space: 2 })
|
|
71
|
-
},
|
|
72
|
-
[Inspectable.NodeInspectSymbol](this: RuleData) {
|
|
73
|
-
return toData(this)
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Makes a rule from plain data. It does not check the data.
|
|
79
|
-
*
|
|
80
|
-
* @internal
|
|
81
|
-
*/
|
|
82
|
-
export const make = (data: RuleData): Rule.Any => {
|
|
83
|
-
const rule = Object.create(Proto) as {
|
|
84
|
-
-readonly [K in keyof RuleData]: RuleData[K]
|
|
85
|
-
}
|
|
86
|
-
Object.assign(rule, toData(data))
|
|
87
|
-
rule.actions = Object.freeze([...data.actions])
|
|
88
|
-
rule.resources = Object.freeze([...data.resources])
|
|
89
|
-
if (data.fields !== undefined) rule.fields = Object.freeze([...data.fields])
|
|
90
|
-
return Object.freeze(rule) as Rule.Any
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** @internal */
|
|
94
|
-
export const isRule = (u: unknown): u is Rule.Any =>
|
|
95
|
-
Predicate.hasProperty(u, TypeId)
|
|
96
|
-
|
|
97
|
-
const toArray = <A>(value: A | ReadonlyArray<A>): ReadonlyArray<A> =>
|
|
98
|
-
Array.isArray(value) ? value : [value as A]
|
|
99
|
-
|
|
100
|
-
const kindName = (target: unknown): string =>
|
|
101
|
-
isResource(target) ? descriptor(target).kind : (target as string)
|
|
102
|
-
|
|
103
|
-
const makeFromOptions =
|
|
104
|
-
(tag: "Allow" | "Deny") =>
|
|
105
|
-
(
|
|
106
|
-
action: string | ReadonlyArray<string>,
|
|
107
|
-
resource: unknown,
|
|
108
|
-
options?: {
|
|
109
|
-
readonly when?: (builder: unknown) => Condition.Ast
|
|
110
|
-
readonly fields?: ReadonlyArray<string>
|
|
111
|
-
readonly reason?: string
|
|
112
|
-
readonly id?: string
|
|
113
|
-
readonly meta?: unknown
|
|
114
|
-
},
|
|
115
|
-
): Rule.Any => {
|
|
116
|
-
const data: {
|
|
117
|
-
-readonly [K in keyof RuleData]: RuleData[K]
|
|
118
|
-
} = {
|
|
119
|
-
_tag: tag,
|
|
120
|
-
actions: toArray(action),
|
|
121
|
-
resources: toArray(resource).map(kindName),
|
|
122
|
-
}
|
|
123
|
-
if (options?.when !== undefined)
|
|
124
|
-
data.when = options.when(conditionBuilder())
|
|
125
|
-
if (options?.fields !== undefined) data.fields = options.fields
|
|
126
|
-
if (options?.reason !== undefined) data.reason = options.reason
|
|
127
|
-
if (options?.id !== undefined) data.id = options.id
|
|
128
|
-
if (options?.meta !== undefined) data.meta = options.meta
|
|
129
|
-
return make(data)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const sharedBuilder = Object.freeze({
|
|
133
|
-
allow: makeFromOptions("Allow"),
|
|
134
|
-
deny: makeFromOptions("Deny"),
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
/** @internal */
|
|
138
|
-
export const builder = (_domain: Domain.Any): any => sharedBuilder
|
|
139
|
-
|
|
140
|
-
const patternCache = new Map<string, RegExp>()
|
|
141
|
-
|
|
142
|
-
const compilePattern = (pattern: string): RegExp => {
|
|
143
|
-
const cached = patternCache.get(pattern)
|
|
144
|
-
if (cached !== undefined) {
|
|
145
|
-
return cached
|
|
146
|
-
}
|
|
147
|
-
let source = ""
|
|
148
|
-
let index = 0
|
|
149
|
-
while (index < pattern.length) {
|
|
150
|
-
const char = pattern[index]!
|
|
151
|
-
if (char === "*") {
|
|
152
|
-
if (pattern[index + 1] === "*") {
|
|
153
|
-
source += ".*"
|
|
154
|
-
index = index + 2
|
|
155
|
-
continue
|
|
156
|
-
}
|
|
157
|
-
source += "[^.]*"
|
|
158
|
-
} else {
|
|
159
|
-
source += char.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
160
|
-
}
|
|
161
|
-
index = index + 1
|
|
162
|
-
}
|
|
163
|
-
const regex = new RegExp(`^${source}$`)
|
|
164
|
-
patternCache.set(pattern, regex)
|
|
165
|
-
return regex
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** @internal */
|
|
169
|
-
export const matchesFieldPattern = (pattern: string, field: string): boolean =>
|
|
170
|
-
pattern.includes("*")
|
|
171
|
-
? compilePattern(pattern).test(field)
|
|
172
|
-
: pattern === field
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Tests the parts of a rule that do not need the resource value: the action,
|
|
176
|
-
* the kind and the field.
|
|
177
|
-
*
|
|
178
|
-
* A rule with fields covers a request with no field only when it is an allow
|
|
179
|
-
* rule. So "may update some fields" is a yes. A deny rule for some fields does
|
|
180
|
-
* not stop a request for the whole value.
|
|
181
|
-
*
|
|
182
|
-
* @internal
|
|
183
|
-
*/
|
|
184
|
-
export const matchesTarget = (
|
|
185
|
-
rule: RuleData,
|
|
186
|
-
action: string,
|
|
187
|
-
kind: string,
|
|
188
|
-
field: string | undefined,
|
|
189
|
-
): boolean => {
|
|
190
|
-
if (!rule.actions.includes("*") && !rule.actions.includes(action)) {
|
|
191
|
-
return false
|
|
192
|
-
}
|
|
193
|
-
if (!rule.resources.includes("*") && !rule.resources.includes(kind)) {
|
|
194
|
-
return false
|
|
195
|
-
}
|
|
196
|
-
if (rule.fields === undefined) {
|
|
197
|
-
return true
|
|
198
|
-
}
|
|
199
|
-
if (field === undefined) {
|
|
200
|
-
return rule._tag === "Allow"
|
|
201
|
-
}
|
|
202
|
-
return rule.fields.some((pattern) => matchesFieldPattern(pattern, field))
|
|
203
|
-
}
|