@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
@@ -145,7 +145,7 @@ exports.GENERATED_ROUTES = [
145
145
  },
146
146
  {
147
147
  status: 401,
148
- description: 'The authorization code is invalid, expired, already used, or was issued for another client, or the client id is unknown'
148
+ description: 'The authorization code is invalid, expired, already used, or was issued for another client, or the client id is unknown. This route is unauthenticated, so an unknown client id is a `401` here where `POST /SASLogon/authorize` (which requires a session) answers `403`'
149
149
  }
150
150
  ]
151
151
  },
@@ -239,6 +239,10 @@ exports.GENERATED_ROUTES = [
239
239
  {
240
240
  status: 200,
241
241
  description: 'Ok'
242
+ },
243
+ {
244
+ status: 401,
245
+ description: 'Admin account required'
242
246
  }
243
247
  ]
244
248
  },
@@ -279,6 +283,14 @@ exports.GENERATED_ROUTES = [
279
283
  {
280
284
  status: 200,
281
285
  description: 'Ok'
286
+ },
287
+ {
288
+ status: 401,
289
+ description: 'Admin account required'
290
+ },
291
+ {
292
+ status: 403,
293
+ description: 'Client ID already exists.'
282
294
  }
283
295
  ]
284
296
  },
@@ -768,7 +780,7 @@ exports.GENERATED_ROUTES = [
768
780
  },
769
781
  {
770
782
  status: 409,
771
- description: 'Folder already exists'
783
+ description: 'Folder already exists. The body is that message as plain text.'
772
784
  }
773
785
  ]
774
786
  },
@@ -808,7 +820,7 @@ exports.GENERATED_ROUTES = [
808
820
  },
809
821
  {
810
822
  status: 409,
811
- description: 'Something already exists at the new path'
823
+ description: 'Something already exists at the new path. The body is that message as plain text.'
812
824
  }
813
825
  ]
814
826
  },
@@ -858,6 +870,14 @@ exports.GENERATED_ROUTES = [
858
870
  {
859
871
  status: 200,
860
872
  description: 'Ok'
873
+ },
874
+ {
875
+ status: 401,
876
+ description: 'Admin account required'
877
+ },
878
+ {
879
+ status: 409,
880
+ description: 'Group name already exists.'
861
881
  }
862
882
  ]
863
883
  },
@@ -886,6 +906,22 @@ exports.GENERATED_ROUTES = [
886
906
  {
887
907
  status: 200,
888
908
  description: 'Ok'
909
+ },
910
+ {
911
+ status: 400,
912
+ description: "Can't add/remove user to 'public' group."
913
+ },
914
+ {
915
+ status: 401,
916
+ description: 'Admin account required'
917
+ },
918
+ {
919
+ status: 404,
920
+ description: 'Group not found. / User not found.'
921
+ },
922
+ {
923
+ status: 405,
924
+ description: "Can't add/remove user to group created by external auth provider."
889
925
  }
890
926
  ]
891
927
  },
@@ -914,6 +950,22 @@ exports.GENERATED_ROUTES = [
914
950
  {
915
951
  status: 200,
916
952
  description: 'Ok'
953
+ },
954
+ {
955
+ status: 400,
956
+ description: "Can't add/remove user to 'public' group."
957
+ },
958
+ {
959
+ status: 401,
960
+ description: 'Admin account required'
961
+ },
962
+ {
963
+ status: 404,
964
+ description: 'Group not found. / User not found.'
965
+ },
966
+ {
967
+ status: 405,
968
+ description: "Can't add/remove user to group created by external auth provider."
917
969
  }
918
970
  ]
919
971
  },
@@ -936,6 +988,14 @@ exports.GENERATED_ROUTES = [
936
988
  {
937
989
  status: 200,
938
990
  description: 'Ok'
991
+ },
992
+ {
993
+ status: 401,
994
+ description: 'Admin account required'
995
+ },
996
+ {
997
+ status: 404,
998
+ description: 'Group not found.'
939
999
  }
940
1000
  ]
941
1001
  },
@@ -958,6 +1018,10 @@ exports.GENERATED_ROUTES = [
958
1018
  {
959
1019
  status: 200,
960
1020
  description: 'Ok'
1021
+ },
1022
+ {
1023
+ status: 404,
1024
+ description: 'Group not found.'
961
1025
  }
962
1026
  ]
963
1027
  },
