@sasjs/api 0.3.0 → 0.4.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/README.md CHANGED
@@ -9,7 +9,7 @@ The plan that this package implements is tracked in `sasjs/cli#1550`.
9
9
 
10
10
  ## What is in it
11
11
 
12
- **The route table** (`ROUTES`, `findRoute`) is every operation in the API - 31 paths, 60 operations - derived from `spec/swagger.yaml` by `npm run sync:routes`.
12
+ **The route table** (`ROUTES`, `findRoute`) is every operation in the API - 31 paths, 61 operations - derived from `spec/swagger.yaml` by `npm run sync:routes`.
13
13
  The statuses come from that document and nowhere else: the overlay cannot add one, and an overlay entry naming a status the document does not state fails the build rather than describing a wish.
14
14
  The document cannot say everything a host needs, so the reviewed overlay adds the body each response carries, the capability a route needs, and whether this package implements it.
15
15
  Where the document is wrong, it is corrected in `sasjs/server` and re-vendored here - never worked around in the overlay.
@@ -89,6 +89,14 @@ Not implemented yet, in the order the plan sets: the rest of identity (`user`, `
89
89
 
90
90
  A status the document states but only a host can send - an account lockout (`429`), a provider failure (`500`), a session-store failure - is marked `hostResponses` in the overlay. It stays in the document, because a client still receives it, and is excluded from the parity check, whose other side is what this package answers; the host's own suite pins it.
91
91
 
92
+ ## What a host must do
93
+
94
+ Three of the sign-in flow's defences are the host's, because the package has no request to read and no clock to trust:
95
+
96
+ - **Re-resolve the caller on every request.** A host with its own session passes the subject in (`session`, `authorize`), so the service re-reads the account behind it and refuses a deactivated one; a host that caches the subject it resolved at sign-in defeats that check. `logout` is the exception and does not refuse: dropping a credential is not access, and refusing would leave the pair in the store.
97
+ - **Lock the account out after repeated failures.** `POST /SASLogon/login` answers `401 Username is not found.` for an unknown username and `401 Invalid Password.` for a wrong one, because that is what the server's own form tells a user - which also means the route cannot tell a caller to stop guessing on its own. The defence is the host's limiter, answering `429` with `Retry-After` in seconds; without it the route is a way to enumerate usernames and account state.
98
+ - **Answer a session-store or identity-provider failure with `500`.** Those are the host's own dependencies, and the overlay marks the statuses as `hostResponses`.
99
+
92
100
  ## Development
93
101
 
94
102
  ```bash
package/build/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @sasjs/api 0.3.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
1
+ /* @sasjs/api 0.4.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;
@@ -744,6 +744,42 @@ var SASjsApi = (() => {
744
744
  }
745
745
  ]
746
746
  },
747
+ {
748
+ id: "DELETE /SASjsApi/drive/deploy",
749
+ method: "DELETE",
750
+ path: "/SASjsApi/drive/deploy",
751
+ tag: "Drive",
752
+ summary: "Remove an app stream entry",
753
+ params: [
754
+ {
755
+ name: "appLoc",
756
+ in: "body",
757
+ required: true,
758
+ type: "string"
759
+ },
760
+ {
761
+ name: "streamServiceName",
762
+ in: "body",
763
+ required: true,
764
+ type: "string"
765
+ }
766
+ ],
767
+ requestBodies: ["application/json"],
768
+ responses: [
769
+ {
770
+ status: 200,
771
+ description: "Ok"
772
+ },
773
+ {
774
+ status: 400,
775
+ description: "Invalid Format"
776
+ },
777
+ {
778
+ status: 404,
779
+ description: "No such app stream entry"
780
+ }
781
+ ]
782
+ },
747
783
  {
748
784
  id: "POST /SASjsApi/drive/deploy",
749
785
  method: "POST",
@@ -2094,6 +2130,15 @@ var SASjsApi = (() => {
2094
2130
  "POST /SASjsApi/drive/deploy/upload": {
2095
2131
  implemented: false
2096
2132
  },
2133
+ // Unpublishing is the counterpart of the two above and lands with them: the
2134
+ // document in sasjs/server states it (sasjs/server#543), so a host may answer
2135
+ // it in the meantime, but this package cannot until the publish side it would
2136
+ // invert is implemented here. The statuses are the document's; `404` is a pair
2137
+ // - the registry name and the appLoc it was published from - that matches no
2138
+ // entry, and the drive's content is deliberately left alone.
2139
+ "DELETE /SASjsApi/drive/deploy": {
2140
+ implemented: false
2141
+ },
2097
2142
  // The lockout and the provider failure are answered before the package is
2098
2143
  // consulted (routes/web/web.ts in sasjs/server).
2099
2144
  "POST /SASLogon/login": {
@@ -2381,7 +2426,8 @@ var SASjsApi = (() => {
2381
2426
  if (!subject) throw unauthorized();
2382
2427
  if (!await store.client(clientId))
2383
2428
  throw new ApiError(403, "Invalid clientId.");
2384
- return store.issueCode({ userId: subject.userId, clientId });
2429
+ const named = await liveSubject(store, subject);
2430
+ return store.issueCode({ userId: named.userId, clientId });
2385
2431
  },
2386
2432
  async token({ clientId, code }) {
2387
2433
  const subject = await store.readCode(code);
@@ -2418,7 +2464,7 @@ var SASjsApi = (() => {
2418
2464
  async session(subject) {
2419
2465
  if (!subject) throw unauthorized();
2420
2466
  const user3 = await store.userById(subject.userId);
2421
- if (!user3) throw unauthorized();
2467
+ if (!user3 || !user3.isActive) throw unauthorized();
2422
2468
  return asSession(user3);
2423
2469
  }
2424
2470
  });
@@ -2656,8 +2702,23 @@ var SASjsApi = (() => {
2656
2702
  const pair = this.pairs.get(keyOf(subject));
2657
2703
  return pair && pair[kind] === token ? subject : void 0;
2658
2704
  }
2705
+ /**
2706
+ * The subject's saved pair, while both of its tokens are still live.
2707
+ *
2708
+ * An exchange hands a saved pair back rather than rotating a second tab's
2709
+ * tokens. That is only an answer if the pair can still be used: a saved pair
2710
+ * whose access token has aged out would sign the caller in and then refuse
2711
+ * every request it makes, so an aged-out pair is reported as absent and the
2712
+ * exchange mints a fresh one. `live()` reads the issue-time expiry, which a
2713
+ * seeded token does not carry - a seeded pair therefore never ages out here.
2714
+ */
2659
2715
  async pairFor(subject) {
2660
- return this.pairs.get(keyOf(subject));
2716
+ const pair = this.pairs.get(keyOf(subject));
2717
+ if (!pair) return void 0;
2718
+ const now = Date.now();
2719
+ const access = this.access.get(pair.accessToken);
2720
+ const refresh = this.refresh.get(pair.refreshToken);
2721
+ return live(access, now) && live(refresh, now) ? pair : void 0;
2661
2722
  }
2662
2723
  async savePair(subject, pair) {
2663
2724
  this.pairs.set(keyOf(subject), pair);
@@ -149,8 +149,24 @@ class MemoryIdentityStore {
149
149
  const pair = this.pairs.get(keyOf(subject));
150
150
  return pair && pair[kind] === token ? subject : undefined;
151
151
  }
152
+ /**
153
+ * The subject's saved pair, while both of its tokens are still live.
154
+ *
155
+ * An exchange hands a saved pair back rather than rotating a second tab's
156
+ * tokens. That is only an answer if the pair can still be used: a saved pair
157
+ * whose access token has aged out would sign the caller in and then refuse
158
+ * every request it makes, so an aged-out pair is reported as absent and the
159
+ * exchange mints a fresh one. `live()` reads the issue-time expiry, which a
160
+ * seeded token does not carry - a seeded pair therefore never ages out here.
161
+ */
152
162
  async pairFor(subject) {
153
- return this.pairs.get(keyOf(subject));
163
+ const pair = this.pairs.get(keyOf(subject));
164
+ if (!pair)
165
+ return undefined;
166
+ const now = Date.now();
167
+ const access = this.access.get(pair.accessToken);
168
+ const refresh = this.refresh.get(pair.refreshToken);
169
+ return live(access, now) && live(refresh, now) ? pair : undefined;
154
170
  }
155
171
  async savePair(subject, pair) {
156
172
  this.pairs.set(keyOf(subject), pair);
@@ -72,7 +72,12 @@ const createIdentityService = (store) => ({
72
72
  throw unauthorized();
73
73
  if (!(await store.client(clientId)))
74
74
  throw new types_js_1.ApiError(403, 'Invalid clientId.');
75
- return store.issueCode({ userId: subject.userId, clientId });
75
+ // A host resolves the caller once, at sign-in; the account may since have
76
+ // been deactivated. sasjs/server re-reads the user on every request and
77
+ // refuses an inactive one, so a session that outlived the account must not
78
+ // go on buying codes here.
79
+ const named = await liveSubject(store, subject);
80
+ return store.issueCode({ userId: named.userId, clientId });
76
81
  },
77
82
  async token({ clientId, code }) {
78
83
  // Read before claiming: a code presented with another client is refused
@@ -116,6 +121,10 @@ const createIdentityService = (store) => ({
116
121
  async logout(subject) {
117
122
  if (!subject)
118
123
  throw unauthorized();
124
+ // Deliberately no liveness check: the caller is asking for its credential to
125
+ // be dropped, and refusing would leave a pair in the store that nothing can
126
+ // use. Every path that READS the account refuses an inactive one, so a
127
+ // deactivated caller's sign-out is housekeeping rather than access.
119
128
  await store.deletePair(subject);
120
129
  },
121
130
  async logoutSession() {
@@ -124,8 +133,10 @@ const createIdentityService = (store) => ({
124
133
  async session(subject) {
125
134
  if (!subject)
126
135
  throw unauthorized();
136
+ // As with `authorize`: the account behind a host's session is re-read here,
137
+ // because the session outlives the account.
127
138
  const user = await store.userById(subject.userId);
128
- if (!user)
139
+ if (!user || !user.isActive)
129
140
  throw unauthorized();
130
141
  return (0, exports.asSession)(user);
131
142
  }
@@ -400,6 +400,42 @@ exports.GENERATED_ROUTES = [
400
400
  }
401
401
  ]
402
402
  },
403
+ {
404
+ id: 'DELETE /SASjsApi/drive/deploy',
405
+ method: 'DELETE',
406
+ path: '/SASjsApi/drive/deploy',
407
+ tag: 'Drive',
408
+ summary: 'Remove an app stream entry',
409
+ params: [
410
+ {
411
+ name: 'appLoc',
412
+ in: 'body',
413
+ required: true,
414
+ type: 'string'
415
+ },
416
+ {
417
+ name: 'streamServiceName',
418
+ in: 'body',
419
+ required: true,
420
+ type: 'string'
421
+ }
422
+ ],
423
+ requestBodies: ['application/json'],
424
+ responses: [
425
+ {
426
+ status: 200,
427
+ description: 'Ok'
428
+ },
429
+ {
430
+ status: 400,
431
+ description: 'Invalid Format'
432
+ },
433
+ {
434
+ status: 404,
435
+ description: 'No such app stream entry'
436
+ }
437
+ ]
438
+ },
403
439
  {
404
440
  id: 'POST /SASjsApi/drive/deploy',
405
441
  method: 'POST',
@@ -77,6 +77,15 @@ exports.OVERLAY = {
77
77
  'POST /SASjsApi/drive/deploy/upload': {
78
78
  implemented: false
79
79
  },
80
+ // Unpublishing is the counterpart of the two above and lands with them: the
81
+ // document in sasjs/server states it (sasjs/server#543), so a host may answer
82
+ // it in the meantime, but this package cannot until the publish side it would
83
+ // invert is implemented here. The statuses are the document's; `404` is a pair
84
+ // - the registry name and the appLoc it was published from - that matches no
85
+ // entry, and the drive's content is deliberately left alone.
86
+ 'DELETE /SASjsApi/drive/deploy': {
87
+ implemented: false
88
+ },
80
89
  // The lockout and the provider failure are answered before the package is
81
90
  // consulted (routes/web/web.ts in sasjs/server).
82
91
  'POST /SASLogon/login': {
@@ -41,6 +41,16 @@ export declare class MemoryIdentityStore implements IdentityStore {
41
41
  readAccess(token: string): Promise<Subject | undefined>;
42
42
  readRefresh(token: string): Promise<Subject | undefined>;
43
43
  private current;
44
+ /**
45
+ * The subject's saved pair, while both of its tokens are still live.
46
+ *
47
+ * An exchange hands a saved pair back rather than rotating a second tab's
48
+ * tokens. That is only an answer if the pair can still be used: a saved pair
49
+ * whose access token has aged out would sign the caller in and then refuse
50
+ * every request it makes, so an aged-out pair is reported as absent and the
51
+ * exchange mints a fresh one. `live()` reads the issue-time expiry, which a
52
+ * seeded token does not carry - a seeded pair therefore never ages out here.
53
+ */
44
54
  pairFor(subject: Subject): Promise<TokenPair | undefined>;
45
55
  savePair(subject: Subject, pair: TokenPair): Promise<void>;
46
56
  deletePair(subject: Subject): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../../src/identity/memory-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACV,MAAM,YAAY,CAAA;AACnB,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AAyDrB;;;;;GAKG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAEhC,OAAO,GAAE,eAAkC;IAsDjD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAI/D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAI9D,0EAA0E;IACpE,eAAe,CACnB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC;IAIb,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI7D,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC;IAoBzE;;;;;OAKG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAIvD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAI9D,OAAO,CAAC,OAAO;IAeT,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAIzD,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAS5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAIpD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKhD"}
1
+ {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../../src/identity/memory-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACV,MAAM,YAAY,CAAA;AACnB,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AAyDrB;;;;;GAKG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAEhC,OAAO,GAAE,eAAkC;IAsDjD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAI/D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAI9D,0EAA0E;IACpE,eAAe,CACnB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC;IAIb,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI7D,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC;IAoBzE;;;;;OAKG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAIvD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAI9D,OAAO,CAAC,OAAO;IAef;;;;;;;;;OASG;IACG,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAWzD,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAS5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAIpD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKhD"}
@@ -146,8 +146,24 @@ export class MemoryIdentityStore {
146
146
  const pair = this.pairs.get(keyOf(subject));
147
147
  return pair && pair[kind] === token ? subject : undefined;
148
148
  }
149
+ /**
150
+ * The subject's saved pair, while both of its tokens are still live.
151
+ *
152
+ * An exchange hands a saved pair back rather than rotating a second tab's
153
+ * tokens. That is only an answer if the pair can still be used: a saved pair
154
+ * whose access token has aged out would sign the caller in and then refuse
155
+ * every request it makes, so an aged-out pair is reported as absent and the
156
+ * exchange mints a fresh one. `live()` reads the issue-time expiry, which a
157
+ * seeded token does not carry - a seeded pair therefore never ages out here.
158
+ */
149
159
  async pairFor(subject) {
150
- return this.pairs.get(keyOf(subject));
160
+ const pair = this.pairs.get(keyOf(subject));
161
+ if (!pair)
162
+ return undefined;
163
+ const now = Date.now();
164
+ const access = this.access.get(pair.accessToken);
165
+ const refresh = this.refresh.get(pair.refreshToken);
166
+ return live(access, now) && live(refresh, now) ? pair : undefined;
151
167
  }
152
168
  async savePair(subject, pair) {
153
169
  this.pairs.set(keyOf(subject), pair);
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/identity/service.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACV,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE;QACJ,GAAG,EAAE,MAAM,CAAA;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,OAAO,EAAE,OAAO,CAAA;QAChB,qBAAqB,EAAE,OAAO,CAAA;KAC/B,CAAA;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IACrE,KAAK,CAAC,WAAW,EAAE;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;KACjB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IACxB,sEAAsE;IACtE,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1E,iDAAiD;IACjD,KAAK,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACtE,uDAAuD;IACvD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACtD,sEAAsE;IACtE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,iFAAiF;IACjF,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAChC,yDAAyD;IACzD,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;CAC9D;AAsBD,eAAO,MAAM,qBAAqB,GAChC,OAAO,aAAa,KACnB,eAiHD,CAAA;AAEF,kDAAkD;AAClD,eAAO,MAAM,SAAS,GAAI,MAAM,YAAY,KAAG,aAM7C,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/identity/service.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACV,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE;QACJ,GAAG,EAAE,MAAM,CAAA;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,OAAO,EAAE,OAAO,CAAA;QAChB,qBAAqB,EAAE,OAAO,CAAA;KAC/B,CAAA;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IACrE,KAAK,CAAC,WAAW,EAAE;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;KACjB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IACxB,sEAAsE;IACtE,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1E,iDAAiD;IACjD,KAAK,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACtE,uDAAuD;IACvD,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACtD,sEAAsE;IACtE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,iFAAiF;IACjF,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAChC,yDAAyD;IACzD,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;CAC9D;AAsBD,eAAO,MAAM,qBAAqB,GAChC,OAAO,aAAa,KACnB,eA6HD,CAAA;AAEF,kDAAkD;AAClD,eAAO,MAAM,SAAS,GAAI,MAAM,YAAY,KAAG,aAM7C,CAAA"}
@@ -69,7 +69,12 @@ export const createIdentityService = (store) => ({
69
69
  throw unauthorized();
70
70
  if (!(await store.client(clientId)))
71
71
  throw new ApiError(403, 'Invalid clientId.');
72
- return store.issueCode({ userId: subject.userId, clientId });
72
+ // A host resolves the caller once, at sign-in; the account may since have
73
+ // been deactivated. sasjs/server re-reads the user on every request and
74
+ // refuses an inactive one, so a session that outlived the account must not
75
+ // go on buying codes here.
76
+ const named = await liveSubject(store, subject);
77
+ return store.issueCode({ userId: named.userId, clientId });
73
78
  },
74
79
  async token({ clientId, code }) {
75
80
  // Read before claiming: a code presented with another client is refused
@@ -113,6 +118,10 @@ export const createIdentityService = (store) => ({
113
118
  async logout(subject) {
114
119
  if (!subject)
115
120
  throw unauthorized();
121
+ // Deliberately no liveness check: the caller is asking for its credential to
122
+ // be dropped, and refusing would leave a pair in the store that nothing can
123
+ // use. Every path that READS the account refuses an inactive one, so a
124
+ // deactivated caller's sign-out is housekeeping rather than access.
116
125
  await store.deletePair(subject);
117
126
  },
118
127
  async logoutSession() {
@@ -121,8 +130,10 @@ export const createIdentityService = (store) => ({
121
130
  async session(subject) {
122
131
  if (!subject)
123
132
  throw unauthorized();
133
+ // As with `authorize`: the account behind a host's session is re-read here,
134
+ // because the session outlives the account.
124
135
  const user = await store.userById(subject.userId);
125
- if (!user)
136
+ if (!user || !user.isActive)
126
137
  throw unauthorized();
127
138
  return asSession(user);
128
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../../src/routes/generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,eAAO,MAAM,gBAAgB,EAAE,cAAc,EA6oD5C,CAAA"}
1
+ {"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../../src/routes/generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAirD5C,CAAA"}
@@ -397,6 +397,42 @@ export const GENERATED_ROUTES = [
397
397
  }
398
398
  ]
399
399
  },
400
+ {
401
+ id: 'DELETE /SASjsApi/drive/deploy',
402
+ method: 'DELETE',
403
+ path: '/SASjsApi/drive/deploy',
404
+ tag: 'Drive',
405
+ summary: 'Remove an app stream entry',
406
+ params: [
407
+ {
408
+ name: 'appLoc',
409
+ in: 'body',
410
+ required: true,
411
+ type: 'string'
412
+ },
413
+ {
414
+ name: 'streamServiceName',
415
+ in: 'body',
416
+ required: true,
417
+ type: 'string'
418
+ }
419
+ ],
420
+ requestBodies: ['application/json'],
421
+ responses: [
422
+ {
423
+ status: 200,
424
+ description: 'Ok'
425
+ },
426
+ {
427
+ status: 400,
428
+ description: 'Invalid Format'
429
+ },
430
+ {
431
+ status: 404,
432
+ description: 'No such app stream entry'
433
+ }
434
+ ]
435
+ },
400
436
  {
401
437
  id: 'POST /SASjsApi/drive/deploy',
402
438
  method: 'POST',
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../../src/routes/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEzD,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,UAAU,EAAE,CAAA;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAED,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAczD,CAAA;AAYD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAoFhD,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,OAAO,EAAE,UAAU,EAA8C,CAAA"}
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../../src/routes/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEzD,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,UAAU,EAAE,CAAA;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAED,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAczD,CAAA;AAYD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA6FhD,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,OAAO,EAAE,UAAU,EAA8C,CAAA"}
@@ -74,6 +74,15 @@ export const OVERLAY = {
74
74
  'POST /SASjsApi/drive/deploy/upload': {
75
75
  implemented: false
76
76
  },
77
+ // Unpublishing is the counterpart of the two above and lands with them: the
78
+ // document in sasjs/server states it (sasjs/server#543), so a host may answer
79
+ // it in the meantime, but this package cannot until the publish side it would
80
+ // invert is implemented here. The statuses are the document's; `404` is a pair
81
+ // - the registry name and the appLoc it was published from - that matches no
82
+ // entry, and the drive's content is deliberately left alone.
83
+ 'DELETE /SASjsApi/drive/deploy': {
84
+ implemented: false
85
+ },
77
86
  // The lockout and the provider failure are answered before the package is
78
87
  // consulted (routes/web/web.ts in sasjs/server).
79
88
  'POST /SASLogon/login': {
@@ -72,8 +72,12 @@ export interface Route extends GeneratedRoute {
72
72
  * the host's limiter, a provider failure is its identity provider. The corpus
73
73
  * does not answer them, so the parity check compares the document without
74
74
  * them; the host's own suite pins them.
75
+ *
76
+ * Optional, and read as empty when absent: a host that builds a `Route` of its
77
+ * own is describing the document, not the lockout, and requiring the field
78
+ * made every such value a compile error.
75
79
  */
76
- hostResponses: number[];
80
+ hostResponses?: number[];
77
81
  capabilities: Capability[];
78
82
  /**
79
83
  * Whether this package carries a handler for the operation. A host may still
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sasjs/api",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "The SASjs Server API, described once - a generated route table, the services behind it, and the conformance corpus both hosts run",
5
5
  "license": "MIT",
6
6
  "author": "SASjs",