@sasjs/api 0.6.1 → 0.7.1

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.
Files changed (36) hide show
  1. package/README.md +3 -2
  2. package/build/browser.js +2218 -118
  3. package/build/cjs/corpus/records.js +1222 -0
  4. package/build/cjs/corpus/runner.js +15 -6
  5. package/build/cjs/host/dispatch.js +190 -2
  6. package/build/cjs/identity/memory-store.js +196 -11
  7. package/build/cjs/identity/records.js +398 -0
  8. package/build/cjs/index.js +8 -1
  9. package/build/cjs/routes/generated.js +184 -4
  10. package/build/cjs/routes/overlay.js +53 -58
  11. package/build/esm/corpus/records.d.ts +25 -0
  12. package/build/esm/corpus/records.d.ts.map +1 -0
  13. package/build/esm/corpus/records.js +1219 -0
  14. package/build/esm/corpus/runner.d.ts.map +1 -1
  15. package/build/esm/corpus/runner.js +15 -6
  16. package/build/esm/host/dispatch.d.ts +9 -0
  17. package/build/esm/host/dispatch.d.ts.map +1 -1
  18. package/build/esm/host/dispatch.js +190 -2
  19. package/build/esm/identity/fixture.d.ts +6 -0
  20. package/build/esm/identity/fixture.d.ts.map +1 -1
  21. package/build/esm/identity/memory-store.d.ts +29 -5
  22. package/build/esm/identity/memory-store.d.ts.map +1 -1
  23. package/build/esm/identity/memory-store.js +196 -11
  24. package/build/esm/identity/records.d.ts +102 -0
  25. package/build/esm/identity/records.d.ts.map +1 -0
  26. package/build/esm/identity/records.js +393 -0
  27. package/build/esm/identity/types.d.ts +102 -2
  28. package/build/esm/identity/types.d.ts.map +1 -1
  29. package/build/esm/index.d.ts +4 -1
  30. package/build/esm/index.d.ts.map +1 -1
  31. package/build/esm/index.js +2 -0
  32. package/build/esm/routes/generated.d.ts.map +1 -1
  33. package/build/esm/routes/generated.js +184 -4
  34. package/build/esm/routes/overlay.d.ts.map +1 -1
  35. package/build/esm/routes/overlay.js +53 -58
  36. package/package.json +3 -2
