@sasjs/api 0.7.2 → 0.9.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.
package/build/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @sasjs/api 0.7.2 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
1
+ /* @sasjs/api 0.9.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
2
2
  "use strict";
3
3
  var SASjsApi = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -23,7 +23,9 @@ var SASjsApi = (() => {
23
23
  var index_exports = {};
24
24
  __export(index_exports, {
25
25
  ADMIN_SIGNED_IN: () => ADMIN_SIGNED_IN,
26
+ ALWAYS_VISIBLE_ROUTES: () => ALWAYS_VISIBLE_ROUTES,
26
27
  ApiError: () => ApiError,
28
+ DEFAULT_GRANTABLE_ROUTES: () => DEFAULT_GRANTABLE_ROUTES,
27
29
  DEFAULT_IDENTITY: () => DEFAULT_IDENTITY,
28
30
  DEPLOY_LIMITS: () => DEPLOY_LIMITS,
29
31
  DEPLOY_PAYLOAD_BYTES: () => DEPLOY_PAYLOAD_BYTES,
@@ -38,7 +40,9 @@ var SASjsApi = (() => {
38
40
  PROVIDER_MANAGED: () => PROVIDER_MANAGED,
39
41
  RECORD_CORPUS: () => RECORD_CORPUS,
40
42
  ROUTES: () => ROUTES,
43
+ STATIC_AUTHORIZED_ROUTES: () => STATIC_AUTHORIZED_ROUTES,
41
44
  TAG_CAPABILITIES: () => TAG_CAPABILITIES,
45
+ TOP_LEVEL_ROUTES: () => TOP_LEVEL_ROUTES,
42
46
  basename: () => basename,
43
47
  checkDeployTree: () => checkDeployTree,
44
48
  createDriveService: () => createDriveService,
@@ -53,13 +57,15 @@ var SASjsApi = (() => {
53
57
  isApiError: () => isApiError,
54
58
  isDeployTree: () => isDeployTree,
55
59
  isRoot: () => isRoot,
60
+ isRouteGranted: () => isRouteGranted,
56
61
  measureDeployTree: () => measureDeployTree,
57
62
  normaliseDrivePath: () => normaliseDrivePath,
58
63
  parseIdentity: () => parseIdentity,
59
64
  routeById: () => routeById,
60
65
  runCase: () => runCase,
61
66
  sanitiseDisplayName: () => sanitiseDisplayName,
62
- servicesFor: () => servicesFor
67
+ servicesFor: () => servicesFor,
68
+ topLevelRouteFor: () => topLevelRouteFor
63
69
  });
64
70
 
65
71
  // src/types.ts
@@ -2457,7 +2463,6 @@ var SASjsApi = (() => {
2457
2463
  var 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.";
2458
2464
  var 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.";
2459
2465
  var HOST_CONFIG = "The body is the host's own configuration - ports, run times and providers - which a mock states for itself and this package cannot know.";
2460
- var PERMISSIONS = "The route inventory is the host's permission model, which this package does not hold.";
2461
2466
  var HOST_OWNED = "The host owns this report: it describes the host's own process and files, which a browser build has no equivalent of.";
2462
2467
  var WEB_BUNDLE = "The host serves its own web bundle; there is no API decision here for this package to answer.";
2463
2468
  var ARCHIVE = "Expanding the uploaded archive is the host's transport work - multer and a zip library on the server, `DecompressionStream` in a worker - and the host hands this package the tree it produces, which is the same tree `POST /SASjsApi/drive/deploy` takes.";
@@ -2604,16 +2609,12 @@ var SASjsApi = (() => {
2604
2609
  // Info - what the host is, and what this caller may use
2605
2610
  "GET /SASjsApi/info": { implemented: false, reason: HOST_CONFIG },
2606
2611
  "GET /SASjsApi/info/authorizedRoutes": {
2607
- implemented: false,
2608
- // the route inventory reports what the CALLER may use, so it needs an
2609
- // identity even though the rest of the Info tag needs nothing
2610
- capabilities: ["identity"],
2611
- reason: PERMISSIONS
2612
+ implemented: true,
2613
+ capabilities: ["identity"]
2612
2614
  },
2613
2615
  "GET /SASjsApi/info/myAuthorizedRoutes": {
2614
- implemented: false,
2615
- capabilities: ["identity"],
2616
- reason: PERMISSIONS
2616
+ implemented: true,
2617
+ capabilities: ["identity"]
2617
2618
  },
2618
2619
  // Permission - the rules the inventory above is derived from
2619
2620
  "GET /SASjsApi/permission": {
@@ -2970,6 +2971,16 @@ var SASjsApi = (() => {
2970
2971
  { password: body.password }
2971
2972
  )
2972
2973
  };
2974
+ case "GET /SASjsApi/info/authorizedRoutes":
2975
+ return {
2976
+ status: 200,
2977
+ body: await required().listAuthorizedRoutes(await caller())
2978
+ };
2979
+ case "GET /SASjsApi/info/myAuthorizedRoutes":
2980
+ return {
2981
+ status: 200,
2982
+ body: await required().myAuthorizedRoutes(await caller())
2983
+ };
2973
2984
  case "GET /SASjsApi/group":
2974
2985
  return { status: 200, body: await required().listGroups(await caller()) };
2975
2986
  case "GET /SASjsApi/group/{uid}":
@@ -3080,6 +3091,8 @@ var SASjsApi = (() => {
3080
3091
  }
3081
3092
  };
3082
3093
  var HANDLED_ROUTES = [
3094
+ "GET /SASjsApi/info/authorizedRoutes",
3095
+ "GET /SASjsApi/info/myAuthorizedRoutes",
3083
3096
  "GET /SASjsApi/drive/file",
3084
3097
  "POST /SASjsApi/drive/file",
3085
3098
  "PATCH /SASjsApi/drive/file",
@@ -3121,6 +3134,40 @@ var SASjsApi = (() => {
3121
3134
  "PATCH /SASjsApi/auth/updatePassword"
3122
3135
  ];
3123
3136
 
3137
+ // src/identity/routeAccess.ts
3138
+ var TOP_LEVEL_ROUTES = ["/AppStream", "/SASjsApi"];
3139
+ var ALWAYS_VISIBLE_ROUTES = ["/AppStream"];
3140
+ var STATIC_AUTHORIZED_ROUTES = [
3141
+ "/SASjsApi/code/execute",
3142
+ "/SASjsApi/stp/execute",
3143
+ "/SASjsApi/drive/deploy",
3144
+ "/SASjsApi/drive/deploy/upload",
3145
+ "/SASjsApi/drive/file",
3146
+ "/SASjsApi/drive/folder",
3147
+ "/SASjsApi/drive/fileTree",
3148
+ "/SASjsApi/drive/rename"
3149
+ ];
3150
+ var DEFAULT_GRANTABLE_ROUTES = [
3151
+ ...TOP_LEVEL_ROUTES,
3152
+ ...STATIC_AUTHORIZED_ROUTES
3153
+ ];
3154
+ var topLevelRouteFor = (routePath) => TOP_LEVEL_ROUTES.find(
3155
+ (route) => routePath.toLowerCase().startsWith(route.toLowerCase())
3156
+ ) ?? routePath;
3157
+ var settingOn = (rules, path) => rules.find((rule) => rule.path === path)?.setting;
3158
+ var GRANT = "Grant";
3159
+ var DENY = "Deny";
3160
+ var isRouteGranted = (rules, path, topLevelRoute = topLevelRouteFor(path)) => {
3161
+ const userSetting = settingOn(rules.user, path) ?? settingOn(rules.user, topLevelRoute);
3162
+ if (userSetting) return userSetting === GRANT;
3163
+ const onPath = rules.group.filter((rule) => rule.path === path);
3164
+ if (onPath.some((rule) => rule.setting === GRANT)) return true;
3165
+ if (onPath.some((rule) => rule.setting === DENY)) return false;
3166
+ return rules.group.some(
3167
+ (rule) => rule.path === topLevelRoute && rule.setting === GRANT
3168
+ );
3169
+ };
3170
+
3124
3171
  // src/identity/service.ts
3125
3172
  var unauthorized = () => new ApiError(401, "Unauthorized");
3126
3173
  var liveSubject = async (store, subject) => {
@@ -3241,7 +3288,8 @@ var SASjsApi = (() => {
3241
3288
  name: group.name,
3242
3289
  description: group.description
3243
3290
  });
3244
- var createRecordService = (store) => {
3291
+ var createRecordService = (store, options = {}) => {
3292
+ const grantableRoutes = options.grantableRoutes || DEFAULT_GRANTABLE_ROUTES;
3245
3293
  const groupDetails = async (group) => {
3246
3294
  const members = [];
3247
3295
  for (const uid of group.users) {
@@ -3266,23 +3314,14 @@ var SASjsApi = (() => {
3266
3314
  return details;
3267
3315
  };
3268
3316
  const findBy = async (by) => by.uid !== void 0 ? store.userById(by.uid) : store.userByName(by.username ?? "");
3269
- const permissionDetails = async (permission) => {
3270
- const response = {
3271
- uid: permission.uid,
3272
- path: permission.path,
3273
- type: permission.type,
3274
- setting: permission.setting
3275
- };
3276
- if (permission.user) {
3277
- const user3 = await store.userById(permission.user);
3278
- if (user3) response.user = asUser(user3);
3279
- }
3280
- if (permission.group) {
3281
- const group = await store.groupById(permission.group);
3282
- if (group) response.group = await groupDetails(group);
3283
- }
3284
- return response;
3285
- };
3317
+ const permissionDetails = async (permission) => ({
3318
+ uid: permission.uid,
3319
+ path: permission.path,
3320
+ type: permission.type,
3321
+ setting: permission.setting,
3322
+ ...permission.user ? { user: permission.user } : {},
3323
+ ...permission.group ? { group: permission.group } : {}
3324
+ });
3286
3325
  return {
3287
3326
  async listUsers(caller) {
3288
3327
  await requireCaller(store, caller);
@@ -3437,6 +3476,32 @@ var SASjsApi = (() => {
3437
3476
  if (!updated) throw new ApiError(400, "Unable to update group.");
3438
3477
  return groupDetails(updated);
3439
3478
  },
3479
+ async listAuthorizedRoutes(caller) {
3480
+ await requireCaller(store, caller);
3481
+ return { paths: [...grantableRoutes] };
3482
+ },
3483
+ async myAuthorizedRoutes(caller) {
3484
+ const reader = await requireCaller(store, caller);
3485
+ if (reader.isAdmin) return { paths: [...grantableRoutes] };
3486
+ const rules = await store.permissions();
3487
+ const mine = {
3488
+ user: rules.filter((rule) => rule.user === reader.uid).map((rule) => ({ path: rule.path, setting: rule.setting })),
3489
+ group: rules.filter(
3490
+ (rule) => rule.group !== void 0 && reader.groups.includes(rule.group)
3491
+ ).map((rule) => ({ path: rule.path, setting: rule.setting }))
3492
+ };
3493
+ const publicGroup = (await store.groups()).find(
3494
+ (group) => group.name === "public"
3495
+ );
3496
+ const publicGrants = publicGroup ? rules.filter(
3497
+ (rule) => rule.group === publicGroup.uid && rule.setting === "Grant"
3498
+ ).map((rule) => rule.path) : [];
3499
+ return {
3500
+ paths: grantableRoutes.filter(
3501
+ (path) => ALWAYS_VISIBLE_ROUTES.includes(path) || publicGrants.includes(path) || isRouteGranted(mine, path, topLevelRouteFor(path))
3502
+ )
3503
+ };
3504
+ },
3440
3505
  async listPermissions(caller) {
3441
3506
  const reader = await requireCaller(store, caller);
3442
3507
  const rules = await store.permissions();
@@ -3583,7 +3648,7 @@ var SASjsApi = (() => {
3583
3648
  permissions: [
3584
3649
  {
3585
3650
  uid: "rule-routes",
3586
- path: "/SASjsApi/info",
3651
+ path: "/SASjsApi/drive/file",
3587
3652
  type: "Route",
3588
3653
  setting: "Grant",
3589
3654
  group: "group-data-controller"
@@ -5054,7 +5119,7 @@ var SASjsApi = (() => {
5054
5119
  request: {
5055
5120
  token: RESTRICTED,
5056
5121
  params: { username: "user2" },
5057
- body: { password: "wrong" }
5122
+ body: { password: "wrong123" }
5058
5123
  },
5059
5124
  expect: { status: 401, error: "Invalid password." }
5060
5125
  },
@@ -5352,6 +5417,70 @@ var SASjsApi = (() => {
5352
5417
  expect: { status: 404, error: "Group not found." }
5353
5418
  },
5354
5419
  // ------------------------------------------------------------ permissions
5420
+ {
5421
+ id: "authorized-routes",
5422
+ route: "GET /SASjsApi/info/authorizedRoutes",
5423
+ description: "answers the routes that accept a rule, to any caller",
5424
+ identity: ADMIN_SIGNED_IN,
5425
+ request: { token: RESTRICTED },
5426
+ expect: {
5427
+ status: 200,
5428
+ body: {
5429
+ paths: [
5430
+ "/AppStream",
5431
+ "/SASjsApi",
5432
+ "/SASjsApi/code/execute",
5433
+ "/SASjsApi/stp/execute",
5434
+ "/SASjsApi/drive/deploy",
5435
+ "/SASjsApi/drive/deploy/upload",
5436
+ "/SASjsApi/drive/file",
5437
+ "/SASjsApi/drive/folder",
5438
+ "/SASjsApi/drive/fileTree",
5439
+ "/SASjsApi/drive/rename"
5440
+ ]
5441
+ }
5442
+ }
5443
+ },
5444
+ {
5445
+ id: "my-authorized-routes-as-admin",
5446
+ route: "GET /SASjsApi/info/myAuthorizedRoutes",
5447
+ description: "answers every route to an administrator",
5448
+ identity: ADMIN_SIGNED_IN,
5449
+ request: { token: ADMIN },
5450
+ expect: {
5451
+ status: 200,
5452
+ body: {
5453
+ paths: [
5454
+ "/AppStream",
5455
+ "/SASjsApi",
5456
+ "/SASjsApi/code/execute",
5457
+ "/SASjsApi/stp/execute",
5458
+ "/SASjsApi/drive/deploy",
5459
+ "/SASjsApi/drive/deploy/upload",
5460
+ "/SASjsApi/drive/file",
5461
+ "/SASjsApi/drive/folder",
5462
+ "/SASjsApi/drive/fileTree",
5463
+ "/SASjsApi/drive/rename"
5464
+ ]
5465
+ }
5466
+ }
5467
+ },
5468
+ {
5469
+ id: "my-authorized-routes-as-user",
5470
+ route: "GET /SASjsApi/info/myAuthorizedRoutes",
5471
+ description: "answers a caller only the routes its rules grant",
5472
+ identity: ADMIN_SIGNED_IN,
5473
+ request: { token: RESTRICTED },
5474
+ expect: {
5475
+ status: 200,
5476
+ // /AppStream is visible whatever the rules say; the two grants come from
5477
+ // DCGroup; /SASjsApi/code/execute is denied to this user by a user rule,
5478
+ // and nothing grants the rest
5479
+ body: {
5480
+ paths: ["/AppStream", "/SASjsApi/stp/execute", "/SASjsApi/drive/file"]
5481
+ }
5482
+ }
5483
+ },
5355
5484
  {
5356
5485
  id: "list-permissions-as-admin",
5357
5486
  route: "GET /SASjsApi/permission",
@@ -5363,55 +5492,24 @@ var SASjsApi = (() => {
5363
5492
  body: [
5364
5493
  {
5365
5494
  uid: "rule-routes",
5366
- path: "/SASjsApi/info",
5495
+ path: "/SASjsApi/drive/file",
5367
5496
  type: "Route",
5368
5497
  setting: "Grant",
5369
- group: {
5370
- uid: "group-data-controller",
5371
- name: "DCGroup",
5372
- description: "Data Controller users",
5373
- isActive: true,
5374
- users: [
5375
- {
5376
- uid: RESTRICTED_UID,
5377
- username: "user2",
5378
- displayName: "Demo User",
5379
- isAdmin: false
5380
- }
5381
- ]
5382
- }
5498
+ group: "group-data-controller"
5383
5499
  },
5384
5500
  {
5385
5501
  uid: "rule-execute",
5386
5502
  path: "/SASjsApi/stp/execute",
5387
5503
  type: "Route",
5388
5504
  setting: "Grant",
5389
- group: {
5390
- uid: "group-data-controller",
5391
- name: "DCGroup",
5392
- description: "Data Controller users",
5393
- isActive: true,
5394
- users: [
5395
- {
5396
- uid: RESTRICTED_UID,
5397
- username: "user2",
5398
- displayName: "Demo User",
5399
- isAdmin: false
5400
- }
5401
- ]
5402
- }
5505
+ group: "group-data-controller"
5403
5506
  },
5404
5507
  {
5405
5508
  uid: "rule-code",
5406
5509
  path: "/SASjsApi/code/execute",
5407
5510
  type: "Route",
5408
5511
  setting: "Deny",
5409
- user: {
5410
- uid: RESTRICTED_UID,
5411
- username: "user2",
5412
- displayName: "Demo User",
5413
- isAdmin: false
5414
- }
5512
+ user: RESTRICTED_UID
5415
5513
  }
5416
5514
  ]
5417
5515
  }
@@ -5427,55 +5525,24 @@ var SASjsApi = (() => {
5427
5525
  body: [
5428
5526
  {
5429
5527
  uid: "rule-routes",
5430
- path: "/SASjsApi/info",
5528
+ path: "/SASjsApi/drive/file",
5431
5529
  type: "Route",
5432
5530
  setting: "Grant",
5433
- group: {
5434
- uid: "group-data-controller",
5435
- name: "DCGroup",
5436
- description: "Data Controller users",
5437
- isActive: true,
5438
- users: [
5439
- {
5440
- uid: RESTRICTED_UID,
5441
- username: "user2",
5442
- displayName: "Demo User",
5443
- isAdmin: false
5444
- }
5445
- ]
5446
- }
5531
+ group: "group-data-controller"
5447
5532
  },
5448
5533
  {
5449
5534
  uid: "rule-execute",
5450
5535
  path: "/SASjsApi/stp/execute",
5451
5536
  type: "Route",
5452
5537
  setting: "Grant",
5453
- group: {
5454
- uid: "group-data-controller",
5455
- name: "DCGroup",
5456
- description: "Data Controller users",
5457
- isActive: true,
5458
- users: [
5459
- {
5460
- uid: RESTRICTED_UID,
5461
- username: "user2",
5462
- displayName: "Demo User",
5463
- isAdmin: false
5464
- }
5465
- ]
5466
- }
5538
+ group: "group-data-controller"
5467
5539
  },
5468
5540
  {
5469
5541
  uid: "rule-code",
5470
5542
  path: "/SASjsApi/code/execute",
5471
5543
  type: "Route",
5472
5544
  setting: "Deny",
5473
- user: {
5474
- uid: RESTRICTED_UID,
5475
- username: "user2",
5476
- displayName: "Demo User",
5477
- isAdmin: false
5478
- }
5545
+ user: RESTRICTED_UID
5479
5546
  }
5480
5547
  ]
5481
5548
  }
@@ -5496,7 +5563,7 @@ var SASjsApi = (() => {
5496
5563
  request: {
5497
5564
  token: ADMIN,
5498
5565
  body: {
5499
- path: "/SASjsApi/code/lint",
5566
+ path: "/SASjsApi/drive/folder",
5500
5567
  type: "Route",
5501
5568
  setting: "Grant",
5502
5569
  principalType: "user",
@@ -5505,7 +5572,7 @@ var SASjsApi = (() => {
5505
5572
  },
5506
5573
  expect: {
5507
5574
  status: 200,
5508
- partial: { path: "/SASjsApi/code/lint", setting: "Grant" }
5575
+ partial: { path: "/SASjsApi/drive/folder", setting: "Grant" }
5509
5576
  }
5510
5577
  },
5511
5578
  {
@@ -5516,7 +5583,7 @@ var SASjsApi = (() => {
5516
5583
  request: {
5517
5584
  token: ADMIN,
5518
5585
  body: {
5519
- path: "/SASjsApi/code/lint",
5586
+ path: "/SASjsApi/drive/rename",
5520
5587
  type: "Route",
5521
5588
  setting: "Grant",
5522
5589
  principalType: "group",
@@ -5526,9 +5593,9 @@ var SASjsApi = (() => {
5526
5593
  expect: {
5527
5594
  status: 200,
5528
5595
  partial: {
5529
- path: "/SASjsApi/code/lint",
5596
+ path: "/SASjsApi/drive/rename",
5530
5597
  setting: "Grant",
5531
- group: { uid: "group-data-controller", name: "DCGroup" }
5598
+ group: "group-data-controller"
5532
5599
  }
5533
5600
  }
5534
5601
  },
@@ -5540,7 +5607,7 @@ var SASjsApi = (() => {
5540
5607
  request: {
5541
5608
  token: ADMIN,
5542
5609
  body: {
5543
- path: "/SASjsApi/code/lint",
5610
+ path: "/SASjsApi/drive/deploy",
5544
5611
  type: "Route",
5545
5612
  setting: "Grant",
5546
5613
  principalType: "role",
@@ -5560,7 +5627,7 @@ var SASjsApi = (() => {
5560
5627
  request: {
5561
5628
  token: ADMIN,
5562
5629
  body: {
5563
- path: "/SASjsApi/code/lint",
5630
+ path: "/SASjsApi/drive/folder",
5564
5631
  type: "Route",
5565
5632
  setting: "Grant",
5566
5633
  principalType: "user",
@@ -5580,7 +5647,7 @@ var SASjsApi = (() => {
5580
5647
  request: {
5581
5648
  token: RESTRICTED,
5582
5649
  body: {
5583
- path: "/SASjsApi/code/lint",
5650
+ path: "/SASjsApi/drive/folder",
5584
5651
  type: "Route",
5585
5652
  setting: "Grant",
5586
5653
  principalType: "user",
@@ -5597,7 +5664,7 @@ var SASjsApi = (() => {
5597
5664
  request: {
5598
5665
  token: ADMIN,
5599
5666
  body: {
5600
- path: "/SASjsApi/code/lint",
5667
+ path: "/SASjsApi/drive/rename",
5601
5668
  type: "Route",
5602
5669
  setting: "Grant",
5603
5670
  principalType: "group",
@@ -5614,7 +5681,7 @@ var SASjsApi = (() => {
5614
5681
  request: {
5615
5682
  token: ADMIN,
5616
5683
  body: {
5617
- path: "/SASjsApi/info",
5684
+ path: "/SASjsApi/drive/file",
5618
5685
  type: "Route",
5619
5686
  setting: "Grant",
5620
5687
  principalType: "group",