@@ -980,6 +1044,10 @@ exports.GENERATED_ROUTES = [
980
1044
  {
981
1045
  status: 200,
982
1046
  description: 'Ok'
1047
+ },
1048
+ {
1049
+ status: 404,
1050
+ description: 'Group not found.'
983
1051
  }
984
1052
  ]
985
1053
  },
@@ -1040,6 +1108,14 @@ exports.GENERATED_ROUTES = [
1040
1108
  {
1041
1109
  status: 200,
1042
1110
  description: 'Ok'
1111
+ },
1112
+ {
1113
+ status: 401,
1114
+ description: 'Admin account required, or no grant on the route'
1115
+ },
1116
+ {
1117
+ status: 404,
1118
+ description: 'User not found.'
1043
1119
  }
1044
1120
  ]
1045
1121
  },
@@ -1086,6 +1162,22 @@ exports.GENERATED_ROUTES = [
1086
1162
  {
1087
1163
  status: 200,
1088
1164
  description: 'Ok'
1165
+ },
1166
+ {
1167
+ status: 400,
1168
+ description: 'Can not add permission for admin user. / Invalid principal type. Valid types are user or group.'
1169
+ },
1170
+ {
1171
+ status: 401,
1172
+ description: 'Admin account required'
1173
+ },
1174
+ {
1175
+ status: 404,
1176
+ description: 'User not found. / Group not found.'
1177
+ },
1178
+ {
1179
+ status: 409,
1180
+ description: 'Permission already exists with provided Path, Type and User. / Permission already exists with provided Path, Type and Group.'
1089
1181
  }
1090
1182
  ]
1091
1183
  },
@@ -1108,6 +1200,14 @@ exports.GENERATED_ROUTES = [
1108
1200
  {
1109
1201
  status: 204,
1110
1202
  description: 'No content'
1203
+ },
1204
+ {
1205
+ status: 401,
1206
+ description: 'Admin account required'
1207
+ },
1208
+ {
1209
+ status: 404,
1210
+ description: 'Permission not found.'
1111
1211
  }
1112
1212
  ]
1113
1213
  },
@@ -1136,6 +1236,14 @@ exports.GENERATED_ROUTES = [
1136
1236
  {
1137
1237
  status: 200,
1138
1238
  description: 'Ok'
1239
+ },
1240
+ {
1241
+ status: 401,
1242
+ description: 'Admin account required'
1243
+ },
1244
+ {
1245
+ status: 404,
1246
+ description: 'Permission not found.'
1139
1247
  }
1140
1248
  ]
1141
1249
  },
@@ -1400,6 +1508,14 @@ exports.GENERATED_ROUTES = [
1400
1508
  {
1401
1509
  status: 200,
1402
1510
  description: 'Ok'
1511
+ },
1512
+ {
1513
+ status: 401,
1514
+ description: 'Admin account required'
1515
+ },
1516
+ {
1517
+ status: 409,
1518
+ description: 'Username already exists.'
1403
1519
  }
1404
1520
  ]
1405
1521
  },
@@ -1428,6 +1544,14 @@ exports.GENERATED_ROUTES = [
1428
1544
  {
1429
1545
  status: 204,
1430
1546
  description: 'No content'
1547
+ },
1548
+ {
1549
+ status: 401,
1550
+ description: 'Admin account required, or invalid password'
1551
+ },
1552
+ {
1553
+ status: 404,
1554
+ description: 'User is not found.'
1431
1555
  }
1432
1556
  ]
1433
1557
  },
@@ -1450,6 +1574,14 @@ exports.GENERATED_ROUTES = [
1450
1574
  {
1451
1575
  status: 200,
1452
1576
  description: 'Ok'
1577
+ },
1578
+ {
1579
+ status: 401,
1580
+ description: 'Unauthorized'
1581
+ },
1582
+ {
1583
+ status: 404,
1584
+ description: 'User is not found.'
1453
1585
  }
1454
1586
  ]
1455
1587
  },
@@ -1508,6 +1640,22 @@ exports.GENERATED_ROUTES = [
1508
1640
  {
1509
1641
  status: 200,
1510
1642
  description: 'Ok'
1643
+ },
1644
+ {
1645
+ status: 401,
1646
+ description: 'Admin account required'
1647
+ },
1648
+ {
1649
+ status: 404,
1650
+ description: 'Unable to find user with <id>'
1651
+ },
1652
+ {
1653
+ status: 405,
1654
+ description: 'Can not update username of user that is created by an external auth provider. / Can not update display name of user that is created by an external auth provider.'
1655
+ },
1656
+ {
1657
+ status: 409,
1658
+ description: 'Username already exists.'
1511
1659
  }
1512
1660
  ]
