@sasjs/api 0.6.0 → 0.7.0

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