@pcg/auth 1.0.0-alpha.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.
@@ -0,0 +1,15 @@
1
+
2
+ 
3
+ > @pcg/auth@1.0.0-alpha.0 build /Users/serg_sadovyi/prj/deepvisionsoftware.github/pcg/packages/auth
4
+ > tsdown
5
+
6
+ ℹ tsdown v0.15.12 powered by rolldown v1.0.0-beta.45
7
+ ℹ Using tsdown config: /Users/serg_sadovyi/prj/deepvisionsoftware.github/pcg/packages/auth/tsdown.config.ts
8
+ ℹ entry: src/index.ts
9
+ ℹ tsconfig: ../../tsconfig.build.json
10
+ ℹ Build start
11
+ ℹ dist/index.js 11.71 kB │ gzip: 3.11 kB
12
+ ℹ dist/index.js.map 20.80 kB │ gzip: 5.58 kB
13
+ ℹ dist/index.d.ts  8.62 kB │ gzip: 2.00 kB
14
+ ℹ 3 files, total: 41.13 kB
15
+ ✔ Build complete in 577ms
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @deep/auth
2
+
3
+ ### Quick Start
4
+
5
+ Install npm package
6
+
7
+ ```tsx
8
+ npm i @deep/auth --save
9
+ ```
10
+
11
+ Use `isGranted` helper to check access rights.
12
+
13
+ ```jsx
14
+ import { isGranted } from '@deep/auth'
15
+ isGranted(user, 'js:episodes:create'); // boolean
16
+ ```
17
+
18
+ ### Resolved Permissions
19
+
20
+ User object `user` must contain `resolvedPemissions` array with array of objects:
21
+
22
+ ```tsx
23
+ user.resolvedPermission = [
24
+ {
25
+ id: "js:episodes:get",
26
+ scopes: []
27
+ },
28
+ {
29
+ id: "js:episodes:list",
30
+ scopes: ["published"]
31
+ }
32
+ ],
33
+
34
+ ```
35
+
36
+ But, is real life we store permissions in database as array of string, like this:
37
+
38
+ ```tsx
39
+ [
40
+ "js:episodes:get",
41
+ "js:episodes[@published]:list"
42
+ ]
43
+ ```
44
+
45
+ Library has special helper, to resolve permissions:
46
+
47
+ ```tsx
48
+ import { resolvePermissions } from '@deep/auth';
49
+
50
+ const permissions = [
51
+ "js:episodes:get",
52
+ "js:episodes[@published]:list"
53
+ ]
54
+
55
+ user.resolvedPermission = resolvePermissions(permissions)
56
+ ```
57
+
58
+ ### Permission Context
59
+
60
+ Some users don't have full access to action, but have access in some execution context. For example, user dont have permission to all episodes, but have permission only to published episodes.
61
+
62
+ In this case we determinate execution context. If user fetch only published episodes, we set scope to `published`. If user has special permission `js:episodes[@published]:list` he will obtain access to this action.
63
+
64
+ ```tsx
65
+ import { isGranted, PermissionContext } from '@deep/auth';
66
+ const ctx = new PermissionContext();
67
+
68
+ if (filter.published) {
69
+ ctx.addScope('published');
70
+ }
71
+
72
+ isGranted(user, 'js:episodes:list'); // boolean
73
+ ```
74
+
75
+ Otherwise, we can build query based on user rights. In this case, we add system roles if user have special permission.
76
+
77
+ ```tsx
78
+ import { isGranted } from '@deep/auth'
79
+ if(isGranted(user, 'js:roles:get', { scopes: ['system'] })) {
80
+ where.or.push({
81
+ type: 'system'
82
+ })
83
+ }
84
+ ```
@@ -0,0 +1,246 @@
1
+ //#region src/permissions/action-scopes.d.ts
2
+ type ActionScopesArray = (string | string[])[];
3
+ /**
4
+ * Action scopes are used to describe
5
+ * what scopes are required to execute an action.
6
+ */
7
+ declare class ActionScopes {
8
+ array: ActionScopesArray;
9
+ anyScope: boolean;
10
+ private idsMap;
11
+ constructor(scopes?: ActionScopesArray);
12
+ /**
13
+ * Set id scope (e.g. 'id#jsu:123')
14
+ * @param id - entity id
15
+ * @returns - this
16
+ * @example
17
+ * scopes.setEntityId('jsu:123')
18
+ *
19
+ * // scopes.getArray() = ['id#jsu:123']
20
+ */
21
+ setEntityId(id: string): this;
22
+ /**
23
+ * Set scope (e.g. 'org', 'org+review')
24
+ * @param scope - scope name (e.g. 'org', 'org+review')
25
+ * @param id - entity id (e.g. 'jsorg:hci')
26
+ * @returns - this
27
+ * @example
28
+ * scopes.set('my')
29
+ *
30
+ * // scopes.getArray() = ['my']
31
+ *
32
+ * scopes.set('org', 'jsorg:hci')
33
+ *
34
+ * // scopes.getArray() = ['org#jsorg:hci']
35
+ */
36
+ set(scope: string, id?: string): this;
37
+ has(scope: string): boolean;
38
+ getArray(): ActionScopesArray;
39
+ /**
40
+ * Permission scopes must cover auth scopes determined in auth context.
41
+ * @param permissionScopes
42
+ */
43
+ canBeExecutedInScopes(permissionScopes: readonly (string | string[])[]): boolean;
44
+ /**
45
+ * Resolve presaved scopes
46
+ * @example
47
+ * authCtx.setScope('org', 'jsorg:hci')
48
+ * [org#hci]
49
+ *
50
+ * authCtx.setScope('org+review');
51
+ * [org#hci, [org#hci, review]]
52
+ **/
53
+ private resolveScopeId;
54
+ }
55
+ //#endregion
56
+ //#region src/types/permissions.d.ts
57
+ /**
58
+ * Resolved permission string to object with scopes
59
+ * @example
60
+ * 'js:core:episodes[org]:get' => { id: 'js:core:episodes:get', scopes: ['org'] }
61
+ */
62
+ interface ResolvedPermission {
63
+ id: string;
64
+ scopes: (string | string[])[];
65
+ }
66
+ /**
67
+ * Resolved permission string to object with scopes
68
+ * @example
69
+ * 'g:js:core:episodes[org]:read' => { id: 'g:js:core:episodes:read', scopes: ['org'] }
70
+ */
71
+ interface ResolvedPermissionGroup {
72
+ id: string;
73
+ scopes: (string | string[])[];
74
+ }
75
+ interface ReadonlyResolvedPermission {
76
+ readonly id: string;
77
+ readonly scopes: readonly (string | string[])[];
78
+ }
79
+ //#endregion
80
+ //#region src/types/user.d.ts
81
+ interface IUser {
82
+ /**
83
+ * User ID
84
+ * @example
85
+ * ```ts
86
+ * 'hcu:2x6g7l8n9op'
87
+ * ```
88
+ */
89
+ id: string;
90
+ /**
91
+ * User's permissions
92
+ * @example
93
+ * ```ts
94
+ * ['js:core:episodes[org]:get', 'js:core:episodes[org]:create']
95
+ * ```
96
+ */
97
+ permissions: readonly string[];
98
+ /**
99
+ * User's resolved permissions
100
+ * @example
101
+ * ```ts
102
+ * [
103
+ * { id: 'js:core:episodes:get', scopes: ['org'] },
104
+ * { id: 'js:core:episodes:create', scopes: ['org'] },
105
+ * ]
106
+ * ```
107
+ */
108
+ resolvedPermissions: ReadonlyResolvedPermission[];
109
+ }
110
+ //#endregion
111
+ //#region src/permissions/helpers.d.ts
112
+ /**
113
+ * Check if user has access to permission
114
+ * @example
115
+ *
116
+ * // Check episode access
117
+ * const scopes = new ActionScopes();
118
+ * scopes.setEntityId(episode.id); // id#js:core:episodes:xxxxx
119
+ * scopes.set('org', episode.organizationId); // org#hcorg:hci
120
+ *
121
+ * isGranted(user, 'js:core:episodes:get', scopes)
122
+ * @param user - user object with resolvedPermissions
123
+ * @param permission - permission string (e.g. 'js:core:episodes:get')
124
+ * @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)
125
+ * @returns - true if user has access to permission
126
+ */
127
+ declare const isGranted: (user: IUser, permission: string, actionScopes?: ActionScopesArray | ActionScopes) => boolean;
128
+ declare const encodeScopes: (scopes: (string | string[])[]) => string;
129
+ /**
130
+ * Inject scopes into permission
131
+ * @param permission - permission string (e.g. 'js:core:episodes:get')
132
+ * @param scopesToInject - scopes to inject (e.g. ['org', 'published'])
133
+ * @returns - permission string with injected scopes (e.g. 'js:core:episodes[org,published]:get')
134
+ * @example
135
+ * injectScopesIntoPermission('js:core:episodes:get', ['org', 'published'])
136
+ * // js:core:episodes[org,published]:get
137
+ */
138
+ declare const injectScopesIntoPermission: (permission: string, scopesToInject: (string | string[])[]) => string;
139
+ /**
140
+ * Concat scopes
141
+ * @param set - set of scopes
142
+ * @param items - items to concat
143
+ * @example
144
+ * concatScopes(['org#hci'], ['org#dv'])
145
+ * // ['org#hci', 'org#dv']
146
+ */
147
+ declare const concatScopes: (set: (string | string[])[], items: (string | string[])[]) => void;
148
+ /**
149
+ * Resolve permission string to object with scopes
150
+ * @param permission - permission string
151
+ * @returns - resolved permission object
152
+ * @example
153
+ * resolvePermission('js:core:episodes[org,published]:get')
154
+ * // { id: 'js:core:episodes:get', scopes: ['org', 'published'] }
155
+ */
156
+ declare const resolvePermission: (permission: string) => ResolvedPermission;
157
+ /**
158
+ * Resolve permissions to array of resolved permissions
159
+ * @param permissions - array of permissions
160
+ * @returns - array of resolved permissions
161
+ * @example
162
+ * resolvePermissions(['js:core:episodes[org]:get', 'js:core:episodes[published]:get'])
163
+ * // [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]
164
+ */
165
+ declare const resolvePermissions: (permissions: string[]) => ResolvedPermission[];
166
+ /**
167
+ * Resolve permission group string to object with scopes
168
+ * @param permissionGroup - permission group string
169
+ * @returns - resolved permission group object
170
+ * @example
171
+ * resolvePermissionGroup('g:js:core:episodes[org]:read')
172
+ * // { id: 'g:js:core:episodes:read', scopes: ['org'] }
173
+ */
174
+ declare const resolvePermissionGroup: (permissionGroup: string) => ResolvedPermission;
175
+ /**
176
+ * Resolve permission groups to array of resolved permission groups
177
+ * @param permissionGroups - array of permission groups
178
+ * @returns - array of resolved permission groups
179
+ * @example
180
+ * resolvePermissionGroups(['g:js:core:episodes[org]:read', 'g:js:core:episodes[published]:read'])
181
+ * // [{ id: 'g:js:core:episodes:read', scopes: ['org', 'published'] }]
182
+ */
183
+ declare const resolvePermissionGroups: (permissionGroups: string[]) => ResolvedPermissionGroup[];
184
+ declare const mergeResolvedPermissions: (array1: ResolvedPermission[], array2: ResolvedPermission[]) => ResolvedPermission[];
185
+ /**
186
+ * Replace scope in array of scopes
187
+ * @param scopes - array of scopes
188
+ * @param from - scope to replace
189
+ * @param to - scope to replace with
190
+ * @returns - array of scopes with replaced scopes
191
+ * @example
192
+ * replaceScope(['org#jsorg:xxx', ['org#jsorg:xxx', 'published']], 'org#jsorg:xxx', 'org#jsorg:hci')
193
+ * // ['org#jsorg:hci', ['org#jsorg:hci', 'published']]
194
+ */
195
+ declare const replaceScope: (scopes: (string | string[])[],
196
+ // [org#jsorg:xxx, [org#jsorg:xxx, published]]
197
+ from: string, to: string | (string | string[])[]) => (string | string[])[];
198
+ //#endregion
199
+ //#region src/permissions/scopes-bulder.d.ts
200
+ declare class ScopesBulder {
201
+ private scopes;
202
+ constructor(scopes?: (string | string[])[]);
203
+ static fromBulder(builder: ScopesBulder): ScopesBulder;
204
+ build(): (string | string[])[];
205
+ clone(): ScopesBulder;
206
+ /**
207
+ * Append scope or scopes to the current scopes
208
+ * @param scope - scope or array of scopes to append
209
+ * @example
210
+ * append('org#jsorg:hci')
211
+ * // ['org#xxx'] => ['org#xxx', 'org#jsorg:hci']
212
+ * append(['org#hci', 'lang#en])
213
+ * // ['org#xxx'] => ['org#xxx', ['org#hci', 'lang#en']]
214
+ */
215
+ append(scope: string | string[]): void;
216
+ /**
217
+ * Extend current scopes with the given scopes
218
+ * @param scopes - scope or array of scopes to extend with
219
+ * @example
220
+ * extend(['lang#en', 'lang#de'])
221
+ * // ['published', 'draft'] => ['published', 'draft', 'lang#en', 'lang#de']
222
+ */
223
+ extend(scopes: (string | string[])[] | ScopesBulder): void;
224
+ /**
225
+ * Join all scopes with the given scope
226
+ * @param scope - scope to join with
227
+ * @example
228
+ * join('org#jsorg:hci', 'before')
229
+ * // ['published', 'draft'] => [['org#jsorg:hci', 'published'], ['org#jsorg:hci', 'draft']]
230
+ * join(['lang#en', 'lang#de'])
231
+ * // ['published', 'draft'] => [['published', 'lang#en'], ['published', 'lang#de'], ['draft', 'lang#en'], ['draft', 'lang#de']]
232
+ */
233
+ join(scopeOrScopes: string | (string | string[])[], pos?: 'before' | 'after'): void;
234
+ /**
235
+ * Replace prefix in all scopes
236
+ * @param from - prefix to replace
237
+ * @param to - prefix to replace with
238
+ * @example
239
+ * replacePrefix('org', 'id')
240
+ * ['org#jsorg:hci'] => ['id#jsorg:hci']
241
+ */
242
+ replacePrefix(from: string, to: string): void;
243
+ }
244
+ //#endregion
245
+ export { ActionScopes, ActionScopesArray, IUser, ReadonlyResolvedPermission, ResolvedPermission, ResolvedPermissionGroup, ScopesBulder, concatScopes, encodeScopes, injectScopesIntoPermission, isGranted, mergeResolvedPermissions, replaceScope, resolvePermission, resolvePermissionGroup, resolvePermissionGroups, resolvePermissions };
246
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,349 @@
1
+ //#region src/permissions/action-scopes.ts
2
+ /**
3
+ * Action scopes are used to describe
4
+ * what scopes are required to execute an action.
5
+ */
6
+ var ActionScopes = class {
7
+ array = [];
8
+ anyScope = false;
9
+ idsMap = /* @__PURE__ */ new Map();
10
+ constructor(scopes) {
11
+ if (scopes) this.array = scopes ?? [];
12
+ }
13
+ /**
14
+ * Set id scope (e.g. 'id#jsu:123')
15
+ * @param id - entity id
16
+ * @returns - this
17
+ * @example
18
+ * scopes.setEntityId('jsu:123')
19
+ *
20
+ * // scopes.getArray() = ['id#jsu:123']
21
+ */
22
+ setEntityId(id) {
23
+ this.set(`id#${id}`);
24
+ return this;
25
+ }
26
+ /**
27
+ * Set scope (e.g. 'org', 'org+review')
28
+ * @param scope - scope name (e.g. 'org', 'org+review')
29
+ * @param id - entity id (e.g. 'jsorg:hci')
30
+ * @returns - this
31
+ * @example
32
+ * scopes.set('my')
33
+ *
34
+ * // scopes.getArray() = ['my']
35
+ *
36
+ * scopes.set('org', 'jsorg:hci')
37
+ *
38
+ * // scopes.getArray() = ['org#jsorg:hci']
39
+ */
40
+ set(scope, id) {
41
+ if (id && /^[A-Za-z_]+$/.test(scope)) this.idsMap.set(scope, id);
42
+ if (scope.includes("+")) {
43
+ const subscopes = scope.split("+");
44
+ this.array.push(subscopes.map((subscope) => this.resolveScopeId(subscope)));
45
+ } else this.array.push(this.resolveScopeId(scope));
46
+ return this;
47
+ }
48
+ has(scope) {
49
+ return this.array.includes(scope);
50
+ }
51
+ getArray() {
52
+ return [...this.array];
53
+ }
54
+ /**
55
+ * Permission scopes must cover auth scopes determined in auth context.
56
+ * @param permissionScopes
57
+ */
58
+ canBeExecutedInScopes(permissionScopes) {
59
+ if (this.array.includes("*")) return true;
60
+ if (permissionScopes.length === 0) return true;
61
+ /**
62
+ * Empty auth scopes but user has permission scopes = no access to action
63
+ *
64
+ * action scopes = []
65
+ * permission scopes = ['org#hci']
66
+ *
67
+ * Example:
68
+ * User try to get list of all entities
69
+ * But user only has permission to entity from org#hci
70
+ */
71
+ if (this.array.length === 0) return false;
72
+ return this.array.some((authScope) => {
73
+ if (Array.isArray(authScope)) return permissionScopes.some((permissionScope) => {
74
+ if (Array.isArray(permissionScope)) return permissionScope.every((sub) => authScope.includes(sub));
75
+ return authScope.includes(permissionScope);
76
+ });
77
+ return permissionScopes.includes(authScope);
78
+ });
79
+ }
80
+ /**
81
+ * Resolve presaved scopes
82
+ * @example
83
+ * authCtx.setScope('org', 'jsorg:hci')
84
+ * [org#hci]
85
+ *
86
+ * authCtx.setScope('org+review');
87
+ * [org#hci, [org#hci, review]]
88
+ **/
89
+ resolveScopeId(scope) {
90
+ return this.idsMap.has(scope) ? `${scope}#${this.idsMap.get(scope)}` : scope;
91
+ }
92
+ };
93
+
94
+ //#endregion
95
+ //#region src/permissions/helpers.ts
96
+ /**
97
+ * Normilize scopes to ActionScopes
98
+ * @example
99
+ * const scopes = normalizeScopes(['org#hci', 'user#hcu:xxxxx') // ActionScopes
100
+ *
101
+ * scopes.set('my')
102
+ */
103
+ const normalizeScopes = (scopes) => {
104
+ if (!(scopes instanceof ActionScopes)) return new ActionScopes(scopes);
105
+ return scopes;
106
+ };
107
+ /**
108
+ * Check if user has access to permission
109
+ * @example
110
+ *
111
+ * // Check episode access
112
+ * const scopes = new ActionScopes();
113
+ * scopes.setEntityId(episode.id); // id#js:core:episodes:xxxxx
114
+ * scopes.set('org', episode.organizationId); // org#hcorg:hci
115
+ *
116
+ * isGranted(user, 'js:core:episodes:get', scopes)
117
+ * @param user - user object with resolvedPermissions
118
+ * @param permission - permission string (e.g. 'js:core:episodes:get')
119
+ * @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)
120
+ * @returns - true if user has access to permission
121
+ */
122
+ const isGranted = (user, permission, actionScopes = []) => {
123
+ const [service, module, resource, action] = permission.split(":");
124
+ if (!user?.resolvedPermissions || user.resolvedPermissions.length === 0) return false;
125
+ const userPermissions = user.resolvedPermissions;
126
+ const scopes = normalizeScopes(actionScopes);
127
+ const permissionCanBeExecutedInScopes = (perm, scopes$1) => !perm.scopes || perm.scopes.length === 0 || scopes$1.canBeExecutedInScopes(perm.scopes);
128
+ const regexp = /* @__PURE__ */ new RegExp(`^(${service}|\\*):(${module}|\\*):(${resource}|\\*):(${action}|\\*)$`);
129
+ if (userPermissions.some((perm) => regexp.test(perm.id) && permissionCanBeExecutedInScopes(perm, scopes))) return true;
130
+ return false;
131
+ };
132
+ const encodeScopes = (scopes) => {
133
+ const scopesString = scopes.map((s) => Array.isArray(s) ? s.join("+") : s).join(",");
134
+ return scopesString.length > 0 ? `[${scopesString}]` : "";
135
+ };
136
+ /**
137
+ * Inject scopes into permission
138
+ * @param permission - permission string (e.g. 'js:core:episodes:get')
139
+ * @param scopesToInject - scopes to inject (e.g. ['org', 'published'])
140
+ * @returns - permission string with injected scopes (e.g. 'js:core:episodes[org,published]:get')
141
+ * @example
142
+ * injectScopesIntoPermission('js:core:episodes:get', ['org', 'published'])
143
+ * // js:core:episodes[org,published]:get
144
+ */
145
+ const injectScopesIntoPermission = (permission, scopesToInject) => {
146
+ const { id, scopes = [] } = resolvePermission(permission);
147
+ const [service, module, resource, action] = id.split(":");
148
+ concatScopes(scopes, scopesToInject);
149
+ return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;
150
+ };
151
+ /**
152
+ * Concat scopes
153
+ * @param set - set of scopes
154
+ * @param items - items to concat
155
+ * @example
156
+ * concatScopes(['org#hci'], ['org#dv'])
157
+ * // ['org#hci', 'org#dv']
158
+ */
159
+ const concatScopes = (set, items) => {
160
+ for (const item of items) if (Array.isArray(item)) {
161
+ if (!set.some((scope) => Array.isArray(scope) && scope.length === item.length && scope.every((s) => item.includes(s)))) set.push(item);
162
+ } else if (!set.includes(item)) set.push(item);
163
+ };
164
+ /**
165
+ * Resolve permission string to object with scopes
166
+ * @param permission - permission string
167
+ * @returns - resolved permission object
168
+ * @example
169
+ * resolvePermission('js:core:episodes[org,published]:get')
170
+ * // { id: 'js:core:episodes:get', scopes: ['org', 'published'] }
171
+ */
172
+ const resolvePermission = (permission) => {
173
+ const matches = /\[(?<scopes>.*?)\]/u.exec(permission);
174
+ if (matches?.[0] && matches.groups?.scopes) {
175
+ const scopes = String(matches.groups.scopes).split(",").map((s) => {
176
+ if (s.includes("+")) return s.split("+");
177
+ return s;
178
+ });
179
+ return {
180
+ id: permission.replace(matches[0], ""),
181
+ scopes
182
+ };
183
+ }
184
+ return {
185
+ id: permission,
186
+ scopes: []
187
+ };
188
+ };
189
+ /**
190
+ * Resolve permissions to array of resolved permissions
191
+ * @param permissions - array of permissions
192
+ * @returns - array of resolved permissions
193
+ * @example
194
+ * resolvePermissions(['js:core:episodes[org]:get', 'js:core:episodes[published]:get'])
195
+ * // [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]
196
+ */
197
+ const resolvePermissions = (permissions) => {
198
+ const resolvedPermissions = [];
199
+ for (const permission of permissions) {
200
+ const { id, scopes } = resolvePermission(permission);
201
+ const currentPermission = resolvedPermissions.find((p) => p.id === id);
202
+ if (currentPermission) concatScopes(currentPermission.scopes, scopes);
203
+ else resolvedPermissions.push({
204
+ id,
205
+ scopes
206
+ });
207
+ }
208
+ return resolvedPermissions;
209
+ };
210
+ /**
211
+ * Resolve permission group string to object with scopes
212
+ * @param permissionGroup - permission group string
213
+ * @returns - resolved permission group object
214
+ * @example
215
+ * resolvePermissionGroup('g:js:core:episodes[org]:read')
216
+ * // { id: 'g:js:core:episodes:read', scopes: ['org'] }
217
+ */
218
+ const resolvePermissionGroup = (permissionGroup) => {
219
+ return resolvePermission(permissionGroup);
220
+ };
221
+ /**
222
+ * Resolve permission groups to array of resolved permission groups
223
+ * @param permissionGroups - array of permission groups
224
+ * @returns - array of resolved permission groups
225
+ * @example
226
+ * resolvePermissionGroups(['g:js:core:episodes[org]:read', 'g:js:core:episodes[published]:read'])
227
+ * // [{ id: 'g:js:core:episodes:read', scopes: ['org', 'published'] }]
228
+ */
229
+ const resolvePermissionGroups = (permissionGroups) => {
230
+ return resolvePermissions(permissionGroups);
231
+ };
232
+ const mergeResolvedPermissions = (array1, array2) => {
233
+ const result = [];
234
+ for (const rp1 of array1) {
235
+ const rps2 = array2.filter((rp2) => rp2.id === rp1.id);
236
+ if (rp1.scopes.length === 0 || rps2.some((rp2) => rp2.id === rp1.id && rp2.scopes.length === 0)) {
237
+ result.push({
238
+ id: rp1.id,
239
+ scopes: []
240
+ });
241
+ continue;
242
+ }
243
+ const scopes = [...rp1.scopes];
244
+ for (const rp2 of rps2) concatScopes(scopes, rp2.scopes);
245
+ result.push({
246
+ id: rp1.id,
247
+ scopes
248
+ });
249
+ }
250
+ for (const rp2 of array2) if (!result.some((rp) => rp.id === rp2.id)) result.push(rp2);
251
+ return result;
252
+ };
253
+ /**
254
+ * Replace scope in array of scopes
255
+ * @param scopes - array of scopes
256
+ * @param from - scope to replace
257
+ * @param to - scope to replace with
258
+ * @returns - array of scopes with replaced scopes
259
+ * @example
260
+ * replaceScope(['org#jsorg:xxx', ['org#jsorg:xxx', 'published']], 'org#jsorg:xxx', 'org#jsorg:hci')
261
+ * // ['org#jsorg:hci', ['org#jsorg:hci', 'published']]
262
+ */
263
+ const replaceScope = (scopes, from, to) => {
264
+ const result = [];
265
+ for (const scope of scopes) if (Array.isArray(scope)) result.push(replaceScope(scope, from, to));
266
+ else if (scope === from) if (Array.isArray(to)) result.push(...to);
267
+ else result.push(to);
268
+ else result.push(scope);
269
+ return result;
270
+ };
271
+
272
+ //#endregion
273
+ //#region src/permissions/scopes-bulder.ts
274
+ var ScopesBulder = class ScopesBulder {
275
+ constructor(scopes = []) {
276
+ this.scopes = scopes;
277
+ }
278
+ static fromBulder(builder) {
279
+ return new ScopesBulder(builder.scopes.slice());
280
+ }
281
+ build() {
282
+ return JSON.parse(JSON.stringify(this.scopes));
283
+ }
284
+ clone() {
285
+ return ScopesBulder.fromBulder(this);
286
+ }
287
+ /**
288
+ * Append scope or scopes to the current scopes
289
+ * @param scope - scope or array of scopes to append
290
+ * @example
291
+ * append('org#jsorg:hci')
292
+ * // ['org#xxx'] => ['org#xxx', 'org#jsorg:hci']
293
+ * append(['org#hci', 'lang#en])
294
+ * // ['org#xxx'] => ['org#xxx', ['org#hci', 'lang#en']]
295
+ */
296
+ append(scope) {
297
+ this.scopes.push(scope);
298
+ }
299
+ /**
300
+ * Extend current scopes with the given scopes
301
+ * @param scopes - scope or array of scopes to extend with
302
+ * @example
303
+ * extend(['lang#en', 'lang#de'])
304
+ * // ['published', 'draft'] => ['published', 'draft', 'lang#en', 'lang#de']
305
+ */
306
+ extend(scopes) {
307
+ if (Array.isArray(scopes)) concatScopes(this.scopes, scopes);
308
+ else if (scopes instanceof ScopesBulder) concatScopes(this.scopes, scopes.scopes);
309
+ }
310
+ /**
311
+ * Join all scopes with the given scope
312
+ * @param scope - scope to join with
313
+ * @example
314
+ * join('org#jsorg:hci', 'before')
315
+ * // ['published', 'draft'] => [['org#jsorg:hci', 'published'], ['org#jsorg:hci', 'draft']]
316
+ * join(['lang#en', 'lang#de'])
317
+ * // ['published', 'draft'] => [['published', 'lang#en'], ['published', 'lang#de'], ['draft', 'lang#en'], ['draft', 'lang#de']]
318
+ */
319
+ join(scopeOrScopes, pos = "after") {
320
+ const result = [];
321
+ const scopesToJoin = Array.isArray(scopeOrScopes) ? scopeOrScopes : [scopeOrScopes];
322
+ if (scopesToJoin.length === 0) return;
323
+ const currentScopesCopy = this.build();
324
+ for (const scope of scopesToJoin) {
325
+ const scopeToJoin = Array.isArray(scope) ? scope : [scope];
326
+ for (const s of currentScopesCopy) if (Array.isArray(s)) result.push(pos === "before" ? [...scopeToJoin, ...s] : [...s, ...scopeToJoin]);
327
+ else result.push(pos === "before" ? [...scopeToJoin, s] : [s, ...scopeToJoin]);
328
+ }
329
+ this.scopes = result;
330
+ }
331
+ /**
332
+ * Replace prefix in all scopes
333
+ * @param from - prefix to replace
334
+ * @param to - prefix to replace with
335
+ * @example
336
+ * replacePrefix('org', 'id')
337
+ * ['org#jsorg:hci'] => ['id#jsorg:hci']
338
+ */
339
+ replacePrefix(from, to) {
340
+ const result = [];
341
+ for (const scope of this.scopes) if (Array.isArray(scope)) result.push(scope.map((s) => s.replace(`${from}#`, `${to}#`)));
342
+ else result.push(scope.replace(`${from}#`, `${to}#`));
343
+ this.scopes = result;
344
+ }
345
+ };
346
+
347
+ //#endregion
348
+ export { ActionScopes, ScopesBulder, concatScopes, encodeScopes, injectScopesIntoPermission, isGranted, mergeResolvedPermissions, replaceScope, resolvePermission, resolvePermissionGroup, resolvePermissionGroups, resolvePermissions };
349
+ //# sourceMappingURL=index.js.map