@sasjs/api 0.6.1 → 0.7.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 +3 -2
- package/build/browser.js +2218 -118
- package/build/cjs/corpus/records.js +1222 -0
- package/build/cjs/corpus/runner.js +15 -6
- package/build/cjs/host/dispatch.js +190 -2
- package/build/cjs/identity/memory-store.js +196 -11
- package/build/cjs/identity/records.js +398 -0
- package/build/cjs/index.js +8 -1
- package/build/cjs/routes/generated.js +184 -4
- package/build/cjs/routes/overlay.js +53 -58
- package/build/esm/corpus/records.d.ts +25 -0
- package/build/esm/corpus/records.d.ts.map +1 -0
- package/build/esm/corpus/records.js +1219 -0
- package/build/esm/corpus/runner.d.ts.map +1 -1
- package/build/esm/corpus/runner.js +15 -6
- package/build/esm/host/dispatch.d.ts +9 -0
- package/build/esm/host/dispatch.d.ts.map +1 -1
- package/build/esm/host/dispatch.js +190 -2
- package/build/esm/identity/fixture.d.ts +6 -0
- package/build/esm/identity/fixture.d.ts.map +1 -1
- package/build/esm/identity/memory-store.d.ts +29 -5
- package/build/esm/identity/memory-store.d.ts.map +1 -1
- package/build/esm/identity/memory-store.js +196 -11
- package/build/esm/identity/records.d.ts +102 -0
- package/build/esm/identity/records.d.ts.map +1 -0
- package/build/esm/identity/records.js +393 -0
- package/build/esm/identity/types.d.ts +102 -2
- package/build/esm/identity/types.d.ts.map +1 -1
- package/build/esm/index.d.ts +4 -1
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +2 -0
- package/build/esm/routes/generated.d.ts.map +1 -1
- package/build/esm/routes/generated.js +184 -4
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +53 -58
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { IdentityClientRecord, IdentityStore, Subject } from './types.js';
|
|
2
|
+
/** The body a client reads for a user, as the server's controllers return it. */
|
|
3
|
+
export interface UserResponse {
|
|
4
|
+
uid: string;
|
|
5
|
+
username: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
isAdmin: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface UserDetailsResponse extends UserResponse {
|
|
10
|
+
isActive: boolean;
|
|
11
|
+
needsToUpdatePassword: boolean;
|
|
12
|
+
autoExec?: string;
|
|
13
|
+
groups?: {
|
|
14
|
+
uid: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
export interface GroupResponse {
|
|
20
|
+
uid: string;
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
}
|
|
24
|
+
export interface GroupDetailsResponse extends GroupResponse {
|
|
25
|
+
isActive: boolean;
|
|
26
|
+
users: UserResponse[];
|
|
27
|
+
}
|
|
28
|
+
export interface PermissionResponse {
|
|
29
|
+
uid: string;
|
|
30
|
+
path: string;
|
|
31
|
+
type: string;
|
|
32
|
+
setting: string;
|
|
33
|
+
user?: UserResponse;
|
|
34
|
+
group?: GroupDetailsResponse;
|
|
35
|
+
}
|
|
36
|
+
export interface RecordService {
|
|
37
|
+
listUsers(caller: Subject | undefined): Promise<UserResponse[]>;
|
|
38
|
+
getUser(caller: Subject | undefined, by: {
|
|
39
|
+
uid?: string;
|
|
40
|
+
username?: string;
|
|
41
|
+
}): Promise<UserDetailsResponse>;
|
|
42
|
+
createUser(caller: Subject | undefined, body: {
|
|
43
|
+
username: string;
|
|
44
|
+
displayName: string;
|
|
45
|
+
password: string;
|
|
46
|
+
isAdmin?: boolean;
|
|
47
|
+
isActive?: boolean;
|
|
48
|
+
autoExec?: string;
|
|
49
|
+
}): Promise<UserDetailsResponse>;
|
|
50
|
+
updateUser(caller: Subject | undefined, by: {
|
|
51
|
+
uid?: string;
|
|
52
|
+
username?: string;
|
|
53
|
+
}, body: {
|
|
54
|
+
username?: string;
|
|
55
|
+
displayName?: string;
|
|
56
|
+
password?: string;
|
|
57
|
+
isAdmin?: boolean;
|
|
58
|
+
isActive?: boolean;
|
|
59
|
+
autoExec?: string;
|
|
60
|
+
}): Promise<UserDetailsResponse>;
|
|
61
|
+
deleteUser(caller: Subject | undefined, by: {
|
|
62
|
+
uid?: string;
|
|
63
|
+
username?: string;
|
|
64
|
+
}, body: {
|
|
65
|
+
password?: string;
|
|
66
|
+
}): Promise<void>;
|
|
67
|
+
listGroups(caller: Subject | undefined): Promise<GroupResponse[]>;
|
|
68
|
+
getGroup(caller: Subject | undefined, by: {
|
|
69
|
+
uid?: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
}): Promise<GroupDetailsResponse>;
|
|
72
|
+
createGroup(caller: Subject | undefined, body: {
|
|
73
|
+
name: string;
|
|
74
|
+
description: string;
|
|
75
|
+
isActive?: boolean;
|
|
76
|
+
}): Promise<GroupDetailsResponse>;
|
|
77
|
+
deleteGroup(caller: Subject | undefined, uid: string): Promise<void>;
|
|
78
|
+
addUserToGroup(caller: Subject | undefined, groupUid: string, userUid: string): Promise<GroupDetailsResponse>;
|
|
79
|
+
removeUserFromGroup(caller: Subject | undefined, groupUid: string, userUid: string): Promise<GroupDetailsResponse>;
|
|
80
|
+
listPermissions(caller: Subject | undefined): Promise<PermissionResponse[]>;
|
|
81
|
+
createPermission(caller: Subject | undefined, body: {
|
|
82
|
+
path: string;
|
|
83
|
+
type: string;
|
|
84
|
+
setting: string;
|
|
85
|
+
principalType: string;
|
|
86
|
+
principalId: string;
|
|
87
|
+
}): Promise<PermissionResponse>;
|
|
88
|
+
updatePermission(caller: Subject | undefined, uid: string, body: {
|
|
89
|
+
setting: string;
|
|
90
|
+
}): Promise<PermissionResponse>;
|
|
91
|
+
deletePermission(caller: Subject | undefined, uid: string): Promise<void>;
|
|
92
|
+
listClients(caller: Subject | undefined): Promise<IdentityClientRecord[]>;
|
|
93
|
+
createClient(caller: Subject | undefined, body: IdentityClientRecord): Promise<IdentityClientRecord>;
|
|
94
|
+
/** `PATCH /SASjsApi/auth/updatePassword`, the caller's own password. */
|
|
95
|
+
updatePassword(caller: Subject | undefined, body: {
|
|
96
|
+
currentPassword: string;
|
|
97
|
+
newPassword: string;
|
|
98
|
+
}): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
export declare const sanitiseDisplayName: (value: unknown) => string;
|
|
101
|
+
export declare const createRecordService: (store: IdentityStore) => RecordService;
|
|
102
|
+
//# sourceMappingURL=records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../../src/identity/records.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users, groups, permission rules and clients: the administration routes'
|
|
3
|
+
* decisions, over the same store the sign-in flow uses.
|
|
4
|
+
*
|
|
5
|
+
* The server answers these from four controllers and two middlewares - the
|
|
6
|
+
* statuses, the messages and the order of the checks are copied from there, so a
|
|
7
|
+
* mock and a server refuse a request the same way. What stays with the host is
|
|
8
|
+
* the *transport* (a session or a bearer token, a CSRF token) and the things a
|
|
9
|
+
* host owns: a new user's home folder, a directory bind, an audit trail.
|
|
10
|
+
*
|
|
11
|
+
* The admin gate is here rather than in a caller because `dispatch` has no
|
|
12
|
+
* middleware: `requireAdmin` is `verifyAdmin`, and `requireSelfOrAdmin` is
|
|
13
|
+
* `verifyAdminIfNeeded`. Both answer `401 Admin account required`, which is what
|
|
14
|
+
* the server sends.
|
|
15
|
+
*/
|
|
16
|
+
import { ApiError } from '../types.js';
|
|
17
|
+
/** The name the server gives the group every account is added to on creation. */
|
|
18
|
+
const ALL_USERS_GROUP = 'all-users';
|
|
19
|
+
/** The group whose membership the API refuses to change. */
|
|
20
|
+
const PUBLIC_GROUP = 'public';
|
|
21
|
+
const unauthorized = () => new ApiError(401, 'Unauthorized');
|
|
22
|
+
/**
|
|
23
|
+
* The caller, while the account behind it may still act.
|
|
24
|
+
*
|
|
25
|
+
* Every administration route reads the caller through this: the server
|
|
26
|
+
* re-reads the user on every request and refuses a deactivated account, so a
|
|
27
|
+
* token or session that outlived the account must not administer anything.
|
|
28
|
+
*/
|
|
29
|
+
const requireCaller = async (store, caller) => {
|
|
30
|
+
if (!caller)
|
|
31
|
+
throw unauthorized();
|
|
32
|
+
const user = await store.userById(caller.userId);
|
|
33
|
+
if (!user || !user.isActive)
|
|
34
|
+
throw unauthorized();
|
|
35
|
+
return user;
|
|
36
|
+
};
|
|
37
|
+
/** `verifyAdmin`: the caller is an administrator, or the route refuses. */
|
|
38
|
+
const requireAdmin = async (store, caller) => {
|
|
39
|
+
const user = await requireCaller(store, caller);
|
|
40
|
+
if (!user.isAdmin)
|
|
41
|
+
throw new ApiError(401, 'Admin account required');
|
|
42
|
+
return user;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* `verifyAdminIfNeeded`: an administrator, or the account being acted on.
|
|
46
|
+
*
|
|
47
|
+
* The server compares `req.params.uid` / `.username` with the caller's own
|
|
48
|
+
* identifier, which is why the target is passed in rather than read from the
|
|
49
|
+
* body - a restful route names its subject in the path.
|
|
50
|
+
*/
|
|
51
|
+
const requireSelfOrAdmin = async (store, caller, target) => {
|
|
52
|
+
const user = await requireCaller(store, caller);
|
|
53
|
+
if (!user.isAdmin && user.uid !== target?.uid)
|
|
54
|
+
throw new ApiError(401, 'Admin account required');
|
|
55
|
+
return user;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* A display name, as the server stores one: control characters out, trimmed and
|
|
59
|
+
* bounded.
|
|
60
|
+
*
|
|
61
|
+
* The value reaches generated stored-program source and webout headers, so the
|
|
62
|
+
* server sanitises on the way in; a host that stored it verbatim would hold a
|
|
63
|
+
* different value from the server for the same request.
|
|
64
|
+
*/
|
|
65
|
+
const MAX_DISPLAY_NAME_LENGTH = 100;
|
|
66
|
+
export const sanitiseDisplayName = (value) => String(value ?? '')
|
|
67
|
+
// eslint-disable-next-line no-control-regex
|
|
68
|
+
.replace(/[\u0000-\u001F\u007F]/g, '')
|
|
69
|
+
.trim()
|
|
70
|
+
.slice(0, MAX_DISPLAY_NAME_LENGTH);
|
|
71
|
+
const asUser = (user) => ({
|
|
72
|
+
uid: user.uid,
|
|
73
|
+
username: user.username,
|
|
74
|
+
displayName: user.displayName,
|
|
75
|
+
isAdmin: user.isAdmin
|
|
76
|
+
});
|
|
77
|
+
const asGroup = (group) => ({
|
|
78
|
+
uid: group.uid,
|
|
79
|
+
name: group.name,
|
|
80
|
+
description: group.description
|
|
81
|
+
});
|
|
82
|
+
export const createRecordService = (store) => {
|
|
83
|
+
const groupDetails = async (group) => {
|
|
84
|
+
const members = [];
|
|
85
|
+
for (const uid of group.users) {
|
|
86
|
+
const user = await store.userById(uid);
|
|
87
|
+
if (user)
|
|
88
|
+
members.push(asUser(user));
|
|
89
|
+
}
|
|
90
|
+
return { ...asGroup(group), isActive: group.isActive, users: members };
|
|
91
|
+
};
|
|
92
|
+
const userDetails = async (user, withAutoExec) => {
|
|
93
|
+
const groups = [];
|
|
94
|
+
for (const uid of user.groups) {
|
|
95
|
+
const group = await store.groupById(uid);
|
|
96
|
+
if (group)
|
|
97
|
+
groups.push(asGroup(group));
|
|
98
|
+
}
|
|
99
|
+
const details = {
|
|
100
|
+
...asUser(user),
|
|
101
|
+
isActive: user.isActive,
|
|
102
|
+
needsToUpdatePassword: user.needsToUpdatePassword,
|
|
103
|
+
groups
|
|
104
|
+
};
|
|
105
|
+
if (withAutoExec)
|
|
106
|
+
details.autoExec = user.autoExec ?? '';
|
|
107
|
+
return details;
|
|
108
|
+
};
|
|
109
|
+
const findBy = async (by) => by.uid !== undefined
|
|
110
|
+
? store.userById(by.uid)
|
|
111
|
+
: store.userByName(by.username ?? '');
|
|
112
|
+
const permissionDetails = async (permission) => {
|
|
113
|
+
const response = {
|
|
114
|
+
uid: permission.uid,
|
|
115
|
+
path: permission.path,
|
|
116
|
+
type: permission.type,
|
|
117
|
+
setting: permission.setting
|
|
118
|
+
};
|
|
119
|
+
if (permission.user) {
|
|
120
|
+
const user = await store.userById(permission.user);
|
|
121
|
+
if (user)
|
|
122
|
+
response.user = asUser(user);
|
|
123
|
+
}
|
|
124
|
+
if (permission.group) {
|
|
125
|
+
const group = await store.groupById(permission.group);
|
|
126
|
+
if (group)
|
|
127
|
+
response.group = await groupDetails(group);
|
|
128
|
+
}
|
|
129
|
+
return response;
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
async listUsers(caller) {
|
|
133
|
+
await requireCaller(store, caller);
|
|
134
|
+
const users = await store.users();
|
|
135
|
+
return users.map(asUser);
|
|
136
|
+
},
|
|
137
|
+
async getUser(caller, by) {
|
|
138
|
+
const reader = await requireCaller(store, caller);
|
|
139
|
+
const user = await findBy(by);
|
|
140
|
+
if (!user)
|
|
141
|
+
throw new ApiError(404, 'User is not found.');
|
|
142
|
+
// the server hands the auto-exec code to an administrator or to the user
|
|
143
|
+
// themselves and omits it for anyone else
|
|
144
|
+
const withAutoExec = reader.isAdmin || reader.uid === user.uid;
|
|
145
|
+
return userDetails(user, withAutoExec);
|
|
146
|
+
},
|
|
147
|
+
async createUser(caller, body) {
|
|
148
|
+
await requireAdmin(store, caller);
|
|
149
|
+
if (await store.userByName(body.username))
|
|
150
|
+
throw new ApiError(409, 'Username already exists.');
|
|
151
|
+
const created = await store.createUser({
|
|
152
|
+
username: body.username,
|
|
153
|
+
displayName: sanitiseDisplayName(body.displayName),
|
|
154
|
+
isAdmin: body.isAdmin === true,
|
|
155
|
+
isActive: body.isActive !== false,
|
|
156
|
+
autoExec: body.autoExec
|
|
157
|
+
});
|
|
158
|
+
await store.setPassword(created.uid, body.password);
|
|
159
|
+
// the server adds every new account to the all-users group when the host
|
|
160
|
+
// has one, so a group-based grant reaches it without an explicit add
|
|
161
|
+
const allUsers = await store.groupByName(ALL_USERS_GROUP);
|
|
162
|
+
if (allUsers)
|
|
163
|
+
await store.addToGroup(allUsers.uid, created.uid);
|
|
164
|
+
const stored = await store.userById(created.uid);
|
|
165
|
+
return userDetails(stored ?? created, true);
|
|
166
|
+
},
|
|
167
|
+
async updateUser(caller, by, body) {
|
|
168
|
+
const current = await findBy(by);
|
|
169
|
+
await requireSelfOrAdmin(store, caller, current);
|
|
170
|
+
const change = {};
|
|
171
|
+
const displayName = sanitiseDisplayName(body.displayName);
|
|
172
|
+
// the provider checks come before the uniqueness check and before the
|
|
173
|
+
// update, in the server's order: a provider-managed account cannot have
|
|
174
|
+
// its identity fields changed, and saying so does not depend on the new
|
|
175
|
+
// value being free
|
|
176
|
+
if (body.username &&
|
|
177
|
+
body.username !== current?.username &&
|
|
178
|
+
current?.authProvider)
|
|
179
|
+
throw new ApiError(405, 'Can not update username of user that is created by an external auth provider.');
|
|
180
|
+
if (displayName &&
|
|
181
|
+
displayName !== current?.displayName &&
|
|
182
|
+
current?.authProvider)
|
|
183
|
+
throw new ApiError(405, 'Can not update display name of user that is created by an external auth provider.');
|
|
184
|
+
if (body.username) {
|
|
185
|
+
const holder = await store.userByName(body.username);
|
|
186
|
+
if (holder && holder.uid !== current?.uid)
|
|
187
|
+
throw new ApiError(409, 'Username already exists.');
|
|
188
|
+
change.username = body.username;
|
|
189
|
+
}
|
|
190
|
+
if (displayName)
|
|
191
|
+
change.displayName = displayName;
|
|
192
|
+
if (body.isAdmin !== undefined)
|
|
193
|
+
change.isAdmin = body.isAdmin;
|
|
194
|
+
if (body.isActive !== undefined)
|
|
195
|
+
change.isActive = body.isActive;
|
|
196
|
+
if (body.autoExec !== undefined)
|
|
197
|
+
change.autoExec = body.autoExec;
|
|
198
|
+
const updated = current
|
|
199
|
+
? await store.updateUser(current.uid, change)
|
|
200
|
+
: undefined;
|
|
201
|
+
if (!updated)
|
|
202
|
+
throw new ApiError(404, `Unable to find user with ${by.uid || by.username}`);
|
|
203
|
+
if (body.password)
|
|
204
|
+
await store.setPassword(updated.uid, body.password);
|
|
205
|
+
const stored = await store.userById(updated.uid);
|
|
206
|
+
return userDetails(stored ?? updated, true);
|
|
207
|
+
},
|
|
208
|
+
async deleteUser(caller, by, body) {
|
|
209
|
+
const current = await findBy(by);
|
|
210
|
+
const actor = await requireSelfOrAdmin(store, caller, current);
|
|
211
|
+
if (!current)
|
|
212
|
+
throw new ApiError(404, 'User is not found.');
|
|
213
|
+
// an administrator deletes without proving the password; anyone else
|
|
214
|
+
// deleting their own account proves it. The caller's admin flag is the
|
|
215
|
+
// server's `@Hidden() isAdmin` query parameter, resolved here from the
|
|
216
|
+
// caller's own record rather than trusted from the request.
|
|
217
|
+
if (!actor.isAdmin) {
|
|
218
|
+
if (!(await store.passwordMatches(current, body.password ?? '')))
|
|
219
|
+
throw new ApiError(401, 'Invalid password.');
|
|
220
|
+
}
|
|
221
|
+
await store.deleteUser(current.uid);
|
|
222
|
+
},
|
|
223
|
+
async listGroups(caller) {
|
|
224
|
+
await requireCaller(store, caller);
|
|
225
|
+
const groups = await store.groups();
|
|
226
|
+
return groups.map(asGroup);
|
|
227
|
+
},
|
|
228
|
+
async getGroup(caller, by) {
|
|
229
|
+
await requireCaller(store, caller);
|
|
230
|
+
const group = by.uid !== undefined
|
|
231
|
+
? await store.groupById(by.uid)
|
|
232
|
+
: await store.groupByName(by.name ?? '');
|
|
233
|
+
if (!group)
|
|
234
|
+
throw new ApiError(404, 'Group not found.');
|
|
235
|
+
return groupDetails(group);
|
|
236
|
+
},
|
|
237
|
+
async createGroup(caller, body) {
|
|
238
|
+
await requireAdmin(store, caller);
|
|
239
|
+
if (await store.groupByName(body.name))
|
|
240
|
+
throw new ApiError(409, 'Group name already exists.');
|
|
241
|
+
const created = await store.createGroup({
|
|
242
|
+
name: body.name,
|
|
243
|
+
description: body.description,
|
|
244
|
+
isActive: body.isActive !== false
|
|
245
|
+
});
|
|
246
|
+
return groupDetails(created);
|
|
247
|
+
},
|
|
248
|
+
async deleteGroup(caller, uid) {
|
|
249
|
+
await requireAdmin(store, caller);
|
|
250
|
+
if (!(await store.groupById(uid)))
|
|
251
|
+
throw new ApiError(404, 'Group not found.');
|
|
252
|
+
await store.deleteGroup(uid);
|
|
253
|
+
},
|
|
254
|
+
async addUserToGroup(caller, groupUid, userUid) {
|
|
255
|
+
await requireAdmin(store, caller);
|
|
256
|
+
const group = await store.groupById(groupUid);
|
|
257
|
+
if (!group)
|
|
258
|
+
throw new ApiError(404, 'Group not found.');
|
|
259
|
+
if (group.name === PUBLIC_GROUP)
|
|
260
|
+
throw new ApiError(400, `Can't add/remove user to '${PUBLIC_GROUP}' group.`);
|
|
261
|
+
if (group.authProvider)
|
|
262
|
+
throw new ApiError(405, "Can't add/remove user to group created by external auth provider.");
|
|
263
|
+
const user = await store.userById(userUid);
|
|
264
|
+
if (!user)
|
|
265
|
+
throw new ApiError(404, 'User not found.');
|
|
266
|
+
if (user.authProvider)
|
|
267
|
+
throw new ApiError(405, "Can't add/remove user to group created by external auth provider.");
|
|
268
|
+
const updated = await store.addToGroup(groupUid, userUid);
|
|
269
|
+
if (!updated)
|
|
270
|
+
throw new ApiError(400, 'Unable to update group.');
|
|
271
|
+
return groupDetails(updated);
|
|
272
|
+
},
|
|
273
|
+
async removeUserFromGroup(caller, groupUid, userUid) {
|
|
274
|
+
await requireAdmin(store, caller);
|
|
275
|
+
const group = await store.groupById(groupUid);
|
|
276
|
+
if (!group)
|
|
277
|
+
throw new ApiError(404, 'Group not found.');
|
|
278
|
+
if (group.name === PUBLIC_GROUP)
|
|
279
|
+
throw new ApiError(400, `Can't add/remove user to '${PUBLIC_GROUP}' group.`);
|
|
280
|
+
if (group.authProvider)
|
|
281
|
+
throw new ApiError(405, "Can't add/remove user to group created by external auth provider.");
|
|
282
|
+
const user = await store.userById(userUid);
|
|
283
|
+
if (!user)
|
|
284
|
+
throw new ApiError(404, 'User not found.');
|
|
285
|
+
if (user.authProvider)
|
|
286
|
+
throw new ApiError(405, "Can't add/remove user to group created by external auth provider.");
|
|
287
|
+
const updated = await store.removeFromGroup(groupUid, userUid);
|
|
288
|
+
if (!updated)
|
|
289
|
+
throw new ApiError(400, 'Unable to update group.');
|
|
290
|
+
return groupDetails(updated);
|
|
291
|
+
},
|
|
292
|
+
async listPermissions(caller) {
|
|
293
|
+
const reader = await requireCaller(store, caller);
|
|
294
|
+
const rules = await store.permissions();
|
|
295
|
+
// an administrator sees every rule; anyone else sees the rules assigned to
|
|
296
|
+
// them and to the groups they belong to, which is what an app renders its
|
|
297
|
+
// menus from
|
|
298
|
+
const visible = reader.isAdmin
|
|
299
|
+
? rules
|
|
300
|
+
: rules.filter((rule) => rule.user === reader.uid ||
|
|
301
|
+
(rule.group !== undefined && reader.groups.includes(rule.group)));
|
|
302
|
+
const out = [];
|
|
303
|
+
for (const rule of visible)
|
|
304
|
+
out.push(await permissionDetails(rule));
|
|
305
|
+
return out;
|
|
306
|
+
},
|
|
307
|
+
async createPermission(caller, body) {
|
|
308
|
+
await requireAdmin(store, caller);
|
|
309
|
+
if (body.principalType === 'user') {
|
|
310
|
+
const user = await store.userById(body.principalId);
|
|
311
|
+
if (!user)
|
|
312
|
+
throw new ApiError(404, 'User not found.');
|
|
313
|
+
if (user.isAdmin)
|
|
314
|
+
throw new ApiError(400, 'Can not add permission for admin user.');
|
|
315
|
+
const exists = (await store.permissions()).some((rule) => rule.path === body.path &&
|
|
316
|
+
rule.type === body.type &&
|
|
317
|
+
rule.user === body.principalId);
|
|
318
|
+
if (exists)
|
|
319
|
+
throw new ApiError(409, 'Permission already exists with provided Path, Type and User.');
|
|
320
|
+
const created = await store.createPermission({
|
|
321
|
+
path: body.path,
|
|
322
|
+
type: body.type,
|
|
323
|
+
setting: body.setting,
|
|
324
|
+
user: body.principalId
|
|
325
|
+
});
|
|
326
|
+
return permissionDetails(created);
|
|
327
|
+
}
|
|
328
|
+
if (body.principalType === 'group') {
|
|
329
|
+
const group = await store.groupById(body.principalId);
|
|
330
|
+
if (!group)
|
|
331
|
+
throw new ApiError(404, 'Group not found.');
|
|
332
|
+
const exists = (await store.permissions()).some((rule) => rule.path === body.path &&
|
|
333
|
+
rule.type === body.type &&
|
|
334
|
+
rule.group === body.principalId);
|
|
335
|
+
if (exists)
|
|
336
|
+
throw new ApiError(409, 'Permission already exists with provided Path, Type and Group.');
|
|
337
|
+
const created = await store.createPermission({
|
|
338
|
+
path: body.path,
|
|
339
|
+
type: body.type,
|
|
340
|
+
setting: body.setting,
|
|
341
|
+
group: body.principalId
|
|
342
|
+
});
|
|
343
|
+
return permissionDetails(created);
|
|
344
|
+
}
|
|
345
|
+
throw new ApiError(400, 'Invalid principal type. Valid types are user or group.');
|
|
346
|
+
},
|
|
347
|
+
async updatePermission(caller, uid, body) {
|
|
348
|
+
await requireAdmin(store, caller);
|
|
349
|
+
const updated = await store.setPermissionSetting(uid, body.setting);
|
|
350
|
+
if (!updated)
|
|
351
|
+
throw new ApiError(404, 'Permission not found.');
|
|
352
|
+
return permissionDetails(updated);
|
|
353
|
+
},
|
|
354
|
+
async deletePermission(caller, uid) {
|
|
355
|
+
await requireAdmin(store, caller);
|
|
356
|
+
if (!(await store.permissionById(uid)))
|
|
357
|
+
throw new ApiError(404, 'Permission not found.');
|
|
358
|
+
await store.deletePermission(uid);
|
|
359
|
+
},
|
|
360
|
+
async listClients(caller) {
|
|
361
|
+
await requireAdmin(store, caller);
|
|
362
|
+
return store.clients();
|
|
363
|
+
},
|
|
364
|
+
async createClient(caller, body) {
|
|
365
|
+
await requireAdmin(store, caller);
|
|
366
|
+
// the server answers `res.status(403).send(err.toString())`, so the body a
|
|
367
|
+
// client receives carries the Error prefix
|
|
368
|
+
if (await store.client(body.clientId))
|
|
369
|
+
throw new ApiError(403, 'Error: Client ID already exists.');
|
|
370
|
+
return store.createClient({
|
|
371
|
+
clientId: body.clientId,
|
|
372
|
+
clientSecret: body.clientSecret,
|
|
373
|
+
accessTokenExpiration: body.accessTokenExpiration,
|
|
374
|
+
refreshTokenExpiration: body.refreshTokenExpiration
|
|
375
|
+
});
|
|
376
|
+
},
|
|
377
|
+
async updatePassword(caller, body) {
|
|
378
|
+
// requireCaller answers 401 for a caller that is absent or deactivated, so
|
|
379
|
+
// the document's 404 (a caller whose record has gone) cannot be reached
|
|
380
|
+
// here - the overlay records that as a host-only status.
|
|
381
|
+
const reader = await requireCaller(store, caller);
|
|
382
|
+
if (reader.authProvider)
|
|
383
|
+
throw new ApiError(405, 'Can not update password of user that is created by an external auth provider.');
|
|
384
|
+
if (!(await store.passwordMatches(reader, body.currentPassword)))
|
|
385
|
+
throw new ApiError(403, 'Invalid current password!');
|
|
386
|
+
await store.setPassword(reader.uid, body.newPassword);
|
|
387
|
+
// the flag is what makes the web interface ask, and the server clears it in
|
|
388
|
+
// the same write: it has to go through the store, or a host keeps refusing
|
|
389
|
+
// every call the caller makes after the change
|
|
390
|
+
await store.updateUser(reader.uid, { needsToUpdatePassword: false });
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
};
|
|
@@ -42,8 +42,8 @@ export interface TokenPair {
|
|
|
42
42
|
* verification, which is never what anyone means.
|
|
43
43
|
*/
|
|
44
44
|
export interface IdentityStore {
|
|
45
|
-
userByName(username: string): Promise<
|
|
46
|
-
userById(uid: string): Promise<
|
|
45
|
+
userByName(username: string): Promise<IdentityUserRecord | undefined>;
|
|
46
|
+
userById(uid: string): Promise<IdentityUserRecord | undefined>;
|
|
47
47
|
/**
|
|
48
48
|
* Whether the password matches. The host owns the check: a bcrypt hash, a
|
|
49
49
|
* directory bind, or a fixture's declared demo password.
|
|
@@ -75,5 +75,105 @@ export interface IdentityStore {
|
|
|
75
75
|
* which is what makes an exchange single-use even when two requests race.
|
|
76
76
|
*/
|
|
77
77
|
claimCode(code: string): Promise<boolean>;
|
|
78
|
+
/** Every user, in the order the host holds them. */
|
|
79
|
+
users(): Promise<IdentityUserRecord[]>;
|
|
80
|
+
/** Creates a user. The caller has already refused a duplicate username. */
|
|
81
|
+
createUser(input: NewUser): Promise<IdentityUserRecord>;
|
|
82
|
+
/** Applies a change to the user named by `uid`; undefined when there is none. */
|
|
83
|
+
updateUser(uid: string, change: UserChange): Promise<IdentityUserRecord | undefined>;
|
|
84
|
+
deleteUser(uid: string): Promise<void>;
|
|
85
|
+
/** Sets the password the host checks against, in the host's own encoding. */
|
|
86
|
+
setPassword(uid: string, password: string): Promise<void>;
|
|
87
|
+
groups(): Promise<IdentityGroupRecord[]>;
|
|
88
|
+
groupById(uid: string): Promise<IdentityGroupRecord | undefined>;
|
|
89
|
+
groupByName(name: string): Promise<IdentityGroupRecord | undefined>;
|
|
90
|
+
createGroup(input: NewGroup): Promise<IdentityGroupRecord>;
|
|
91
|
+
deleteGroup(uid: string): Promise<void>;
|
|
92
|
+
/** Membership, answering the updated group; undefined when there is none. */
|
|
93
|
+
addToGroup(groupUid: string, userUid: string): Promise<IdentityGroupRecord | undefined>;
|
|
94
|
+
removeFromGroup(groupUid: string, userUid: string): Promise<IdentityGroupRecord | undefined>;
|
|
95
|
+
permissions(): Promise<IdentityPermissionRecord[]>;
|
|
96
|
+
permissionById(uid: string): Promise<IdentityPermissionRecord | undefined>;
|
|
97
|
+
createPermission(input: NewPermission): Promise<IdentityPermissionRecord>;
|
|
98
|
+
setPermissionSetting(uid: string, setting: string): Promise<IdentityPermissionRecord | undefined>;
|
|
99
|
+
deletePermission(uid: string): Promise<void>;
|
|
100
|
+
clients(): Promise<IdentityClientRecord[]>;
|
|
101
|
+
createClient(input: IdentityClientRecord): Promise<IdentityClientRecord>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A user as the administration routes report one: the fields the sign-in flow
|
|
105
|
+
* reads, plus what those routes expose.
|
|
106
|
+
*
|
|
107
|
+
* `autoExec` and `authProvider` are optional because a host that does not hold
|
|
108
|
+
* them (the browser build's fixture, a directory-backed server) answers without
|
|
109
|
+
* them, and `groups` is empty rather than absent so a client always gets a list.
|
|
110
|
+
*/
|
|
111
|
+
export interface IdentityUserRecord extends IdentityUser {
|
|
112
|
+
autoExec?: string;
|
|
113
|
+
/** Set when the account is managed by an external auth provider. */
|
|
114
|
+
authProvider?: string;
|
|
115
|
+
/** The uids of the groups the user belongs to. */
|
|
116
|
+
groups: string[];
|
|
117
|
+
}
|
|
118
|
+
export interface IdentityGroupRecord {
|
|
119
|
+
uid: string;
|
|
120
|
+
name: string;
|
|
121
|
+
description: string;
|
|
122
|
+
isActive: boolean;
|
|
123
|
+
/** The uids of the members. */
|
|
124
|
+
users: string[];
|
|
125
|
+
/** Set when the group is managed by an external auth provider. */
|
|
126
|
+
authProvider?: string;
|
|
127
|
+
}
|
|
128
|
+
export interface IdentityPermissionRecord {
|
|
129
|
+
uid: string;
|
|
130
|
+
/** The route the rule is about, e.g. `/SASjsApi/code/execute`. */
|
|
131
|
+
path: string;
|
|
132
|
+
/** `Route` is the only kind the API defines. */
|
|
133
|
+
type: string;
|
|
134
|
+
/** `Grant` or `Deny`. */
|
|
135
|
+
setting: string;
|
|
136
|
+
/** The uid the rule is assigned to, when it is a user's. */
|
|
137
|
+
user?: string;
|
|
138
|
+
/** The uid the rule is assigned to, when it is a group's. */
|
|
139
|
+
group?: string;
|
|
140
|
+
}
|
|
141
|
+
/** A client as the administration routes report one: the flow's fields plus the secret. */
|
|
142
|
+
export interface IdentityClientRecord extends IdentityClient {
|
|
143
|
+
clientSecret: string;
|
|
144
|
+
}
|
|
145
|
+
/** What creating a user takes. The password reaches `setPassword`, not this. */
|
|
146
|
+
export interface NewUser {
|
|
147
|
+
username: string;
|
|
148
|
+
displayName: string;
|
|
149
|
+
isAdmin: boolean;
|
|
150
|
+
isActive: boolean;
|
|
151
|
+
autoExec?: string;
|
|
152
|
+
authProvider?: string;
|
|
153
|
+
}
|
|
154
|
+
/** The fields an update may carry; omitted fields are left alone. */
|
|
155
|
+
export interface UserChange {
|
|
156
|
+
username?: string;
|
|
157
|
+
displayName?: string;
|
|
158
|
+
isAdmin?: boolean;
|
|
159
|
+
isActive?: boolean;
|
|
160
|
+
autoExec?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Cleared by a password change: `sasjs/server` refuses every other API call
|
|
163
|
+
* while this is set, so a host must be able to clear it through the port.
|
|
164
|
+
*/
|
|
165
|
+
needsToUpdatePassword?: boolean;
|
|
166
|
+
}
|
|
167
|
+
export interface NewGroup {
|
|
168
|
+
name: string;
|
|
169
|
+
description: string;
|
|
170
|
+
isActive: boolean;
|
|
171
|
+
}
|
|
172
|
+
export interface NewPermission {
|
|
173
|
+
path: string;
|
|
174
|
+
type: string;
|
|
175
|
+
setting: string;
|
|
176
|
+
user?: string;
|
|
177
|
+
group?: string;
|
|
78
178
|
}
|
|
79
179
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/identity/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,kEAAkE;IAClE,qBAAqB,EAAE,MAAM,CAAA;IAC7B,qCAAqC;IACrC,sBAAsB,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/identity/types.ts"],"names":[],"mappings":"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"}
|
package/build/esm/index.d.ts
CHANGED
|
@@ -27,13 +27,16 @@ export type { RouteOverlay } from './routes/overlay.js';
|
|
|
27
27
|
export { dispatch, HANDLED_ROUTES } from './host/dispatch.js';
|
|
28
28
|
export type { ApiRequest, ApiResponse, Services } from './host/dispatch.js';
|
|
29
29
|
export { createIdentityService } from './identity/service.js';
|
|
30
|
+
export { createRecordService, sanitiseDisplayName } from './identity/records.js';
|
|
31
|
+
export type { GroupDetailsResponse, GroupResponse, PermissionResponse, RecordService, UserDetailsResponse, UserResponse } from './identity/records.js';
|
|
30
32
|
export type { IdentityService, LoginResult, SessionResult } from './identity/service.js';
|
|
31
33
|
export { MemoryIdentityStore } from './identity/memory-store.js';
|
|
32
34
|
export { DEFAULT_IDENTITY, IDENTITY_FORMAT, parseIdentity } from './identity/fixture.js';
|
|
33
35
|
export type { IdentityFixture, IdentityFixtureClient, IdentityFixtureCode, IdentityFixtureGroup, IdentityFixturePermission, IdentityFixtureToken, IdentityFixtureUser } from './identity/fixture.js';
|
|
34
|
-
export type { IdentityClient, IdentityStore, IdentityUser, Subject, TokenPair } from './identity/types.js';
|
|
36
|
+
export type { IdentityClient, IdentityClientRecord, IdentityGroupRecord, IdentityPermissionRecord, IdentityStore, IdentityUser, IdentityUserRecord, NewGroup, NewPermission, NewUser, Subject, TokenPair, UserChange } from './identity/types.js';
|
|
35
37
|
export { DRIVE_CORPUS } from './corpus/drive.js';
|
|
36
38
|
export { IDENTITY_CORPUS } from './corpus/identity.js';
|
|
39
|
+
export { RECORD_CORPUS, ADMIN_SIGNED_IN, PROVIDER_MANAGED } from './corpus/records.js';
|
|
37
40
|
export type { CorpusCase } from './corpus/drive.js';
|
|
38
41
|
export { runCase, servicesFor } from './corpus/runner.js';
|
|
39
42
|
export type { CaseResult } from './corpus/runner.js';
|
package/build/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAC7D,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAC7D,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAChF,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACb,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,OAAO,EACP,OAAO,EACP,SAAS,EACT,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EACL,aAAa,EACb,eAAe,EACf,gBAAgB,EACjB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA"}
|