@travetto/auth 2.1.4 → 2.2.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 +13 -8
- package/package.json +3 -3
- package/src/extension/model.ts +13 -12
- package/src/util.ts +14 -8
- package/test-support/model.ts +1 -1
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ export interface Principal<D = { [key: string]: any }> {
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
As referenced above, a [Principal Structure](https://github.com/travetto/travetto/tree/main/module/auth/src/types/principal.ts#L8) is defined as a user with respect to a security context. This can be information the application knows about the user (authorized) or what a separate service may know about a user (3rd-party
|
|
58
|
+
As referenced above, a [Principal Structure](https://github.com/travetto/travetto/tree/main/module/auth/src/types/principal.ts#L8) is defined as a user with respect to a security context. This can be information the application knows about the user (authorized) or what a separate service may know about a user (3rd-party authentication).
|
|
59
59
|
|
|
60
60
|
## Authentication
|
|
61
61
|
|
|
@@ -72,7 +72,7 @@ export interface Authenticator<T = unknown, P extends Principal = Principal, C =
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
The [Authenticator](https://github.com/travetto/travetto/tree/main/module/auth/src/types/authenticator.ts#L8) only requires one method to be defined, and that is `authenticate`. This method receives a generic payload, and a supplemental context as an input. The interface is
|
|
75
|
+
The [Authenticator](https://github.com/travetto/travetto/tree/main/module/auth/src/types/authenticator.ts#L8) only requires one method to be defined, and that is `authenticate`. This method receives a generic payload, and a supplemental context as an input. The interface is responsible for converting that to an authenticated principal.
|
|
76
76
|
|
|
77
77
|
### Example
|
|
78
78
|
The [JWT](https://github.com/travetto/travetto/tree/main/module/jwt#readme "JSON Web Token implementation") module is a good example of an authenticator. This is a common use case for simple internal auth.
|
|
@@ -105,7 +105,7 @@ Overall, the structure is simple, but drives home the primary use cases of the f
|
|
|
105
105
|
* To have access to the principal
|
|
106
106
|
|
|
107
107
|
## Common Utilities
|
|
108
|
-
The [AuthUtil](https://github.com/travetto/travetto/tree/main/module/auth/src/util.ts#
|
|
108
|
+
The [AuthUtil](https://github.com/travetto/travetto/tree/main/module/auth/src/util.ts#L24) provides the following functionality:
|
|
109
109
|
|
|
110
110
|
**Code: Auth util structure**
|
|
111
111
|
```typescript
|
|
@@ -118,6 +118,11 @@ type PermissionChecker = {
|
|
|
118
118
|
all: (perms: PermSet) => boolean;
|
|
119
119
|
any: (perms: PermSet) => boolean;
|
|
120
120
|
};
|
|
121
|
+
type PermissionCheckerSet = {
|
|
122
|
+
includes: (perms: PermSet) => boolean;
|
|
123
|
+
excludes: (perms: PermSet) => boolean;
|
|
124
|
+
check: (value: PermSet) => boolean;
|
|
125
|
+
};
|
|
121
126
|
/**
|
|
122
127
|
* Standard auth utilities
|
|
123
128
|
*/
|
|
@@ -135,7 +140,7 @@ export class AuthUtil {
|
|
|
135
140
|
* @param exclude Which permissions to exclude
|
|
136
141
|
* @param matchAll Whether not all permissions should be matched
|
|
137
142
|
*/
|
|
138
|
-
static permissionChecker(include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any') ;
|
|
143
|
+
static permissionChecker(include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): PermissionCheckerSet ;
|
|
139
144
|
/**
|
|
140
145
|
* Build a permission checker off of an include, and exclude set
|
|
141
146
|
*
|
|
@@ -143,7 +148,7 @@ export class AuthUtil {
|
|
|
143
148
|
* @param exclude Which permissions to exclude
|
|
144
149
|
* @param matchAll Whether not all permissions should be matched
|
|
145
150
|
*/
|
|
146
|
-
static checkPermissions(permissions: Iterable<string>, include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any') ;
|
|
151
|
+
static checkPermissions(permissions: Iterable<string>, include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): void ;
|
|
147
152
|
/**
|
|
148
153
|
* Generate a hash for a given value
|
|
149
154
|
*
|
|
@@ -153,7 +158,7 @@ export class AuthUtil {
|
|
|
153
158
|
* @param keylen Length of hash
|
|
154
159
|
* @param digest Digest method
|
|
155
160
|
*/
|
|
156
|
-
static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256') ;
|
|
161
|
+
static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256'): Promise<string> ;
|
|
157
162
|
/**
|
|
158
163
|
* Generate a salted password, with the ability to validate the password
|
|
159
164
|
*
|
|
@@ -161,7 +166,7 @@ export class AuthUtil {
|
|
|
161
166
|
* @param salt Salt value, or if a number, length of salt
|
|
162
167
|
* @param validator Optional function to validate your password
|
|
163
168
|
*/
|
|
164
|
-
static async generatePassword(password: string, salt: number | string = 32) ;
|
|
169
|
+
static async generatePassword(password: string, salt: number | string = 32): Promise<{ salt: string, hash: string }> ;
|
|
165
170
|
}
|
|
166
171
|
```
|
|
167
172
|
|
|
@@ -248,7 +253,7 @@ export class User implements RegisteredPrincipal {
|
|
|
248
253
|
|
|
249
254
|
## Configuration
|
|
250
255
|
|
|
251
|
-
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
|
|
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.
|
|
252
257
|
|
|
253
258
|
**Code: Principal Source configuration**
|
|
254
259
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/auth",
|
|
3
3
|
"displayName": "Authentication",
|
|
4
|
-
"version": "2.1
|
|
4
|
+
"version": "2.2.1",
|
|
5
5
|
"description": "Authentication scaffolding for the travetto framework",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"authentication",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"directory": "module/auth"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@travetto/base": "^2.1
|
|
28
|
+
"@travetto/base": "^2.2.1"
|
|
29
29
|
},
|
|
30
30
|
"optionalPeerDependencies": {
|
|
31
|
-
"@travetto/model": "^2.1
|
|
31
|
+
"@travetto/model": "^2.2.1"
|
|
32
32
|
},
|
|
33
33
|
"private": false,
|
|
34
34
|
"publishConfig": {
|
package/src/extension/model.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
67
67
|
* Retrieve user by id
|
|
68
68
|
* @param userId The user id to retrieve
|
|
69
69
|
*/
|
|
70
|
-
async #retrieve(userId: string) {
|
|
70
|
+
async #retrieve(userId: string): Promise<T> {
|
|
71
71
|
return await this.#modelService.get<T>(this.#cls, userId);
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -75,7 +75,7 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
75
75
|
* Convert identity to a principal
|
|
76
76
|
* @param ident The registered identity to resolve
|
|
77
77
|
*/
|
|
78
|
-
async #resolvePrincipal(ident: RegisteredPrincipal) {
|
|
78
|
+
async #resolvePrincipal(ident: RegisteredPrincipal): Promise<RegisteredPrincipal> {
|
|
79
79
|
const user = await this.#retrieve(ident.id);
|
|
80
80
|
return this.toPrincipal(user);
|
|
81
81
|
}
|
|
@@ -85,7 +85,7 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
85
85
|
* @param userId The user id to authenticate against
|
|
86
86
|
* @param password The password to authenticate against
|
|
87
87
|
*/
|
|
88
|
-
async #authenticate(userId: string, password: string) {
|
|
88
|
+
async #authenticate(userId: string, password: string): Promise<RegisteredPrincipal> {
|
|
89
89
|
const ident = await this.#resolvePrincipal({ id: userId, details: {} });
|
|
90
90
|
|
|
91
91
|
const hash = await AuthUtil.generateHash(password, ident.salt!);
|
|
@@ -97,7 +97,7 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
async postConstruct() {
|
|
100
|
+
async postConstruct(): Promise<void> {
|
|
101
101
|
if (isStorageSupported(this.#modelService) && EnvUtil.isDynamic()) {
|
|
102
102
|
await this.#modelService.createModel?.(this.#cls);
|
|
103
103
|
}
|
|
@@ -107,7 +107,8 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
107
107
|
* Register a user
|
|
108
108
|
* @param user The user to register
|
|
109
109
|
*/
|
|
110
|
-
async register(user: OptionalId<T>) {
|
|
110
|
+
async register(user: OptionalId<T>): Promise<T> {
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
111
112
|
const ident = this.toPrincipal(user as T);
|
|
112
113
|
|
|
113
114
|
try {
|
|
@@ -115,9 +116,9 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
115
116
|
await this.#retrieve(ident.id);
|
|
116
117
|
throw new AppError('That id is already taken.', 'data');
|
|
117
118
|
}
|
|
118
|
-
} catch (
|
|
119
|
-
if (!(
|
|
120
|
-
throw
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (!(err instanceof NotFoundError)) {
|
|
121
|
+
throw err;
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
|
|
@@ -133,11 +134,11 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
133
134
|
|
|
134
135
|
/**
|
|
135
136
|
* Change a password
|
|
136
|
-
* @param userId The user id to
|
|
137
|
+
* @param userId The user id to affect
|
|
137
138
|
* @param password The new password
|
|
138
139
|
* @param oldPassword The old password
|
|
139
140
|
*/
|
|
140
|
-
async changePassword(userId: string, password: string, oldPassword?: string) {
|
|
141
|
+
async changePassword(userId: string, password: string, oldPassword?: string): Promise<T> {
|
|
141
142
|
const user = await this.#retrieve(userId);
|
|
142
143
|
const ident = this.toPrincipal(user);
|
|
143
144
|
|
|
@@ -182,7 +183,7 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
182
183
|
* @param principal
|
|
183
184
|
* @returns Authorized principal
|
|
184
185
|
*/
|
|
185
|
-
authorize(principal: RegisteredPrincipal) {
|
|
186
|
+
authorize(principal: RegisteredPrincipal): Promise<RegisteredPrincipal> {
|
|
186
187
|
return this.#resolvePrincipal(principal);
|
|
187
188
|
}
|
|
188
189
|
|
|
@@ -191,7 +192,7 @@ export class ModelAuthService<T extends ModelType> implements
|
|
|
191
192
|
* @param payload
|
|
192
193
|
* @returns Authenticated principal
|
|
193
194
|
*/
|
|
194
|
-
authenticate(payload: T) {
|
|
195
|
+
authenticate(payload: T): Promise<RegisteredPrincipal> {
|
|
195
196
|
const { id, password } = this.toPrincipal(payload);
|
|
196
197
|
return this.#authenticate(id, password!);
|
|
197
198
|
}
|
package/src/util.ts
CHANGED
|
@@ -12,6 +12,12 @@ type PermissionChecker = {
|
|
|
12
12
|
any: (perms: PermSet) => boolean;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
type PermissionCheckerSet = {
|
|
16
|
+
includes: (perms: PermSet) => boolean;
|
|
17
|
+
excludes: (perms: PermSet) => boolean;
|
|
18
|
+
check: (value: PermSet) => boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
15
21
|
/**
|
|
16
22
|
* Standard auth utilities
|
|
17
23
|
*/
|
|
@@ -28,11 +34,11 @@ export class AuthUtil {
|
|
|
28
34
|
*/
|
|
29
35
|
static #buildChecker(perms: Iterable<string>, defaultIfEmpty: boolean): PermissionChecker {
|
|
30
36
|
const permArr = [...perms].map(x => x.toLowerCase());
|
|
31
|
-
let all = (_: PermSet) => defaultIfEmpty;
|
|
32
|
-
let any = (_: PermSet) => defaultIfEmpty;
|
|
37
|
+
let all = (_: PermSet): boolean => defaultIfEmpty;
|
|
38
|
+
let any = (_: PermSet): boolean => defaultIfEmpty;
|
|
33
39
|
if (permArr.length) {
|
|
34
|
-
all = (uPerms: PermSet) => permArr.every(x => uPerms.has(x));
|
|
35
|
-
any = (uPerms: PermSet) => permArr.some(x => uPerms.has(x));
|
|
40
|
+
all = (uPerms: PermSet): boolean => permArr.every(x => uPerms.has(x));
|
|
41
|
+
any = (uPerms: PermSet): boolean => permArr.some(x => uPerms.has(x));
|
|
36
42
|
}
|
|
37
43
|
return { all, any };
|
|
38
44
|
}
|
|
@@ -44,7 +50,7 @@ export class AuthUtil {
|
|
|
44
50
|
* @param exclude Which permissions to exclude
|
|
45
51
|
* @param matchAll Whether not all permissions should be matched
|
|
46
52
|
*/
|
|
47
|
-
static permissionChecker(include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any') {
|
|
53
|
+
static permissionChecker(include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): PermissionCheckerSet {
|
|
48
54
|
const incKey = [...include].sort().join(',');
|
|
49
55
|
const excKey = [...exclude].sort().join(',');
|
|
50
56
|
|
|
@@ -70,7 +76,7 @@ export class AuthUtil {
|
|
|
70
76
|
* @param exclude Which permissions to exclude
|
|
71
77
|
* @param matchAll Whether not all permissions should be matched
|
|
72
78
|
*/
|
|
73
|
-
static checkPermissions(permissions: Iterable<string>, include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any') {
|
|
79
|
+
static checkPermissions(permissions: Iterable<string>, include: Iterable<string>, exclude: Iterable<string>, mode: 'all' | 'any' = 'any'): void {
|
|
74
80
|
const { check } = this.permissionChecker(include, exclude, mode);
|
|
75
81
|
if (!check(!(permissions instanceof Set) ? new Set(permissions) : permissions)) {
|
|
76
82
|
throw new AppError('Insufficient permissions', 'permissions');
|
|
@@ -87,7 +93,7 @@ export class AuthUtil {
|
|
|
87
93
|
* @param keylen Length of hash
|
|
88
94
|
* @param digest Digest method
|
|
89
95
|
*/
|
|
90
|
-
static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256') {
|
|
96
|
+
static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256'): Promise<string> {
|
|
91
97
|
const half = Math.trunc(Math.ceil(keylen / 2));
|
|
92
98
|
return pbkdf2(value, salt, iterations, half, digest).then(x => x.toString('hex').substring(0, keylen));
|
|
93
99
|
}
|
|
@@ -99,7 +105,7 @@ export class AuthUtil {
|
|
|
99
105
|
* @param salt Salt value, or if a number, length of salt
|
|
100
106
|
* @param validator Optional function to validate your password
|
|
101
107
|
*/
|
|
102
|
-
static async generatePassword(password: string, salt: number | string = 32) {
|
|
108
|
+
static async generatePassword(password: string, salt: number | string = 32): Promise<{ salt: string, hash: string }> {
|
|
103
109
|
if (!password) {
|
|
104
110
|
throw new AppError('Password is required', 'data');
|
|
105
111
|
}
|
package/test-support/model.ts
CHANGED
|
@@ -26,7 +26,7 @@ class User implements RegisteredPrincipal {
|
|
|
26
26
|
|
|
27
27
|
class TestConfig {
|
|
28
28
|
@InjectableFactory()
|
|
29
|
-
static
|
|
29
|
+
static getAuthService(@Inject(TestModelSvcⲐ) svc: ModelCrudSupport): ModelAuthService<User> {
|
|
30
30
|
const src = new ModelAuthService<User>(
|
|
31
31
|
svc,
|
|
32
32
|
User,
|