@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 +9 -1
- package/build/browser.js +65 -4
- package/build/cjs/identity/memory-store.js +17 -1
- package/build/cjs/identity/service.js +13 -2
- package/build/cjs/routes/generated.js +36 -0
- package/build/cjs/routes/overlay.js +9 -0
- package/build/esm/identity/memory-store.d.ts +10 -0
- package/build/esm/identity/memory-store.d.ts.map +1 -1
- package/build/esm/identity/memory-store.js +17 -1
- package/build/esm/identity/service.d.ts.map +1 -1
- package/build/esm/identity/service.js +13 -2
- package/build/esm/routes/generated.d.ts.map +1 -1
- package/build/esm/routes/generated.js +36 -0
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +9 -0
- package/build/esm/types.d.ts +5 -1
- package/build/esm/types.d.ts.map +1 -1
- package/package.json +1 -1
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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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': {
|
package/build/esm/types.d.ts
CHANGED
|
@@ -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
|
|
80
|
+
hostResponses?: number[];
|
|
77
81
|
capabilities: Capability[];
|
|
78
82
|
/**
|
|
79
83
|
* Whether this package carries a handler for the operation. A host may still
|
package/build/esm/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAK/B,QAAQ,CAAC,MAAM,EAAE,MAAM;IAJzB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,QAAO;gBAGb,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,QACH,CAAA;AAElD;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,UAAU,GACV,KAAK,GACL,MAAM,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;AAEpE,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;AAE7E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,aAAa,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,qEAAqE;IACrE,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,SAAS,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,KAAM,SAAQ,cAAc;IAC3C,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED