@pcg/auth 1.0.0-alpha.1 → 1.0.0-alpha.3
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 +571 -53
- package/dist/functional-scopes-COiTBSy_.js +79 -0
- package/dist/functional-scopes-COiTBSy_.js.map +1 -0
- package/dist/functional-scopes-ut8Z6MmG.d.ts +118 -0
- package/dist/functional-scopes-ut8Z6MmG.d.ts.map +1 -0
- package/dist/index.d.ts +5 -57
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -33
- package/dist/index.js.map +1 -1
- package/dist/scopes.d.ts +2 -0
- package/dist/scopes.js +3 -0
- package/package.json +16 -6
package/README.md
CHANGED
|
@@ -1,84 +1,602 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @pcg/auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Authorization library for checking user access rights. Supports a flexible permission system with scopes, wildcard access, and NestJS integration.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
```
|
|
8
|
-
npm i @
|
|
7
|
+
```bash
|
|
8
|
+
npm i @pcg/auth
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Quick Start
|
|
12
12
|
|
|
13
|
-
```
|
|
14
|
-
import { isGranted } from '@
|
|
15
|
-
|
|
13
|
+
```typescript
|
|
14
|
+
import { isGranted, resolvePermissions } from '@pcg/auth';
|
|
15
|
+
import * as s from '@pcg/auth/scopes';
|
|
16
|
+
|
|
17
|
+
// Prepare user object
|
|
18
|
+
const user = {
|
|
19
|
+
id: 'hcu:2x6g7l8n9op',
|
|
20
|
+
permissions: ['js:core:episodes[org#hcorg:company1]:get'],
|
|
21
|
+
resolvedPermissions: resolvePermissions(['js:core:episodes[org#hcorg:company1]:get']),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Check access
|
|
25
|
+
isGranted(user, 'js:core:episodes:get', s.org('hcorg:company1')); // true
|
|
26
|
+
isGranted(user, 'js:core:episodes:get', s.org('hcorg:other')); // false
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Exports
|
|
30
|
+
|
|
31
|
+
The library provides two entry points:
|
|
32
|
+
|
|
33
|
+
| Import | Description |
|
|
34
|
+
|--------|-------------|
|
|
35
|
+
| `@pcg/auth` | Core functions: `isGranted`, `resolvePermissions`, `ActionScopes`, `ScopesBulder`, types |
|
|
36
|
+
| `@pcg/auth/scopes` | Functional scope builders: `org`, `id`, `user`, `and`, `anyScope`, etc. |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Permission String Format
|
|
41
|
+
|
|
42
|
+
**Pattern**: `<service>:<module>:<resource>[scopes]:<action>`
|
|
43
|
+
|
|
44
|
+
| Component | Description | Examples |
|
|
45
|
+
|-----------|-------------|----------|
|
|
46
|
+
| service | Application identifier | `js`, `tl`, `bo`, `*` |
|
|
47
|
+
| module | Module within the service | `core`, `mam`, `*` |
|
|
48
|
+
| resource | Entity type | `episodes`, `users`, `roles` |
|
|
49
|
+
| scopes | Constraints (optional) | `[org]`, `[org,published]`, `[org+published]` |
|
|
50
|
+
| action | Operation | `get`, `list`, `create`, `update`, `delete`, `*` |
|
|
51
|
+
|
|
52
|
+
### Permission String Examples
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
'js:core:episodes:get' // No scopes — full access to the action
|
|
56
|
+
'js:core:episodes[org]:get' // Restricted to organization
|
|
57
|
+
'js:core:episodes[org,published]:get' // OR — needs ANY of the scopes
|
|
58
|
+
'js:core:episodes[org+published]:get' // AND — needs ALL scopes
|
|
59
|
+
'js:core:episodes[org#hcorg:company1]:get' // Bound to a specific organization
|
|
60
|
+
'js:*:*:*' // Wildcard — all actions in the service
|
|
61
|
+
'*:*:*:*' // Super admin — everything
|
|
16
62
|
```
|
|
17
63
|
|
|
18
|
-
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Scope Logic
|
|
67
|
+
|
|
68
|
+
### OR Logic (comma `,`)
|
|
19
69
|
|
|
20
|
-
User
|
|
70
|
+
User is granted access if the action scope matches **any** of their scopes.
|
|
21
71
|
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
id: "js:episodes:get",
|
|
26
|
-
scopes: []
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: "js:episodes:list",
|
|
30
|
-
scopes: ["published"]
|
|
31
|
-
}
|
|
32
|
-
],
|
|
72
|
+
```typescript
|
|
73
|
+
// Permission: 'js:core:episodes[org,published]:get'
|
|
74
|
+
// Resolved scopes: ['org', 'published']
|
|
33
75
|
|
|
76
|
+
isGranted(user, 'js:core:episodes:get', ['org']); // true
|
|
77
|
+
isGranted(user, 'js:core:episodes:get', ['published']); // true
|
|
78
|
+
isGranted(user, 'js:core:episodes:get', ['draft']); // false
|
|
34
79
|
```
|
|
35
80
|
|
|
36
|
-
|
|
81
|
+
### AND Logic (plus `+`)
|
|
37
82
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
]
|
|
83
|
+
User is granted access **only** if the action scope contains **all** specified scopes.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Permission: 'js:core:episodes[org+published]:get'
|
|
87
|
+
// Resolved scopes: [['org', 'published']] ← array within array
|
|
88
|
+
|
|
89
|
+
isGranted(user, 'js:core:episodes:get', ['org']); // false
|
|
90
|
+
isGranted(user, 'js:core:episodes:get', [['org', 'published']]); // true
|
|
43
91
|
```
|
|
44
92
|
|
|
45
|
-
|
|
93
|
+
### Mixed Logic
|
|
46
94
|
|
|
47
|
-
```
|
|
48
|
-
|
|
95
|
+
```typescript
|
|
96
|
+
// Permission: 'js:core:episodes[published,org+draft]:get'
|
|
97
|
+
// Resolved scopes: ['published', ['org', 'draft']]
|
|
49
98
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
]
|
|
99
|
+
// Access granted if:
|
|
100
|
+
// - scope is 'published', OR
|
|
101
|
+
// - both 'org' AND 'draft' scopes
|
|
54
102
|
|
|
55
|
-
user
|
|
103
|
+
isGranted(user, 'js:core:episodes:get', ['published']); // true
|
|
104
|
+
isGranted(user, 'js:core:episodes:get', [['org', 'draft']]); // true
|
|
105
|
+
isGranted(user, 'js:core:episodes:get', ['org']); // false
|
|
56
106
|
```
|
|
57
107
|
|
|
58
|
-
###
|
|
108
|
+
### Scope ID (hash `#`)
|
|
109
|
+
|
|
110
|
+
Scopes can include a binding to a specific entity ID.
|
|
59
111
|
|
|
60
|
-
|
|
112
|
+
```typescript
|
|
113
|
+
// Permission: 'js:core:episodes[org#hcorg:company1]:get'
|
|
61
114
|
|
|
62
|
-
|
|
115
|
+
isGranted(user, 'js:core:episodes:get', ['org#hcorg:company1']); // true
|
|
116
|
+
isGranted(user, 'js:core:episodes:get', ['org#hcorg:company2']); // false
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Wildcard (`*`)
|
|
120
|
+
|
|
121
|
+
The `['*']` action scope bypasses scope checking.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
isGranted(user, 'js:core:episodes:get', ['*']); // true — if user has the permission with any scope
|
|
125
|
+
```
|
|
63
126
|
|
|
64
|
-
|
|
65
|
-
import { isGranted, PermissionContext } from '@deep/auth';
|
|
66
|
-
const ctx = new PermissionContext();
|
|
127
|
+
### Results Matrix
|
|
67
128
|
|
|
68
|
-
|
|
69
|
-
|
|
129
|
+
| User Permission | Action Scopes | Result |
|
|
130
|
+
|-----------------|---------------|--------|
|
|
131
|
+
| `episodes:get` (no scopes) | any | `true` |
|
|
132
|
+
| `episodes[org]:get` | `['org']` | `true` |
|
|
133
|
+
| `episodes[org]:get` | `['published']` | `false` |
|
|
134
|
+
| `episodes[org]:get` | `[]` (empty) | `false` |
|
|
135
|
+
| `episodes[org]:get` | `['*']` | `true` |
|
|
136
|
+
| `episodes[org,published]:get` | `['org']` | `true` |
|
|
137
|
+
| `episodes[org+published]:get` | `['org']` | `false` |
|
|
138
|
+
| `episodes[org+published]:get` | `[['org', 'published']]` | `true` |
|
|
139
|
+
| `episodes[org#hcorg:A]:get` | `['org#hcorg:A']` | `true` |
|
|
140
|
+
| `episodes[org#hcorg:A]:get` | `['org#hcorg:B']` | `false` |
|
|
141
|
+
| `js:*:*:*` | any | `true` |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Types
|
|
146
|
+
|
|
147
|
+
### IUser
|
|
148
|
+
|
|
149
|
+
The user object must contain `resolvedPermissions`:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
interface IUser {
|
|
153
|
+
id: string; // 'hcu:2x6g7l8n9op'
|
|
154
|
+
permissions: readonly string[]; // Raw permission strings
|
|
155
|
+
resolvedPermissions: ReadonlyResolvedPermission[]; // Parsed permissions
|
|
70
156
|
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### ResolvedPermission
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
interface ResolvedPermission {
|
|
163
|
+
id: string; // Permission without scope brackets
|
|
164
|
+
scopes: (string | string[])[]; // Parsed scopes
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Examples:
|
|
168
|
+
// 'js:core:episodes[org,published]:get'
|
|
169
|
+
// → { id: 'js:core:episodes:get', scopes: ['org', 'published'] }
|
|
170
|
+
//
|
|
171
|
+
// 'js:core:episodes[org+published]:get'
|
|
172
|
+
// → { id: 'js:core:episodes:get', scopes: [['org', 'published']] }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## API
|
|
178
|
+
|
|
179
|
+
### `isGranted(user, permission, actionScopes?)`
|
|
180
|
+
|
|
181
|
+
Main function for checking access. Returns `boolean`.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { isGranted } from '@pcg/auth';
|
|
185
|
+
import * as s from '@pcg/auth/scopes';
|
|
186
|
+
|
|
187
|
+
// Without scopes
|
|
188
|
+
isGranted(user, 'js:core:episodes:get');
|
|
189
|
+
|
|
190
|
+
// With single scope (no array needed)
|
|
191
|
+
isGranted(user, 'js:core:episodes:get', s.org('hcorg:company1'));
|
|
192
|
+
|
|
193
|
+
// With wildcard
|
|
194
|
+
isGranted(user, 'js:core:episodes:list', s.anyScope());
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### `resolvePermission(permission)`
|
|
71
198
|
|
|
72
|
-
|
|
199
|
+
Parses a single permission string into an object.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { resolvePermission } from '@pcg/auth';
|
|
203
|
+
|
|
204
|
+
resolvePermission('js:core:episodes[org,published]:get');
|
|
205
|
+
// { id: 'js:core:episodes:get', scopes: ['org', 'published'] }
|
|
206
|
+
|
|
207
|
+
resolvePermission('js:core:episodes[org+published]:get');
|
|
208
|
+
// { id: 'js:core:episodes:get', scopes: [['org', 'published']] }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `resolvePermissions(permissions)`
|
|
212
|
+
|
|
213
|
+
Parses an array of permission strings. Matching IDs are merged — their scopes are combined.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { resolvePermissions } from '@pcg/auth';
|
|
217
|
+
|
|
218
|
+
resolvePermissions([
|
|
219
|
+
'js:core:episodes[org]:get',
|
|
220
|
+
'js:core:episodes[published]:get',
|
|
221
|
+
]);
|
|
222
|
+
// [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### `mergeResolvedPermissions(array1, array2)`
|
|
226
|
+
|
|
227
|
+
Merges two arrays of resolved permissions. Empty scopes (`[]`) mean full access and take precedence when merging.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { mergeResolvedPermissions } from '@pcg/auth';
|
|
231
|
+
|
|
232
|
+
mergeResolvedPermissions(
|
|
233
|
+
[{ id: 'js:core:episodes:get', scopes: ['org#hci'] }],
|
|
234
|
+
[{ id: 'js:core:episodes:get', scopes: ['org#dv'] }],
|
|
235
|
+
);
|
|
236
|
+
// [{ id: 'js:core:episodes:get', scopes: ['org#hci', 'org#dv'] }]
|
|
237
|
+
|
|
238
|
+
// Empty scopes = full access
|
|
239
|
+
mergeResolvedPermissions(
|
|
240
|
+
[{ id: 'js:core:episodes:get', scopes: ['org#hci'] }],
|
|
241
|
+
[{ id: 'js:core:episodes:get', scopes: [] }],
|
|
242
|
+
);
|
|
243
|
+
// [{ id: 'js:core:episodes:get', scopes: [] }]
|
|
73
244
|
```
|
|
74
245
|
|
|
75
|
-
|
|
246
|
+
### `encodeScopes(scopes)`
|
|
247
|
+
|
|
248
|
+
Converts a scopes array back to string format.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { encodeScopes } from '@pcg/auth';
|
|
252
|
+
|
|
253
|
+
encodeScopes(['org#xxx', 'user#xxx']);
|
|
254
|
+
// '[org#xxx,user#xxx]'
|
|
255
|
+
|
|
256
|
+
encodeScopes([['org#xxx', 'published']]);
|
|
257
|
+
// '[org#xxx+published]'
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### `injectScopesIntoPermission(permission, scopes)`
|
|
261
|
+
|
|
262
|
+
Adds scopes to a permission string.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { injectScopesIntoPermission } from '@pcg/auth';
|
|
266
|
+
|
|
267
|
+
injectScopesIntoPermission('js:core:episodes:create', ['org']);
|
|
268
|
+
// 'js:core:episodes[org]:create'
|
|
269
|
+
|
|
270
|
+
injectScopesIntoPermission('js:core:episodes[org]:create', ['shared']);
|
|
271
|
+
// 'js:core:episodes[org,shared]:create'
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### `replaceScope(scopes, from, to)`
|
|
275
|
+
|
|
276
|
+
Replaces specific scope values in an array.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { replaceScope } from '@pcg/auth';
|
|
280
|
+
|
|
281
|
+
replaceScope(['assigned'], 'assigned', 'brand#brd:xxx');
|
|
282
|
+
// ['brand#brd:xxx']
|
|
283
|
+
|
|
284
|
+
// Works with AND scopes
|
|
285
|
+
replaceScope([['assigned', 'lang']], 'assigned', 'brand#brd:xxx');
|
|
286
|
+
// [['brand#brd:xxx', 'lang']]
|
|
287
|
+
|
|
288
|
+
// Sequential replacement
|
|
289
|
+
let scopes = [['assigned', 'lang']];
|
|
290
|
+
scopes = replaceScope(scopes, 'assigned', 'brand#brd:xxx');
|
|
291
|
+
scopes = replaceScope(scopes, 'lang', 'lang#en');
|
|
292
|
+
// [['brand#brd:xxx', 'lang#en']]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Functional Scope Builders (recommended)
|
|
298
|
+
|
|
299
|
+
Imported from `@pcg/auth/scopes`. Pure functions with no classes — convenient for inline use.
|
|
300
|
+
|
|
301
|
+
`isGranted` accepts a single scope string directly or an array for multiple scopes.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import * as s from '@pcg/auth/scopes';
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Available Functions
|
|
308
|
+
|
|
309
|
+
| Function | Returns | Example |
|
|
310
|
+
|----------|---------|---------|
|
|
311
|
+
| `s.anyScope()` | `ActionScopesArray` | `s.anyScope()` → `['*']` |
|
|
312
|
+
| `s.org(id)` | `string` | `s.org('jsorg:hci')` → `'org#jsorg:hci'` |
|
|
313
|
+
| `s.id(entityId)` | `string` | `s.id('ep:123')` → `'id#ep:123'` |
|
|
314
|
+
| `s.user(id)` | `string` | `s.user('hcu:xxx')` → `'user#hcu:xxx'` |
|
|
315
|
+
| `s.form(id)` | `string` | `s.form('contact')` → `'form#contact'` |
|
|
316
|
+
| `s.group(id)` | `string` | `s.group('hcgrp:ZT9')` → `'grp#hcgrp:ZT9'` |
|
|
317
|
+
| `s.scope(name, id?)` | `string` | `s.scope('orggroup', 'hcgrp:ZT9')` → `'orggroup#hcgrp:ZT9'` |
|
|
318
|
+
| `s.and(...items)` | `string[]` | `s.and(s.org('hci'), 'published')` → `['org#hci', 'published']` |
|
|
319
|
+
|
|
320
|
+
### Usage Examples
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import * as s from '@pcg/auth/scopes';
|
|
324
|
+
import { isGranted } from '@pcg/auth';
|
|
325
|
+
|
|
326
|
+
// Single scope — no array needed
|
|
327
|
+
isGranted(user, 'js:core:brands:update', s.org(brand.orgId));
|
|
76
328
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
329
|
+
// Multiple scopes (OR) — use array
|
|
330
|
+
isGranted(user, 'js:core:episodes:get', [s.org(episode.orgId), s.id(episode.id)]);
|
|
331
|
+
|
|
332
|
+
// AND scopes (reuse variable)
|
|
333
|
+
const org = s.org(episode.orgId);
|
|
334
|
+
isGranted(user, 'js:core:episodes:get', [org, s.and(org, 'published')]);
|
|
335
|
+
|
|
336
|
+
// Wildcard
|
|
337
|
+
isGranted(user, 'js:core:episodes:list', s.anyScope());
|
|
338
|
+
|
|
339
|
+
// Custom scope
|
|
340
|
+
isGranted(user, 'js:mam:episodes:create', s.scope('action', 'approve'));
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## ActionScopes (legacy)
|
|
346
|
+
|
|
347
|
+
The `ActionScopes` class is still supported for backward compatibility. Use functional scope builders for new code.
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { ActionScopes, isGranted } from '@pcg/auth';
|
|
351
|
+
|
|
352
|
+
const scopes = new ActionScopes();
|
|
353
|
+
scopes.set('org', 'hcorg:company1'); // org#hcorg:company1
|
|
354
|
+
scopes.set('org+published'); // AND: ['org#hcorg:company1', 'published']
|
|
355
|
+
scopes.setEntityId('jse:episode123'); // id#jse:episode123
|
|
356
|
+
|
|
357
|
+
isGranted(user, 'js:core:episodes:get', scopes);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## ScopesBulder
|
|
363
|
+
|
|
364
|
+
Utility for building complex scope combinations.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { ScopesBulder } from '@pcg/auth';
|
|
368
|
+
|
|
369
|
+
const builder = new ScopesBulder();
|
|
370
|
+
|
|
371
|
+
// Add scopes
|
|
372
|
+
builder.append('org#hci');
|
|
373
|
+
builder.extend(['org#hci', 'org#dv']);
|
|
374
|
+
|
|
375
|
+
// Join — creates AND combinations (cartesian product)
|
|
376
|
+
builder.extend(['published', 'draft']);
|
|
377
|
+
builder.join('org#hcc', 'before');
|
|
378
|
+
// [['org#hcc', 'published'], ['org#hcc', 'draft']]
|
|
379
|
+
|
|
380
|
+
// Replace prefix
|
|
381
|
+
builder.replacePrefix('org', 'id');
|
|
382
|
+
// ['org#hcorg:hci'] → ['id#hcorg:hci']
|
|
383
|
+
|
|
384
|
+
// Clone and build
|
|
385
|
+
const clone = builder.clone();
|
|
386
|
+
const scopes = builder.build();
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Example: Multi-language, multi-org access
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
const builder = new ScopesBulder();
|
|
393
|
+
builder.extend(['org#hci', 'org#dv']);
|
|
394
|
+
builder.join(['lang#en', 'lang#de']);
|
|
395
|
+
builder.build();
|
|
396
|
+
// [
|
|
397
|
+
// ['org#hci', 'lang#en'],
|
|
398
|
+
// ['org#hci', 'lang#de'],
|
|
399
|
+
// ['org#dv', 'lang#en'],
|
|
400
|
+
// ['org#dv', 'lang#de'],
|
|
401
|
+
// ]
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## NestJS Integration
|
|
407
|
+
|
|
408
|
+
### Basic CRUD
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import * as s from '@pcg/auth/scopes';
|
|
412
|
+
|
|
413
|
+
@Resolver(() => Episode)
|
|
414
|
+
@UseGuards(GraphQLJwtAuthGuard, GraphQLPermissionsGuard)
|
|
415
|
+
export class EpisodesResolver {
|
|
416
|
+
|
|
417
|
+
@Query(() => Episode)
|
|
418
|
+
@UsePermission('js:core:episodes:get')
|
|
419
|
+
async episode(
|
|
420
|
+
@Args('id') id: string,
|
|
421
|
+
@ActionContextParam() ctx: ActionContext,
|
|
422
|
+
) {
|
|
423
|
+
const episode = await this.episodesService.findOne(id);
|
|
424
|
+
|
|
425
|
+
ctx.validateAccess(
|
|
426
|
+
'js:core:episodes:get',
|
|
427
|
+
[s.org(episode.organizationId), s.id(episode.id)],
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
return episode;
|
|
431
|
+
}
|
|
83
432
|
}
|
|
84
|
-
```
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Two-Level Permission Check
|
|
436
|
+
|
|
437
|
+
1. **`@UsePermission()`** — guard checks whether the user has access to the action with **any** scope (uses `['*']` wildcard)
|
|
438
|
+
2. **`ctx.validateAccess()`** — inside the resolver, checks **specific scopes** for the given entity
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import * as s from '@pcg/auth/scopes';
|
|
442
|
+
|
|
443
|
+
@Mutation(() => Episode)
|
|
444
|
+
@UsePermission('js:mam:episodes:update') // Step 1: does the user have this permission at all?
|
|
445
|
+
async updateEpisode(
|
|
446
|
+
@Args('input') input: UpdateEpisodeInput,
|
|
447
|
+
@ActionContextParam() ctx: ActionContext,
|
|
448
|
+
) {
|
|
449
|
+
const episode = await this.episodeService.findOne(input.id);
|
|
450
|
+
|
|
451
|
+
// Step 2: check the specific scope
|
|
452
|
+
ctx.validateAccess(
|
|
453
|
+
'js:mam:episodes:update',
|
|
454
|
+
s.org(episode.organizationId),
|
|
455
|
+
);
|
|
456
|
+
// If the episode belongs to company2 but the user only has access to company1 — throws an error
|
|
457
|
+
|
|
458
|
+
return this.episodeService.update(input, ctx);
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### `ctx.validateAccess()` vs `ctx.isGranted()`
|
|
463
|
+
|
|
464
|
+
| Method | Behavior | When to use |
|
|
465
|
+
|--------|----------|-------------|
|
|
466
|
+
| `ctx.validateAccess()` | Throws an error if access is denied | When access is mandatory |
|
|
467
|
+
| `ctx.isGranted()` | Returns `boolean` | When branching logic is needed |
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// validateAccess — throws if access is denied
|
|
471
|
+
ctx.validateAccess('js:core:episodes:get', s.org(episode.orgId));
|
|
472
|
+
|
|
473
|
+
// isGranted — for conditional logic
|
|
474
|
+
if (ctx.isGranted('js:core:episodes:list', s.anyScope())) {
|
|
475
|
+
return this.episodeService.findAll();
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Conditional Access: Full vs Scoped
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import * as s from '@pcg/auth/scopes';
|
|
483
|
+
|
|
484
|
+
@Query(() => [Episode])
|
|
485
|
+
@UsePermission('js:mam:episodes:list')
|
|
486
|
+
async episodes(@ActionContextParam() ctx: ActionContext) {
|
|
487
|
+
// Admin: full access (permission without scopes)
|
|
488
|
+
if (ctx.isGranted('js:mam:episodes:list', s.anyScope())) {
|
|
489
|
+
return this.episodeService.findAll();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// User: organization-scoped access
|
|
493
|
+
if (ctx.isGranted(
|
|
494
|
+
'js:mam:episodes:list',
|
|
495
|
+
s.org(ctx.user.organizationId),
|
|
496
|
+
)) {
|
|
497
|
+
return this.episodeService.findByOrg(ctx.user.organizationId);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return [];
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Status-Based Access (published/draft)
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import * as s from '@pcg/auth/scopes';
|
|
508
|
+
|
|
509
|
+
// Permission: 'js:mam:episodes[org+published]:get'
|
|
510
|
+
// Can only view published episodes within their organization
|
|
511
|
+
|
|
512
|
+
async getEpisode(id: string, ctx: ActionContext) {
|
|
513
|
+
const episode = await this.repository.findOne(id);
|
|
514
|
+
|
|
515
|
+
const org = s.org(episode.organizationId);
|
|
516
|
+
const items: s.ScopeItem[] = [org];
|
|
517
|
+
|
|
518
|
+
if (episode.status === 'published') {
|
|
519
|
+
items.push('published', s.and(org, 'published'));
|
|
520
|
+
} else if (episode.status === 'draft') {
|
|
521
|
+
items.push('draft', s.and(org, 'draft'));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
ctx.validateAccess('js:mam:episodes:get', items);
|
|
525
|
+
|
|
526
|
+
return episode;
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Public Resources
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
import * as s from '@pcg/auth/scopes';
|
|
534
|
+
|
|
535
|
+
// AUTHORIZED: 'tl:core:forms[public]:get'
|
|
536
|
+
// ORGANIZATION_ADMIN: 'tl:core:forms[assigned-org]:get' → resolved to org#tlorg:xxx
|
|
537
|
+
|
|
538
|
+
@Query(() => Form)
|
|
539
|
+
@UsePermission('tl:core:forms:get')
|
|
540
|
+
async form(
|
|
541
|
+
@Args() input: FetchFormInput,
|
|
542
|
+
@ActionContextParam() ctx: ActionContext,
|
|
543
|
+
) {
|
|
544
|
+
const form = await this.formsService.getOneByOrFail(input, ctx);
|
|
545
|
+
|
|
546
|
+
const items: s.ScopeItem[] = [
|
|
547
|
+
s.org(form.organizationId),
|
|
548
|
+
s.id(form.id),
|
|
549
|
+
];
|
|
550
|
+
|
|
551
|
+
if (form.audienceType === FormAudienceType.PUBLIC) {
|
|
552
|
+
items.push('public');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
ctx.validateAccess('tl:core:forms:get', items);
|
|
556
|
+
|
|
557
|
+
return form;
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Preparing the User Object
|
|
564
|
+
|
|
565
|
+
Permission strings are stored in the database as an array of strings. Before checking access, they must be parsed into `resolvedPermissions`:
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
import { resolvePermissions } from '@pcg/auth';
|
|
569
|
+
|
|
570
|
+
const rawPermissions = [
|
|
571
|
+
'js:core:episodes[org]:get',
|
|
572
|
+
'js:core:episodes[published]:get',
|
|
573
|
+
'js:core:episodes[org]:create',
|
|
574
|
+
'js:mam:*[org]:*',
|
|
575
|
+
];
|
|
576
|
+
|
|
577
|
+
const user = {
|
|
578
|
+
id: 'hcu:2x6g7l8n9op',
|
|
579
|
+
permissions: rawPermissions,
|
|
580
|
+
resolvedPermissions: resolvePermissions(rawPermissions),
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// resolvedPermissions:
|
|
584
|
+
// [
|
|
585
|
+
// { id: 'js:core:episodes:get', scopes: ['org', 'published'] },
|
|
586
|
+
// { id: 'js:core:episodes:create', scopes: ['org'] },
|
|
587
|
+
// { id: 'js:mam:*:*', scopes: ['org'] },
|
|
588
|
+
// ]
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## Best Practices
|
|
594
|
+
|
|
595
|
+
1. **Permission naming**: lowercase with colons — `js:core:episodes:get`
|
|
596
|
+
2. **Standard actions**: `get`, `list`, `create`, `update`, `delete`
|
|
597
|
+
3. **Scopes**: use `org` for organization, `id#` for entity
|
|
598
|
+
4. **Functional builders**: prefer `@pcg/auth/scopes` over the `ActionScopes` class
|
|
599
|
+
5. **Checks in resolvers**: all scope checks should be done in the resolver, NOT in the service layer
|
|
600
|
+
6. **`@UsePermission`**: for basic access control at the guard level
|
|
601
|
+
7. **`ctx.validateAccess()`**: for scope-specific checks inside the resolver
|
|
602
|
+
8. **Do not use** `assigned-org`, `assigned-form`, `assigned-group` in action scopes — these are meant for role definitions and are automatically resolved to `org#xxx` / `id#xxx`
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region src/permissions/functional-scopes.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a wildcard scope that matches any permission scope.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* isGranted(user, 'js:core:episodes:list', anyScope())
|
|
7
|
+
*/
|
|
8
|
+
function anyScope() {
|
|
9
|
+
return ["*"];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create an organization scope.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* org('jsorg:hci') // => 'org#jsorg:hci'
|
|
16
|
+
*/
|
|
17
|
+
function org(id$1) {
|
|
18
|
+
return `org#${id$1}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create an entity id scope.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* id('ep:123') // => 'id#ep:123'
|
|
25
|
+
*/
|
|
26
|
+
function id(entityId) {
|
|
27
|
+
return `id#${entityId}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a user scope.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* user('hcu:xxx') // => 'user#hcu:xxx'
|
|
34
|
+
*/
|
|
35
|
+
function user(id$1) {
|
|
36
|
+
return `user#${id$1}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a form scope.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* form('contact') // => 'form#contact'
|
|
43
|
+
*/
|
|
44
|
+
function form(id$1) {
|
|
45
|
+
return `form#${id$1}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a group scope.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* group('hcgrp:ZT9') // => 'grp#hcgrp:ZT9'
|
|
52
|
+
*/
|
|
53
|
+
function group(id$1) {
|
|
54
|
+
return `grp#${id$1}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create a named scope with an optional ID.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* scope('published') // => 'published'
|
|
61
|
+
* scope('orggroup', 'hcgrp:ZT9mt8j9lSR') // => 'orggroup#hcgrp:ZT9mt8j9lSR'
|
|
62
|
+
*/
|
|
63
|
+
function scope(name, id$1) {
|
|
64
|
+
return id$1 ? `${name}#${id$1}` : name;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Combine multiple scopes with AND logic (all must match).
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* and(org('jsorg:hci'), 'published')
|
|
71
|
+
* // => ['org#jsorg:hci', 'published']
|
|
72
|
+
*/
|
|
73
|
+
function and(...items) {
|
|
74
|
+
return items;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { id as a, user as c, group as i, anyScope as n, org as o, form as r, scope as s, and as t };
|
|
79
|
+
//# sourceMappingURL=functional-scopes-COiTBSy_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functional-scopes-COiTBSy_.js","names":["id"],"sources":["../src/permissions/functional-scopes.ts"],"sourcesContent":["import { type ActionScopesArray } from './action-scopes.js';\n\nexport type ScopeItem = string | string[];\n\n/**\n * Create a wildcard scope that matches any permission scope.\n *\n * @example\n * isGranted(user, 'js:core:episodes:list', anyScope())\n */\nexport function anyScope(): ActionScopesArray {\n return ['*'];\n}\n\n/**\n * Create an organization scope.\n *\n * @example\n * org('jsorg:hci') // => 'org#jsorg:hci'\n */\nexport function org(id: string): string {\n return `org#${id}`;\n}\n\n/**\n * Create an entity id scope.\n *\n * @example\n * id('ep:123') // => 'id#ep:123'\n */\nexport function id(entityId: string): string {\n return `id#${entityId}`;\n}\n\n/**\n * Create a user scope.\n *\n * @example\n * user('hcu:xxx') // => 'user#hcu:xxx'\n */\nexport function user(id: string): string {\n return `user#${id}`;\n}\n\n/**\n * Create a form scope.\n *\n * @example\n * form('contact') // => 'form#contact'\n */\nexport function form(id: string): string {\n return `form#${id}`;\n}\n\n/**\n * Create a group scope.\n *\n * @example\n * group('hcgrp:ZT9') // => 'grp#hcgrp:ZT9'\n */\nexport function group(id: string): string {\n return `grp#${id}`;\n}\n\n/**\n * Create a named scope with an optional ID.\n *\n * @example\n * scope('published') // => 'published'\n * scope('orggroup', 'hcgrp:ZT9mt8j9lSR') // => 'orggroup#hcgrp:ZT9mt8j9lSR'\n */\nexport function scope(name: string, id?: string): string {\n return id ? `${name}#${id}` : name;\n}\n\n/**\n * Combine multiple scopes with AND logic (all must match).\n *\n * @example\n * and(org('jsorg:hci'), 'published')\n * // => ['org#jsorg:hci', 'published']\n */\nexport function and(...items: string[]): string[] {\n return items;\n}\n"],"mappings":";;;;;;;AAUA,SAAgB,WAA8B;AAC5C,QAAO,CAAC,IAAI;;;;;;;;AASd,SAAgB,IAAI,MAAoB;AACtC,QAAO,OAAOA;;;;;;;;AAShB,SAAgB,GAAG,UAA0B;AAC3C,QAAO,MAAM;;;;;;;;AASf,SAAgB,KAAK,MAAoB;AACvC,QAAO,QAAQA;;;;;;;;AASjB,SAAgB,KAAK,MAAoB;AACvC,QAAO,QAAQA;;;;;;;;AASjB,SAAgB,MAAM,MAAoB;AACxC,QAAO,OAAOA;;;;;;;;;AAUhB,SAAgB,MAAM,MAAc,MAAqB;AACvD,QAAOA,OAAK,GAAG,KAAK,GAAGA,SAAO;;;;;;;;;AAUhC,SAAgB,IAAI,GAAG,OAA2B;AAChD,QAAO"}
|
|
@@ -0,0 +1,118 @@
|
|
|
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/permissions/functional-scopes.d.ts
|
|
57
|
+
type ScopeItem = string | string[];
|
|
58
|
+
/**
|
|
59
|
+
* Create a wildcard scope that matches any permission scope.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* isGranted(user, 'js:core:episodes:list', anyScope())
|
|
63
|
+
*/
|
|
64
|
+
declare function anyScope(): ActionScopesArray;
|
|
65
|
+
/**
|
|
66
|
+
* Create an organization scope.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* org('jsorg:hci') // => 'org#jsorg:hci'
|
|
70
|
+
*/
|
|
71
|
+
declare function org(id: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Create an entity id scope.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* id('ep:123') // => 'id#ep:123'
|
|
77
|
+
*/
|
|
78
|
+
declare function id(entityId: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Create a user scope.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* user('hcu:xxx') // => 'user#hcu:xxx'
|
|
84
|
+
*/
|
|
85
|
+
declare function user(id: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* Create a form scope.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* form('contact') // => 'form#contact'
|
|
91
|
+
*/
|
|
92
|
+
declare function form(id: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Create a group scope.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* group('hcgrp:ZT9') // => 'grp#hcgrp:ZT9'
|
|
98
|
+
*/
|
|
99
|
+
declare function group(id: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* Create a named scope with an optional ID.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* scope('published') // => 'published'
|
|
105
|
+
* scope('orggroup', 'hcgrp:ZT9mt8j9lSR') // => 'orggroup#hcgrp:ZT9mt8j9lSR'
|
|
106
|
+
*/
|
|
107
|
+
declare function scope(name: string, id?: string): string;
|
|
108
|
+
/**
|
|
109
|
+
* Combine multiple scopes with AND logic (all must match).
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* and(org('jsorg:hci'), 'published')
|
|
113
|
+
* // => ['org#jsorg:hci', 'published']
|
|
114
|
+
*/
|
|
115
|
+
declare function and(...items: string[]): string[];
|
|
116
|
+
//#endregion
|
|
117
|
+
export { group as a, scope as c, ActionScopesArray as d, form as i, user as l, and as n, id as o, anyScope as r, org as s, ScopeItem as t, ActionScopes as u };
|
|
118
|
+
//# sourceMappingURL=functional-scopes-ut8Z6MmG.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functional-scopes-ut8Z6MmG.d.ts","names":[],"sources":["../src/permissions/action-scopes.ts","../src/permissions/functional-scopes.ts"],"sourcesContent":[],"mappings":";KAAY,iBAAA;AAAZ;AAMA;;;AA6Dc,cA7DD,YAAA,CA6DC;EAAiB,KAAA,EA5Df,iBA4De;;;uBAxDR;ECTX;AAQZ;AAUA;AAUA;AAUA;AAUA;AAUA;AAWA;AAWA;;;;;;;;;;;;;;;;;;cDfc;;;;;;;;;;;;;;;;;;;AAnEF,KCEA,SAAA,GDFiB,MAAA,GAAA,MAAA,EAAA;AAM7B;;;;;;iBCIgB,QAAA,CAAA,GAAY;;AAR5B;AAQA;AAUA;AAUA;AAUA;AAUgB,iBA9BA,GAAA,CA8BI,EAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAUpB;AAWA;AAWA;;;;iBApDgB,EAAA;;;;;;;iBAUA,IAAA;;;;;;;iBAUA,IAAA;;;;;;;iBAUA,KAAA;;;;;;;;iBAWA,KAAA;;;;;;;;iBAWA,GAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,59 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
|
1
|
+
import { a as group, c as scope, d as ActionScopesArray, i as form, l as user, n as and, o as id, r as anyScope, s as org, t as ScopeItem, u as ActionScopes } from "./functional-scopes-ut8Z6MmG.js";
|
|
2
|
+
|
|
56
3
|
//#region src/types/permissions.d.ts
|
|
4
|
+
|
|
57
5
|
/**
|
|
58
6
|
* Resolved permission string to object with scopes
|
|
59
7
|
* @example
|
|
@@ -124,7 +72,7 @@ interface IUser {
|
|
|
124
72
|
* @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)
|
|
125
73
|
* @returns - true if user has access to permission
|
|
126
74
|
*/
|
|
127
|
-
declare const isGranted: (user: IUser, permission: string, actionScopes?: ActionScopesArray | ActionScopes) => boolean;
|
|
75
|
+
declare const isGranted: (user: IUser, permission: string, actionScopes?: ActionScopesArray | ActionScopes | string) => boolean;
|
|
128
76
|
declare const encodeScopes: (scopes: (string | string[])[]) => string;
|
|
129
77
|
/**
|
|
130
78
|
* Inject scopes into permission
|
|
@@ -242,5 +190,5 @@ declare class ScopesBulder {
|
|
|
242
190
|
replacePrefix(from: string, to: string): void;
|
|
243
191
|
}
|
|
244
192
|
//#endregion
|
|
245
|
-
export { ActionScopes, ActionScopesArray, IUser, ReadonlyResolvedPermission, ResolvedPermission, ResolvedPermissionGroup, ScopesBulder, concatScopes, encodeScopes, injectScopesIntoPermission, isGranted, mergeResolvedPermissions, replaceScope, resolvePermission, resolvePermissionGroup, resolvePermissionGroups, resolvePermissions };
|
|
193
|
+
export { ActionScopes, ActionScopesArray, IUser, ReadonlyResolvedPermission, ResolvedPermission, ResolvedPermissionGroup, ScopeItem, ScopesBulder, and, anyScope, concatScopes, encodeScopes, form, group, id, injectScopesIntoPermission, isGranted, mergeResolvedPermissions, org, replaceScope, resolvePermission, resolvePermissionGroup, resolvePermissionGroups, resolvePermissions, scope, user };
|
|
246
194
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types/permissions.ts","../src/types/user.ts","../src/permissions/helpers.ts","../src/permissions/scopes-bulder.ts"],"sourcesContent":[],"mappings":";;;;;;;AAKA;AAUA;AAKiB,UAfA,kBAAA,CAe0B;;;;AClB3C;;;;ACwCA;AACQ,UF5BS,uBAAA,CE4BT;EAEQ,EAAA,EAAA,MAAA;EAAoB,MAAA,EAAA,CAAA,MAAA,GAAA,MAAA,EAAA,CAAA,EAAA;;AAgCvB,UFzDI,0BAAA,CE+DhB;EAWY,SAAA,EAAA,EAAA,MAAA;EAqBA,SAAA,MAqBZ,EAAA,SAAA,CAAA,MAAA,GAAA,MAAA,EAAA,CAAA,EAAA;AAUD;;;UDhJiB,KAAA;;ADGjB;AAUA;AAKA;;;;EClBiB,EAAA,EAAA,MAAK;;;;ACwCtB;;;;EAGgD,WAAA,EAAA,SAAA,MAAA,EAAA;EAgCnC;AAiBb;AAqBA;AA+BA;AAkCA;AAiCA;AAaA;AAOA;;;EAE8B,mBAAA,ED5MP,0BC4MO,EAAA;;;;AFtO9B;AAUA;AAKA;;;;AClBA;;;;ACwCA;;;;;AAmCa,cAnCA,SAyCZ,EAAA,CAAA,IAAA,EAxCO,KAwCP,EAAA,UAAA,EAAA,MAAA,EAAA,YAAA,CAAA,EAtCe,iBAsCf,GAtCmC,YAsCnC,GAAA,MAAA,EAAA,GAAA,OAAA;AAWY,cAjBA,YA4BZ,EAAA,CAAA,MAAA,EAAA,CAAA,MAAA,GAAA,MAAA,EAAA,CAAA,EAAA,EAAA,GAAA,MAAA;AAUD;AA+BA;AAkCA;AAiCA;AAaA;AAOA;;;;AAE8B,cA7IjB,0BA6IiB,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,cAAA,EAAA,CAAA,MAAA,GAAA,MAAA,EAAA,CAAA,EAAA,EAAA,GAAA,MAAA;AAmD9B;;;;AC5RA;;;;AAmCyC,cD8E5B,YC9E4B,EAAA,CAAA,GAAA,EAAA,CAAA,MAAA,GAAA,MAAA,EAAA,CAAA,EAAA,EAAA,KAAA,EAAA,CAAA,MAAA,GAAA,MAAA,EAAA,CAAA,EAAA,EAAA,GAAA,IAAA;;;;;;;;;cD6G5B,2CAA0C;;;;;;;;;cAkC1C,+CAEV;;;;;;;;;cA+BU,qDAAiD;;;;;;;;;cAajD,yDAEV;cAKU,mCACH,8BACA,yBAAoB;;;;;;;;;;;cAmDjB;;;;;cC5RA,YAAA;;;EHGI,OAAA,UAAA,CAAA,OAAkB,EGAN,YHAM,CAAA,EGAM,YHAN;EAUlB,KAAA,CAAA,CAAA,EAAA,CAAA,MAAA,GAAA,MAAA,EAAuB,CAAA,EAAA;EAKvB,KAAA,CAAA,CAAA,EGPV,YHOU;;;;AClBjB;;;;ACwCA;;EAGgB,MAAA,CAAA,KAAA,EAAA,MAAA,GAAA,MAAA,EAAA,CAAA,EAAA,IAAA;EAAoB;;AAgCpC;AAiBA;AAqBA;AA+BA;AAkCA;EAiCa,MAAA,CAAA,MAAA,EAAA,CAAA,MAAA,GAGZ,MAAA,EAAA,CAAA,EAAA,GCnLwC,YDgLqB,CAAA,EAAA,IAG7D;EAUY;AAOb;;;;;AAqDA;;;;EC5Ra"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { a as id, c as user, i as group, n as anyScope, o as org, r as form, s as scope, t as and } from "./functional-scopes-COiTBSy_.js";
|
|
2
|
+
|
|
1
3
|
//#region src/permissions/action-scopes.ts
|
|
2
4
|
/**
|
|
3
5
|
* Action scopes are used to describe
|
|
@@ -19,8 +21,8 @@ var ActionScopes = class {
|
|
|
19
21
|
*
|
|
20
22
|
* // scopes.getArray() = ['id#jsu:123']
|
|
21
23
|
*/
|
|
22
|
-
setEntityId(id) {
|
|
23
|
-
this.set(`id#${id}`);
|
|
24
|
+
setEntityId(id$1) {
|
|
25
|
+
this.set(`id#${id$1}`);
|
|
24
26
|
return this;
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
@@ -37,16 +39,16 @@ var ActionScopes = class {
|
|
|
37
39
|
*
|
|
38
40
|
* // scopes.getArray() = ['org#jsorg:hci']
|
|
39
41
|
*/
|
|
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("+");
|
|
42
|
+
set(scope$1, id$1) {
|
|
43
|
+
if (id$1 && /^[A-Za-z_]+$/.test(scope$1)) this.idsMap.set(scope$1, id$1);
|
|
44
|
+
if (scope$1.includes("+")) {
|
|
45
|
+
const subscopes = scope$1.split("+");
|
|
44
46
|
this.array.push(subscopes.map((subscope) => this.resolveScopeId(subscope)));
|
|
45
|
-
} else this.array.push(this.resolveScopeId(scope));
|
|
47
|
+
} else this.array.push(this.resolveScopeId(scope$1));
|
|
46
48
|
return this;
|
|
47
49
|
}
|
|
48
|
-
has(scope) {
|
|
49
|
-
return this.array.includes(scope);
|
|
50
|
+
has(scope$1) {
|
|
51
|
+
return this.array.includes(scope$1);
|
|
50
52
|
}
|
|
51
53
|
getArray() {
|
|
52
54
|
return [...this.array];
|
|
@@ -86,9 +88,9 @@ var ActionScopes = class {
|
|
|
86
88
|
* authCtx.setScope('org+review');
|
|
87
89
|
* [org#hci, [org#hci, review]]
|
|
88
90
|
**/
|
|
89
|
-
resolveScopeId(scope) {
|
|
90
|
-
const id = this.idsMap.get(scope);
|
|
91
|
-
return id !== void 0 ? `${scope}#${id}` : scope;
|
|
91
|
+
resolveScopeId(scope$1) {
|
|
92
|
+
const id$1 = this.idsMap.get(scope$1);
|
|
93
|
+
return id$1 !== void 0 ? `${scope$1}#${id$1}` : scope$1;
|
|
92
94
|
}
|
|
93
95
|
};
|
|
94
96
|
|
|
@@ -102,8 +104,9 @@ var ActionScopes = class {
|
|
|
102
104
|
* scopes.set('my')
|
|
103
105
|
*/
|
|
104
106
|
const normalizeScopes = (scopes) => {
|
|
105
|
-
if (
|
|
106
|
-
return scopes;
|
|
107
|
+
if (scopes instanceof ActionScopes) return scopes;
|
|
108
|
+
if (typeof scopes === "string") return new ActionScopes([scopes]);
|
|
109
|
+
return new ActionScopes(scopes);
|
|
107
110
|
};
|
|
108
111
|
/**
|
|
109
112
|
* Check if user has access to permission
|
|
@@ -120,10 +123,10 @@ const normalizeScopes = (scopes) => {
|
|
|
120
123
|
* @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)
|
|
121
124
|
* @returns - true if user has access to permission
|
|
122
125
|
*/
|
|
123
|
-
const isGranted = (user, permission, actionScopes = []) => {
|
|
126
|
+
const isGranted = (user$1, permission, actionScopes = []) => {
|
|
124
127
|
const [service, module, resource, action] = permission.split(":");
|
|
125
|
-
if (user.resolvedPermissions.length === 0) return false;
|
|
126
|
-
const userPermissions = user.resolvedPermissions;
|
|
128
|
+
if (user$1.resolvedPermissions.length === 0) return false;
|
|
129
|
+
const userPermissions = user$1.resolvedPermissions;
|
|
127
130
|
const scopes = normalizeScopes(actionScopes);
|
|
128
131
|
const permissionCanBeExecutedInScopes = (perm, scopes$1) => perm.scopes.length === 0 || scopes$1.canBeExecutedInScopes(perm.scopes);
|
|
129
132
|
const regexp = /* @__PURE__ */ new RegExp(`^(${service}|\\*):(${module}|\\*):(${resource}|\\*):(${action}|\\*)$`);
|
|
@@ -144,8 +147,8 @@ const encodeScopes = (scopes) => {
|
|
|
144
147
|
* // js:core:episodes[org,published]:get
|
|
145
148
|
*/
|
|
146
149
|
const injectScopesIntoPermission = (permission, scopesToInject) => {
|
|
147
|
-
const { id, scopes } = resolvePermission(permission);
|
|
148
|
-
const [service, module, resource, action] = id.split(":");
|
|
150
|
+
const { id: id$1, scopes } = resolvePermission(permission);
|
|
151
|
+
const [service, module, resource, action] = id$1.split(":");
|
|
149
152
|
concatScopes(scopes, scopesToInject);
|
|
150
153
|
return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;
|
|
151
154
|
};
|
|
@@ -159,7 +162,7 @@ const injectScopesIntoPermission = (permission, scopesToInject) => {
|
|
|
159
162
|
*/
|
|
160
163
|
const concatScopes = (set, items) => {
|
|
161
164
|
for (const item of items) if (Array.isArray(item)) {
|
|
162
|
-
if (!set.some((scope) => Array.isArray(scope) && scope.length === item.length && scope.every((s) => item.includes(s)))) set.push(item);
|
|
165
|
+
if (!set.some((scope$1) => Array.isArray(scope$1) && scope$1.length === item.length && scope$1.every((s) => item.includes(s)))) set.push(item);
|
|
163
166
|
} else if (!set.includes(item)) set.push(item);
|
|
164
167
|
};
|
|
165
168
|
/**
|
|
@@ -198,11 +201,11 @@ const resolvePermission = (permission) => {
|
|
|
198
201
|
const resolvePermissions = (permissions) => {
|
|
199
202
|
const resolvedPermissions = [];
|
|
200
203
|
for (const permission of permissions) {
|
|
201
|
-
const { id, scopes } = resolvePermission(permission);
|
|
202
|
-
const currentPermission = resolvedPermissions.find((p) => p.id === id);
|
|
204
|
+
const { id: id$1, scopes } = resolvePermission(permission);
|
|
205
|
+
const currentPermission = resolvedPermissions.find((p) => p.id === id$1);
|
|
203
206
|
if (currentPermission) concatScopes(currentPermission.scopes, scopes);
|
|
204
207
|
else resolvedPermissions.push({
|
|
205
|
-
id,
|
|
208
|
+
id: id$1,
|
|
206
209
|
scopes
|
|
207
210
|
});
|
|
208
211
|
}
|
|
@@ -263,10 +266,10 @@ const mergeResolvedPermissions = (array1, array2) => {
|
|
|
263
266
|
*/
|
|
264
267
|
const replaceScope = (scopes, from, to) => {
|
|
265
268
|
const result = [];
|
|
266
|
-
for (const scope of scopes) if (Array.isArray(scope)) result.push(replaceScope(scope, from, to));
|
|
267
|
-
else if (scope === from) if (Array.isArray(to)) result.push(...to);
|
|
269
|
+
for (const scope$1 of scopes) if (Array.isArray(scope$1)) result.push(replaceScope(scope$1, from, to));
|
|
270
|
+
else if (scope$1 === from) if (Array.isArray(to)) result.push(...to);
|
|
268
271
|
else result.push(to);
|
|
269
|
-
else result.push(scope);
|
|
272
|
+
else result.push(scope$1);
|
|
270
273
|
return result;
|
|
271
274
|
};
|
|
272
275
|
|
|
@@ -294,8 +297,8 @@ var ScopesBulder = class ScopesBulder {
|
|
|
294
297
|
* append(['org#hci', 'lang#en])
|
|
295
298
|
* // ['org#xxx'] => ['org#xxx', ['org#hci', 'lang#en']]
|
|
296
299
|
*/
|
|
297
|
-
append(scope) {
|
|
298
|
-
this.scopes.push(scope);
|
|
300
|
+
append(scope$1) {
|
|
301
|
+
this.scopes.push(scope$1);
|
|
299
302
|
}
|
|
300
303
|
/**
|
|
301
304
|
* Extend current scopes with the given scopes
|
|
@@ -322,8 +325,8 @@ var ScopesBulder = class ScopesBulder {
|
|
|
322
325
|
const scopesToJoin = Array.isArray(scopeOrScopes) ? scopeOrScopes : [scopeOrScopes];
|
|
323
326
|
if (scopesToJoin.length === 0) return;
|
|
324
327
|
const currentScopesCopy = this.build();
|
|
325
|
-
for (const scope of scopesToJoin) {
|
|
326
|
-
const scopeToJoin = Array.isArray(scope) ? scope : [scope];
|
|
328
|
+
for (const scope$1 of scopesToJoin) {
|
|
329
|
+
const scopeToJoin = Array.isArray(scope$1) ? scope$1 : [scope$1];
|
|
327
330
|
for (const s of currentScopesCopy) if (Array.isArray(s)) result.push(pos === "before" ? [...scopeToJoin, ...s] : [...s, ...scopeToJoin]);
|
|
328
331
|
else result.push(pos === "before" ? [...scopeToJoin, s] : [s, ...scopeToJoin]);
|
|
329
332
|
}
|
|
@@ -339,12 +342,12 @@ var ScopesBulder = class ScopesBulder {
|
|
|
339
342
|
*/
|
|
340
343
|
replacePrefix(from, to) {
|
|
341
344
|
const result = [];
|
|
342
|
-
for (const scope of this.scopes) if (Array.isArray(scope)) result.push(scope.map((s) => s.replace(`${from}#`, `${to}#`)));
|
|
343
|
-
else result.push(scope.replace(`${from}#`, `${to}#`));
|
|
345
|
+
for (const scope$1 of this.scopes) if (Array.isArray(scope$1)) result.push(scope$1.map((s) => s.replace(`${from}#`, `${to}#`)));
|
|
346
|
+
else result.push(scope$1.replace(`${from}#`, `${to}#`));
|
|
344
347
|
this.scopes = result;
|
|
345
348
|
}
|
|
346
349
|
};
|
|
347
350
|
|
|
348
351
|
//#endregion
|
|
349
|
-
export { ActionScopes, ScopesBulder, concatScopes, encodeScopes, injectScopesIntoPermission, isGranted, mergeResolvedPermissions, replaceScope, resolvePermission, resolvePermissionGroup, resolvePermissionGroups, resolvePermissions };
|
|
352
|
+
export { ActionScopes, ScopesBulder, and, anyScope, concatScopes, encodeScopes, form, group, id, injectScopesIntoPermission, isGranted, mergeResolvedPermissions, org, replaceScope, resolvePermission, resolvePermissionGroup, resolvePermissionGroups, resolvePermissions, scope, user };
|
|
350
353
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["scopes","scopes: (string | string[])[]","resolvedPermissions: ResolvedPermission[]","result: ResolvedPermission[]","result: (string | string[])[]","scopes: (string | string[])[]","result: (string | string[])[]"],"sources":["../src/permissions/action-scopes.ts","../src/permissions/helpers.ts","../src/permissions/scopes-bulder.ts"],"sourcesContent":["export type ActionScopesArray = (string | string[])[];\n\n/**\n * Action scopes are used to describe\n * what scopes are required to execute an action.\n */\nexport class ActionScopes {\n public array: ActionScopesArray = [];\n public anyScope = false;\n private idsMap = new Map<string, string>();\n\n constructor(scopes?: ActionScopesArray) {\n if (scopes) {\n this.array = scopes;\n }\n }\n\n /**\n * Set id scope (e.g. 'id#jsu:123')\n * @param id - entity id\n * @returns - this\n * @example\n * scopes.setEntityId('jsu:123')\n *\n * // scopes.getArray() = ['id#jsu:123']\n */\n setEntityId(id: string): this {\n this.set(`id#${id}`);\n\n return this;\n }\n\n /**\n * Set scope (e.g. 'org', 'org+review')\n * @param scope - scope name (e.g. 'org', 'org+review')\n * @param id - entity id (e.g. 'jsorg:hci')\n * @returns - this\n * @example\n * scopes.set('my')\n *\n * // scopes.getArray() = ['my']\n *\n * scopes.set('org', 'jsorg:hci')\n *\n * // scopes.getArray() = ['org#jsorg:hci']\n */\n set(scope: string, id?: string): this {\n if (id && /^[A-Za-z_]+$/.test(scope)) {\n this.idsMap.set(scope, id);\n }\n\n if (scope.includes('+')) {\n const subscopes = scope.split('+');\n this.array.push(\n subscopes.map((subscope) => this.resolveScopeId(subscope)),\n );\n } else {\n this.array.push(this.resolveScopeId(scope));\n }\n\n return this;\n }\n\n has(scope: string): boolean {\n return this.array.includes(scope);\n }\n\n getArray(): ActionScopesArray {\n return [...this.array];\n }\n\n /**\n * Permission scopes must cover auth scopes determined in auth context.\n * @param permissionScopes\n */\n canBeExecutedInScopes(\n permissionScopes: readonly (string | string[])[],\n ): boolean {\n // if scopes has * then it can be executed in any scope\n if (this.array.includes('*')) {\n return true;\n }\n\n /* action scopes = ['user#jsu:123', 'org#hci', ['org#hci', 'draft']]\n /* permission scopes: ['user#jsu:123', ['org#hci', 'draft']]\n\n /**\n * empty user permission scopes = full access to action\n */\n if (permissionScopes.length === 0) {\n return true;\n }\n\n /**\n * Empty auth scopes but user has permission scopes = no access to action\n *\n * action scopes = []\n * permission scopes = ['org#hci']\n *\n * Example:\n * User try to get list of all entities\n * But user only has permission to entity from org#hci\n */\n if (this.array.length === 0) {\n return false;\n }\n\n return this.array.some((authScope) => {\n if (Array.isArray(authScope)) {\n // scopes ['org#hci', 'draft'] == permissionScope ['org#hci', 'draft']\n\n return permissionScopes.some((permissionScope) => {\n if (Array.isArray(permissionScope)) {\n return permissionScope.every(\n (sub) => authScope.includes(sub),\n );\n }\n\n return authScope.includes(permissionScope);\n });\n }\n\n return permissionScopes.includes(authScope); // 'user#jsu:123' == 'user#jsu:123'\n });\n }\n\n /**\n * Resolve presaved scopes\n * @example\n * authCtx.setScope('org', 'jsorg:hci')\n * [org#hci]\n *\n * authCtx.setScope('org+review');\n * [org#hci, [org#hci, review]]\n **/\n private resolveScopeId(scope: string) {\n const id = this.idsMap.get(scope);\n return id !== undefined\n ? `${scope}#${id}`\n : scope;\n }\n}\n","import {\n type ReadonlyResolvedPermission, type ResolvedPermission, type ResolvedPermissionGroup,\n} from '../types/permissions.js';\nimport { type IUser } from '../types/user.js';\nimport { ActionScopes, type ActionScopesArray } from './action-scopes.js';\n\n/**\n * Normilize scopes to ActionScopes\n * @example\n * const scopes = normalizeScopes(['org#hci', 'user#hcu:xxxxx') // ActionScopes\n *\n * scopes.set('my')\n */\nconst normalizeScopes = (\n scopes: ActionScopesArray | ActionScopes,\n): ActionScopes => {\n if (!(scopes instanceof ActionScopes)) {\n return new ActionScopes(scopes);\n }\n\n return scopes;\n};\n\n/**\n * Check if user has access to permission\n * @example\n *\n * // Check episode access\n * const scopes = new ActionScopes();\n * scopes.setEntityId(episode.id); // id#js:core:episodes:xxxxx\n * scopes.set('org', episode.organizationId); // org#hcorg:hci\n *\n * isGranted(user, 'js:core:episodes:get', scopes)\n * @param user - user object with resolvedPermissions\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)\n * @returns - true if user has access to permission\n */\nexport const isGranted = (\n user: IUser,\n permission: string,\n actionScopes: ActionScopesArray | ActionScopes = [],\n): boolean => {\n const [service, module, resource, action] = permission.split(':');\n\n if (user.resolvedPermissions.length === 0) {\n return false;\n }\n\n const userPermissions = user.resolvedPermissions;\n\n const scopes = normalizeScopes(actionScopes);\n\n const permissionCanBeExecutedInScopes = (perm: ReadonlyResolvedPermission, scopes: ActionScopes) =>\n (perm.scopes.length === 0 || scopes.canBeExecutedInScopes(perm.scopes));\n\n const regexp = new RegExp(`^(${service}|\\\\*):(${module}|\\\\*):(${resource}|\\\\*):(${action}|\\\\*)$`);\n\n // novajs:*:*:* or novajs:*:*[org]:*\n // novajs:module:*:* or novajs:module:*[org]:*\n // novajs:module:resource:* or novajs:module:resource:[org]:*\n // novajs:modules:resource:create\n // *:modules:resource:create\n if (userPermissions.some(\n (perm) => regexp.test(perm.id)\n && permissionCanBeExecutedInScopes(perm, scopes))\n ) {\n return true;\n }\n\n return false;\n};\n\nexport const encodeScopes = (scopes: (string | string[])[]): string => {\n const scopesString = scopes\n .map((s) => (Array.isArray(s) ? s.join('+') : s))\n .join(',');\n\n return scopesString.length > 0 ? `[${scopesString}]` : '';\n};\n\n/**\n * Inject scopes into permission\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param scopesToInject - scopes to inject (e.g. ['org', 'published'])\n * @returns - permission string with injected scopes (e.g. 'js:core:episodes[org,published]:get')\n * @example\n * injectScopesIntoPermission('js:core:episodes:get', ['org', 'published'])\n * // js:core:episodes[org,published]:get\n */\nexport const injectScopesIntoPermission = (\n permission: string,\n scopesToInject: (string | string[])[],\n) => {\n const { id, scopes } = resolvePermission(permission);\n\n const [service, module, resource, action] = id.split(':');\n\n concatScopes(scopes, scopesToInject);\n\n return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;\n};\n\n/**\n * Concat scopes\n * @param set - set of scopes\n * @param items - items to concat\n * @example\n * concatScopes(['org#hci'], ['org#dv'])\n * // ['org#hci', 'org#dv']\n */\nexport const concatScopes = (\n set: (string | string[])[],\n items: (string | string[])[],\n): void => {\n for (const item of items) {\n if (Array.isArray(item)) {\n // check if scopes not includes array with subscopes rp2Scope\n if (\n !set.some(\n (scope) =>\n Array.isArray(scope)\n && scope.length === item.length\n && scope.every((s) => item.includes(s)),\n )\n ) {\n set.push(item);\n }\n } else if (!set.includes(item)) {\n set.push(item);\n }\n }\n};\n\n/**\n * Resolve permission string to object with scopes\n * @param permission - permission string\n * @returns - resolved permission object\n * @example\n * resolvePermission('js:core:episodes[org,published]:get')\n * // { id: 'js:core:episodes:get', scopes: ['org', 'published'] }\n */\nexport const resolvePermission = (permission: string): ResolvedPermission => {\n const matches = /\\[(?<scopes>.*?)\\]/u.exec(permission);\n\n if (matches?.[0] && matches.groups?.scopes) {\n const scopes: (string | string[])[] = matches.groups.scopes\n .split(',') // scopes with OR logic\n .map((s) => {\n if (s.includes('+')) {\n return s.split('+'); // scopes with AND logic\n }\n\n return s;\n });\n\n return {\n id: permission.replace(matches[0], ''),\n scopes,\n };\n }\n\n return {\n id: permission,\n scopes: [],\n };\n};\n\n/**\n * Resolve permissions to array of resolved permissions\n * @param permissions - array of permissions\n * @returns - array of resolved permissions\n * @example\n * resolvePermissions(['js:core:episodes[org]:get', 'js:core:episodes[published]:get'])\n * // [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissions = (\n permissions: string[],\n): ResolvedPermission[] => {\n const resolvedPermissions: ResolvedPermission[] = [];\n\n for (const permission of permissions) {\n const { id, scopes } = resolvePermission(permission);\n\n const currentPermission = resolvedPermissions.find(\n (p) => p.id === id,\n );\n\n if (currentPermission) {\n concatScopes(currentPermission.scopes, scopes);\n } else {\n resolvedPermissions.push({\n id,\n scopes,\n });\n }\n }\n\n return resolvedPermissions;\n};\n\n/**\n * Resolve permission group string to object with scopes\n * @param permissionGroup - permission group string\n * @returns - resolved permission group object\n * @example\n * resolvePermissionGroup('g:js:core:episodes[org]:read')\n * // { id: 'g:js:core:episodes:read', scopes: ['org'] }\n */\nexport const resolvePermissionGroup = (permissionGroup: string) => {\n // Currently it has the same algorithm\n return resolvePermission(permissionGroup);\n};\n\n/**\n * Resolve permission groups to array of resolved permission groups\n * @param permissionGroups - array of permission groups\n * @returns - array of resolved permission groups\n * @example\n * resolvePermissionGroups(['g:js:core:episodes[org]:read', 'g:js:core:episodes[published]:read'])\n * // [{ id: 'g:js:core:episodes:read', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissionGroups = (\n permissionGroups: string[],\n): ResolvedPermissionGroup[] => {\n // Currently it has the same algorithm\n return resolvePermissions(permissionGroups);\n};\n\nexport const mergeResolvedPermissions = (\n array1: ResolvedPermission[],\n array2: ResolvedPermission[],\n) => {\n const result: ResolvedPermission[] = [];\n\n for (const rp1 of array1) {\n const rps2 = array2.filter((rp2) => rp2.id === rp1.id);\n\n /* Empty scopes = has full access */\n if (\n rp1.scopes.length === 0\n || rps2.some((rp2) => rp2.id === rp1.id && rp2.scopes.length === 0)\n ) {\n result.push({\n id: rp1.id,\n scopes: [],\n });\n\n continue;\n }\n\n // [org#hci, user#hcu:xxxxx, [org#hci, review]]\n const scopes = [...rp1.scopes];\n for (const rp2 of rps2) {\n concatScopes(scopes, rp2.scopes);\n }\n\n result.push({\n id: rp1.id,\n scopes,\n });\n }\n\n for (const rp2 of array2) {\n if (!result.some((rp) => rp.id === rp2.id)) {\n result.push(rp2);\n }\n }\n\n return result;\n};\n\n/**\n * Replace scope in array of scopes\n * @param scopes - array of scopes\n * @param from - scope to replace\n * @param to - scope to replace with\n * @returns - array of scopes with replaced scopes\n * @example\n * replaceScope(['org#jsorg:xxx', ['org#jsorg:xxx', 'published']], 'org#jsorg:xxx', 'org#jsorg:hci')\n * // ['org#jsorg:hci', ['org#jsorg:hci', 'published']]\n */\nexport const replaceScope = (\n scopes: (string | string[])[], // [org#jsorg:xxx, [org#jsorg:xxx, published]]\n from: string,\n to: string | (string | string[])[],\n) => {\n const result: (string | string[])[] = [];\n for (const scope of scopes) {\n if (Array.isArray(scope)) {\n result.push(replaceScope(scope, from, to) as string[]);\n } else if (scope === from) {\n if (Array.isArray(to)) {\n result.push(...to);\n } else {\n result.push(to);\n }\n } else {\n result.push(scope);\n }\n }\n\n return result;\n};\n","import { concatScopes } from './helpers.js';\n\nexport class ScopesBulder {\n constructor(private scopes: (string | string[])[] = []) {}\n\n static fromBulder(builder: ScopesBulder) {\n return new ScopesBulder(builder.scopes.slice());\n }\n\n build() {\n return JSON.parse(JSON.stringify(this.scopes)) as (string | string[])[];\n }\n\n clone() {\n return ScopesBulder.fromBulder(this);\n }\n\n /**\n * Append scope or scopes to the current scopes\n * @param scope - scope or array of scopes to append\n * @example\n * append('org#jsorg:hci')\n * // ['org#xxx'] => ['org#xxx', 'org#jsorg:hci']\n * append(['org#hci', 'lang#en])\n * // ['org#xxx'] => ['org#xxx', ['org#hci', 'lang#en']]\n */\n append(scope: string | string[]) {\n this.scopes.push(scope);\n }\n\n /**\n * Extend current scopes with the given scopes\n * @param scopes - scope or array of scopes to extend with\n * @example\n * extend(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => ['published', 'draft', 'lang#en', 'lang#de']\n */\n extend(scopes: (string | string[])[] | ScopesBulder) {\n if (Array.isArray(scopes)) {\n concatScopes(this.scopes, scopes);\n } else if (scopes instanceof ScopesBulder) {\n concatScopes(this.scopes, scopes.scopes);\n }\n }\n\n /**\n * Join all scopes with the given scope\n * @param scope - scope to join with\n * @example\n * join('org#jsorg:hci', 'before')\n * // ['published', 'draft'] => [['org#jsorg:hci', 'published'], ['org#jsorg:hci', 'draft']]\n * join(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => [['published', 'lang#en'], ['published', 'lang#de'], ['draft', 'lang#en'], ['draft', 'lang#de']]\n */\n join(scopeOrScopes: string | (string | string[])[], pos: 'before' | 'after' = 'after') {\n const result: (string | string[])[] = [];\n\n const scopesToJoin = Array.isArray(scopeOrScopes) ? scopeOrScopes : [scopeOrScopes];\n\n if (scopesToJoin.length === 0) {\n return;\n }\n\n const currentScopesCopy = this.build();\n for (const scope of scopesToJoin) {\n const scopeToJoin = Array.isArray(scope) ? scope : [scope];\n\n for (const s of currentScopesCopy) {\n if (Array.isArray(s)) {\n result.push(pos === 'before' ? [...scopeToJoin, ...s] : [...s, ...scopeToJoin]);\n } else {\n result.push(pos === 'before' ? [...scopeToJoin, s] : [s, ...scopeToJoin]);\n }\n }\n }\n\n this.scopes = result;\n }\n\n /**\n * Replace prefix in all scopes\n * @param from - prefix to replace\n * @param to - prefix to replace with\n * @example\n * replacePrefix('org', 'id')\n * ['org#jsorg:hci'] => ['id#jsorg:hci']\n */\n replacePrefix(from: string, to: string) {\n const result: (string | string[])[] = [];\n for (const scope of this.scopes) {\n if (Array.isArray(scope)) {\n result.push(scope.map((s) => s.replace(`${from}#`, `${to}#`)));\n } else {\n result.push(scope.replace(`${from}#`, `${to}#`));\n }\n }\n this.scopes = result;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["id","scope","user","scopes","id","scope","scopes: (string | string[])[]","resolvedPermissions: ResolvedPermission[]","result: ResolvedPermission[]","result: (string | string[])[]","scopes: (string | string[])[]","scope","result: (string | string[])[]"],"sources":["../src/permissions/action-scopes.ts","../src/permissions/helpers.ts","../src/permissions/scopes-bulder.ts"],"sourcesContent":["export type ActionScopesArray = (string | string[])[];\n\n/**\n * Action scopes are used to describe\n * what scopes are required to execute an action.\n */\nexport class ActionScopes {\n public array: ActionScopesArray = [];\n public anyScope = false;\n private idsMap = new Map<string, string>();\n\n constructor(scopes?: ActionScopesArray) {\n if (scopes) {\n this.array = scopes;\n }\n }\n\n /**\n * Set id scope (e.g. 'id#jsu:123')\n * @param id - entity id\n * @returns - this\n * @example\n * scopes.setEntityId('jsu:123')\n *\n * // scopes.getArray() = ['id#jsu:123']\n */\n setEntityId(id: string): this {\n this.set(`id#${id}`);\n\n return this;\n }\n\n /**\n * Set scope (e.g. 'org', 'org+review')\n * @param scope - scope name (e.g. 'org', 'org+review')\n * @param id - entity id (e.g. 'jsorg:hci')\n * @returns - this\n * @example\n * scopes.set('my')\n *\n * // scopes.getArray() = ['my']\n *\n * scopes.set('org', 'jsorg:hci')\n *\n * // scopes.getArray() = ['org#jsorg:hci']\n */\n set(scope: string, id?: string): this {\n if (id && /^[A-Za-z_]+$/.test(scope)) {\n this.idsMap.set(scope, id);\n }\n\n if (scope.includes('+')) {\n const subscopes = scope.split('+');\n this.array.push(\n subscopes.map((subscope) => this.resolveScopeId(subscope)),\n );\n } else {\n this.array.push(this.resolveScopeId(scope));\n }\n\n return this;\n }\n\n has(scope: string): boolean {\n return this.array.includes(scope);\n }\n\n getArray(): ActionScopesArray {\n return [...this.array];\n }\n\n /**\n * Permission scopes must cover auth scopes determined in auth context.\n * @param permissionScopes\n */\n canBeExecutedInScopes(\n permissionScopes: readonly (string | string[])[],\n ): boolean {\n // if scopes has * then it can be executed in any scope\n if (this.array.includes('*')) {\n return true;\n }\n\n /* action scopes = ['user#jsu:123', 'org#hci', ['org#hci', 'draft']]\n /* permission scopes: ['user#jsu:123', ['org#hci', 'draft']]\n\n /**\n * empty user permission scopes = full access to action\n */\n if (permissionScopes.length === 0) {\n return true;\n }\n\n /**\n * Empty auth scopes but user has permission scopes = no access to action\n *\n * action scopes = []\n * permission scopes = ['org#hci']\n *\n * Example:\n * User try to get list of all entities\n * But user only has permission to entity from org#hci\n */\n if (this.array.length === 0) {\n return false;\n }\n\n return this.array.some((authScope) => {\n if (Array.isArray(authScope)) {\n // scopes ['org#hci', 'draft'] == permissionScope ['org#hci', 'draft']\n\n return permissionScopes.some((permissionScope) => {\n if (Array.isArray(permissionScope)) {\n return permissionScope.every(\n (sub) => authScope.includes(sub),\n );\n }\n\n return authScope.includes(permissionScope);\n });\n }\n\n return permissionScopes.includes(authScope); // 'user#jsu:123' == 'user#jsu:123'\n });\n }\n\n /**\n * Resolve presaved scopes\n * @example\n * authCtx.setScope('org', 'jsorg:hci')\n * [org#hci]\n *\n * authCtx.setScope('org+review');\n * [org#hci, [org#hci, review]]\n **/\n private resolveScopeId(scope: string) {\n const id = this.idsMap.get(scope);\n return id !== undefined\n ? `${scope}#${id}`\n : scope;\n }\n}\n","import {\n type ReadonlyResolvedPermission, type ResolvedPermission, type ResolvedPermissionGroup,\n} from '../types/permissions.js';\nimport { type IUser } from '../types/user.js';\nimport { ActionScopes, type ActionScopesArray } from './action-scopes.js';\n\n/**\n * Normilize scopes to ActionScopes\n * @example\n * const scopes = normalizeScopes(['org#hci', 'user#hcu:xxxxx') // ActionScopes\n *\n * scopes.set('my')\n */\nconst normalizeScopes = (\n scopes: ActionScopesArray | ActionScopes | string,\n): ActionScopes => {\n if (scopes instanceof ActionScopes) {\n return scopes;\n }\n\n if (typeof scopes === 'string') {\n return new ActionScopes([scopes]);\n }\n\n return new ActionScopes(scopes);\n};\n\n/**\n * Check if user has access to permission\n * @example\n *\n * // Check episode access\n * const scopes = new ActionScopes();\n * scopes.setEntityId(episode.id); // id#js:core:episodes:xxxxx\n * scopes.set('org', episode.organizationId); // org#hcorg:hci\n *\n * isGranted(user, 'js:core:episodes:get', scopes)\n * @param user - user object with resolvedPermissions\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param actionScopes - action scopes (e.g. ['org#hcorg:hci'] or ActionScopes instance)\n * @returns - true if user has access to permission\n */\nexport const isGranted = (\n user: IUser,\n permission: string,\n actionScopes: ActionScopesArray | ActionScopes | string = [],\n): boolean => {\n const [service, module, resource, action] = permission.split(':');\n\n if (user.resolvedPermissions.length === 0) {\n return false;\n }\n\n const userPermissions = user.resolvedPermissions;\n\n const scopes = normalizeScopes(actionScopes);\n\n const permissionCanBeExecutedInScopes = (perm: ReadonlyResolvedPermission, scopes: ActionScopes) =>\n (perm.scopes.length === 0 || scopes.canBeExecutedInScopes(perm.scopes));\n\n const regexp = new RegExp(`^(${service}|\\\\*):(${module}|\\\\*):(${resource}|\\\\*):(${action}|\\\\*)$`);\n\n // novajs:*:*:* or novajs:*:*[org]:*\n // novajs:module:*:* or novajs:module:*[org]:*\n // novajs:module:resource:* or novajs:module:resource:[org]:*\n // novajs:modules:resource:create\n // *:modules:resource:create\n if (userPermissions.some(\n (perm) => regexp.test(perm.id)\n && permissionCanBeExecutedInScopes(perm, scopes))\n ) {\n return true;\n }\n\n return false;\n};\n\nexport const encodeScopes = (scopes: (string | string[])[]): string => {\n const scopesString = scopes\n .map((s) => (Array.isArray(s) ? s.join('+') : s))\n .join(',');\n\n return scopesString.length > 0 ? `[${scopesString}]` : '';\n};\n\n/**\n * Inject scopes into permission\n * @param permission - permission string (e.g. 'js:core:episodes:get')\n * @param scopesToInject - scopes to inject (e.g. ['org', 'published'])\n * @returns - permission string with injected scopes (e.g. 'js:core:episodes[org,published]:get')\n * @example\n * injectScopesIntoPermission('js:core:episodes:get', ['org', 'published'])\n * // js:core:episodes[org,published]:get\n */\nexport const injectScopesIntoPermission = (\n permission: string,\n scopesToInject: (string | string[])[],\n) => {\n const { id, scopes } = resolvePermission(permission);\n\n const [service, module, resource, action] = id.split(':');\n\n concatScopes(scopes, scopesToInject);\n\n return `${service}:${module}:${resource}${encodeScopes(scopes)}:${action}`;\n};\n\n/**\n * Concat scopes\n * @param set - set of scopes\n * @param items - items to concat\n * @example\n * concatScopes(['org#hci'], ['org#dv'])\n * // ['org#hci', 'org#dv']\n */\nexport const concatScopes = (\n set: (string | string[])[],\n items: (string | string[])[],\n): void => {\n for (const item of items) {\n if (Array.isArray(item)) {\n // check if scopes not includes array with subscopes rp2Scope\n if (\n !set.some(\n (scope) =>\n Array.isArray(scope)\n && scope.length === item.length\n && scope.every((s) => item.includes(s)),\n )\n ) {\n set.push(item);\n }\n } else if (!set.includes(item)) {\n set.push(item);\n }\n }\n};\n\n/**\n * Resolve permission string to object with scopes\n * @param permission - permission string\n * @returns - resolved permission object\n * @example\n * resolvePermission('js:core:episodes[org,published]:get')\n * // { id: 'js:core:episodes:get', scopes: ['org', 'published'] }\n */\nexport const resolvePermission = (permission: string): ResolvedPermission => {\n const matches = /\\[(?<scopes>.*?)\\]/u.exec(permission);\n\n if (matches?.[0] && matches.groups?.scopes) {\n const scopes: (string | string[])[] = matches.groups.scopes\n .split(',') // scopes with OR logic\n .map((s) => {\n if (s.includes('+')) {\n return s.split('+'); // scopes with AND logic\n }\n\n return s;\n });\n\n return {\n id: permission.replace(matches[0], ''),\n scopes,\n };\n }\n\n return {\n id: permission,\n scopes: [],\n };\n};\n\n/**\n * Resolve permissions to array of resolved permissions\n * @param permissions - array of permissions\n * @returns - array of resolved permissions\n * @example\n * resolvePermissions(['js:core:episodes[org]:get', 'js:core:episodes[published]:get'])\n * // [{ id: 'js:core:episodes:get', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissions = (\n permissions: string[],\n): ResolvedPermission[] => {\n const resolvedPermissions: ResolvedPermission[] = [];\n\n for (const permission of permissions) {\n const { id, scopes } = resolvePermission(permission);\n\n const currentPermission = resolvedPermissions.find(\n (p) => p.id === id,\n );\n\n if (currentPermission) {\n concatScopes(currentPermission.scopes, scopes);\n } else {\n resolvedPermissions.push({\n id,\n scopes,\n });\n }\n }\n\n return resolvedPermissions;\n};\n\n/**\n * Resolve permission group string to object with scopes\n * @param permissionGroup - permission group string\n * @returns - resolved permission group object\n * @example\n * resolvePermissionGroup('g:js:core:episodes[org]:read')\n * // { id: 'g:js:core:episodes:read', scopes: ['org'] }\n */\nexport const resolvePermissionGroup = (permissionGroup: string) => {\n // Currently it has the same algorithm\n return resolvePermission(permissionGroup);\n};\n\n/**\n * Resolve permission groups to array of resolved permission groups\n * @param permissionGroups - array of permission groups\n * @returns - array of resolved permission groups\n * @example\n * resolvePermissionGroups(['g:js:core:episodes[org]:read', 'g:js:core:episodes[published]:read'])\n * // [{ id: 'g:js:core:episodes:read', scopes: ['org', 'published'] }]\n */\nexport const resolvePermissionGroups = (\n permissionGroups: string[],\n): ResolvedPermissionGroup[] => {\n // Currently it has the same algorithm\n return resolvePermissions(permissionGroups);\n};\n\nexport const mergeResolvedPermissions = (\n array1: ResolvedPermission[],\n array2: ResolvedPermission[],\n) => {\n const result: ResolvedPermission[] = [];\n\n for (const rp1 of array1) {\n const rps2 = array2.filter((rp2) => rp2.id === rp1.id);\n\n /* Empty scopes = has full access */\n if (\n rp1.scopes.length === 0\n || rps2.some((rp2) => rp2.id === rp1.id && rp2.scopes.length === 0)\n ) {\n result.push({\n id: rp1.id,\n scopes: [],\n });\n\n continue;\n }\n\n // [org#hci, user#hcu:xxxxx, [org#hci, review]]\n const scopes = [...rp1.scopes];\n for (const rp2 of rps2) {\n concatScopes(scopes, rp2.scopes);\n }\n\n result.push({\n id: rp1.id,\n scopes,\n });\n }\n\n for (const rp2 of array2) {\n if (!result.some((rp) => rp.id === rp2.id)) {\n result.push(rp2);\n }\n }\n\n return result;\n};\n\n/**\n * Replace scope in array of scopes\n * @param scopes - array of scopes\n * @param from - scope to replace\n * @param to - scope to replace with\n * @returns - array of scopes with replaced scopes\n * @example\n * replaceScope(['org#jsorg:xxx', ['org#jsorg:xxx', 'published']], 'org#jsorg:xxx', 'org#jsorg:hci')\n * // ['org#jsorg:hci', ['org#jsorg:hci', 'published']]\n */\nexport const replaceScope = (\n scopes: (string | string[])[], // [org#jsorg:xxx, [org#jsorg:xxx, published]]\n from: string,\n to: string | (string | string[])[],\n) => {\n const result: (string | string[])[] = [];\n for (const scope of scopes) {\n if (Array.isArray(scope)) {\n result.push(replaceScope(scope, from, to) as string[]);\n } else if (scope === from) {\n if (Array.isArray(to)) {\n result.push(...to);\n } else {\n result.push(to);\n }\n } else {\n result.push(scope);\n }\n }\n\n return result;\n};\n","import { concatScopes } from './helpers.js';\n\nexport class ScopesBulder {\n constructor(private scopes: (string | string[])[] = []) {}\n\n static fromBulder(builder: ScopesBulder) {\n return new ScopesBulder(builder.scopes.slice());\n }\n\n build() {\n return JSON.parse(JSON.stringify(this.scopes)) as (string | string[])[];\n }\n\n clone() {\n return ScopesBulder.fromBulder(this);\n }\n\n /**\n * Append scope or scopes to the current scopes\n * @param scope - scope or array of scopes to append\n * @example\n * append('org#jsorg:hci')\n * // ['org#xxx'] => ['org#xxx', 'org#jsorg:hci']\n * append(['org#hci', 'lang#en])\n * // ['org#xxx'] => ['org#xxx', ['org#hci', 'lang#en']]\n */\n append(scope: string | string[]) {\n this.scopes.push(scope);\n }\n\n /**\n * Extend current scopes with the given scopes\n * @param scopes - scope or array of scopes to extend with\n * @example\n * extend(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => ['published', 'draft', 'lang#en', 'lang#de']\n */\n extend(scopes: (string | string[])[] | ScopesBulder) {\n if (Array.isArray(scopes)) {\n concatScopes(this.scopes, scopes);\n } else if (scopes instanceof ScopesBulder) {\n concatScopes(this.scopes, scopes.scopes);\n }\n }\n\n /**\n * Join all scopes with the given scope\n * @param scope - scope to join with\n * @example\n * join('org#jsorg:hci', 'before')\n * // ['published', 'draft'] => [['org#jsorg:hci', 'published'], ['org#jsorg:hci', 'draft']]\n * join(['lang#en', 'lang#de'])\n * // ['published', 'draft'] => [['published', 'lang#en'], ['published', 'lang#de'], ['draft', 'lang#en'], ['draft', 'lang#de']]\n */\n join(scopeOrScopes: string | (string | string[])[], pos: 'before' | 'after' = 'after') {\n const result: (string | string[])[] = [];\n\n const scopesToJoin = Array.isArray(scopeOrScopes) ? scopeOrScopes : [scopeOrScopes];\n\n if (scopesToJoin.length === 0) {\n return;\n }\n\n const currentScopesCopy = this.build();\n for (const scope of scopesToJoin) {\n const scopeToJoin = Array.isArray(scope) ? scope : [scope];\n\n for (const s of currentScopesCopy) {\n if (Array.isArray(s)) {\n result.push(pos === 'before' ? [...scopeToJoin, ...s] : [...s, ...scopeToJoin]);\n } else {\n result.push(pos === 'before' ? [...scopeToJoin, s] : [s, ...scopeToJoin]);\n }\n }\n }\n\n this.scopes = result;\n }\n\n /**\n * Replace prefix in all scopes\n * @param from - prefix to replace\n * @param to - prefix to replace with\n * @example\n * replacePrefix('org', 'id')\n * ['org#jsorg:hci'] => ['id#jsorg:hci']\n */\n replacePrefix(from: string, to: string) {\n const result: (string | string[])[] = [];\n for (const scope of this.scopes) {\n if (Array.isArray(scope)) {\n result.push(scope.map((s) => s.replace(`${from}#`, `${to}#`)));\n } else {\n result.push(scope.replace(`${from}#`, `${to}#`));\n }\n }\n this.scopes = result;\n }\n}\n"],"mappings":"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"}
|
package/dist/scopes.d.ts
ADDED
package/dist/scopes.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pcg/auth",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.3",
|
|
4
4
|
"description": "Authorization library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -14,18 +14,28 @@
|
|
|
14
14
|
"type": "module",
|
|
15
15
|
"main": "dist/index.js",
|
|
16
16
|
"types": "dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./scopes": {
|
|
23
|
+
"types": "./dist/scopes.d.ts",
|
|
24
|
+
"import": "./dist/scopes.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
17
27
|
"files": [
|
|
18
28
|
"dist"
|
|
19
29
|
],
|
|
20
|
-
"devDependencies": {
|
|
21
|
-
"@vitest/ui": "^4.0.8",
|
|
22
|
-
"vitest": "^4.0.8"
|
|
23
|
-
},
|
|
24
30
|
"scripts": {
|
|
25
31
|
"dev": "tsdown --watch",
|
|
26
32
|
"build": "tsdown",
|
|
27
33
|
"test": "vitest run",
|
|
28
34
|
"test:watch": "vitest",
|
|
29
35
|
"lint": "eslint \"src/**/*.ts\" --fix"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@vitest/ui": "^4.0.8",
|
|
39
|
+
"vitest": "^4.0.8"
|
|
30
40
|
}
|
|
31
|
-
}
|
|
41
|
+
}
|