@sasjs/api 0.2.1 → 0.3.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.
- package/README.md +36 -8
- package/build/browser.js +804 -4
- package/build/cjs/corpus/identity.js +268 -0
- package/build/cjs/corpus/runner.js +11 -2
- package/build/cjs/host/dispatch.js +57 -2
- package/build/cjs/identity/fixture.js +118 -0
- package/build/cjs/identity/memory-store.js +195 -0
- package/build/cjs/identity/service.js +153 -0
- package/build/cjs/identity/types.js +11 -0
- package/build/cjs/index.js +11 -1
- package/build/cjs/routes/generated.js +72 -0
- package/build/cjs/routes/index.js +6 -0
- package/build/cjs/routes/overlay.js +38 -0
- package/build/esm/corpus/drive.d.ts +14 -0
- package/build/esm/corpus/drive.d.ts.map +1 -1
- package/build/esm/corpus/identity.d.ts +17 -0
- package/build/esm/corpus/identity.d.ts.map +1 -0
- package/build/esm/corpus/identity.js +265 -0
- package/build/esm/corpus/runner.d.ts +6 -1
- package/build/esm/corpus/runner.d.ts.map +1 -1
- package/build/esm/corpus/runner.js +11 -2
- package/build/esm/host/dispatch.d.ts +16 -0
- package/build/esm/host/dispatch.d.ts.map +1 -1
- package/build/esm/host/dispatch.js +57 -2
- package/build/esm/identity/fixture.d.ts +84 -0
- package/build/esm/identity/fixture.d.ts.map +1 -0
- package/build/esm/identity/fixture.js +114 -0
- package/build/esm/identity/memory-store.d.ts +61 -0
- package/build/esm/identity/memory-store.d.ts.map +1 -0
- package/build/esm/identity/memory-store.js +191 -0
- package/build/esm/identity/service.d.ts +48 -0
- package/build/esm/identity/service.d.ts.map +1 -0
- package/build/esm/identity/service.js +148 -0
- package/build/esm/identity/types.d.ts +79 -0
- package/build/esm/identity/types.d.ts.map +1 -0
- package/build/esm/identity/types.js +10 -0
- package/build/esm/index.d.ts +7 -0
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +4 -0
- package/build/esm/routes/generated.d.ts.map +1 -1
- package/build/esm/routes/generated.js +72 -0
- package/build/esm/routes/index.d.ts.map +1 -1
- package/build/esm/routes/index.js +6 -0
- package/build/esm/routes/overlay.d.ts +9 -0
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +38 -0
- package/build/esm/types.d.ts +11 -0
- package/build/esm/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.asSession = exports.createIdentityService = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The sign-in flow's decisions: what each route answers, in which order, with
|
|
6
|
+
* which wording.
|
|
7
|
+
*
|
|
8
|
+
* The transport stays with the host - a session cookie and a CSRF token on a
|
|
9
|
+
* server, whatever the browser build holds - so nothing here reads a request.
|
|
10
|
+
* A host resolves the caller (a bearer token through `authenticate`, a session
|
|
11
|
+
* it already has) and passes the subject in; the service decides whether that
|
|
12
|
+
* is enough for the operation to proceed.
|
|
13
|
+
*/
|
|
14
|
+
const types_js_1 = require("../types.js");
|
|
15
|
+
const unauthorized = () => new types_js_1.ApiError(401, 'Unauthorized');
|
|
16
|
+
/**
|
|
17
|
+
* The subject, provided the account behind it may still hold a token.
|
|
18
|
+
*
|
|
19
|
+
* `authenticate` reads a token on every request and refuses a deactivated
|
|
20
|
+
* account; an exchange or a refresh that skipped the same check would mint a
|
|
21
|
+
* fresh pair for exactly that account, so the check sits beside every path that
|
|
22
|
+
* reaches `store.issue`.
|
|
23
|
+
*/
|
|
24
|
+
const liveSubject = async (store, subject) => {
|
|
25
|
+
const user = await store.userById(subject.userId);
|
|
26
|
+
if (!user || !user.isActive)
|
|
27
|
+
throw unauthorized();
|
|
28
|
+
return subject;
|
|
29
|
+
};
|
|
30
|
+
const createIdentityService = (store) => ({
|
|
31
|
+
async authenticate(token) {
|
|
32
|
+
if (token === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
const subject = await store.readAccess(token);
|
|
35
|
+
if (!subject)
|
|
36
|
+
return undefined;
|
|
37
|
+
// A token names a user who may since have been deactivated. sasjs/server
|
|
38
|
+
// checks `isActive` on every request, so a token that outlived the account
|
|
39
|
+
// must stop resolving here too - otherwise the browser host honours a
|
|
40
|
+
// caller the server would refuse.
|
|
41
|
+
const user = await store.userById(subject.userId);
|
|
42
|
+
return user && user.isActive ? subject : undefined;
|
|
43
|
+
},
|
|
44
|
+
async login({ username, password }) {
|
|
45
|
+
const user = await store.userByName(username);
|
|
46
|
+
// An unknown username and a wrong password answer with different messages
|
|
47
|
+
// on purpose: the server's login form tells the caller which one it was,
|
|
48
|
+
// and the account lockout - the defence against guessing - is the host's.
|
|
49
|
+
if (!user)
|
|
50
|
+
throw new types_js_1.ApiError(401, 'Username is not found.');
|
|
51
|
+
if (!(await store.passwordMatches(user, password)))
|
|
52
|
+
throw new types_js_1.ApiError(401, 'Invalid Password.');
|
|
53
|
+
// Checked after the password, so the answer is not a way to learn that an
|
|
54
|
+
// account exists but is switched off without knowing its password.
|
|
55
|
+
if (!user.isActive)
|
|
56
|
+
throw new types_js_1.ApiError(401, 'This account is not active.');
|
|
57
|
+
return {
|
|
58
|
+
loggedIn: true,
|
|
59
|
+
user: {
|
|
60
|
+
uid: user.uid,
|
|
61
|
+
username: user.username,
|
|
62
|
+
displayName: user.displayName,
|
|
63
|
+
isAdmin: user.isAdmin,
|
|
64
|
+
needsToUpdatePassword: user.needsToUpdatePassword
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
async authorize(subject, clientId) {
|
|
69
|
+
// The same answer the server's middleware gives a caller with no token: the
|
|
70
|
+
// two hosts must not describe the same refusal differently.
|
|
71
|
+
if (!subject)
|
|
72
|
+
throw unauthorized();
|
|
73
|
+
if (!(await store.client(clientId)))
|
|
74
|
+
throw new types_js_1.ApiError(403, 'Invalid 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 });
|
|
81
|
+
},
|
|
82
|
+
async token({ clientId, code }) {
|
|
83
|
+
// Read before claiming: a code presented with another client is refused
|
|
84
|
+
// with the same answer as one that does not verify, and it is not spent, so
|
|
85
|
+
// the caller can still exchange it for the client it was issued for.
|
|
86
|
+
const subject = await store.readCode(code);
|
|
87
|
+
if (!subject || subject.clientId !== clientId)
|
|
88
|
+
throw new types_js_1.ApiError(401, 'Invalid Auth Code');
|
|
89
|
+
// The client is read before the code is spent and before an existing pair is
|
|
90
|
+
// handed back: both the pair and the tokens it would buy belong to the
|
|
91
|
+
// client, so a client deleted since the code was issued refuses the
|
|
92
|
+
// exchange - and refuses it without consuming the code.
|
|
93
|
+
const client = await store.client(clientId);
|
|
94
|
+
if (!client)
|
|
95
|
+
throw new types_js_1.ApiError(401, 'Invalid clientId.');
|
|
96
|
+
const named = await liveSubject(store, subject);
|
|
97
|
+
if (!(await store.claimCode(code)))
|
|
98
|
+
throw new types_js_1.ApiError(401, 'Invalid Auth Code');
|
|
99
|
+
// Re-exchanging a code for the same user and client while a valid pair
|
|
100
|
+
// exists returns that pair: another tab holding the old tokens must not be
|
|
101
|
+
// signed out by a second exchange.
|
|
102
|
+
const existing = await store.pairFor(named);
|
|
103
|
+
if (existing)
|
|
104
|
+
return existing;
|
|
105
|
+
const pair = await store.issue(named, client);
|
|
106
|
+
await store.savePair(named, pair);
|
|
107
|
+
return pair;
|
|
108
|
+
},
|
|
109
|
+
async refresh(token) {
|
|
110
|
+
const subject = token === undefined ? undefined : await store.readRefresh(token);
|
|
111
|
+
if (!subject)
|
|
112
|
+
throw unauthorized();
|
|
113
|
+
const client = await store.client(subject.clientId);
|
|
114
|
+
if (!client)
|
|
115
|
+
throw new types_js_1.ApiError(401, 'Invalid clientId.');
|
|
116
|
+
const named = await liveSubject(store, subject);
|
|
117
|
+
const pair = await store.issue(named, client);
|
|
118
|
+
await store.savePair(named, pair);
|
|
119
|
+
return pair;
|
|
120
|
+
},
|
|
121
|
+
async logout(subject) {
|
|
122
|
+
if (!subject)
|
|
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.
|
|
128
|
+
await store.deletePair(subject);
|
|
129
|
+
},
|
|
130
|
+
async logoutSession() {
|
|
131
|
+
return 'OK!';
|
|
132
|
+
},
|
|
133
|
+
async session(subject) {
|
|
134
|
+
if (!subject)
|
|
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.
|
|
138
|
+
const user = await store.userById(subject.userId);
|
|
139
|
+
if (!user || !user.isActive)
|
|
140
|
+
throw unauthorized();
|
|
141
|
+
return (0, exports.asSession)(user);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
exports.createIdentityService = createIdentityService;
|
|
145
|
+
/** A subject, as the session route reports it. */
|
|
146
|
+
const asSession = (user) => ({
|
|
147
|
+
uid: user.uid,
|
|
148
|
+
username: user.username,
|
|
149
|
+
displayName: user.displayName,
|
|
150
|
+
isAdmin: user.isAdmin,
|
|
151
|
+
needsToUpdatePassword: user.needsToUpdatePassword
|
|
152
|
+
});
|
|
153
|
+
exports.asSession = asSession;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The identity the API works over, as a host supplies it.
|
|
4
|
+
*
|
|
5
|
+
* A server holds users in a database, verifies a bcrypt hash and signs JWTs; a
|
|
6
|
+
* browser build holds a seeded fixture, compares a plain-text demo password and
|
|
7
|
+
* mints opaque strings. Those differences are the host's, so they live behind
|
|
8
|
+
* this port - the statuses, the messages and the order of the checks are the
|
|
9
|
+
* service's, and both hosts run the same corpus over them.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/build/cjs/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.servicesFor = exports.runCase = exports.DRIVE_CORPUS = exports.HANDLED_ROUTES = exports.dispatch = exports.TAG_CAPABILITIES = exports.OVERLAY = exports.findRoute = exports.routeById = exports.ROUTES = exports.MemoryDriveStore = exports.createDriveService = exports.isRoot = exports.basename = exports.dirname = exports.hasBlockedExtension = exports.normaliseDrivePath = void 0;
|
|
17
|
+
exports.servicesFor = exports.runCase = exports.IDENTITY_CORPUS = exports.DRIVE_CORPUS = exports.parseIdentity = exports.IDENTITY_FORMAT = exports.DEFAULT_IDENTITY = exports.MemoryIdentityStore = exports.createIdentityService = exports.HANDLED_ROUTES = exports.dispatch = exports.TAG_CAPABILITIES = exports.OVERLAY = exports.findRoute = exports.routeById = exports.ROUTES = exports.MemoryDriveStore = exports.createDriveService = exports.isRoot = exports.basename = exports.dirname = exports.hasBlockedExtension = exports.normaliseDrivePath = void 0;
|
|
18
18
|
/**
|
|
19
19
|
* `@sasjs/api` - the SASjs Server API described once.
|
|
20
20
|
*
|
|
@@ -51,8 +51,18 @@ Object.defineProperty(exports, "TAG_CAPABILITIES", { enumerable: true, get: func
|
|
|
51
51
|
var dispatch_js_1 = require("./host/dispatch.js");
|
|
52
52
|
Object.defineProperty(exports, "dispatch", { enumerable: true, get: function () { return dispatch_js_1.dispatch; } });
|
|
53
53
|
Object.defineProperty(exports, "HANDLED_ROUTES", { enumerable: true, get: function () { return dispatch_js_1.HANDLED_ROUTES; } });
|
|
54
|
+
var service_js_2 = require("./identity/service.js");
|
|
55
|
+
Object.defineProperty(exports, "createIdentityService", { enumerable: true, get: function () { return service_js_2.createIdentityService; } });
|
|
56
|
+
var memory_store_js_2 = require("./identity/memory-store.js");
|
|
57
|
+
Object.defineProperty(exports, "MemoryIdentityStore", { enumerable: true, get: function () { return memory_store_js_2.MemoryIdentityStore; } });
|
|
58
|
+
var fixture_js_1 = require("./identity/fixture.js");
|
|
59
|
+
Object.defineProperty(exports, "DEFAULT_IDENTITY", { enumerable: true, get: function () { return fixture_js_1.DEFAULT_IDENTITY; } });
|
|
60
|
+
Object.defineProperty(exports, "IDENTITY_FORMAT", { enumerable: true, get: function () { return fixture_js_1.IDENTITY_FORMAT; } });
|
|
61
|
+
Object.defineProperty(exports, "parseIdentity", { enumerable: true, get: function () { return fixture_js_1.parseIdentity; } });
|
|
54
62
|
var drive_js_1 = require("./corpus/drive.js");
|
|
55
63
|
Object.defineProperty(exports, "DRIVE_CORPUS", { enumerable: true, get: function () { return drive_js_1.DRIVE_CORPUS; } });
|
|
64
|
+
var identity_js_1 = require("./corpus/identity.js");
|
|
65
|
+
Object.defineProperty(exports, "IDENTITY_CORPUS", { enumerable: true, get: function () { return identity_js_1.IDENTITY_CORPUS; } });
|
|
56
66
|
var runner_js_1 = require("./corpus/runner.js");
|
|
57
67
|
Object.defineProperty(exports, "runCase", { enumerable: true, get: function () { return runner_js_1.runCase; } });
|
|
58
68
|
Object.defineProperty(exports, "servicesFor", { enumerable: true, get: function () { return runner_js_1.servicesFor; } });
|
|
@@ -87,6 +87,10 @@ exports.GENERATED_ROUTES = [
|
|
|
87
87
|
{
|
|
88
88
|
status: 204,
|
|
89
89
|
description: 'No content'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
status: 401,
|
|
93
|
+
description: 'No valid access token was supplied'
|
|
90
94
|
}
|
|
91
95
|
]
|
|
92
96
|
},
|
|
@@ -102,6 +106,10 @@ exports.GENERATED_ROUTES = [
|
|
|
102
106
|
{
|
|
103
107
|
status: 200,
|
|
104
108
|
description: 'Ok'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
status: 401,
|
|
112
|
+
description: 'The refresh token is missing, invalid or expired, or the client id is unknown'
|
|
105
113
|
}
|
|
106
114
|
]
|
|
107
115
|
},
|
|
@@ -130,6 +138,14 @@ exports.GENERATED_ROUTES = [
|
|
|
130
138
|
{
|
|
131
139
|
status: 200,
|
|
132
140
|
description: 'Ok'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
status: 400,
|
|
144
|
+
description: 'The request body is missing `clientId` or `code`'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
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'
|
|
133
149
|
}
|
|
134
150
|
]
|
|
135
151
|
},
|
|
@@ -158,6 +174,26 @@ exports.GENERATED_ROUTES = [
|
|
|
158
174
|
{
|
|
159
175
|
status: 204,
|
|
160
176
|
description: 'No content'
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
status: 400,
|
|
180
|
+
description: 'The request body is missing `currentPassword` or `newPassword`'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
status: 401,
|
|
184
|
+
description: 'No valid access token was supplied'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
status: 403,
|
|
188
|
+
description: 'The current password is incorrect'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
status: 404,
|
|
192
|
+
description: 'The authenticated user no longer exists'
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
status: 405,
|
|
196
|
+
description: 'The account authenticates through an external provider, which holds its password'
|
|
161
197
|
}
|
|
162
198
|
]
|
|
163
199
|
},
|
|
@@ -1071,6 +1107,10 @@ exports.GENERATED_ROUTES = [
|
|
|
1071
1107
|
{
|
|
1072
1108
|
status: 200,
|
|
1073
1109
|
description: 'Ok'
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
status: 401,
|
|
1113
|
+
description: 'No valid access token or session was supplied'
|
|
1074
1114
|
}
|
|
1075
1115
|
]
|
|
1076
1116
|
},
|
|
@@ -1554,6 +1594,18 @@ exports.GENERATED_ROUTES = [
|
|
|
1554
1594
|
{
|
|
1555
1595
|
status: 200,
|
|
1556
1596
|
description: 'Ok'
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
status: 400,
|
|
1600
|
+
description: 'The request body is missing `clientId`, or the CSRF token is missing or invalid'
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
status: 401,
|
|
1604
|
+
description: 'No signed-in session'
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
status: 403,
|
|
1608
|
+
description: 'The client id is unknown'
|
|
1557
1609
|
}
|
|
1558
1610
|
]
|
|
1559
1611
|
},
|
|
@@ -1582,6 +1634,22 @@ exports.GENERATED_ROUTES = [
|
|
|
1582
1634
|
{
|
|
1583
1635
|
status: 200,
|
|
1584
1636
|
description: 'Ok'
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
status: 400,
|
|
1640
|
+
description: 'The request body is missing `username` or `password`'
|
|
1641
|
+
},
|
|
1642
|
+
{
|
|
1643
|
+
status: 401,
|
|
1644
|
+
description: 'The credentials are not valid, or password sign-in is disabled for local accounts'
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
status: 429,
|
|
1648
|
+
description: 'Too many failed attempts for this account'
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
status: 500,
|
|
1652
|
+
description: 'An unexpected error occurred while signing in'
|
|
1585
1653
|
}
|
|
1586
1654
|
]
|
|
1587
1655
|
},
|
|
@@ -1597,6 +1665,10 @@ exports.GENERATED_ROUTES = [
|
|
|
1597
1665
|
{
|
|
1598
1666
|
status: 200,
|
|
1599
1667
|
description: 'Ok'
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
status: 500,
|
|
1671
|
+
description: 'The session store failed to replace the session'
|
|
1600
1672
|
}
|
|
1601
1673
|
]
|
|
1602
1674
|
}
|
|
@@ -36,9 +36,15 @@ const merge = (generated) => {
|
|
|
36
36
|
...(body === undefined ? {} : { body })
|
|
37
37
|
};
|
|
38
38
|
});
|
|
39
|
+
// A host-only status has to be one the document states: the annotation marks
|
|
40
|
+
// where a status is answered, it does not add one.
|
|
41
|
+
for (const status of overlay.hostResponses || [])
|
|
42
|
+
if (!documented.has(status))
|
|
43
|
+
throw new Error(`src/routes/overlay.ts marks status ${status} of ${generated.id} as host-only, and the OpenAPI document does not state it. Correct the document in sasjs/server and re-vendor spec/swagger.yaml, or drop the annotation.`);
|
|
39
44
|
return {
|
|
40
45
|
...generated,
|
|
41
46
|
responses,
|
|
47
|
+
hostResponses: overlay.hostResponses || [],
|
|
42
48
|
capabilities: overlay.capabilities || overlay_js_1.TAG_CAPABILITIES[generated.tag] || [],
|
|
43
49
|
implemented: overlay.implemented === true
|
|
44
50
|
};
|
|
@@ -18,6 +18,13 @@ exports.TAG_CAPABILITIES = {
|
|
|
18
18
|
Web: ['identity']
|
|
19
19
|
};
|
|
20
20
|
const success = { status: 'success' };
|
|
21
|
+
const user = {
|
|
22
|
+
uid: '',
|
|
23
|
+
username: '',
|
|
24
|
+
displayName: '',
|
|
25
|
+
isAdmin: false,
|
|
26
|
+
needsToUpdatePassword: false
|
|
27
|
+
};
|
|
21
28
|
exports.OVERLAY = {
|
|
22
29
|
// the route inventory reports what the CALLER may use, so it needs an identity
|
|
23
30
|
// even though the rest of the Info tag needs nothing
|
|
@@ -69,6 +76,37 @@ exports.OVERLAY = {
|
|
|
69
76
|
},
|
|
70
77
|
'POST /SASjsApi/drive/deploy/upload': {
|
|
71
78
|
implemented: false
|
|
79
|
+
},
|
|
80
|
+
// The lockout and the provider failure are answered before the package is
|
|
81
|
+
// consulted (routes/web/web.ts in sasjs/server).
|
|
82
|
+
'POST /SASLogon/login': {
|
|
83
|
+
implemented: true,
|
|
84
|
+
hostResponses: [429, 500],
|
|
85
|
+
responses: { 200: { loggedIn: true, user } }
|
|
86
|
+
},
|
|
87
|
+
// The sign-in flow. The statuses are the document's; these entries state the
|
|
88
|
+
// body a client keys on, and that this package answers the operation.
|
|
89
|
+
'POST /SASjsApi/auth/token': {
|
|
90
|
+
implemented: true,
|
|
91
|
+
responses: { 200: { accessToken: '', refreshToken: '' } }
|
|
92
|
+
},
|
|
93
|
+
'POST /SASjsApi/auth/refresh': {
|
|
94
|
+
implemented: true,
|
|
95
|
+
responses: { 200: { accessToken: '', refreshToken: '' } }
|
|
96
|
+
},
|
|
97
|
+
'POST /SASjsApi/auth/logout': { implemented: true },
|
|
98
|
+
'POST /SASLogon/authorize': {
|
|
99
|
+
implemented: true,
|
|
100
|
+
responses: { 200: { code: '' } }
|
|
101
|
+
},
|
|
102
|
+
'GET /SASLogon/logout': {
|
|
103
|
+
implemented: true,
|
|
104
|
+
hostResponses: [500],
|
|
105
|
+
responses: { 200: 'OK!' }
|
|
106
|
+
},
|
|
107
|
+
'GET /SASjsApi/session': {
|
|
108
|
+
implemented: true,
|
|
109
|
+
responses: { 200: user }
|
|
72
110
|
}
|
|
73
111
|
};
|
|
74
112
|
/** Every method the table may state, in the order a diff reads best. */
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* records each place the two disagree.
|
|
13
13
|
*/
|
|
14
14
|
import type { ApiRequest } from '../host/dispatch.js';
|
|
15
|
+
import type { IdentityFixture } from '../identity/fixture.js';
|
|
15
16
|
export interface CorpusCase {
|
|
16
17
|
/** Stable name, used as the test's title. */
|
|
17
18
|
id: string;
|
|
@@ -20,6 +21,8 @@ export interface CorpusCase {
|
|
|
20
21
|
description: string;
|
|
21
22
|
/** Files the case starts from. */
|
|
22
23
|
seed?: Record<string, string | Uint8Array>;
|
|
24
|
+
/** The identity the case starts from; the default fixture when omitted. */
|
|
25
|
+
identity?: IdentityFixture;
|
|
23
26
|
request: ApiRequest;
|
|
24
27
|
expect: {
|
|
25
28
|
status: number;
|
|
@@ -27,6 +30,17 @@ export interface CorpusCase {
|
|
|
27
30
|
contents?: string | Uint8Array;
|
|
28
31
|
/** The refusal's message, where the case expects one. */
|
|
29
32
|
error?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The properties a body must carry, where the host mints the rest - a user's
|
|
35
|
+
* `uid` is the host's identifier, so a case pinning one would be pinning a
|
|
36
|
+
* fixture value into the contract.
|
|
37
|
+
*/
|
|
38
|
+
partial?: Record<string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* The properties a body must carry, where the values are the host's - a
|
|
41
|
+
* minted token is not a value a case can state.
|
|
42
|
+
*/
|
|
43
|
+
keys?: string[];
|
|
30
44
|
};
|
|
31
45
|
}
|
|
32
46
|
export declare const DRIVE_CORPUS: CorpusCase[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/corpus/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/corpus/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IAC1C,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;QAC9B,yDAAyD;QACzD,KAAK,CAAC,EAAE,MAAM,CAAA;QACd;;;;WAIG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACjC;;;WAGG;QACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAChB,CAAA;CACF;AAWD,eAAO,MAAM,YAAY,EAAE,UAAU,EA8SpC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-in flow's conformance corpus.
|
|
3
|
+
*
|
|
4
|
+
* A case names an operation, the request a client would make, the identity the
|
|
5
|
+
* host starts from, and the answer the caller expects. Where the answer carries
|
|
6
|
+
* a value the host mints - a token, a code - the case states the properties
|
|
7
|
+
* instead, because the value is the host's and asserting it would pin one host's
|
|
8
|
+
* cryptography into the contract.
|
|
9
|
+
*/
|
|
10
|
+
import type { CorpusCase } from './drive.js';
|
|
11
|
+
import { type IdentityFixture } from '../identity/fixture.js';
|
|
12
|
+
/** The default fixture, plus a caller that is already signed in. */
|
|
13
|
+
export declare const SIGNED_IN: IdentityFixture;
|
|
14
|
+
/** The same identity, with the restricted user switched off. */
|
|
15
|
+
export declare const DEACTIVATED: IdentityFixture;
|
|
16
|
+
export declare const IDENTITY_CORPUS: CorpusCase[];
|
|
17
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../../src/corpus/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE/E,oEAAoE;AACpE,eAAO,MAAM,SAAS,EAAE,eA2BvB,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,WAAW,EAAE,eAKzB,CAAA;AAUD,eAAO,MAAM,eAAe,EAAE,UAAU,EAkOvC,CAAA"}
|