@@ -0,0 +1,1222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RECORD_CORPUS = exports.PROVIDER_MANAGED = exports.ADMIN_SIGNED_IN = void 0;
4
+ const fixture_js_1 = require("../identity/fixture.js");
5
+ /** The admin and the restricted user, signed in. */
6
+ const SIGNED_IN_TOKENS = [
7
+ {
8
+ username: 'admin',
9
+ clientId: 'web_app',
10
+ accessToken: 'admin-access',
11
+ refreshToken: 'admin-refresh'
12
+ },
13
+ {
14
+ username: 'user2',
15
+ clientId: 'web_app',
16
+ accessToken: 'user-access',
17
+ refreshToken: 'user-refresh'
18
+ }
19
+ ];
20
+ /** The default identity, with an administrator and the restricted user signed in. */
21
+ exports.ADMIN_SIGNED_IN = {
22
+ ...fixture_js_1.DEFAULT_IDENTITY,
23
+ tokens: SIGNED_IN_TOKENS
24
+ };
25
+ /**
26
+ * The same identity, plus what the provider and public-group refusals need: an
27
+ * account and a group managed by an external provider, and the reserved group.
28
+ */
29
+ exports.PROVIDER_MANAGED = {
30
+ ...exports.ADMIN_SIGNED_IN,
31
+ tokens: [
32
+ ...SIGNED_IN_TOKENS,
33
+ {
34
+ username: 'diruser',
35
+ clientId: 'web_app',
36
+ accessToken: 'dir-access',
37
+ refreshToken: 'dir-refresh'
38
+ }
39
+ ],
40
+ users: [
41
+ ...exports.ADMIN_SIGNED_IN.users,
42
+ {
43
+ uid: 'user-directory',
44
+ username: 'diruser',
45
+ displayName: 'Directory User',
46
+ password: 'dir123',
47
+ isAdmin: false,
48
+ isActive: true,
49
+ needsToUpdatePassword: false,
50
+ authProvider: 'oidc'
51
+ }
52
+ ],
53
+ groups: [
54
+ ...exports.ADMIN_SIGNED_IN.groups,
55
+ {
56
+ uid: 'group-directory',
57
+ name: 'Directory',
58
+ description: 'Managed by the directory',
59
+ isActive: true,
60
+ users: [],
61
+ authProvider: 'oidc'
62
+ },
63
+ {
64
+ uid: 'group-public',
65
+ name: 'public',
66
+ description: 'Everyone',
67
+ isActive: true,
68
+ users: []
69
+ }
70
+ ]
71
+ };
72
+ const ADMIN = 'admin-access';
73
+ const RESTRICTED = 'user-access';
74
+ const RESTRICTED_UID = 'user-restricted';
75
+ const ADMIN_UID = 'user-admin';
76
+ exports.RECORD_CORPUS = [
77
+ // ------------------------------------------------------------------ users
78
+ {
79
+ id: 'list-users',
80
+ route: 'GET /SASjsApi/user',
81
+ description: 'lists the accounts any signed-in caller may see',
82
+ identity: exports.ADMIN_SIGNED_IN,
83
+ request: { token: ADMIN },
84
+ expect: {
85
+ status: 200,
86
+ body: [
87
+ {
88
+ uid: ADMIN_UID,
89
+ username: 'admin',
90
+ displayName: 'Demo Admin',
91
+ isAdmin: true
92
+ },
93
+ {
94
+ uid: RESTRICTED_UID,
95
+ username: 'user2',
96
+ displayName: 'Demo User',
97
+ isAdmin: false
98
+ }
99
+ ]
100
+ }
101
+ },
102
+ {
103
+ id: 'get-user-self',
104
+ route: 'GET /SASjsApi/user/{uid}',
105
+ description: 'returns the caller their own details, with the auto-exec code',
106
+ identity: exports.ADMIN_SIGNED_IN,
107
+ request: { token: RESTRICTED, params: { uid: RESTRICTED_UID } },
108
+ expect: {
109
+ status: 200,
110
+ partial: {
111
+ uid: RESTRICTED_UID,
112
+ username: 'user2',
113
+ isActive: true,
114
+ isAdmin: false,
115
+ autoExec: ''
116
+ }
117
+ }
118
+ },
119
+ {
120
+ id: 'get-user-unauthenticated',
121
+ route: 'GET /SASjsApi/user/{uid}',
122
+ description: 'refuses a read with no caller at all',
123
+ identity: exports.ADMIN_SIGNED_IN,
124
+ request: { params: { uid: RESTRICTED_UID } },
125
+ expect: { status: 401, error: 'Unauthorized' }
126
+ },
127
+ {
128
+ id: 'get-user-missing',
129
+ route: 'GET /SASjsApi/user/{uid}',
130
+ description: 'answers 404 for an identifier no account has',
131
+ identity: exports.ADMIN_SIGNED_IN,
132
+ request: { token: ADMIN, params: { uid: 'user-none' } },
133
+ expect: { status: 404, error: 'User is not found.' }
134
+ },
135
+ {
136
+ id: 'get-user-by-username',
137
+ route: 'GET /SASjsApi/user/by/username/{username}',
138
+ description: 'finds an account by username',
139
+ identity: exports.ADMIN_SIGNED_IN,
140
+ request: { token: ADMIN, params: { username: 'user2' } },
141
+ expect: {
142
+ status: 200,
143
+ partial: { uid: RESTRICTED_UID, username: 'user2', isAdmin: false }
144
+ }
145
+ },
146
+ {
147
+ id: 'get-user-by-username-unauthenticated',
148
+ route: 'GET /SASjsApi/user/by/username/{username}',
149
+ description: 'refuses the by-username read with no caller',
150
+ identity: exports.ADMIN_SIGNED_IN,
151
+ request: { params: { username: 'user2' } },
152
+ expect: { status: 401, error: 'Unauthorized' }
153
+ },
154
+ {
155
+ id: 'get-user-by-username-missing',
156
+ route: 'GET /SASjsApi/user/by/username/{username}',
157
+ description: 'answers 404 for a username no account has',
158
+ identity: exports.ADMIN_SIGNED_IN,
159
+ request: { token: ADMIN, params: { username: 'nobody' } },
160
+ expect: { status: 404, error: 'User is not found.' }
161
+ },
162
+ {
163
+ id: 'create-user',
164
+ route: 'POST /SASjsApi/user',
165
+ description: 'creates an account, sanitised, and reports it',
166
+ identity: exports.ADMIN_SIGNED_IN,
167
+ request: {
168
+ token: ADMIN,
169
+ body: {
170
+ username: 'newuser',
171
+ displayName: ' New\u0001 User ',
172
+ password: 'newpass',
173
+ isAdmin: false,
174
+ isActive: true
175
+ }
176
+ },
177
+ expect: {
178
+ status: 200,
179
+ partial: {
180
+ username: 'newuser',
181
+ displayName: 'New User',
182
+ isAdmin: false,
183
+ isActive: true
184
+ }
185
+ }
186
+ },
187
+ {
188
+ id: 'create-user-not-admin',
189
+ route: 'POST /SASjsApi/user',
190
+ description: 'refuses a creation by a caller who is not an administrator',
191
+ identity: exports.ADMIN_SIGNED_IN,
192
+ request: {
193
+ token: RESTRICTED,
194
+ body: {
195
+ username: 'newuser',
196
+ displayName: 'New User',
197
+ password: 'newpass'
198
+ }
199
+ },
200
+ expect: { status: 401, error: 'Admin account required' }
201
+ },
202
+ {
203
+ id: 'create-user-anonymous-incomplete',
204
+ route: 'POST /SASjsApi/user',
205
+ description: 'answers 401 before the payload schema, anonymously and empty',
206
+ identity: exports.ADMIN_SIGNED_IN,
207
+ request: { body: {} },
208
+ expect: { status: 401, error: 'Unauthorized' }
209
+ },
210
+ {
211
+ id: 'create-user-duplicate',
212
+ route: 'POST /SASjsApi/user',
213
+ description: 'refuses a username that is taken',
214
+ identity: exports.ADMIN_SIGNED_IN,
215
+ request: {
216
+ token: ADMIN,
217
+ body: { username: 'user2', displayName: 'Another', password: 'newpass' }
218
+ },
219
+ expect: { status: 409, error: 'Username already exists.' }
220
+ },
221
+ {
222
+ id: 'update-user',
223
+ route: 'PATCH /SASjsApi/user/{uid}',
224
+ description: "changes an account's display name",
225
+ identity: exports.ADMIN_SIGNED_IN,
226
+ request: {
227
+ token: ADMIN,
228
+ params: { uid: RESTRICTED_UID },
229
+ body: {
230
+ displayName: 'Renamed User',
231
+ username: 'user2',
232
+ password: 'user123'
233
+ }
234
+ },
235
+ expect: {
236
+ status: 200,
237
+ partial: { uid: RESTRICTED_UID, displayName: 'Renamed User' }
238
+ }
239
+ },
240
+ {
241
+ id: 'update-user-not-admin',
242
+ route: 'PATCH /SASjsApi/user/{uid}',
243
+ description: "refuses a change to somebody else's account",
244
+ identity: exports.ADMIN_SIGNED_IN,
245
+ request: {
246
+ token: RESTRICTED,
247
+ params: { uid: ADMIN_UID },
248
+ body: { displayName: 'Hijacked', username: 'admin', password: 'admin123' }
249
+ },
250
+ expect: { status: 401, error: 'Admin account required' }
251
+ },
252
+ {
253
+ id: 'update-user-missing',
254
+ route: 'PATCH /SASjsApi/user/{uid}',
255
+ description: 'answers 404 with the identifier it looked up',
256
+ identity: exports.ADMIN_SIGNED_IN,
257
+ request: {
258
+ token: ADMIN,
259
+ params: { uid: 'user-none' },
260
+ body: { displayName: 'Nobody', username: 'nobody', password: 'none123' }
261
+ },
262
+ expect: { status: 404, error: 'Unable to find user with user-none' }
263
+ },
264
+ {
265
+ id: 'update-user-provider',
266
+ route: 'PATCH /SASjsApi/user/{uid}',
267
+ description: 'refuses a username change on a provider-managed account',
268
+ identity: exports.PROVIDER_MANAGED,
269
+ request: {
270
+ token: ADMIN,
271
+ params: { uid: 'user-directory' },
272
+ body: {
273
+ username: 'renamed',
274
+ displayName: 'Directory User',
275
+ password: 'dir123'
276
+ }
277
+ },
278
+ expect: {
279
+ status: 405,
280
+ error: 'Can not update username of user that is created by an external auth provider.'
281
+ }
282
+ },
283
+ {
284
+ id: 'update-user-username-taken',
285
+ route: 'PATCH /SASjsApi/user/{uid}',
286
+ description: 'refuses a username another account already has',
287
+ identity: exports.ADMIN_SIGNED_IN,
288
+ request: {
289
+ token: ADMIN,
290
+ params: { uid: RESTRICTED_UID },
291
+ body: { username: 'admin', displayName: 'Demo User', password: 'user123' }
292
+ },
293
+ expect: { status: 409, error: 'Username already exists.' }
294
+ },
295
+ {
296
+ id: 'update-user-by-username',
297
+ route: 'PATCH /SASjsApi/user/by/username/{username}',
298
+ description: 'changes an account looked up by username',
299
+ identity: exports.ADMIN_SIGNED_IN,
300
+ request: {
301
+ token: ADMIN,
302
+ params: { username: 'user2' },
303
+ body: {
304
+ isActive: false,
305
+ username: 'user2',
306
+ displayName: 'Demo User',
307
+ password: 'user123'
308
+ }
309
+ },
310
+ expect: { status: 200, partial: { username: 'user2', isActive: false } }
311
+ },
312
+ {
313
+ id: 'update-user-by-username-not-admin',
314
+ route: 'PATCH /SASjsApi/user/by/username/{username}',
315
+ description: 'refuses a change to another account by username',
316
+ identity: exports.ADMIN_SIGNED_IN,
317
+ request: {
318
+ token: RESTRICTED,
319
+ params: { username: 'admin' },
320
+ body: { displayName: 'Hijacked', username: 'admin', password: 'admin123' }
321
+ },
322
+ expect: { status: 401, error: 'Admin account required' }
323
+ },
324
+ {
325
+ id: 'update-user-by-username-missing',
326
+ route: 'PATCH /SASjsApi/user/by/username/{username}',
327
+ description: 'answers 404 with the username it looked up',
328
+ identity: exports.ADMIN_SIGNED_IN,
329
+ request: {
330
+ token: ADMIN,
331
+ params: { username: 'nobody' },
332
+ body: { displayName: 'Nobody', username: 'nobody', password: 'none123' }
333
+ },
334
+ expect: { status: 404, error: 'Unable to find user with nobody' }
335
+ },
336
+ {
337
+ id: 'update-user-by-username-provider',
338
+ route: 'PATCH /SASjsApi/user/by/username/{username}',
339
+ description: 'refuses a display-name change on a provider-managed account',
340
+ identity: exports.PROVIDER_MANAGED,
341
+ request: {
342
+ token: ADMIN,
343
+ params: { username: 'diruser' },
344
+ body: { displayName: 'Renamed', username: 'diruser', password: 'dir123' }
345
+ },
346
+ expect: {
347
+ status: 405,
348
+ error: 'Can not update display name of user that is created by an external auth provider.'
349
+ }
350
+ },
351
+ {
352
+ id: 'update-user-by-username-taken',
353
+ route: 'PATCH /SASjsApi/user/by/username/{username}',
354
+ description: 'refuses a username another account already has',
355
+ identity: exports.ADMIN_SIGNED_IN,
356
+ request: {
357
+ token: ADMIN,
358
+ params: { username: 'user2' },
359
+ body: { username: 'admin', displayName: 'Demo User', password: 'user123' }
360
+ },
361
+ expect: { status: 409, error: 'Username already exists.' }
362
+ },
363
+ {
364
+ id: 'delete-user',
365
+ route: 'DELETE /SASjsApi/user/{uid}',
366
+ description: 'deletes an account as an administrator, without a password',
367
+ identity: exports.ADMIN_SIGNED_IN,
368
+ request: { token: ADMIN, params: { uid: RESTRICTED_UID }, body: {} },
369
+ expect: { status: 204 }
370
+ },
371
+ {
372
+ id: 'delete-user-not-admin',
373
+ route: 'DELETE /SASjsApi/user/{uid}',
374
+ description: "refuses a deletion of somebody else's account",
375
+ identity: exports.ADMIN_SIGNED_IN,
376
+ request: { token: RESTRICTED, params: { uid: ADMIN_UID }, body: {} },
377
+ expect: { status: 401, error: 'Admin account required' }
378
+ },
379
+ {
380
+ id: 'delete-user-missing',
381
+ route: 'DELETE /SASjsApi/user/{uid}',
382
+ description: 'answers 404 for an identifier no account has',
383
+ identity: exports.ADMIN_SIGNED_IN,
384
+ request: { token: ADMIN, params: { uid: 'user-none' }, body: {} },
385
+ expect: { status: 404, error: 'User is not found.' }
386
+ },
387
+ {
388
+ id: 'delete-user-by-username',
389
+ route: 'DELETE /SASjsApi/user/by/username/{username}',
390
+ description: 'deletes an account looked up by username',
391
+ identity: exports.ADMIN_SIGNED_IN,
392
+ request: {
393
+ token: ADMIN,
394
+ params: { username: 'user2' },
395
+ body: {}
396
+ },
397
+ expect: { status: 204 }
398
+ },
399
+ {
400
+ id: 'delete-user-by-username-wrong-password',
401
+ route: 'DELETE /SASjsApi/user/by/username/{username}',
402
+ description: 'refuses a self-deletion whose password is wrong',
403
+ identity: exports.ADMIN_SIGNED_IN,
404
+ request: {
405
+ token: RESTRICTED,
406
+ params: { username: 'user2' },
407
+ body: { password: 'wrong' }
408
+ },
409
+ expect: { status: 401, error: 'Invalid password.' }
410
+ },
411
+ {
412
+ id: 'delete-user-by-username-missing',
413
+ route: 'DELETE /SASjsApi/user/by/username/{username}',
414
+ description: 'answers 404 for a username no account has',
415
+ identity: exports.ADMIN_SIGNED_IN,
416
+ request: { token: ADMIN, params: { username: 'nobody' }, body: {} },
417
+ expect: { status: 404, error: 'User is not found.' }
418
+ },
419
+ // ----------------------------------------------------------------- groups
420
+ {
421
+ id: 'list-groups',
422
+ route: 'GET /SASjsApi/group',
423
+ description: 'lists the groups',
424
+ identity: exports.ADMIN_SIGNED_IN,
425
+ request: { token: RESTRICTED },
426
+ expect: {
427
+ status: 200,
428
+ body: [
429
+ {
430
+ uid: 'group-data-controller',
431
+ name: 'DCGroup',
432
+ description: 'Data Controller users'
433
+ },
434
+ {
435
+ uid: 'group-admins',
436
+ name: 'Admins',
437
+ description: 'Everything the API offers'
438
+ }
439
+ ]
440
+ }
441
+ },
442
+ {
443
+ id: 'get-group',
444
+ route: 'GET /SASjsApi/group/{uid}',
445
+ description: 'returns a group and its members',
446
+ identity: exports.ADMIN_SIGNED_IN,
447
+ request: { token: RESTRICTED, params: { uid: 'group-admins' } },
448
+ expect: {
449
+ status: 200,
450
+ partial: {
451
+ uid: 'group-admins',
452
+ name: 'Admins',
453
+ isActive: true,
454
+ users: [
455
+ {
456
+ uid: ADMIN_UID,
457
+ username: 'admin',
458
+ displayName: 'Demo Admin',
459
+ isAdmin: true
460
+ }
461
+ ]
462
+ }
463
+ }
464
+ },
465
+ {
466
+ id: 'get-group-missing',
467
+ route: 'GET /SASjsApi/group/{uid}',
468
+ description: 'answers 404 for an identifier no group has',
469
+ identity: exports.ADMIN_SIGNED_IN,
470
+ request: { token: ADMIN, params: { uid: 'group-none' } },
471
+ expect: { status: 404, error: 'Group not found.' }
472
+ },
473
+ {
474
+ id: 'get-group-by-name',
475
+ route: 'GET /SASjsApi/group/by/groupname/{name}',
476
+ description: 'finds a group by name',
477
+ identity: exports.ADMIN_SIGNED_IN,
478
+ request: { token: ADMIN, params: { name: 'DCGroup' } },
479
+ expect: {
480
+ status: 200,
481
+ partial: { uid: 'group-data-controller', name: 'DCGroup' }
482
+ }
483
+ },
484
+ {
485
+ id: 'get-group-by-name-missing',
486
+ route: 'GET /SASjsApi/group/by/groupname/{name}',
487
+ description: 'answers 404 for a name no group has',
488
+ identity: exports.ADMIN_SIGNED_IN,
489
+ request: { token: ADMIN, params: { name: 'nope' } },
490
+ expect: { status: 404, error: 'Group not found.' }
491
+ },
492
+ {
493
+ id: 'create-group',
494
+ route: 'POST /SASjsApi/group',
495
+ description: 'creates a group with no members',
496
+ identity: exports.ADMIN_SIGNED_IN,
497
+ request: {
498
+ token: ADMIN,
499
+ body: { name: 'NewGroup', description: 'A new group', isActive: true }
500
+ },
501
+ expect: {
502
+ status: 200,
503
+ partial: {
504
+ name: 'NewGroup',
505
+ description: 'A new group',
506
+ isActive: true,
507
+ users: []
508
+ }
509
+ }
510
+ },
511
+ {
512
+ id: 'create-group-not-admin',
513
+ route: 'POST /SASjsApi/group',
514
+ description: 'refuses a creation by a caller who is not an administrator',
515
+ identity: exports.ADMIN_SIGNED_IN,
516
+ request: {
517
+ token: RESTRICTED,
518
+ body: { name: 'NewGroup', description: 'x' }
519
+ },
520
+ expect: { status: 401, error: 'Admin account required' }
521
+ },
522
+ {
523
+ id: 'create-group-duplicate',
524
+ route: 'POST /SASjsApi/group',
525
+ description: 'refuses a name that is taken',
526
+ identity: exports.ADMIN_SIGNED_IN,
527
+ request: { token: ADMIN, body: { name: 'DCGroup', description: 'again' } },
528
+ expect: { status: 409, error: 'Group name already exists.' }
529
+ },
530
+ {
531
+ id: 'add-user-to-group',
532
+ route: 'POST /SASjsApi/group/{groupUid}/{userUid}',
533
+ description: 'adds a member and returns the group',
534
+ identity: exports.ADMIN_SIGNED_IN,
535
+ request: {
536
+ token: ADMIN,
537
+ params: { groupUid: 'group-admins', userUid: RESTRICTED_UID }
538
+ },
539
+ expect: {
540
+ status: 200,
541
+ partial: { uid: 'group-admins', name: 'Admins' }
542
+ }
543
+ },
544
+ {
545
+ id: 'add-user-to-public-group',
546
+ route: 'POST /SASjsApi/group/{groupUid}/{userUid}',
547
+ description: 'refuses a change to the reserved public group',
548
+ identity: exports.PROVIDER_MANAGED,
549
+ request: {
550
+ token: ADMIN,
551
+ params: { groupUid: 'group-public', userUid: RESTRICTED_UID }
552
+ },
553
+ expect: { status: 400, error: "Can't add/remove user to 'public' group." }
554
+ },
555
+ {
556
+ id: 'add-user-to-group-not-admin',
557
+ route: 'POST /SASjsApi/group/{groupUid}/{userUid}',
558
+ description: 'refuses a membership change by a non-administrator',
559
+ identity: exports.ADMIN_SIGNED_IN,
560
+ request: {
561
+ token: RESTRICTED,
562
+ params: { groupUid: 'group-admins', userUid: RESTRICTED_UID }
563
+ },
564
+ expect: { status: 401, error: 'Admin account required' }
565
+ },
566
+ {
567
+ id: 'add-user-to-group-missing-group',
568
+ route: 'POST /SASjsApi/group/{groupUid}/{userUid}',
569
+ description: 'answers 404 for a group that does not exist',
570
+ identity: exports.ADMIN_SIGNED_IN,
571
+ request: {
572
+ token: ADMIN,
573
+ params: { groupUid: 'group-none', userUid: RESTRICTED_UID }
574
+ },
575
+ expect: { status: 404, error: 'Group not found.' }
576
+ },
577
+ {
578
+ id: 'add-user-to-group-provider',
579
+ route: 'POST /SASjsApi/group/{groupUid}/{userUid}',
580
+ description: 'refuses a membership change on a provider-managed group',
581
+ identity: exports.PROVIDER_MANAGED,
582
+ request: {
583
+ token: ADMIN,
584
+ params: { groupUid: 'group-directory', userUid: RESTRICTED_UID }
585
+ },
586
+ expect: {
587
+ status: 405,
588
+ error: "Can't add/remove user to group created by external auth provider."
589
+ }
590
+ },
591
+ {
592
+ id: 'remove-user-from-group',
593
+ route: 'DELETE /SASjsApi/group/{groupUid}/{userUid}',
594
+ description: 'removes a member and returns the group',
595
+ identity: exports.ADMIN_SIGNED_IN,
596
+ request: {
597
+ token: ADMIN,
598
+ params: { groupUid: 'group-data-controller', userUid: RESTRICTED_UID }
599
+ },
600
+ expect: {
601
+ status: 200,
602
+ partial: { uid: 'group-data-controller', name: 'DCGroup', users: [] }
603
+ }
604
+ },
605
+ {
606
+ id: 'add-provider-user-to-group',
607
+ route: 'POST /SASjsApi/group/{groupUid}/{userUid}',
608
+ description: 'refuses a membership change for a provider-managed user',
609
+ identity: exports.PROVIDER_MANAGED,
610
+ request: {
611
+ token: ADMIN,
612
+ params: { groupUid: 'group-admins', userUid: 'user-directory' }
613
+ },
614
+ expect: {
615
+ status: 405,
616
+ error: "Can't add/remove user to group created by external auth provider."
617
+ }
618
+ },
619
+ {
620
+ id: 'remove-provider-user-from-group',
621
+ route: 'DELETE /SASjsApi/group/{groupUid}/{userUid}',
622
+ description: 'refuses a membership change for a provider-managed user',
623
+ identity: exports.PROVIDER_MANAGED,
624
+ request: {
625
+ token: ADMIN,
626
+ params: { groupUid: 'group-admins', userUid: 'user-directory' }
627
+ },
628
+ expect: {
629
+ status: 405,
630
+ error: "Can't add/remove user to group created by external auth provider."
631
+ }
632
+ },
633
+ {
634
+ id: 'remove-user-from-public-group',
635
+ route: 'DELETE /SASjsApi/group/{groupUid}/{userUid}',
636
+ description: 'refuses a change to the reserved public group',
637
+ identity: exports.PROVIDER_MANAGED,
638
+ request: {
639
+ token: ADMIN,
640
+ params: { groupUid: 'group-public', userUid: RESTRICTED_UID }
641
+ },
642
+ expect: { status: 400, error: "Can't add/remove user to 'public' group." }
643
+ },
644
+ {
645
+ id: 'remove-user-from-group-not-admin',
646
+ route: 'DELETE /SASjsApi/group/{groupUid}/{userUid}',
647
+ description: 'refuses a membership change by a non-administrator',
648
+ identity: exports.ADMIN_SIGNED_IN,
649
+ request: {
650
+ token: RESTRICTED,
651
+ params: { groupUid: 'group-data-controller', userUid: RESTRICTED_UID }
652
+ },
653
+ expect: { status: 401, error: 'Admin account required' }
654
+ },
655
+ {
656
+ id: 'remove-user-from-group-missing-user',
657
+ route: 'DELETE /SASjsApi/group/{groupUid}/{userUid}',
658
+ description: 'answers 404 for a member that does not exist',
659
+ identity: exports.ADMIN_SIGNED_IN,
660
+ request: {
661
+ token: ADMIN,
662
+ params: { groupUid: 'group-admins', userUid: 'user-none' }
663
+ },
664
+ expect: { status: 404, error: 'User not found.' }
665
+ },
666
+ {
667
+ id: 'remove-user-from-group-provider',
668
+ route: 'DELETE /SASjsApi/group/{groupUid}/{userUid}',
669
+ description: 'refuses a membership change on a provider-managed group',
670
+ identity: exports.PROVIDER_MANAGED,
671
+ request: {
672
+ token: ADMIN,
673
+ params: { groupUid: 'group-directory', userUid: RESTRICTED_UID }
674
+ },
675
+ expect: {
676
+ status: 405,
677
+ error: "Can't add/remove user to group created by external auth provider."
678
+ }
679
+ },
680
+ {
681
+ id: 'delete-group',
682
+ route: 'DELETE /SASjsApi/group/{uid}',
683
+ description: 'deletes a group',
684
+ identity: exports.ADMIN_SIGNED_IN,
685
+ request: { token: ADMIN, params: { uid: 'group-data-controller' } },
686
+ expect: { status: 200 }
687
+ },
688
+ {
689
+ id: 'delete-group-not-admin',
690
+ route: 'DELETE /SASjsApi/group/{uid}',
691
+ description: 'refuses a deletion by a caller who is not an administrator',
692
+ identity: exports.ADMIN_SIGNED_IN,
693
+ request: { token: RESTRICTED, params: { uid: 'group-admins' } },
694
+ expect: { status: 401, error: 'Admin account required' }
695
+ },
696
+ {
697
+ id: 'delete-group-missing',
698
+ route: 'DELETE /SASjsApi/group/{uid}',
699
+ description: 'answers 404 for an identifier no group has',
700
+ identity: exports.ADMIN_SIGNED_IN,
701
+ request: { token: ADMIN, params: { uid: 'group-none' } },
702
+ expect: { status: 404, error: 'Group not found.' }
703
+ },
704
+ // ------------------------------------------------------------ permissions
705
+ {
706
+ id: 'list-permissions-as-admin',
707
+ route: 'GET /SASjsApi/permission',
708
+ description: 'returns every rule to an administrator',
709
+ identity: exports.ADMIN_SIGNED_IN,
710
+ request: { token: ADMIN },
711
+ expect: {
712
+ status: 200,
713
+ body: [
714
+ {
715
+ uid: 'rule-routes',
716
+ path: '/SASjsApi/info',
717
+ type: 'Route',
718
+ setting: 'Grant',
719
+ group: {
720
+ uid: 'group-data-controller',
721
+ name: 'DCGroup',
722
+ description: 'Data Controller users',
723
+ isActive: true,
724
+ users: [
725
+ {
726
+ uid: RESTRICTED_UID,
727
+ username: 'user2',
728
+ displayName: 'Demo User',
729
+ isAdmin: false
730
+ }
731
+ ]
732
+ }
733
+ },
734
+ {
735
+ uid: 'rule-execute',
736
+ path: '/SASjsApi/stp/execute',
737
+ type: 'Route',
738
+ setting: 'Grant',
739
+ group: {
740
+ uid: 'group-data-controller',
741
+ name: 'DCGroup',
742
+ description: 'Data Controller users',
743
+ isActive: true,
744
+ users: [
745
+ {
746
+ uid: RESTRICTED_UID,
747
+ username: 'user2',
748
+ displayName: 'Demo User',
749
+ isAdmin: false
750
+ }
751
+ ]
752
+ }
753
+ },
754
+ {
755
+ uid: 'rule-code',
756
+ path: '/SASjsApi/code/execute',
757
+ type: 'Route',
758
+ setting: 'Deny',
759
+ user: {
760
+ uid: RESTRICTED_UID,
761
+ username: 'user2',
762
+ displayName: 'Demo User',
763
+ isAdmin: false
764
+ }
765
+ }
766
+ ]
767
+ }
768
+ },
769
+ {
770
+ id: 'list-permissions-as-user',
771
+ route: 'GET /SASjsApi/permission',
772
+ description: "returns a caller only their own rules and their groups'",
773
+ identity: exports.ADMIN_SIGNED_IN,
774
+ request: { token: RESTRICTED },
775
+ expect: {
776
+ status: 200,
777
+ body: [
778
+ {
779
+ uid: 'rule-routes',
780
+ path: '/SASjsApi/info',
781
+ type: 'Route',
782
+ setting: 'Grant',
783
+ group: {
784
+ uid: 'group-data-controller',
785
+ name: 'DCGroup',
786
+ description: 'Data Controller users',
787
+ isActive: true,
788
+ users: [
789
+ {
790
+ uid: RESTRICTED_UID,
791
+ username: 'user2',
792
+ displayName: 'Demo User',
793
+ isAdmin: false
794
+ }
795
+ ]
796
+ }
797
+ },
798
+ {
799
+ uid: 'rule-execute',
800
+ path: '/SASjsApi/stp/execute',
801
+ type: 'Route',
802
+ setting: 'Grant',
803
+ group: {
804
+ uid: 'group-data-controller',
805
+ name: 'DCGroup',
806
+ description: 'Data Controller users',
807
+ isActive: true,
808
+ users: [
809
+ {
810
+ uid: RESTRICTED_UID,
811
+ username: 'user2',
812
+ displayName: 'Demo User',
813
+ isAdmin: false
814
+ }
815
+ ]
816
+ }
817
+ },
818
+ {
819
+ uid: 'rule-code',
820
+ path: '/SASjsApi/code/execute',
821
+ type: 'Route',
822
+ setting: 'Deny',
823
+ user: {
824
+ uid: RESTRICTED_UID,
825
+ username: 'user2',
826
+ displayName: 'Demo User',
827
+ isAdmin: false
828
+ }
829
+ }
830
+ ]
831
+ }
832
+ },
833
+ {
834
+ id: 'list-permissions-unauthenticated',
835
+ route: 'GET /SASjsApi/permission',
836
+ description: 'refuses the list with no caller',
837
+ identity: exports.ADMIN_SIGNED_IN,
838
+ request: {},
839
+ expect: { status: 401, error: 'Unauthorized' }
840
+ },
841
+ {
842
+ id: 'create-permission-user',
843
+ route: 'POST /SASjsApi/permission',
844
+ description: 'creates a rule for a user',
845
+ identity: exports.ADMIN_SIGNED_IN,
846
+ request: {
847
+ token: ADMIN,
848
+ body: {
849
+ path: '/SASjsApi/code/lint',
850
+ type: 'Route',
851
+ setting: 'Grant',
852
+ principalType: 'user',
853
+ principalId: RESTRICTED_UID
854
+ }
855
+ },
856
+ expect: {
857
+ status: 200,
858
+ partial: { path: '/SASjsApi/code/lint', setting: 'Grant' }
859
+ }
860
+ },
861
+ {
862
+ id: 'create-permission-group',
863
+ route: 'POST /SASjsApi/permission',
864
+ description: 'creates a rule for a group',
865
+ identity: exports.ADMIN_SIGNED_IN,
866
+ request: {
867
+ token: ADMIN,
868
+ body: {
869
+ path: '/SASjsApi/code/lint',
870
+ type: 'Route',
871
+ setting: 'Grant',
872
+ principalType: 'group',
873
+ principalId: 'group-data-controller'
874
+ }
875
+ },
876
+ expect: {
877
+ status: 200,
878
+ partial: {
879
+ path: '/SASjsApi/code/lint',
880
+ setting: 'Grant',
881
+ group: { uid: 'group-data-controller', name: 'DCGroup' }
882
+ }
883
+ }
884
+ },
885
+ {
886
+ id: 'create-permission-invalid-principal-type',
887
+ route: 'POST /SASjsApi/permission',
888
+ description: 'refuses a principal type that is neither a user nor a group',
889
+ identity: exports.ADMIN_SIGNED_IN,
890
+ request: {
891
+ token: ADMIN,
892
+ body: {
893
+ path: '/SASjsApi/code/lint',
894
+ type: 'Route',
895
+ setting: 'Grant',
896
+ principalType: 'role',
897
+ principalId: 'anything'
898
+ }
899
+ },
900
+ expect: {
901
+ status: 400,
902
+ error: 'Invalid principal type. Valid types are user or group.'
903
+ }
904
+ },
905
+ {
906
+ id: 'create-permission-admin-principal',
907
+ route: 'POST /SASjsApi/permission',
908
+ description: 'refuses a rule assigned to an administrator',
909
+ identity: exports.ADMIN_SIGNED_IN,
910
+ request: {
911
+ token: ADMIN,
912
+ body: {
913
+ path: '/SASjsApi/code/lint',
914
+ type: 'Route',
915
+ setting: 'Grant',
916
+ principalType: 'user',
917
+ principalId: ADMIN_UID
918
+ }
919
+ },
920
+ expect: {
921
+ status: 400,
922
+ error: 'Can not add permission for admin user.'
923
+ }
924
+ },
925
+ {
926
+ id: 'create-permission-not-admin',
927
+ route: 'POST /SASjsApi/permission',
928
+ description: 'refuses a creation by a non-administrator',
929
+ identity: exports.ADMIN_SIGNED_IN,
930
+ request: {
931
+ token: RESTRICTED,
932
+ body: {
933
+ path: '/SASjsApi/code/lint',
934
+ type: 'Route',
935
+ setting: 'Grant',
936
+ principalType: 'user',
937
+ principalId: RESTRICTED_UID
938
+ }
939
+ },
940
+ expect: { status: 401, error: 'Admin account required' }
941
+ },
942
+ {
943
+ id: 'create-permission-missing-principal',
944
+ route: 'POST /SASjsApi/permission',
945
+ description: 'answers 404 for a principal that does not exist',
946
+ identity: exports.ADMIN_SIGNED_IN,
947
+ request: {
948
+ token: ADMIN,
949
+ body: {
950
+ path: '/SASjsApi/code/lint',
951
+ type: 'Route',
952
+ setting: 'Grant',
953
+ principalType: 'group',
954
+ principalId: 'group-none'
955
+ }
956
+ },
957
+ expect: { status: 404, error: 'Group not found.' }
958
+ },
959
+ {
960
+ id: 'create-permission-duplicate-group',
961
+ route: 'POST /SASjsApi/permission',
962
+ description: 'refuses a group rule that already exists',
963
+ identity: exports.ADMIN_SIGNED_IN,
964
+ request: {
965
+ token: ADMIN,
966
+ body: {
967
+ path: '/SASjsApi/info',
968
+ type: 'Route',
969
+ setting: 'Grant',
970
+ principalType: 'group',
971
+ principalId: 'group-data-controller'
972
+ }
973
+ },
974
+ expect: {
975
+ status: 409,
976
+ error: 'Permission already exists with provided Path, Type and Group.'
977
+ }
978
+ },
979
+ {
980
+ id: 'create-permission-duplicate',
981
+ route: 'POST /SASjsApi/permission',
982
+ description: 'refuses a rule that already exists',
983
+ identity: exports.ADMIN_SIGNED_IN,
984
+ request: {
985
+ token: ADMIN,
986
+ body: {
987
+ path: '/SASjsApi/code/execute',
988
+ type: 'Route',
989
+ setting: 'Deny',
990
+ principalType: 'user',
991
+ principalId: RESTRICTED_UID
992
+ }
993
+ },
994
+ expect: {
995
+ status: 409,
996
+ error: 'Permission already exists with provided Path, Type and User.'
997
+ }
998
+ },
999
+ {
1000
+ id: 'update-permission',
1001
+ route: 'PATCH /SASjsApi/permission/{uid}',
1002
+ description: 'flips a rule from Deny to Grant',
1003
+ identity: exports.ADMIN_SIGNED_IN,
1004
+ request: {
1005
+ token: ADMIN,
1006
+ params: { uid: 'rule-code' },
1007
+ body: { setting: 'Grant' }
1008
+ },
1009
+ expect: { status: 200, partial: { uid: 'rule-code', setting: 'Grant' } }
1010
+ },
1011
+ {
1012
+ id: 'update-permission-not-admin',
1013
+ route: 'PATCH /SASjsApi/permission/{uid}',
1014
+ description: 'refuses a change by a non-administrator',
1015
+ identity: exports.ADMIN_SIGNED_IN,
1016
+ request: {
1017
+ token: RESTRICTED,
1018
+ params: { uid: 'rule-code' },
1019
+ body: { setting: 'Grant' }
1020
+ },
1021
+ expect: { status: 401, error: 'Admin account required' }
1022
+ },
1023
+ {
1024
+ id: 'update-permission-missing',
1025
+ route: 'PATCH /SASjsApi/permission/{uid}',
1026
+ description: 'answers 404 for a rule that does not exist',
1027
+ identity: exports.ADMIN_SIGNED_IN,
1028
+ request: {
1029
+ token: ADMIN,
1030
+ params: { uid: 'rule-none' },
1031
+ body: { setting: 'Grant' }
1032
+ },
1033
+ expect: { status: 404, error: 'Permission not found.' }
1034
+ },
1035
+ {
1036
+ id: 'delete-permission',
1037
+ route: 'DELETE /SASjsApi/permission/{uid}',
1038
+ description: 'deletes a rule',
1039
+ identity: exports.ADMIN_SIGNED_IN,
1040
+ request: { token: ADMIN, params: { uid: 'rule-code' } },
1041
+ expect: { status: 204 }
1042
+ },
1043
+ {
1044
+ id: 'delete-permission-not-admin',
1045
+ route: 'DELETE /SASjsApi/permission/{uid}',
1046
+ description: 'refuses a deletion by a non-administrator',
1047
+ identity: exports.ADMIN_SIGNED_IN,
1048
+ request: { token: RESTRICTED, params: { uid: 'rule-code' } },
1049
+ expect: { status: 401, error: 'Admin account required' }
1050
+ },
1051
+ {
1052
+ id: 'delete-permission-missing',
1053
+ route: 'DELETE /SASjsApi/permission/{uid}',
1054
+ description: 'answers 404 for a rule that does not exist',
1055
+ identity: exports.ADMIN_SIGNED_IN,
1056
+ request: { token: ADMIN, params: { uid: 'rule-none' } },
1057
+ expect: { status: 404, error: 'Permission not found.' }
1058
+ },
1059
+ // --------------------------------------------------------------- clients
1060
+ {
1061
+ id: 'list-clients',
1062
+ route: 'GET /SASjsApi/client',
1063
+ description: 'lists the registered clients',
1064
+ identity: exports.ADMIN_SIGNED_IN,
1065
+ request: { token: ADMIN },
1066
+ expect: {
1067
+ status: 200,
1068
+ body: [
1069
+ {
1070
+ clientId: 'web_app',
1071
+ clientSecret: 'web_app',
1072
+ accessTokenExpiration: 86400,
1073
+ refreshTokenExpiration: 2592000
1074
+ },
1075
+ {
1076
+ clientId: 'sasjs',
1077
+ clientSecret: 'sasjs',
1078
+ accessTokenExpiration: 3600,
1079
+ refreshTokenExpiration: 86400
1080
+ }
1081
+ ]
1082
+ }
1083
+ },
1084
+ {
1085
+ id: 'list-clients-not-admin',
1086
+ route: 'GET /SASjsApi/client',
1087
+ description: 'refuses the list to a caller who is not an administrator',
1088
+ identity: exports.ADMIN_SIGNED_IN,
1089
+ request: { token: RESTRICTED },
1090
+ expect: { status: 401, error: 'Admin account required' }
1091
+ },
1092
+ {
1093
+ id: 'create-client',
1094
+ route: 'POST /SASjsApi/client',
1095
+ description: 'registers a client',
1096
+ identity: exports.ADMIN_SIGNED_IN,
1097
+ request: {
1098
+ token: ADMIN,
1099
+ body: {
1100
+ clientId: 'new_app',
1101
+ clientSecret: 'new_secret',
1102
+ accessTokenExpiration: 3600,
1103
+ refreshTokenExpiration: 86400
1104
+ }
1105
+ },
1106
+ expect: {
1107
+ status: 200,
1108
+ body: {
1109
+ clientId: 'new_app',
1110
+ clientSecret: 'new_secret',
1111
+ accessTokenExpiration: 3600,
1112
+ refreshTokenExpiration: 86400
1113
+ }
1114
+ }
1115
+ },
1116
+ {
1117
+ id: 'create-client-defaults',
1118
+ route: 'POST /SASjsApi/client',
1119
+ description: 'applies the schema defaults when the lifetimes are omitted',
1120
+ identity: exports.ADMIN_SIGNED_IN,
1121
+ request: {
1122
+ token: ADMIN,
1123
+ body: { clientId: 'default_app', clientSecret: 'default_secret' }
1124
+ },
1125
+ expect: {
1126
+ status: 200,
1127
+ body: {
1128
+ clientId: 'default_app',
1129
+ clientSecret: 'default_secret',
1130
+ accessTokenExpiration: 86400,
1131
+ refreshTokenExpiration: 2592000
1132
+ }
1133
+ }
1134
+ },
1135
+ {
1136
+ id: 'create-client-not-admin',
1137
+ route: 'POST /SASjsApi/client',
1138
+ description: 'refuses a registration by a non-administrator',
1139
+ identity: exports.ADMIN_SIGNED_IN,
1140
+ request: {
1141
+ token: RESTRICTED,
1142
+ body: {
1143
+ clientId: 'new_app',
1144
+ clientSecret: 'new_secret',
1145
+ accessTokenExpiration: 3600,
1146
+ refreshTokenExpiration: 86400
1147
+ }
1148
+ },
1149
+ expect: { status: 401, error: 'Admin account required' }
1150
+ },
1151
+ {
1152
+ id: 'create-client-duplicate',
1153
+ route: 'POST /SASjsApi/client',
1154
+ description: 'refuses a client id that is registered, with the 403 the server sends',
1155
+ identity: exports.ADMIN_SIGNED_IN,
1156
+ request: {
1157
+ token: ADMIN,
1158
+ body: {
1159
+ clientId: 'web_app',
1160
+ clientSecret: 'other',
1161
+ accessTokenExpiration: 3600,
1162
+ refreshTokenExpiration: 86400
1163
+ }
1164
+ },
1165
+ expect: { status: 403, error: 'Error: Client ID already exists.' }
1166
+ },
1167
+ // -------------------------------------------------------------- passwords
1168
+ {
1169
+ id: 'update-password',
1170
+ route: 'PATCH /SASjsApi/auth/updatePassword',
1171
+ description: 'changes the caller’s own password',
1172
+ identity: exports.ADMIN_SIGNED_IN,
1173
+ request: {
1174
+ token: RESTRICTED,
1175
+ body: { currentPassword: 'user123', newPassword: 'changed123' }
1176
+ },
1177
+ expect: { status: 204 }
1178
+ },
1179
+ {
1180
+ id: 'update-password-missing-field',
1181
+ route: 'PATCH /SASjsApi/auth/updatePassword',
1182
+ description: 'refuses a body without the current password',
1183
+ identity: exports.ADMIN_SIGNED_IN,
1184
+ request: { token: RESTRICTED, body: { newPassword: 'changed123' } },
1185
+ expect: { status: 400, error: '"currentPassword" is required' }
1186
+ },
1187
+ {
1188
+ id: 'update-password-unauthenticated',
1189
+ route: 'PATCH /SASjsApi/auth/updatePassword',
1190
+ description: 'refuses the change with no caller',
1191
+ identity: exports.ADMIN_SIGNED_IN,
1192
+ request: {
1193
+ body: { currentPassword: 'user123', newPassword: 'changed123' }
1194
+ },
1195
+ expect: { status: 401, error: 'Unauthorized' }
1196
+ },
1197
+ {
1198
+ id: 'update-password-wrong-current',
1199
+ route: 'PATCH /SASjsApi/auth/updatePassword',
1200
+ description: 'refuses a change whose current password is wrong',
1201
+ identity: exports.ADMIN_SIGNED_IN,
1202
+ request: {
1203
+ token: RESTRICTED,
1204
+ body: { currentPassword: 'wrong', newPassword: 'changed123' }
1205
+ },
1206
+ expect: { status: 403, error: 'Invalid current password!' }
1207
+ },
1208
+ {
1209
+ id: 'update-password-provider',
1210
+ route: 'PATCH /SASjsApi/auth/updatePassword',
1211
+ description: 'refuses a password change on a provider-managed account',
1212
+ identity: exports.PROVIDER_MANAGED,
1213
+ request: {
1214
+ token: 'dir-access',
1215
+ body: { currentPassword: 'dir123', newPassword: 'changed123' }
1216
+ },
1217
+ expect: {
1218
+ status: 405,
1219
+ error: 'Can not update password of user that is created by an external auth provider.'
1220
+ }
1221
+ }
1222
+ ];