@sasjs/api 0.6.0 → 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 (40) hide show
  1. package/README.md +3 -2
  2. package/build/browser.js +2231 -118
  3. package/build/cjs/corpus/records.js +1222 -0
  4. package/build/cjs/corpus/runner.js +15 -6
  5. package/build/cjs/drive/deploy.js +49 -10
  6. package/build/cjs/host/dispatch.js +190 -2
  7. package/build/cjs/identity/memory-store.js +196 -11
  8. package/build/cjs/identity/records.js +398 -0
  9. package/build/cjs/index.js +8 -1
  10. package/build/cjs/routes/generated.js +184 -4
  11. package/build/cjs/routes/overlay.js +53 -58
  12. package/build/esm/corpus/records.d.ts +25 -0
  13. package/build/esm/corpus/records.d.ts.map +1 -0
  14. package/build/esm/corpus/records.js +1219 -0
  15. package/build/esm/corpus/runner.d.ts.map +1 -1
  16. package/build/esm/corpus/runner.js +15 -6
  17. package/build/esm/drive/deploy.d.ts +10 -0
  18. package/build/esm/drive/deploy.d.ts.map +1 -1
  19. package/build/esm/drive/deploy.js +48 -9
  20. package/build/esm/host/dispatch.d.ts +9 -0
  21. package/build/esm/host/dispatch.d.ts.map +1 -1
  22. package/build/esm/host/dispatch.js +190 -2
  23. package/build/esm/identity/fixture.d.ts +6 -0
  24. package/build/esm/identity/fixture.d.ts.map +1 -1
  25. package/build/esm/identity/memory-store.d.ts +29 -5
  26. package/build/esm/identity/memory-store.d.ts.map +1 -1
  27. package/build/esm/identity/memory-store.js +196 -11
  28. package/build/esm/identity/records.d.ts +102 -0
  29. package/build/esm/identity/records.d.ts.map +1 -0
  30. package/build/esm/identity/records.js +393 -0
  31. package/build/esm/identity/types.d.ts +102 -2
  32. package/build/esm/identity/types.d.ts.map +1 -1
  33. package/build/esm/index.d.ts +4 -1
  34. package/build/esm/index.d.ts.map +1 -1
  35. package/build/esm/index.js +2 -0
  36. package/build/esm/routes/generated.d.ts.map +1 -1
  37. package/build/esm/routes/generated.js +184 -4
  38. package/build/esm/routes/overlay.d.ts.map +1 -1
  39. package/build/esm/routes/overlay.js +53 -58
  40. package/package.json +1 -1