1513
1661
  },
@@ -1536,6 +1684,14 @@ exports.GENERATED_ROUTES = [
1536
1684
  {
1537
1685
  status: 204,
1538
1686
  description: 'No content'
1687
+ },
1688
+ {
1689
+ status: 401,
1690
+ description: 'Admin account required, or invalid password'
1691
+ },
1692
+ {
1693
+ status: 404,
1694
+ description: 'User is not found.'
1539
1695
  }
1540
1696
  ]
1541
1697
  },
@@ -1558,6 +1714,14 @@ exports.GENERATED_ROUTES = [
1558
1714
  {
1559
1715
  status: 200,
1560
1716
  description: 'Ok'
1717
+ },
1718
+ {
1719
+ status: 401,
1720
+ description: 'Unauthorized'
1721
+ },
1722
+ {
1723
+ status: 404,
1724
+ description: 'User is not found.'
1561
1725
  }
1562
1726
  ]
1563
1727
  },
@@ -1616,6 +1780,22 @@ exports.GENERATED_ROUTES = [
1616
1780
  {
1617
1781
  status: 200,
1618
1782
  description: 'Ok'
1783
+ },
1784
+ {
1785
+ status: 401,
1786
+ description: 'Admin account required'
1787
+ },
1788
+ {
1789
+ status: 404,
1790
+ description: 'Unable to find user with <username>'
1791
+ },
1792
+ {
1793
+ status: 405,
1794
+ description: 'Can not update username of user that is created by an external auth provider. / Can not update display name of user that is created by an external auth provider.'
1795
+ },
1796
+ {
1797
+ status: 409,
1798
+ description: 'Username already exists.'
1619
1799
  }
1620
1800
  ]
1621
1801
  },
@@ -1649,7 +1829,7 @@ exports.GENERATED_ROUTES = [
1649
1829
  },
1650
1830
  {
1651
1831
  status: 403,
1652
- description: 'The client id is unknown'
1832
+ description: 'The client id is unknown. This route runs under a signed-in session, so the caller is authenticated and an unknown client is refused as forbidden; the unauthenticated `POST /SASjsApi/auth/token` answers `401` for the same condition'
1653
1833
  }
1654
1834
  ]
1655
1835
  },
