@travetto/auth 2.2.4 → 3.0.0-rc.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 CHANGED
@@ -63,7 +63,8 @@ As referenced above, a [Principal Structure](https://github.com/travetto/travett
63
63
  ```typescript
64
64
  export interface Authenticator<T = unknown, P extends Principal = Principal, C = unknown> {
65
65
  /**
66
- * Verify the payload, verifying the payload is correctly identified.
66
+ * Verify the payload, ensuring the payload is correctly identified.
67
+ *
67
68
  * @returns Valid principal if authenticated
68
69
  * @returns undefined if authentication is valid, but incomplete (multi-step)
69
70
  * @throws AppError if authentication fails
@@ -105,7 +106,7 @@ Overall, the structure is simple, but drives home the primary use cases of the f
105
106
  * To have access to the principal
106
107
 
107
108
  ## Common Utilities
108
- The [AuthUtil](https://github.com/travetto/travetto/tree/main/module/auth/src/util.ts#L24) provides the following functionality:
109
+ The [AuthUtil](https://github.com/travetto/travetto/tree/main/module/auth/src/util.ts#L11) provides the following functionality:
109
110
 
110
111
  **Code: Auth util structure**
111
112
  ```typescript
@@ -113,42 +114,16 @@ import * as crypto from 'crypto';
113
114
  import * as util from 'util';
114
115
  import { AppError, Util } from '@travetto/base';
115
116
  const pbkdf2 = util.promisify(crypto.pbkdf2);
116
- type PermSet = Set<string> | ReadonlySet<string>;
117
- type PermissionChecker = {
118
- all: (perms: PermSet) => boolean;
119
- any: (perms: PermSet) => boolean;
120
- };
121
- type PermissionCheckerSet = {
122
- includes: (perms: PermSet) => boolean;
123
- excludes: (perms: PermSet) => boolean;
124
- check: (value: PermSet) => boolean;
125
- };
126
117
  /**
127
118
  * Standard auth utilities
128
119
  */
129
120
  export class AuthUtil {
130
121
  /**
131
- * Build a permission checker against the provided permissions
132
- *
133
- * @param perms Set of permissions to check
134
- * @param defaultIfEmpty If no perms passed, default to empty
135
- */
136
- /**
137
- * Build a permission checker off of an include, and exclude set
138
- *
139
- * @param include Which permissions to include
140
- * @param exclude Which permissions to exclude
141
- * @param matchAll Whether not all permissions should be matched
142
- */
143
- static permissionChecker(include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): PermissionCheckerSet ;
144
- /**
145
- * Build a permission checker off of an include, and exclude set
122
+ * Build matcher for role permissions in allow/deny fashion
146
123
  *
147
- * @param include Which permissions to include
148
- * @param exclude Which permissions to exclude
149
- * @param matchAll Whether not all permissions should be matched
124
+ * @param roles Roles to build matcher for
150
125
  */
151
- static checkPermissions(permissions: Iterable<string>, include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): void ;
126
+ static roleMatcher(roles: string[]): (perms: Set<string>) => boolean ;
152
127
  /**
153
128
  * Generate a hash for a given value
154
129
  *
@@ -170,155 +145,18 @@ export class AuthUtil {
170
145
  }
171
146
  ```
172
147
 
173
- `permissionSetChecker` is probably the only functionality that needs to be explained.The function operates in a `DENY` / `ALLOW` mode. This means that a permission check will succeed only if:
148
+ `roleMatcher` is probably the only functionality that needs to be explained. The function extends the core allow/deny matcher functionality from [Base](https://github.com/travetto/travetto/tree/main/module/base#readme "Application phase management, environment config and common utilities for travetto applications.")'s Util class.
174
149
 
175
-
176
- * The user is logged in
177
- * If `matchAll` is false:
178
- * The user does not have any permissions in the exclusion list
179
- * The include list is empty, or the user has at least one permission in the include list.
180
- * Else
181
- * The user does not have all permissions in the exclusion list
182
- * The include list is empty, or the user has all permissions in the include list.
183
-
184
- ## Extension - Model
185
-
186
- This module also supports the integration between the [Authentication](https://github.com/travetto/travetto/tree/main/module/auth#readme "Authentication scaffolding for the travetto framework") module and the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.").
187
-
188
- The asset module requires an [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11) to provide functionality for reading and storing user information. You can use any existing providers to serve as your [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11), or you can roll your own.
189
-
190
- **Install: provider**
191
- ```bash
192
- npm install @travetto/model-{provider}
193
- ```
150
+ An example of role checks could be:
194
151
 
195
- Currently, the following are packages that provide [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11):
196
152
 
197
- * [DynamoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-dynamodb#readme "DynamoDB backing for the travetto model module.") - @travetto/model-dynamodb
198
- * [Elasticsearch Model Source](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch#readme "Elasticsearch backing for the travetto model module, with real-time modeling support for Elasticsearch mappings.") @travetto/model-elasticsearch
199
- * [Firestore Model Support](https://github.com/travetto/travetto/tree/main/module/model-firestore#readme "Firestore backing for the travetto model module.") @travetto/model-firestore
200
- * [MongoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-mongo#readme "Mongo backing for the travetto model module.") @travetto/model-mongo
201
- * [Redis Model Support](https://github.com/travetto/travetto/tree/main/module/model-redis#readme "Redis backing for the travetto model module.") @travetto/model-redis
202
- * [S3 Model Support](https://github.com/travetto/travetto/tree/main/module/model-s3#readme "S3 backing for the travetto model module.") @travetto/model-s3
203
- * [SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.") @travetto/model-sql
204
-
205
- The module itself is fairly straightforward, and truly the only integration point for this module to work is defined at the model level. The contract for authentication is established in code as providing translation to and from a [Registered Principal](https://github.com/travetto/travetto/tree/main/module/auth/src/extension/model.ts#L15)
206
-
207
- A registered principal extends the base concept of an principal, by adding in additional fields needed for local registration, specifically password management information.
208
-
209
- **Code: Registered Principal**
210
- ```typescript
211
- export interface RegisteredPrincipal extends Principal {
212
- /**
213
- * Password hash
214
- */
215
- hash?: string;
216
- /**
217
- * Password salt
218
- */
219
- salt?: string;
220
- /**
221
- * Temporary Reset Token
222
- */
223
- resetToken?: string;
224
- /**
225
- * End date for the reset token
226
- */
227
- resetExpires?: Date;
228
- /**
229
- * The actual password, only used on password set/update
230
- */
231
- password?: string;
232
- }
233
- ```
234
-
235
- **Code: A valid user model**
236
- ```typescript
237
- import { Model } from '@travetto/model';
238
- import { RegisteredPrincipal } from '@travetto/auth';
239
-
240
- @Model()
241
- export class User implements RegisteredPrincipal {
242
- id: string;
243
- source: string;
244
- details: Record<string, unknown>;
245
- password?: string;
246
- salt: string;
247
- hash: string;
248
- resetToken?: string;
249
- resetExpires?: Date;
250
- permissions: string[];
251
- }
252
- ```
253
-
254
- ## Configuration
255
-
256
- Additionally, there exists a common practice of mapping various external security principals into a local contract. These external identities, as provided from countless authentication schemes, need to be homogenized for use. This has been handled in other frameworks by using external configuration, and creating a mapping between the two set of fields. Within this module, the mappings are defined as functions in which you can translate to the model from an identity or to an identity from a model.
257
-
258
- **Code: Principal Source configuration**
259
- ```typescript
260
- import { InjectableFactory } from '@travetto/di';
261
- import { ModelAuthService, RegisteredPrincipal } from '@travetto/auth';
262
- import { ModelCrudSupport } from '@travetto/model';
263
-
264
- import { User } from './model';
265
-
266
- class AuthConfig {
267
- @InjectableFactory()
268
- static getModelAuthService(svc: ModelCrudSupport) {
269
- return new ModelAuthService(
270
- svc,
271
- User,
272
- (u: User) => ({ // This converts User to a RegisteredPrincipal
273
- source: 'model',
274
- provider: 'model',
275
- id: u.id,
276
- permissions: u.permissions,
277
- hash: u.hash,
278
- salt: u.salt,
279
- resetToken: u.resetToken,
280
- resetExpires: u.resetExpires,
281
- password: u.password,
282
- details: u,
283
- }),
284
- (u: Partial<RegisteredPrincipal>) => User.from(({ // This converts a RegisteredPrincipal to a User
285
- id: u.id,
286
- permissions: [...(u.permissions || [])],
287
- hash: u.hash,
288
- salt: u.salt,
289
- resetToken: u.resetToken,
290
- resetExpires: u.resetExpires,
291
- })
292
- )
293
- );
294
- }
295
- }
296
- ```
297
-
298
- **Code: Sample usage**
299
- ```typescript
300
- import { AppError } from '@travetto/base';
301
- import { Injectable, Inject } from '@travetto/di';
302
- import { ModelAuthService } from '@travetto/auth';
153
+ * Admin
154
+ * !Editor
155
+ * Owner+Author
303
156
 
304
- import { User } from './model';
305
-
306
- @Injectable()
307
- class UserService {
308
-
309
- @Inject()
310
- private auth: ModelAuthService<User>;
311
-
312
- async authenticate(identity: User) {
313
- try {
314
- return await this.auth.authenticate(identity);
315
- } catch (err) {
316
- if (err instanceof AppError && err.category === 'notfound') {
317
- return await this.auth.register(identity);
318
- } else {
319
- throw err;
320
- }
321
- }
322
- }
323
- }
324
- ```
157
+ The code would check the list in order, which would result in the following logic:
158
+
159
+ * If the user is an admin, always allow
160
+ * If the user has the editor role, deny
161
+ * If the user is both an owner and an author allow
162
+ * By default, deny due to the presence of positive checks
package/index.ts CHANGED
@@ -1,7 +1,4 @@
1
1
  export * from './src/util';
2
2
  export * from './src/types/authenticator';
3
3
  export * from './src/types/authorizer';
4
- export * from './src/types/principal';
5
-
6
- // Named export needed for proxying
7
- export { ModelAuthService, RegisteredPrincipal } from './src/extension/model';
4
+ export * from './src/types/principal';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@travetto/auth",
3
3
  "displayName": "Authentication",
4
- "version": "2.2.4",
4
+ "version": "3.0.0-rc.1",
5
5
  "description": "Authentication scaffolding for the travetto framework",
6
6
  "keywords": [
7
7
  "authentication",
@@ -16,8 +16,7 @@
16
16
  },
17
17
  "files": [
18
18
  "index.ts",
19
- "src",
20
- "test-support"
19
+ "src"
21
20
  ],
22
21
  "main": "index.ts",
23
22
  "repository": {
@@ -25,10 +24,7 @@
25
24
  "directory": "module/auth"
26
25
  },
27
26
  "dependencies": {
28
- "@travetto/base": "^2.2.4"
29
- },
30
- "optionalPeerDependencies": {
31
- "@travetto/model": "^2.2.4"
27
+ "@travetto/base": "^3.0.0-rc.1"
32
28
  },
33
29
  "private": false,
34
30
  "publishConfig": {
@@ -1,6 +1,3 @@
1
- import { Schema } from '@travetto/schema'; // @line-if @travetto/schema
2
-
3
- @Schema() // @line-if @travetto/schema
4
1
  export class PrincipalTarget {
5
2
  id: string;
6
3
  expiresAt?: Date;
@@ -7,7 +7,8 @@ import { Principal } from './principal';
7
7
  */
8
8
  export interface Authenticator<T = unknown, P extends Principal = Principal, C = unknown> {
9
9
  /**
10
- * Verify the payload, verifying the payload is correctly identified.
10
+ * Verify the payload, ensuring the payload is correctly identified.
11
+ *
11
12
  * @returns Valid principal if authenticated
12
13
  * @returns undefined if authentication is valid, but incomplete (multi-step)
13
14
  * @throws AppError if authentication fails
@@ -2,7 +2,7 @@
2
2
  * A user principal, including permissions and details
3
3
  *
4
4
  * @concrete ../internal/types:PrincipalTarget
5
- * @augments `@trv:rest/Context` // @line-if @travetto/rest
5
+ * @augments `@trv:rest/Context`
6
6
  */
7
7
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
8
  export interface Principal<D = { [key: string]: any }> {
package/src/util.ts CHANGED
@@ -5,85 +5,32 @@ import { AppError, Util } from '@travetto/base';
5
5
 
6
6
  const pbkdf2 = util.promisify(crypto.pbkdf2);
7
7
 
8
- type PermSet = Set<string> | ReadonlySet<string>;
9
-
10
- type PermissionChecker = {
11
- all: (perms: PermSet) => boolean;
12
- any: (perms: PermSet) => boolean;
13
- };
14
-
15
- type PermissionCheckerSet = {
16
- includes: (perms: PermSet) => boolean;
17
- excludes: (perms: PermSet) => boolean;
18
- check: (value: PermSet) => boolean;
19
- };
20
-
21
8
  /**
22
9
  * Standard auth utilities
23
10
  */
24
11
  export class AuthUtil {
25
12
 
26
- static #checkExcCache = new Map<string, PermissionChecker>();
27
- static #checkIncCache = new Map<string, PermissionChecker>();
28
-
29
- /**
30
- * Build a permission checker against the provided permissions
31
- *
32
- * @param perms Set of permissions to check
33
- * @param defaultIfEmpty If no perms passed, default to empty
34
- */
35
- static #buildChecker(perms: Iterable<string>, defaultIfEmpty: boolean): PermissionChecker {
36
- const permArr = [...perms].map(x => x.toLowerCase());
37
- let all = (_: PermSet): boolean => defaultIfEmpty;
38
- let any = (_: PermSet): boolean => defaultIfEmpty;
39
- if (permArr.length) {
40
- all = (uPerms: PermSet): boolean => permArr.every(x => uPerms.has(x));
41
- any = (uPerms: PermSet): boolean => permArr.some(x => uPerms.has(x));
13
+ static #matchPermissionSet(rule: string[], perms: Set<string>): boolean {
14
+ for (const el of rule) {
15
+ if (!perms.has(el)) {
16
+ return false;
17
+ }
42
18
  }
43
- return { all, any };
19
+ return true;
44
20
  }
45
21
 
46
22
  /**
47
- * Build a permission checker off of an include, and exclude set
23
+ * Build matcher for role permissions in allow/deny fashion
48
24
  *
49
- * @param include Which permissions to include
50
- * @param exclude Which permissions to exclude
51
- * @param matchAll Whether not all permissions should be matched
25
+ * @param roles Roles to build matcher for
52
26
  */
53
- static permissionChecker(include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): PermissionCheckerSet {
54
- const incKey = [...include].sort().join(',');
55
- const excKey = [...exclude].sort().join(',');
56
-
57
- if (!this.#checkIncCache.has(incKey)) {
58
- this.#checkIncCache.set(incKey, this.#buildChecker(include, true));
59
- }
60
- if (!this.#checkExcCache.has(excKey)) {
61
- this.#checkExcCache.set(excKey, this.#buildChecker(exclude, false));
62
- }
63
-
64
- const includes = this.#checkIncCache.get(incKey)![mode];
65
- const excludes = this.#checkExcCache.get(excKey)![mode];
66
-
67
- return {
68
- includes, excludes, check: (perms: PermSet) => includes(perms) && !excludes(perms)
69
- };
27
+ static roleMatcher(roles: string[]): (perms: Set<string>) => boolean {
28
+ return Util.allowDenyMatcher<string[], [Set<string>]>(roles,
29
+ x => x.split('|'),
30
+ this.#matchPermissionSet.bind(this),
31
+ );
70
32
  }
71
33
 
72
- /**
73
- * Build a permission checker off of an include, and exclude set
74
- *
75
- * @param include Which permissions to include
76
- * @param exclude Which permissions to exclude
77
- * @param matchAll Whether not all permissions should be matched
78
- */
79
- static checkPermissions(permissions: Iterable<string>, include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): void {
80
- const { check } = this.permissionChecker(include, exclude, mode);
81
- if (!check(!(permissions instanceof Set) ? new Set(permissions) : permissions)) {
82
- throw new AppError('Insufficient permissions', 'permissions');
83
- }
84
- }
85
-
86
-
87
34
  /**
88
35
  * Generate a hash for a given value
89
36
  *
@@ -1,199 +0,0 @@
1
- // @file-if @travetto/model
2
- import { AppError, Util, Class } from '@travetto/base';
3
- import { ModelCrudSupport, ModelType, NotFoundError, OptionalId } from '@travetto/model';
4
- import { EnvUtil } from '@travetto/boot';
5
- import { isStorageSupported } from '@travetto/model/src/internal/service/common';
6
-
7
- import { Principal } from '../types/principal';
8
- import { Authenticator } from '../types/authenticator';
9
- import { Authorizer } from '../types/authorizer';
10
- import { AuthUtil } from '../util';
11
-
12
- /**
13
- * A set of registration data
14
- */
15
- export interface RegisteredPrincipal extends Principal {
16
- /**
17
- * Password hash
18
- */
19
- hash?: string;
20
- /**
21
- * Password salt
22
- */
23
- salt?: string;
24
- /**
25
- * Temporary Reset Token
26
- */
27
- resetToken?: string;
28
- /**
29
- * End date for the reset token
30
- */
31
- resetExpires?: Date;
32
- /**
33
- * The actual password, only used on password set/update
34
- */
35
- password?: string;
36
- }
37
-
38
- /**
39
- * A model-based auth service
40
- */
41
- export class ModelAuthService<T extends ModelType> implements
42
- Authenticator<T, RegisteredPrincipal>,
43
- Authorizer<RegisteredPrincipal>
44
- {
45
-
46
- #modelService: ModelCrudSupport;
47
- #cls: Class<T>;
48
-
49
- /**
50
- * Build a Model Principal Source
51
- *
52
- * @param cls Model class for the principal
53
- * @param toPrincipal Convert a model to an principal
54
- * @param fromPrincipal Convert an identity to the model
55
- */
56
- constructor(
57
- modelService: ModelCrudSupport,
58
- cls: Class<T>,
59
- public toPrincipal: (t: T) => RegisteredPrincipal,
60
- public fromPrincipal: (t: Partial<RegisteredPrincipal>) => Partial<T>,
61
- ) {
62
- this.#modelService = modelService;
63
- this.#cls = cls;
64
- }
65
-
66
- /**
67
- * Retrieve user by id
68
- * @param userId The user id to retrieve
69
- */
70
- async #retrieve(userId: string): Promise<T> {
71
- return await this.#modelService.get<T>(this.#cls, userId);
72
- }
73
-
74
- /**
75
- * Convert identity to a principal
76
- * @param ident The registered identity to resolve
77
- */
78
- async #resolvePrincipal(ident: RegisteredPrincipal): Promise<RegisteredPrincipal> {
79
- const user = await this.#retrieve(ident.id);
80
- return this.toPrincipal(user);
81
- }
82
-
83
- /**
84
- * Authenticate password for model id
85
- * @param userId The user id to authenticate against
86
- * @param password The password to authenticate against
87
- */
88
- async #authenticate(userId: string, password: string): Promise<RegisteredPrincipal> {
89
- const ident = await this.#resolvePrincipal({ id: userId, details: {} });
90
-
91
- const hash = await AuthUtil.generateHash(password, ident.salt!);
92
- if (hash !== ident.hash) {
93
- throw new AppError('Invalid password', 'authentication');
94
- } else {
95
- delete ident.password;
96
- return ident;
97
- }
98
- }
99
-
100
- async postConstruct(): Promise<void> {
101
- if (isStorageSupported(this.#modelService) && EnvUtil.isDynamic()) {
102
- await this.#modelService.createModel?.(this.#cls);
103
- }
104
- }
105
-
106
- /**
107
- * Register a user
108
- * @param user The user to register
109
- */
110
- async register(user: OptionalId<T>): Promise<T> {
111
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
112
- const ident = this.toPrincipal(user as T);
113
-
114
- try {
115
- if (ident.id) {
116
- await this.#retrieve(ident.id);
117
- throw new AppError('That id is already taken.', 'data');
118
- }
119
- } catch (err) {
120
- if (!(err instanceof NotFoundError)) {
121
- throw err;
122
- }
123
- }
124
-
125
- const fields = await AuthUtil.generatePassword(ident.password!);
126
-
127
- ident.password = undefined; // Clear it out on set
128
-
129
- Object.assign(user, this.fromPrincipal(fields));
130
-
131
- const res: T = await this.#modelService.create(this.#cls, user);
132
- return res;
133
- }
134
-
135
- /**
136
- * Change a password
137
- * @param userId The user id to affect
138
- * @param password The new password
139
- * @param oldPassword The old password
140
- */
141
- async changePassword(userId: string, password: string, oldPassword?: string): Promise<T> {
142
- const user = await this.#retrieve(userId);
143
- const ident = this.toPrincipal(user);
144
-
145
- if (oldPassword === ident.resetToken) {
146
- if (ident.resetExpires && ident.resetExpires.getTime() < Date.now()) {
147
- throw new AppError('Reset token has expired', 'data');
148
- }
149
- } else if (oldPassword !== undefined) {
150
- const pw = await AuthUtil.generateHash(oldPassword, ident.salt!);
151
- if (pw !== ident.hash) {
152
- throw new AppError('Old password is required to change', 'authentication');
153
- }
154
- }
155
-
156
- const fields = await AuthUtil.generatePassword(password);
157
- Object.assign(user, this.fromPrincipal(fields));
158
-
159
- return await this.#modelService.update(this.#cls, user);
160
- }
161
-
162
- /**
163
- * Generate a reset token
164
- * @param userId The user to reset for
165
- */
166
- async generateResetToken(userId: string): Promise<RegisteredPrincipal> {
167
- const user = await this.#retrieve(userId);
168
- const ident = this.toPrincipal(user);
169
- const salt = await Util.uuid();
170
-
171
- ident.resetToken = await AuthUtil.generateHash(Util.uuid(), salt, 25000, 32);
172
- ident.resetExpires = Util.timeFromNow('1h');
173
-
174
- Object.assign(user, this.fromPrincipal(ident));
175
-
176
- await this.#modelService.update(this.#cls, user);
177
-
178
- return ident;
179
- }
180
-
181
- /**
182
- * Authorize principal into known user
183
- * @param principal
184
- * @returns Authorized principal
185
- */
186
- authorize(principal: RegisteredPrincipal): Promise<RegisteredPrincipal> {
187
- return this.#resolvePrincipal(principal);
188
- }
189
-
190
- /**
191
- * Authenticate entity into a principal
192
- * @param payload
193
- * @returns Authenticated principal
194
- */
195
- authenticate(payload: T): Promise<RegisteredPrincipal> {
196
- const { id, password } = this.toPrincipal(payload);
197
- return this.#authenticate(id, password!);
198
- }
199
- }
@@ -1,89 +0,0 @@
1
- // @file-if @travetto/model
2
- import * as assert from 'assert';
3
-
4
- import { AppError, Class } from '@travetto/base';
5
- import { Suite, Test } from '@travetto/test';
6
- import { Inject, InjectableFactory } from '@travetto/di';
7
- import { ModelCrudSupport, Model } from '@travetto/model';
8
- import { InjectableSuite } from '@travetto/di/test-support/suite';
9
- import { ModelSuite } from '@travetto/model/test-support/suite';
10
-
11
- import { ModelAuthService, RegisteredPrincipal } from '..';
12
-
13
- export const TestModelSvcⲐ = Symbol.for('@trv:auth/test-model-svc');
14
-
15
- @Model({ autoCreate: false })
16
- class User implements RegisteredPrincipal {
17
- id: string;
18
- password?: string;
19
- salt?: string;
20
- hash?: string;
21
- resetToken?: string;
22
- resetExpires?: Date;
23
- permissions?: string[];
24
- details: Record<string, unknown>;
25
- }
26
-
27
- class TestConfig {
28
- @InjectableFactory()
29
- static getAuthService(@Inject(TestModelSvcⲐ) svc: ModelCrudSupport): ModelAuthService<User> {
30
- const src = new ModelAuthService<User>(
31
- svc,
32
- User,
33
- u => ({ ...u, details: u, source: 'model' }),
34
- reg => User.from({ ...reg })
35
- );
36
- return src;
37
- }
38
- }
39
-
40
- @Suite()
41
- @ModelSuite()
42
- @InjectableSuite()
43
- export abstract class AuthModelServiceSuite {
44
-
45
- serviceClass: Class;
46
- configClass: Class;
47
-
48
- @Inject()
49
- authService: ModelAuthService<User>;
50
-
51
- @Inject(TestModelSvcⲐ)
52
- svc: ModelCrudSupport;
53
-
54
- @Test()
55
- async register() {
56
- const pre = User.from({
57
- password: 'bob',
58
- details: {}
59
- });
60
-
61
- const user = await this.authService.register(pre);
62
- assert.ok(user.hash);
63
- assert.ok(user.id);
64
- }
65
-
66
- @Test()
67
- async authenticate() {
68
- const pre = User.from({
69
- id: this.svc.uuid(),
70
- password: 'bob',
71
- details: {}
72
- });
73
-
74
- try {
75
- await this.authService.authenticate(pre);
76
- assert.fail('Should not have gotten here');
77
- } catch (err) {
78
- if (err instanceof AppError && err.category === 'notfound') {
79
- const user = await this.authService.register(pre);
80
- assert.ok(user.hash);
81
- assert.ok(user.id);
82
- } else {
83
- throw err;
84
- }
85
- }
86
-
87
- await assert.doesNotReject(() => this.authService.authenticate(pre));
88
- }
89
- }