@@ -39,9 +39,12 @@ const live = (issued, now) => issued && (issued.expiresAt === undefined || issue
39
39
  */
40
40
  export class MemoryIdentityStore {
41
41
  constructor(fixture = DEFAULT_IDENTITY) {
42
- this.users = [];
42
+ this.userList = [];
43
43
  this.passwords = new Map();
44
- this.clients = new Map();
44
+ this.groupList = new Map();
45
+ this.permissionList = new Map();
46
+ this.clientRecords = new Map();
47
+ this.clientMap = new Map();
45
48
  this.pairs = new Map();
46
49
  this.access = new Map();
47
50
  this.refresh = new Map();
@@ -50,24 +53,60 @@ export class MemoryIdentityStore {
50
53
  if (fixture.format !== IDENTITY_FORMAT)
51
54
  throw new ApiError(500, `The identity fixture states format ${String(fixture.format)}; this store reads ${IDENTITY_FORMAT}.`);
52
55
  for (const user of fixture.users) {
53
- this.users.push({
56
+ this.userList.push({
54
57
  uid: user.uid,
55
58
  username: user.username,
56
59
  displayName: user.displayName,
57
60
  isAdmin: user.isAdmin,
58
61
  isActive: user.isActive,
59
- needsToUpdatePassword: user.needsToUpdatePassword
62
+ needsToUpdatePassword: user.needsToUpdatePassword,
63
+ authProvider: user.authProvider,
64
+ autoExec: user.autoExec
60
65
  });
61
66
  this.passwords.set(user.uid, user.password);
62
67
  }
63
- for (const client of fixture.clients)
64
- this.clients.set(client.clientId, {
68
+ for (const client of fixture.clients) {
69
+ this.clientMap.set(client.clientId, {
65
70
  clientId: client.clientId,
66
71
  accessTokenExpiration: client.accessTokenExpiration,
67
72
  refreshTokenExpiration: client.refreshTokenExpiration
68
73
  });
74
+ this.clientRecords.set(client.clientId, {
75
+ clientId: client.clientId,
76
+ clientSecret: client.clientSecret,
77
+ accessTokenExpiration: client.accessTokenExpiration,
78
+ refreshTokenExpiration: client.refreshTokenExpiration
79
+ });
80
+ }
81
+ for (const group of fixture.groups) {
82
+ const uid = group.uid;
83
+ this.groupList.set(uid, {
84
+ uid,
85
+ name: group.name,
86
+ description: group.description,
87
+ isActive: group.isActive,
88
+ authProvider: group.authProvider,
89
+ users: group.users
90
+ .map((username) => this.userList.find((user) => user.username === username)?.uid)
91
+ .filter((member) => member !== undefined)
92
+ });
93
+ }
94
+ for (const permission of fixture.permissions) {
95
+ this.permissionList.set(permission.uid, {
96
+ uid: permission.uid,
97
+ path: permission.path,
98
+ type: permission.type,
99
+ setting: permission.setting,
100
+ user: permission.user
101
+ ? this.userList.find((user) => user.uid === permission.user)?.uid
102
+ : undefined,
103
+ group: permission.group
104
+ ? this.groupList.get(permission.group)?.uid
105
+ : undefined
106
+ });
107
+ }
69
108
  for (const token of fixture.tokens || []) {
70
- const user = this.users.find((candidate) => candidate.username === token.username);
109
+ const user = this.userList.find((candidate) => candidate.username === token.username);
71
110
  if (!user)
72
111
  continue;
73
112
  const subject = { userId: user.uid, clientId: token.clientId };
@@ -84,7 +123,7 @@ export class MemoryIdentityStore {
84
123
  });
85
124
  }
86
125
  for (const code of fixture.codes || []) {
87
- const user = this.users.find((candidate) => candidate.username === code.username);
126
+ const user = this.userList.find((candidate) => candidate.username === code.username);
88
127
  if (!user)
89
128
  continue;
90
129
  this.codes.set(code.code, {
@@ -93,17 +132,19 @@ export class MemoryIdentityStore {
93
132
  }
94
133
  }
95
134
  async userByName(username) {
96
- return this.users.find((user) => user.username === username);
135
+ const user = this.userList.find((candidate) => candidate.username === username);
136
+ return user ? this.recordOf(user) : undefined;
97
137
  }
98
138
  async userById(uid) {
99
- return this.users.find((user) => user.uid === uid);
139
+ const user = this.userList.find((candidate) => candidate.uid === uid);
140
+ return user ? this.recordOf(user) : undefined;
100
141
  }
101
142
  /** A fixture holds the password in clear, so the check is an equality. */
102
143
  async passwordMatches(user, password) {
103
144
  return this.passwords.get(user.uid) === password;
104
145
  }
105
146
  async client(clientId) {
106
- return this.clients.get(clientId);
147
+ return this.clientMap.get(clientId);
107
148
  }
108
149
  async issue(subject, client) {
109
150
  // The lifetimes are the client's, recorded against the instant of issue: a
@@ -188,4 +229,148 @@ export class MemoryIdentityStore {
188
229
  this.claimed.add(code);
189
230
  return true;
190
231
  }
232
+ // ---------------------------------------------------------------- records
233
+ async users() {
234
+ return this.userList.map((user) => this.recordOf(user));
235
+ }
236
+ /** A user as the administration routes report one: groups derived, not stored. */
237
+ recordOf(user) {
238
+ return {
239
+ ...user,
240
+ groups: [...this.groupList.values()]
241
+ .filter((group) => group.users.includes(user.uid))
242
+ .map((group) => group.uid)
243
+ };
244
+ }
245
+ async createUser(input) {
246
+ const user = {
247
+ uid: `user-${randomHex(6)}`,
248
+ username: input.username,
249
+ displayName: input.displayName,
250
+ isAdmin: input.isAdmin,
251
+ isActive: input.isActive,
252
+ needsToUpdatePassword: false,
253
+ autoExec: input.autoExec,
254
+ authProvider: input.authProvider
255
+ };
256
+ this.userList.push(user);
257
+ return this.recordOf(user);
258
+ }
259
+ async updateUser(uid, change) {
260
+ const user = this.userList.find((candidate) => candidate.uid === uid);
261
+ if (!user)
262
+ return undefined;
263
+ if (change.username !== undefined)
264
+ user.username = change.username;
265
+ if (change.displayName !== undefined)
266
+ user.displayName = change.displayName;
267
+ if (change.isAdmin !== undefined)
268
+ user.isAdmin = change.isAdmin;
269
+ if (change.isActive !== undefined)
270
+ user.isActive = change.isActive;
271
+ if (change.needsToUpdatePassword !== undefined)
272
+ user.needsToUpdatePassword = change.needsToUpdatePassword;
273
+ if (change.autoExec !== undefined)
274
+ user.autoExec = change.autoExec;
275
+ return this.recordOf(user);
276
+ }
277
+ async deleteUser(uid) {
278
+ const index = this.userList.findIndex((user) => user.uid === uid);
279
+ if (index === -1)
280
+ return;
281
+ this.userList.splice(index, 1);
282
+ this.passwords.delete(uid);
283
+ for (const group of this.groupList.values())
284
+ group.users = group.users.filter((member) => member !== uid);
285
+ for (const [key, permission] of this.permissionList)
286
+ if (permission.user === uid)
287
+ this.permissionList.delete(key);
288
+ for (const key of this.pairs.keys())
289
+ if (key.startsWith(`${uid}|`))
290
+ this.pairs.delete(key);
291
+ }
292
+ async setPassword(uid, password) {
293
+ this.passwords.set(uid, password);
294
+ }
295
+ async groups() {
296
+ return [...this.groupList.values()];
297
+ }
298
+ async groupById(uid) {
299
+ return this.groupList.get(uid);
300
+ }
301
+ async groupByName(name) {
302
+ return [...this.groupList.values()].find((group) => group.name === name);
303
+ }
304
+ async createGroup(input) {
305
+ const group = {
306
+ uid: `group-${randomHex(6)}`,
307
+ name: input.name,
308
+ description: input.description,
309
+ isActive: input.isActive,
310
+ users: []
311
+ };
312
+ this.groupList.set(group.uid, group);
313
+ return group;
314
+ }
315
+ async deleteGroup(uid) {
316
+ this.groupList.delete(uid);
317
+ for (const [key, permission] of this.permissionList)
318
+ if (permission.group === uid)
319
+ this.permissionList.delete(key);
320
+ }
321
+ async addToGroup(groupUid, userUid) {
322
+ const group = this.groupList.get(groupUid);
323
+ if (!group)
324
+ return undefined;
325
+ if (!group.users.includes(userUid))
326
+ group.users.push(userUid);
327
+ return group;
328
+ }
329
+ async removeFromGroup(groupUid, userUid) {
330
+ const group = this.groupList.get(groupUid);
331
+ if (!group)
332
+ return undefined;
333
+ group.users = group.users.filter((member) => member !== userUid);
334
+ return group;
335
+ }
336
+ async permissions() {
337
+ return [...this.permissionList.values()];
338
+ }
339
+ async permissionById(uid) {
340
+ return this.permissionList.get(uid);
341
+ }
342
+ async createPermission(input) {
343
+ const permission = {
344
+ uid: `rule-${randomHex(6)}`,
345
+ path: input.path,
346
+ type: input.type,
347
+ setting: input.setting,
348
+ user: input.user,
349
+ group: input.group
350
+ };
351
+ this.permissionList.set(permission.uid, permission);
352
+ return permission;
353
+ }
354
+ async setPermissionSetting(uid, setting) {
355
+ const permission = this.permissionList.get(uid);
356
+ if (!permission)
357
+ return undefined;
358
+ permission.setting = setting;
359
+ return permission;
360
+ }
361
+ async deletePermission(uid) {
362
+ this.permissionList.delete(uid);
363
+ }
364
+ async clients() {
365
+ return [...this.clientRecords.values()];
366
+ }
367
+ async createClient(input) {
368
+ this.clientRecords.set(input.clientId, input);
369
+ this.clientMap.set(input.clientId, {
370
+ clientId: input.clientId,
371
+ accessTokenExpiration: input.accessTokenExpiration,
372
+ refreshTokenExpiration: input.refreshTokenExpiration
373
+ });
374
+ return input;
375
+ }
191
376
  }
@@ -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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"}