@@ -25,7 +25,7 @@ exports.TAG_CAPABILITIES = {
25
25
  * operation waits on, and thirty copies of the same sentence would say the same
26
26
  * thing thirty times.
27
27
  */
28
- const IDENTITY_RECORDS = 'The identity service answers the sign-in flow. User, group, permission rule, client and provider records need read and write methods on the same store, which this package does not have yet.';
28
+ const DIRECTORY = 'Synchronising with an external auth provider needs a directory port, which this package does not have: a host decides which directory it holds accounts in, and answers this itself.';
29
29
  const EDITOR = 'Linting and formatting come from @sasjs/lint, which this package does not depend on. They land with the code routes so an editor has one wiring.';
30
30
  const RUNTIME = 'Starting a program needs a runtime port, which this package does not have: a host decides which languages it holds, and answers this itself until then.';
31
31
  const SESSION_RECORD = "A triggered session's record, log, output and state belong to the runtime that ran the program, and this package has no runtime port.";
@@ -80,18 +80,20 @@ exports.OVERLAY = {
80
80
  },
81
81
  'POST /SASjsApi/auth/logout': { implemented: true },
82
82
  'PATCH /SASjsApi/auth/updatePassword': {
83
- implemented: false,
84
- reason: IDENTITY_RECORDS
83
+ implemented: true,
84
+ // As with the permission list: the document's 404 is a caller whose record
85
+ // has gone, which a host's store can see and this package cannot.
86
+ hostResponses: [404]
85
87
  },
86
88
  // Auth Config - the providers a host was started with, and an LDAP import
87
89
  'GET /SASjsApi/authConfig': { implemented: false, reason: HOST_CONFIG },
88
90
  'POST /SASjsApi/authConfig/synchroniseWithLDAP': {
89
91
  implemented: false,
90
- reason: IDENTITY_RECORDS
92
+ reason: DIRECTORY
91
93
  },
92
94
  // Client - the OAuth clients the identity store already holds for sign-in
93
- 'GET /SASjsApi/client': { implemented: false, reason: IDENTITY_RECORDS },
94
- 'POST /SASjsApi/client': { implemented: false, reason: IDENTITY_RECORDS },
95
+ 'GET /SASjsApi/client': { implemented: true },
96
+ 'POST /SASjsApi/client': { implemented: true },
95
97
  // Code - the editor's three calls, and the runtime one
96
98
  'POST /SASjsApi/code/execute': { implemented: false, reason: RUNTIME },
97
99
  'POST /SASjsApi/code/trigger': { implemented: false, reason: RUNTIME },
@@ -165,25 +167,13 @@ exports.OVERLAY = {
165
167
  reason: UNPUBLISH
166
168
  },
167
169
  // Group - members and the groups themselves
168
- 'GET /SASjsApi/group': { implemented: false, reason: IDENTITY_RECORDS },
169
- 'GET /SASjsApi/group/{uid}': { implemented: false, reason: IDENTITY_RECORDS },
170
- 'GET /SASjsApi/group/by/groupname/{name}': {
171
- implemented: false,
172
- reason: IDENTITY_RECORDS
173
- },
174
- 'POST /SASjsApi/group': { implemented: false, reason: IDENTITY_RECORDS },
175
- 'POST /SASjsApi/group/{groupUid}/{userUid}': {
176
- implemented: false,
177
- reason: IDENTITY_RECORDS
178
- },
179
- 'DELETE /SASjsApi/group/{groupUid}/{userUid}': {
180
- implemented: false,
181
- reason: IDENTITY_RECORDS
182
- },
183
- 'DELETE /SASjsApi/group/{uid}': {
184
- implemented: false,
185
- reason: IDENTITY_RECORDS
186
- },
170
+ 'GET /SASjsApi/group': { implemented: true },
171
+ 'GET /SASjsApi/group/{uid}': { implemented: true },
172
+ 'GET /SASjsApi/group/by/groupname/{name}': { implemented: true },
173
+ 'POST /SASjsApi/group': { implemented: true },
174
+ 'POST /SASjsApi/group/{groupUid}/{userUid}': { implemented: true },
175
+ 'DELETE /SASjsApi/group/{groupUid}/{userUid}': { implemented: true },
176
+ 'DELETE /SASjsApi/group/{uid}': { implemented: true },
187
177
  // Info - what the host is, and what this caller may use
188
178
  'GET /SASjsApi/info': { implemented: false, reason: HOST_CONFIG },
189
179
  'GET /SASjsApi/info/authorizedRoutes': {
@@ -199,16 +189,24 @@ exports.OVERLAY = {
199
189
  reason: PERMISSIONS
200
190
  },
201
191
  // Permission - the rules the inventory above is derived from
202
- 'GET /SASjsApi/permission': { implemented: false, reason: IDENTITY_RECORDS },
203
- 'POST /SASjsApi/permission': { implemented: false, reason: IDENTITY_RECORDS },
204
- 'PATCH /SASjsApi/permission/{uid}': {
205
- implemented: false,
206
- reason: IDENTITY_RECORDS
207
- },
208
- 'DELETE /SASjsApi/permission/{uid}': {
209
- implemented: false,
210
- reason: IDENTITY_RECORDS
211
- },
192
+ 'GET /SASjsApi/permission': {
193
+ implemented: true,
194
+ // The document states 404 for a caller whose record has gone - a token that
195
+ // outlived its account. Here a token stops resolving when the account does,
196
+ // so this package answers 401 before it gets that far; the host's own store
197
+ // is what can still see the difference.
198
+ hostResponses: [404]
199
+ },
200
+ 'POST /SASjsApi/permission': { implemented: true },
201
+ 'PATCH /SASjsApi/permission/{uid}': { implemented: true },
202
+ // The three deletes answer 204 here and 200 on the wire: the server's route
203
+ // handlers send `res.status(200).send('Account Deleted!')` and
204
+ // `'Permission Deleted!'`, while tsoa emitted 204 for a void handler, so the
205
+ // document understates the status. Correcting it is a decorator in
206
+ // sasjs/server (`@SuccessResponse('200', ...)`), and this package follows the
207
+ // document until that release lands - a local 200 would fail the schema check
208
+ // that keeps this table honest.
209
+ 'DELETE /SASjsApi/permission/{uid}': { implemented: true },
212
210
  // Session - the caller's own session, then the record behind a triggered one
213
211
  'GET /SASjsApi/session': {
214
212
  implemented: true,
@@ -235,29 +233,26 @@ exports.OVERLAY = {
235
233
  'POST /SASjsApi/stp/execute': { implemented: false, reason: RUNTIME },
236
234
  'POST /SASjsApi/stp/trigger': { implemented: false, reason: RUNTIME },
237
235
  // User - the accounts the identity store already holds for sign-in
238
- 'GET /SASjsApi/user': { implemented: false, reason: IDENTITY_RECORDS },
239
- 'GET /SASjsApi/user/{uid}': { implemented: false, reason: IDENTITY_RECORDS },
240
- 'GET /SASjsApi/user/by/username/{username}': {
241
- implemented: false,
242
- reason: IDENTITY_RECORDS
243
- },
244
- 'POST /SASjsApi/user': { implemented: false, reason: IDENTITY_RECORDS },
245
- 'PATCH /SASjsApi/user/{uid}': {
246
- implemented: false,
247
- reason: IDENTITY_RECORDS
248
- },
249
- 'PATCH /SASjsApi/user/by/username/{username}': {
250
- implemented: false,
251
- reason: IDENTITY_RECORDS
252
- },
253
- 'DELETE /SASjsApi/user/{uid}': {
254
- implemented: false,
255
- reason: IDENTITY_RECORDS
256
- },
257
- 'DELETE /SASjsApi/user/by/username/{username}': {
258
- implemented: false,
259
- reason: IDENTITY_RECORDS
260
- }
236
+ // Reading accounts is open to any authenticated caller, which is what
237
+ // sasjs/server's routes do (`authenticateAccessToken` only): the document once
238
+ // said `Admin account required` here, and sasjs/server#551 corrected it to
239
+ // `Unauthorized` because that is the refusal a caller actually receives. The
240
+ // breadth is the server's behaviour, copied deliberately - the auto-exec code
241
+ // is the one field withheld from anyone but an administrator or the account
242
+ // itself.
243
+ 'GET /SASjsApi/user': { implemented: true },
244
+ 'GET /SASjsApi/user/{uid}': { implemented: true },
245
+ 'GET /SASjsApi/user/by/username/{username}': { implemented: true },
246
+ 'POST /SASjsApi/user': { implemented: true },
247
+ 'PATCH /SASjsApi/user/{uid}': { implemented: true },
248
+ 'PATCH /SASjsApi/user/by/username/{username}': { implemented: true },
249
+ // The document also marks `password` optional on these deletes, while the
250
+ // server's schema requires it for anyone who is not an administrator, and for
251
+ // an account deleting itself. A host should treat a passwordless delete from a
252
+ // non-admin as the refusal it is; this package answers 401 `Invalid password.`
253
+ // where the server's schema answers 400 `"password" is required`.
254
+ 'DELETE /SASjsApi/user/{uid}': { implemented: true },
255
+ 'DELETE /SASjsApi/user/by/username/{username}': { implemented: true }
261
256
  };
262
257
  /** Every method the table may state, in the order a diff reads best. */
263
258
  exports.METHODS = ['GET', 'POST', 'PATCH', 'DELETE', 'PUT'];
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The administration routes' conformance corpus: users, groups, permission rules
3
+ * and clients, as requests in and answers out.
4
+ *
5
+ * Every status the document states for these routes has a case here - that is
6
+ * what `spec/corpus-parity.spec.ts` enforces in both directions - so a refusal
7
+ * is part of the contract rather than something a reader has to infer from the
8
+ * controller.
9
+ *
10
+ * The fixtures are small on purpose: one administrator, one restricted user, the
11
+ * two groups they belong to, three rules, and - for the refusals that only an
12
+ * externally-managed account can provoke - a provider-managed user and group and
13
+ * the reserved `public` group.
14
+ */
15
+ import type { CorpusCase } from './drive.js';
16
+ import { type IdentityFixture } from '../identity/fixture.js';
17
+ /** The default identity, with an administrator and the restricted user signed in. */
18
+ export declare const ADMIN_SIGNED_IN: IdentityFixture;
19
+ /**
20
+ * The same identity, plus what the provider and public-group refusals need: an
21
+ * account and a group managed by an external provider, and the reserved group.
22
+ */
23
+ export declare const PROVIDER_MANAGED: IdentityFixture;
24
+ export declare const RECORD_CORPUS: CorpusCase[];
25
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../../src/corpus/records.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAA;AAkB/B,qFAAqF;AACrF,eAAO,MAAM,eAAe,EAAE,eAG7B,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,eA0C9B,CAAA;AAOD,eAAO,MAAM,aAAa,EAAE,UAAU,EA0oCrC,CAAA"}