@travetto/auth-model 5.0.17 → 6.0.0-rc.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 CHANGED
@@ -37,7 +37,7 @@ Currently, the following are packages that provide [CRUD](https://github.com/tra
37
37
  * [SQLite Model Service](https://github.com/travetto/travetto/tree/main/module/model-sqlite#readme "SQLite backing for the travetto model module, with real-time modeling support for SQL schemas.") - @travetto/model-sqlite
38
38
  * [Memory Model Support](https://github.com/travetto/travetto/tree/main/module/model-memory#readme "Memory backing for the travetto model module.") - @travetto/model-memory
39
39
  * [File Model Support](https://github.com/travetto/travetto/tree/main/module/model-file#readme "File system backing for the travetto model module.") - @travetto/model-file
40
- 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-model/src/model.ts#L9).
40
+ 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-model/src/model.ts#L11).
41
41
 
42
42
  A registered principal extends the base concept of an principal, by adding in additional fields needed for local registration, specifically password management information.
43
43
 
@@ -92,7 +92,7 @@ Additionally, there exists a common practice of mapping various external securit
92
92
  **Code: Principal Source configuration**
93
93
  ```typescript
94
94
  import { InjectableFactory } from '@travetto/di';
95
- import { ModelAuthService, RegisteredPrincipal } from '@travetto/auth-model';
95
+ import { ModelAuthService } from '@travetto/auth-model';
96
96
  import { ModelCrudSupport } from '@travetto/model';
97
97
 
98
98
  import { User } from './model';
@@ -103,10 +103,10 @@ class AuthConfig {
103
103
  return new ModelAuthService(
104
104
  svc,
105
105
  User,
106
- (u: User) => ({ // This converts User to a RegisteredPrincipal
106
+ u => ({ // This converts User to a RegisteredPrincipal
107
107
  source: 'model',
108
108
  provider: 'model',
109
- id: u.id,
109
+ id: u.id!,
110
110
  permissions: u.permissions,
111
111
  hash: u.hash,
112
112
  salt: u.salt,
@@ -115,7 +115,7 @@ class AuthConfig {
115
115
  password: u.password,
116
116
  details: u,
117
117
  }),
118
- (u: Partial<RegisteredPrincipal>) => User.from(({ // This converts a RegisteredPrincipal to a User
118
+ u => User.from(({ // This converts a RegisteredPrincipal to a User
119
119
  id: u.id,
120
120
  permissions: [...(u.permissions || [])],
121
121
  hash: u.hash,
@@ -156,3 +156,37 @@ class UserService {
156
156
  }
157
157
  }
158
158
  ```
159
+
160
+ ## Common Utilities
161
+ The [AuthModelUtil](https://github.com/travetto/travetto/tree/main/module/auth-model/src/util.ts#L11) provides the following functionality:
162
+
163
+ **Code: Auth util structure**
164
+ ```typescript
165
+ import crypto from 'node:crypto';
166
+ import util from 'node:util';
167
+ import { AppError, Util } from '@travetto/runtime';
168
+ const pbkdf2 = util.promisify(crypto.pbkdf2);
169
+ /**
170
+ * Standard auth utilities
171
+ */
172
+ export class AuthModelUtil {
173
+ /**
174
+ * Generate a hash for a given value
175
+ *
176
+ * @param value Value to hash
177
+ * @param salt The salt value
178
+ * @param iterations Number of iterations on hashing
179
+ * @param keylen Length of hash
180
+ * @param digest Digest method
181
+ */
182
+ static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256'): Promise<string>;
183
+ /**
184
+ * Generate a salted password, with the ability to validate the password
185
+ *
186
+ * @param password
187
+ * @param salt Salt value, or if a number, length of salt
188
+ * @param validator Optional function to validate your password
189
+ */
190
+ static async generatePassword(password: string, salt: number | string = 32): Promise<{ salt: string, hash: string }>;
191
+ }
192
+ ```
package/__index__.ts CHANGED
@@ -1 +1,2 @@
1
- export * from './src/model';
1
+ export * from './src/model';
2
+ export * from './src/util';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/auth-model",
3
- "version": "5.0.17",
3
+ "version": "6.0.0-rc.0",
4
4
  "description": "Authentication model support for the Travetto framework",
5
5
  "keywords": [
6
6
  "authentication",
@@ -25,11 +25,11 @@
25
25
  "directory": "module/auth-model"
26
26
  },
27
27
  "dependencies": {
28
- "@travetto/auth": "^5.0.16",
29
- "@travetto/model": "^5.0.17"
28
+ "@travetto/auth": "^6.0.0-rc.0",
29
+ "@travetto/model": "^6.0.0-rc.0"
30
30
  },
31
31
  "peerDependencies": {
32
- "@travetto/test": "^5.0.18"
32
+ "@travetto/test": "^6.0.0-rc.0"
33
33
  },
34
34
  "peerDependenciesMeta": {
35
35
  "@travetto/test": {
package/src/model.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Util, Class, TimeUtil, Runtime } from '@travetto/runtime';
2
2
  import { ModelCrudSupport, ModelType, NotFoundError, OptionalId } from '@travetto/model';
3
- import { AuthUtil, Principal, Authenticator, Authorizer, AuthenticationError } from '@travetto/auth';
3
+ import { Principal, Authenticator, Authorizer, AuthenticationError } from '@travetto/auth';
4
4
  import { isStorageSupported } from '@travetto/model/src/internal/service/common';
5
5
 
6
+ import { AuthModelUtil } from './util';
7
+
6
8
  /**
7
9
  * A set of registration data
8
10
  */
@@ -29,16 +31,20 @@ export interface RegisteredPrincipal extends Principal {
29
31
  password?: string;
30
32
  }
31
33
 
34
+ type ToPrincipal<T extends ModelType> = (t: OptionalId<T>) => RegisteredPrincipal;
35
+ type FromPrincipal<T extends ModelType> = (t: Partial<RegisteredPrincipal>) => Partial<T>;
36
+
32
37
  /**
33
38
  * A model-based auth service
34
39
  */
35
- export class ModelAuthService<T extends ModelType> implements
36
- Authenticator<T, RegisteredPrincipal>,
37
- Authorizer<RegisteredPrincipal> {
40
+ export class ModelAuthService<T extends ModelType> implements Authenticator<T>, Authorizer {
38
41
 
39
42
  #modelService: ModelCrudSupport;
40
43
  #cls: Class<T>;
41
44
 
45
+ toPrincipal: ToPrincipal<T>;
46
+ fromPrincipal: FromPrincipal<T>;
47
+
42
48
  /**
43
49
  * Build a Model Principal Source
44
50
  *
@@ -49,11 +55,13 @@ export class ModelAuthService<T extends ModelType> implements
49
55
  constructor(
50
56
  modelService: ModelCrudSupport,
51
57
  cls: Class<T>,
52
- public toPrincipal: (t: OptionalId<T>) => RegisteredPrincipal,
53
- public fromPrincipal: (t: Partial<RegisteredPrincipal>) => Partial<T>,
58
+ toPrincipal: ToPrincipal<T>,
59
+ fromPrincipal: FromPrincipal<T>,
54
60
  ) {
55
61
  this.#modelService = modelService;
56
62
  this.#cls = cls;
63
+ this.toPrincipal = toPrincipal;
64
+ this.fromPrincipal = fromPrincipal;
57
65
  }
58
66
 
59
67
  /**
@@ -81,7 +89,7 @@ export class ModelAuthService<T extends ModelType> implements
81
89
  async #authenticate(userId: string, password: string): Promise<RegisteredPrincipal> {
82
90
  const ident = await this.#resolvePrincipal({ id: userId, details: {} });
83
91
 
84
- const hash = await AuthUtil.generateHash(password, ident.salt!);
92
+ const hash = await AuthModelUtil.generateHash(password, ident.salt!);
85
93
  if (hash !== ident.hash) {
86
94
  throw new AuthenticationError('Invalid password');
87
95
  } else {
@@ -114,7 +122,7 @@ export class ModelAuthService<T extends ModelType> implements
114
122
  }
115
123
  }
116
124
 
117
- const fields = await AuthUtil.generatePassword(ident.password!);
125
+ const fields = await AuthModelUtil.generatePassword(ident.password!);
118
126
 
119
127
  ident.password = undefined; // Clear it out on set
120
128
 
@@ -139,13 +147,13 @@ export class ModelAuthService<T extends ModelType> implements
139
147
  throw new AuthenticationError('Reset token has expired', { category: 'data' });
140
148
  }
141
149
  } else if (oldPassword !== undefined) {
142
- const pw = await AuthUtil.generateHash(oldPassword, ident.salt!);
150
+ const pw = await AuthModelUtil.generateHash(oldPassword, ident.salt!);
143
151
  if (pw !== ident.hash) {
144
152
  throw new AuthenticationError('Old password is required to change');
145
153
  }
146
154
  }
147
155
 
148
- const fields = await AuthUtil.generatePassword(password);
156
+ const fields = await AuthModelUtil.generatePassword(password);
149
157
  Object.assign(user, this.fromPrincipal(fields));
150
158
 
151
159
  return await this.#modelService.update(this.#cls, user);
@@ -160,7 +168,7 @@ export class ModelAuthService<T extends ModelType> implements
160
168
  const ident = this.toPrincipal(user);
161
169
  const salt = await Util.uuid();
162
170
 
163
- ident.resetToken = await AuthUtil.generateHash(Util.uuid(), salt, 25000, 32);
171
+ ident.resetToken = await AuthModelUtil.generateHash(Util.uuid(), salt, 25000, 32);
164
172
  ident.resetExpires = TimeUtil.fromNow(1, 'h');
165
173
 
166
174
  Object.assign(user, this.fromPrincipal(ident));
package/src/util.ts ADDED
@@ -0,0 +1,44 @@
1
+ import crypto from 'node:crypto';
2
+ import util from 'node:util';
3
+
4
+ import { AppError, Util } from '@travetto/runtime';
5
+
6
+ const pbkdf2 = util.promisify(crypto.pbkdf2);
7
+
8
+ /**
9
+ * Standard auth utilities
10
+ */
11
+ export class AuthModelUtil {
12
+
13
+ /**
14
+ * Generate a hash for a given value
15
+ *
16
+ * @param value Value to hash
17
+ * @param salt The salt value
18
+ * @param iterations Number of iterations on hashing
19
+ * @param keylen Length of hash
20
+ * @param digest Digest method
21
+ */
22
+ static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256'): Promise<string> {
23
+ const half = Math.trunc(Math.ceil(keylen / 2));
24
+ return pbkdf2(value, salt, iterations, half, digest).then(x => x.toString('hex').substring(0, keylen));
25
+ }
26
+
27
+ /**
28
+ * Generate a salted password, with the ability to validate the password
29
+ *
30
+ * @param password
31
+ * @param salt Salt value, or if a number, length of salt
32
+ * @param validator Optional function to validate your password
33
+ */
34
+ static async generatePassword(password: string, salt: number | string = 32): Promise<{ salt: string, hash: string }> {
35
+ if (!password) {
36
+ throw new AppError('Password is required', { category: 'data' });
37
+ }
38
+
39
+ salt = typeof salt === 'number' ? Util.uuid(salt) : salt;
40
+ const hash = await this.generateHash(password, salt);
41
+
42
+ return { salt, hash };
43
+ }
44
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2020 ArcSine Technologies
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.