@spinajs/rbac 2.0.525 → 2.0.526
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/lib/cjs/permission-service.d.ts +45 -2
- package/lib/cjs/permission-service.d.ts.map +1 -1
- package/lib/cjs/permission-service.js +54 -3
- package/lib/cjs/permission-service.js.map +1 -1
- package/lib/mjs/permission-service.d.ts +45 -2
- package/lib/mjs/permission-service.d.ts.map +1 -1
- package/lib/mjs/permission-service.js +52 -2
- package/lib/mjs/permission-service.js.map +1 -1
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +11 -11
|
@@ -15,8 +15,21 @@ export type PermissionGrantScope = 'any' | 'own' | 'none';
|
|
|
15
15
|
* `usePermission()` anywhere else share one instance safely.
|
|
16
16
|
*/
|
|
17
17
|
export declare abstract class PermissionService {
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The rbac resource this service guards. A class constructor is the preferred form — its NAME
|
|
20
|
+
* is the accesscontrol grant key, resolved directly with no descriptor or metadata lookup, and
|
|
21
|
+
* ANY class qualifies (an ORM model, a DTO, a plain feature class). A string stays legal for
|
|
22
|
+
* grant keys that have no class behind them (config-only pseudo-resources).
|
|
23
|
+
*/
|
|
24
|
+
protected abstract readonly Resource: Class<unknown> | string;
|
|
25
|
+
/**
|
|
26
|
+
* The grant key {@link Resource} resolves to, self-checked against the registered grants:
|
|
27
|
+
* with a class as the resource the class NAME is the config contract, so a renamed class
|
|
28
|
+
* would otherwise fail closed silently (probeGrant answers false for a key accesscontrol
|
|
29
|
+
* has never seen). Checked here, it fails loud at first use instead — a programming error,
|
|
30
|
+
* not a refusal. Skipped when no AccessControl is registered; the probe itself throws then.
|
|
31
|
+
*/
|
|
32
|
+
protected resourceName(): string;
|
|
20
33
|
/**
|
|
21
34
|
* The acting user: explicit argument wins, else the rbac AsyncLocalStorage store.
|
|
22
35
|
* No user anywhere throws — a permission decision must never silently pass without
|
|
@@ -45,6 +58,36 @@ export declare abstract class PermissionService {
|
|
|
45
58
|
protected noUserError(): Error;
|
|
46
59
|
private storage;
|
|
47
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Model-aware permission service: the generic verb gate computed from {@link Resource} plus the
|
|
63
|
+
* accesscontrol grants, so a concrete domain service declares only what config cannot state —
|
|
64
|
+
* what "own" MEANS for its resource — plus its typed errors and genuine business rules.
|
|
65
|
+
*
|
|
66
|
+
* `TModel` is the ownership CARRIER, not necessarily a persisted row: anything `owns` can decide
|
|
67
|
+
* from. The Resource class doubles as the grant key and as the compile-time subject type, so the
|
|
68
|
+
* declaration cannot drift from what `assert` accepts.
|
|
69
|
+
*
|
|
70
|
+
* `read` deliberately has no special treatment here, but domain convention is to not gate reads
|
|
71
|
+
* through `assert` at all: a refused read should answer an empty list or 404 from the scoped
|
|
72
|
+
* query layer, never a 403.
|
|
73
|
+
*/
|
|
74
|
+
export declare abstract class ResourceRules<TModel> extends PermissionService {
|
|
75
|
+
protected abstract readonly Resource: Class<TModel>;
|
|
76
|
+
/**
|
|
77
|
+
* THE one domain fact the grants config cannot express: does this user own this subject.
|
|
78
|
+
* Runs only when the grant projects to `own` scope — an `any` grant never calls it.
|
|
79
|
+
*/
|
|
80
|
+
protected abstract owns(user: User, subject: TModel): Promise<boolean> | boolean;
|
|
81
|
+
/**
|
|
82
|
+
* The generic gate: grant first (refusing `none` with {@link noPermissionError}), ownership
|
|
83
|
+
* second when the grant is `own`-scoped. A missing subject under `own` scope refuses too —
|
|
84
|
+
* ownership of nothing is undecidable, and an undecidable permission never passes. Verbs on
|
|
85
|
+
* the collection (a `create:any` insert) pass with no subject because scope is `any`.
|
|
86
|
+
*/
|
|
87
|
+
assert(verb: PermissionVerb, subject?: TModel, user?: User): Promise<void>;
|
|
88
|
+
/** Override hook for typed domain errors, like {@link noPermissionError}. */
|
|
89
|
+
protected notOwnedError(user: User, _subject: TModel | undefined): Error;
|
|
90
|
+
}
|
|
48
91
|
/**
|
|
49
92
|
* Retrieves a permission service from DI — sugar for domain services and plain
|
|
50
93
|
* functions down the callstack, so code reads as intent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-service.d.ts","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAM,MAAM,aAAa,CAAC;AAGxC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACrE,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,8BAAsB,iBAAiB;IACrC
|
|
1
|
+
{"version":3,"file":"permission-service.d.ts","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PermissionService = void 0;
|
|
3
|
+
exports.ResourceRules = exports.PermissionService = void 0;
|
|
4
4
|
exports.usePermission = usePermission;
|
|
5
5
|
exports.probeGrant = probeGrant;
|
|
6
6
|
exports.effectiveRoles = effectiveRoles;
|
|
@@ -20,6 +20,21 @@ const exceptions_1 = require("@spinajs/exceptions");
|
|
|
20
20
|
* `usePermission()` anywhere else share one instance safely.
|
|
21
21
|
*/
|
|
22
22
|
class PermissionService {
|
|
23
|
+
/**
|
|
24
|
+
* The grant key {@link Resource} resolves to, self-checked against the registered grants:
|
|
25
|
+
* with a class as the resource the class NAME is the config contract, so a renamed class
|
|
26
|
+
* would otherwise fail closed silently (probeGrant answers false for a key accesscontrol
|
|
27
|
+
* has never seen). Checked here, it fails loud at first use instead — a programming error,
|
|
28
|
+
* not a refusal. Skipped when no AccessControl is registered; the probe itself throws then.
|
|
29
|
+
*/
|
|
30
|
+
resourceName() {
|
|
31
|
+
const name = typeof this.Resource === 'function' ? this.Resource.name : this.Resource;
|
|
32
|
+
const ac = di_1.DI.get('AccessControl');
|
|
33
|
+
if (ac && !ac.hasResource(name)) {
|
|
34
|
+
throw new Error(`rbac resource '${name}' (guarded by ${this.constructor.name}) is not present in the AccessControl grants — renamed class or missing grants config?`);
|
|
35
|
+
}
|
|
36
|
+
return name;
|
|
37
|
+
}
|
|
23
38
|
/**
|
|
24
39
|
* The acting user: explicit argument wins, else the rbac AsyncLocalStorage store.
|
|
25
40
|
* No user anywhere throws — a permission decision must never silently pass without
|
|
@@ -51,7 +66,7 @@ class PermissionService {
|
|
|
51
66
|
}
|
|
52
67
|
/** Single guarded probe against this.Resource — an unknown role answers false. */
|
|
53
68
|
can(roles, permission) {
|
|
54
|
-
return probeGrant(roles, permission, this.
|
|
69
|
+
return probeGrant(roles, permission, this.resourceName())?.granted ?? false;
|
|
55
70
|
}
|
|
56
71
|
/** Resolve acting user + scope, refusing `none` with the (overridable) domain error. */
|
|
57
72
|
assertGrant(verb, user) {
|
|
@@ -78,7 +93,7 @@ class PermissionService {
|
|
|
78
93
|
return storage.run({ ...parent, User: user, ActiveRole: undefined }, async () => fn());
|
|
79
94
|
}
|
|
80
95
|
noPermissionError(user, verb) {
|
|
81
|
-
return new exceptions_1.Forbidden(`user ${user.Id} has no ${verb} grant for resource ${this.
|
|
96
|
+
return new exceptions_1.Forbidden(`user ${user.Id} has no ${verb} grant for resource ${this.resourceName()}`);
|
|
82
97
|
}
|
|
83
98
|
noUserError() {
|
|
84
99
|
return new exceptions_1.Forbidden('no acting user: none was given and none is present in the execution context');
|
|
@@ -88,6 +103,42 @@ class PermissionService {
|
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
exports.PermissionService = PermissionService;
|
|
106
|
+
/**
|
|
107
|
+
* Model-aware permission service: the generic verb gate computed from {@link Resource} plus the
|
|
108
|
+
* accesscontrol grants, so a concrete domain service declares only what config cannot state —
|
|
109
|
+
* what "own" MEANS for its resource — plus its typed errors and genuine business rules.
|
|
110
|
+
*
|
|
111
|
+
* `TModel` is the ownership CARRIER, not necessarily a persisted row: anything `owns` can decide
|
|
112
|
+
* from. The Resource class doubles as the grant key and as the compile-time subject type, so the
|
|
113
|
+
* declaration cannot drift from what `assert` accepts.
|
|
114
|
+
*
|
|
115
|
+
* `read` deliberately has no special treatment here, but domain convention is to not gate reads
|
|
116
|
+
* through `assert` at all: a refused read should answer an empty list or 404 from the scoped
|
|
117
|
+
* query layer, never a 403.
|
|
118
|
+
*/
|
|
119
|
+
class ResourceRules extends PermissionService {
|
|
120
|
+
/**
|
|
121
|
+
* The generic gate: grant first (refusing `none` with {@link noPermissionError}), ownership
|
|
122
|
+
* second when the grant is `own`-scoped. A missing subject under `own` scope refuses too —
|
|
123
|
+
* ownership of nothing is undecidable, and an undecidable permission never passes. Verbs on
|
|
124
|
+
* the collection (a `create:any` insert) pass with no subject because scope is `any`.
|
|
125
|
+
*/
|
|
126
|
+
async assert(verb, subject, user) {
|
|
127
|
+
const { user: acting, scope } = this.assertGrant(verb, user);
|
|
128
|
+
if (scope !== 'own') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (subject === undefined || !(await this.owns(acting, subject))) {
|
|
132
|
+
throw this.notOwnedError(acting, subject);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Override hook for typed domain errors, like {@link noPermissionError}. */
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
137
|
+
notOwnedError(user, _subject) {
|
|
138
|
+
return new exceptions_1.Forbidden(`user ${user.Id} does not own the ${this.resourceName()} subject of this action`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.ResourceRules = ResourceRules;
|
|
91
142
|
/**
|
|
92
143
|
* Retrieves a permission service from DI — sugar for domain services and plain
|
|
93
144
|
* functions down the callstack, so code reads as intent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-service.js","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":";;;;;;AAAA,uDAAqD;AACrD,iDAA0D;AAC1D,oCAAwC;AACxC,oDAAgD;AAOhD;;;;;;;;;;GAUG;AACH;
|
|
1
|
+
{"version":3,"file":"permission-service.js","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,8 +15,21 @@ export type PermissionGrantScope = 'any' | 'own' | 'none';
|
|
|
15
15
|
* `usePermission()` anywhere else share one instance safely.
|
|
16
16
|
*/
|
|
17
17
|
export declare abstract class PermissionService {
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The rbac resource this service guards. A class constructor is the preferred form — its NAME
|
|
20
|
+
* is the accesscontrol grant key, resolved directly with no descriptor or metadata lookup, and
|
|
21
|
+
* ANY class qualifies (an ORM model, a DTO, a plain feature class). A string stays legal for
|
|
22
|
+
* grant keys that have no class behind them (config-only pseudo-resources).
|
|
23
|
+
*/
|
|
24
|
+
protected abstract readonly Resource: Class<unknown> | string;
|
|
25
|
+
/**
|
|
26
|
+
* The grant key {@link Resource} resolves to, self-checked against the registered grants:
|
|
27
|
+
* with a class as the resource the class NAME is the config contract, so a renamed class
|
|
28
|
+
* would otherwise fail closed silently (probeGrant answers false for a key accesscontrol
|
|
29
|
+
* has never seen). Checked here, it fails loud at first use instead — a programming error,
|
|
30
|
+
* not a refusal. Skipped when no AccessControl is registered; the probe itself throws then.
|
|
31
|
+
*/
|
|
32
|
+
protected resourceName(): string;
|
|
20
33
|
/**
|
|
21
34
|
* The acting user: explicit argument wins, else the rbac AsyncLocalStorage store.
|
|
22
35
|
* No user anywhere throws — a permission decision must never silently pass without
|
|
@@ -45,6 +58,36 @@ export declare abstract class PermissionService {
|
|
|
45
58
|
protected noUserError(): Error;
|
|
46
59
|
private storage;
|
|
47
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Model-aware permission service: the generic verb gate computed from {@link Resource} plus the
|
|
63
|
+
* accesscontrol grants, so a concrete domain service declares only what config cannot state —
|
|
64
|
+
* what "own" MEANS for its resource — plus its typed errors and genuine business rules.
|
|
65
|
+
*
|
|
66
|
+
* `TModel` is the ownership CARRIER, not necessarily a persisted row: anything `owns` can decide
|
|
67
|
+
* from. The Resource class doubles as the grant key and as the compile-time subject type, so the
|
|
68
|
+
* declaration cannot drift from what `assert` accepts.
|
|
69
|
+
*
|
|
70
|
+
* `read` deliberately has no special treatment here, but domain convention is to not gate reads
|
|
71
|
+
* through `assert` at all: a refused read should answer an empty list or 404 from the scoped
|
|
72
|
+
* query layer, never a 403.
|
|
73
|
+
*/
|
|
74
|
+
export declare abstract class ResourceRules<TModel> extends PermissionService {
|
|
75
|
+
protected abstract readonly Resource: Class<TModel>;
|
|
76
|
+
/**
|
|
77
|
+
* THE one domain fact the grants config cannot express: does this user own this subject.
|
|
78
|
+
* Runs only when the grant projects to `own` scope — an `any` grant never calls it.
|
|
79
|
+
*/
|
|
80
|
+
protected abstract owns(user: User, subject: TModel): Promise<boolean> | boolean;
|
|
81
|
+
/**
|
|
82
|
+
* The generic gate: grant first (refusing `none` with {@link noPermissionError}), ownership
|
|
83
|
+
* second when the grant is `own`-scoped. A missing subject under `own` scope refuses too —
|
|
84
|
+
* ownership of nothing is undecidable, and an undecidable permission never passes. Verbs on
|
|
85
|
+
* the collection (a `create:any` insert) pass with no subject because scope is `any`.
|
|
86
|
+
*/
|
|
87
|
+
assert(verb: PermissionVerb, subject?: TModel, user?: User): Promise<void>;
|
|
88
|
+
/** Override hook for typed domain errors, like {@link noPermissionError}. */
|
|
89
|
+
protected notOwnedError(user: User, _subject: TModel | undefined): Error;
|
|
90
|
+
}
|
|
48
91
|
/**
|
|
49
92
|
* Retrieves a permission service from DI — sugar for domain services and plain
|
|
50
93
|
* functions down the callstack, so code reads as intent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-service.d.ts","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAM,MAAM,aAAa,CAAC;AAGxC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACrE,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,8BAAsB,iBAAiB;IACrC
|
|
1
|
+
{"version":3,"file":"permission-service.d.ts","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,21 @@ import { Forbidden } from '@spinajs/exceptions';
|
|
|
14
14
|
* `usePermission()` anywhere else share one instance safely.
|
|
15
15
|
*/
|
|
16
16
|
export class PermissionService {
|
|
17
|
+
/**
|
|
18
|
+
* The grant key {@link Resource} resolves to, self-checked against the registered grants:
|
|
19
|
+
* with a class as the resource the class NAME is the config contract, so a renamed class
|
|
20
|
+
* would otherwise fail closed silently (probeGrant answers false for a key accesscontrol
|
|
21
|
+
* has never seen). Checked here, it fails loud at first use instead — a programming error,
|
|
22
|
+
* not a refusal. Skipped when no AccessControl is registered; the probe itself throws then.
|
|
23
|
+
*/
|
|
24
|
+
resourceName() {
|
|
25
|
+
const name = typeof this.Resource === 'function' ? this.Resource.name : this.Resource;
|
|
26
|
+
const ac = DI.get('AccessControl');
|
|
27
|
+
if (ac && !ac.hasResource(name)) {
|
|
28
|
+
throw new Error(`rbac resource '${name}' (guarded by ${this.constructor.name}) is not present in the AccessControl grants — renamed class or missing grants config?`);
|
|
29
|
+
}
|
|
30
|
+
return name;
|
|
31
|
+
}
|
|
17
32
|
/**
|
|
18
33
|
* The acting user: explicit argument wins, else the rbac AsyncLocalStorage store.
|
|
19
34
|
* No user anywhere throws — a permission decision must never silently pass without
|
|
@@ -45,7 +60,7 @@ export class PermissionService {
|
|
|
45
60
|
}
|
|
46
61
|
/** Single guarded probe against this.Resource — an unknown role answers false. */
|
|
47
62
|
can(roles, permission) {
|
|
48
|
-
return probeGrant(roles, permission, this.
|
|
63
|
+
return probeGrant(roles, permission, this.resourceName())?.granted ?? false;
|
|
49
64
|
}
|
|
50
65
|
/** Resolve acting user + scope, refusing `none` with the (overridable) domain error. */
|
|
51
66
|
assertGrant(verb, user) {
|
|
@@ -72,7 +87,7 @@ export class PermissionService {
|
|
|
72
87
|
return storage.run({ ...parent, User: user, ActiveRole: undefined }, async () => fn());
|
|
73
88
|
}
|
|
74
89
|
noPermissionError(user, verb) {
|
|
75
|
-
return new Forbidden(`user ${user.Id} has no ${verb} grant for resource ${this.
|
|
90
|
+
return new Forbidden(`user ${user.Id} has no ${verb} grant for resource ${this.resourceName()}`);
|
|
76
91
|
}
|
|
77
92
|
noUserError() {
|
|
78
93
|
return new Forbidden('no acting user: none was given and none is present in the execution context');
|
|
@@ -81,6 +96,41 @@ export class PermissionService {
|
|
|
81
96
|
return DI.resolve(AsyncLocalStorage);
|
|
82
97
|
}
|
|
83
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Model-aware permission service: the generic verb gate computed from {@link Resource} plus the
|
|
101
|
+
* accesscontrol grants, so a concrete domain service declares only what config cannot state —
|
|
102
|
+
* what "own" MEANS for its resource — plus its typed errors and genuine business rules.
|
|
103
|
+
*
|
|
104
|
+
* `TModel` is the ownership CARRIER, not necessarily a persisted row: anything `owns` can decide
|
|
105
|
+
* from. The Resource class doubles as the grant key and as the compile-time subject type, so the
|
|
106
|
+
* declaration cannot drift from what `assert` accepts.
|
|
107
|
+
*
|
|
108
|
+
* `read` deliberately has no special treatment here, but domain convention is to not gate reads
|
|
109
|
+
* through `assert` at all: a refused read should answer an empty list or 404 from the scoped
|
|
110
|
+
* query layer, never a 403.
|
|
111
|
+
*/
|
|
112
|
+
export class ResourceRules extends PermissionService {
|
|
113
|
+
/**
|
|
114
|
+
* The generic gate: grant first (refusing `none` with {@link noPermissionError}), ownership
|
|
115
|
+
* second when the grant is `own`-scoped. A missing subject under `own` scope refuses too —
|
|
116
|
+
* ownership of nothing is undecidable, and an undecidable permission never passes. Verbs on
|
|
117
|
+
* the collection (a `create:any` insert) pass with no subject because scope is `any`.
|
|
118
|
+
*/
|
|
119
|
+
async assert(verb, subject, user) {
|
|
120
|
+
const { user: acting, scope } = this.assertGrant(verb, user);
|
|
121
|
+
if (scope !== 'own') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (subject === undefined || !(await this.owns(acting, subject))) {
|
|
125
|
+
throw this.notOwnedError(acting, subject);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Override hook for typed domain errors, like {@link noPermissionError}. */
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
130
|
+
notOwnedError(user, _subject) {
|
|
131
|
+
return new Forbidden(`user ${user.Id} does not own the ${this.resourceName()} subject of this action`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
84
134
|
/**
|
|
85
135
|
* Retrieves a permission service from DI — sugar for domain services and plain
|
|
86
136
|
* functions down the callstack, so code reads as intent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-service.js","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAc,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAS,EAAE,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAOhD;;;;;;;;;;GAUG;AACH,MAAM,OAAgB,iBAAiB;
|
|
1
|
+
{"version":3,"file":"permission-service.js","sourceRoot":"","sources":["../../src/permission-service.ts"],"names":[],"mappings":"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"}
|