@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.
Files changed (36) hide show
  1. package/README.md +3 -2
  2. package/build/browser.js +2218 -118
  3. package/build/cjs/corpus/records.js +1222 -0
  4. package/build/cjs/corpus/runner.js +15 -6
  5. package/build/cjs/host/dispatch.js +190 -2
  6. package/build/cjs/identity/memory-store.js +196 -11
  7. package/build/cjs/identity/records.js +398 -0
  8. package/build/cjs/index.js +8 -1
  9. package/build/cjs/routes/generated.js +184 -4
  10. package/build/cjs/routes/overlay.js +53 -58
  11. package/build/esm/corpus/records.d.ts +25 -0
  12. package/build/esm/corpus/records.d.ts.map +1 -0
  13. package/build/esm/corpus/records.js +1219 -0
  14. package/build/esm/corpus/runner.d.ts.map +1 -1
  15. package/build/esm/corpus/runner.js +15 -6
  16. package/build/esm/host/dispatch.d.ts +9 -0
  17. package/build/esm/host/dispatch.d.ts.map +1 -1
  18. package/build/esm/host/dispatch.js +190 -2
  19. package/build/esm/identity/fixture.d.ts +6 -0
  20. package/build/esm/identity/fixture.d.ts.map +1 -1
  21. package/build/esm/identity/memory-store.d.ts +29 -5
  22. package/build/esm/identity/memory-store.d.ts.map +1 -1
  23. package/build/esm/identity/memory-store.js +196 -11
  24. package/build/esm/identity/records.d.ts +102 -0
  25. package/build/esm/identity/records.d.ts.map +1 -0
  26. package/build/esm/identity/records.js +393 -0
  27. package/build/esm/identity/types.d.ts +102 -2
  28. package/build/esm/identity/types.d.ts.map +1 -1
  29. package/build/esm/index.d.ts +4 -1
  30. package/build/esm/index.d.ts.map +1 -1
  31. package/build/esm/index.js +2 -0
  32. package/build/esm/routes/generated.d.ts.map +1 -1
  33. package/build/esm/routes/generated.js +184 -4
  34. package/build/esm/routes/overlay.d.ts.map +1 -1
  35. package/build/esm/routes/overlay.js +53 -58
  36. 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<IdentityUser | undefined>;
46
- userById(uid: string): Promise<IdentityUser | undefined>;
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,YAAY,GAAG,SAAS,CAAC,CAAA;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAA;IACxD;;;OAGG;IACH,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvE,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAA;IAC7D,+DAA+D;IAC/D,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACnE,qFAAqF;IACrF,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IACvD,6EAA6E;IAC7E,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IACxD,sEAAsE;IACtE,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;IACzD,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,0DAA0D;IAC1D,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IACpD;;;;OAIG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC1C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/identity/types.ts"],"names":[],"mappings":"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"}
@@ -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';
@@ -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,EACV,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,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"}
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"}