@travetto/auth-model 7.0.0-rc.1 → 7.0.0-rc.3
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 +21 -21
- package/package.json +4 -4
- package/src/model.ts +27 -33
- package/src/util.ts +1 -1
- package/support/test/model.ts +1 -1
package/README.md
CHANGED
|
@@ -100,29 +100,29 @@ import { User } from './model.ts';
|
|
|
100
100
|
|
|
101
101
|
class AuthConfig {
|
|
102
102
|
@InjectableFactory()
|
|
103
|
-
static getModelAuthService(
|
|
103
|
+
static getModelAuthService(service: ModelCrudSupport) {
|
|
104
104
|
return new ModelAuthService(
|
|
105
|
-
|
|
105
|
+
service,
|
|
106
106
|
User,
|
|
107
|
-
|
|
107
|
+
user => ({ // This converts User to a RegisteredPrincipal
|
|
108
108
|
source: 'model',
|
|
109
109
|
provider: 'model',
|
|
110
|
-
id:
|
|
111
|
-
permissions:
|
|
112
|
-
hash:
|
|
113
|
-
salt:
|
|
114
|
-
resetToken:
|
|
115
|
-
resetExpires:
|
|
116
|
-
password:
|
|
117
|
-
details:
|
|
110
|
+
id: user.id!,
|
|
111
|
+
permissions: user.permissions,
|
|
112
|
+
hash: user.hash,
|
|
113
|
+
salt: user.salt,
|
|
114
|
+
resetToken: user.resetToken,
|
|
115
|
+
resetExpires: user.resetExpires,
|
|
116
|
+
password: user.password,
|
|
117
|
+
details: user,
|
|
118
118
|
}),
|
|
119
|
-
|
|
120
|
-
id:
|
|
121
|
-
permissions: [...(
|
|
122
|
-
hash:
|
|
123
|
-
salt:
|
|
124
|
-
resetToken:
|
|
125
|
-
resetExpires:
|
|
119
|
+
user => User.from(({ // This converts a RegisteredPrincipal to a User
|
|
120
|
+
id: user.id,
|
|
121
|
+
permissions: [...(user.permissions || [])],
|
|
122
|
+
hash: user.hash,
|
|
123
|
+
salt: user.salt,
|
|
124
|
+
resetToken: user.resetToken,
|
|
125
|
+
resetExpires: user.resetExpires,
|
|
126
126
|
})
|
|
127
127
|
)
|
|
128
128
|
);
|
|
@@ -147,11 +147,11 @@ class UserService {
|
|
|
147
147
|
async authenticate(identity: User) {
|
|
148
148
|
try {
|
|
149
149
|
return await this.auth.authenticate(identity);
|
|
150
|
-
} catch (
|
|
151
|
-
if (
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof AppError && error.category === 'notfound') {
|
|
152
152
|
return await this.auth.register(identity);
|
|
153
153
|
} else {
|
|
154
|
-
throw
|
|
154
|
+
throw error;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/auth-model",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.3",
|
|
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": "^7.0.0-rc.
|
|
29
|
-
"@travetto/model": "^7.0.0-rc.
|
|
28
|
+
"@travetto/auth": "^7.0.0-rc.3",
|
|
29
|
+
"@travetto/model": "^7.0.0-rc.3"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@travetto/test": "^7.0.0-rc.
|
|
32
|
+
"@travetto/test": "^7.0.0-rc.3"
|
|
33
33
|
},
|
|
34
34
|
"peerDependenciesMeta": {
|
|
35
35
|
"@travetto/test": {
|
package/src/model.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Util, Class, TimeUtil,
|
|
1
|
+
import { Util, Class, TimeUtil, castTo } from '@travetto/runtime';
|
|
2
2
|
import { ModelCrudSupport, ModelType, NotFoundError, OptionalId, ModelStorageUtil } from '@travetto/model';
|
|
3
3
|
import { Principal, Authenticator, Authorizer, AuthenticationError } from '@travetto/auth';
|
|
4
4
|
|
|
@@ -31,8 +31,8 @@ export interface RegisteredPrincipal extends Principal {
|
|
|
31
31
|
password?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
type ToPrincipal<T extends ModelType> = (
|
|
35
|
-
type FromPrincipal<T extends ModelType> = (
|
|
34
|
+
type ToPrincipal<T extends ModelType> = (item: OptionalId<T>) => RegisteredPrincipal;
|
|
35
|
+
type FromPrincipal<T extends ModelType> = (item: Partial<RegisteredPrincipal>) => Partial<T>;
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* A model-based auth service
|
|
@@ -74,10 +74,10 @@ export class ModelAuthService<T extends ModelType> implements Authenticator<T>,
|
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Convert identity to a principal
|
|
77
|
-
* @param
|
|
77
|
+
* @param identity The registered identity to resolve
|
|
78
78
|
*/
|
|
79
|
-
async #resolvePrincipal(
|
|
80
|
-
const user = await this.#retrieve(
|
|
79
|
+
async #resolvePrincipal(identity: RegisteredPrincipal): Promise<RegisteredPrincipal> {
|
|
80
|
+
const user = await this.#retrieve(identity.id);
|
|
81
81
|
return this.toPrincipal(user);
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -87,19 +87,13 @@ export class ModelAuthService<T extends ModelType> implements Authenticator<T>,
|
|
|
87
87
|
* @param password The password to authenticate against
|
|
88
88
|
*/
|
|
89
89
|
async #authenticate(userId: string, password: string): Promise<RegisteredPrincipal> {
|
|
90
|
-
const
|
|
90
|
+
const identity = await this.#resolvePrincipal({ id: userId, details: {} });
|
|
91
91
|
|
|
92
|
-
const hash = await AuthModelUtil.generateHash(password,
|
|
93
|
-
if (hash !==
|
|
92
|
+
const hash = await AuthModelUtil.generateHash(password, identity.salt!);
|
|
93
|
+
if (hash !== identity.hash) {
|
|
94
94
|
throw new AuthenticationError('Invalid password');
|
|
95
95
|
} else {
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async postConstruct(): Promise<void> {
|
|
101
|
-
if (ModelStorageUtil.isSupported(this.#modelService) && Runtime.dynamic) {
|
|
102
|
-
await this.#modelService.createModel?.(this.#cls);
|
|
96
|
+
return identity;
|
|
103
97
|
}
|
|
104
98
|
}
|
|
105
99
|
|
|
@@ -108,20 +102,20 @@ export class ModelAuthService<T extends ModelType> implements Authenticator<T>,
|
|
|
108
102
|
* @param user The user to register
|
|
109
103
|
*/
|
|
110
104
|
async register(user: OptionalId<T>): Promise<T> {
|
|
111
|
-
const
|
|
105
|
+
const identity = this.toPrincipal(user);
|
|
112
106
|
|
|
113
107
|
try {
|
|
114
|
-
if (
|
|
115
|
-
await this.#retrieve(
|
|
108
|
+
if (identity.id) {
|
|
109
|
+
await this.#retrieve(identity.id);
|
|
116
110
|
throw new AuthenticationError('That id is already taken.', { category: 'data' });
|
|
117
111
|
}
|
|
118
|
-
} catch (
|
|
119
|
-
if (!(
|
|
120
|
-
throw
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (!(error instanceof NotFoundError)) {
|
|
114
|
+
throw error;
|
|
121
115
|
}
|
|
122
116
|
}
|
|
123
117
|
|
|
124
|
-
const fields = await AuthModelUtil.generatePassword(
|
|
118
|
+
const fields = await AuthModelUtil.generatePassword(identity.password!);
|
|
125
119
|
const output: Partial<T> = { ...user, ...this.fromPrincipal(fields) };
|
|
126
120
|
return await this.#modelService.create(this.#cls, this.#cls.from(castTo(output)));
|
|
127
121
|
}
|
|
@@ -134,15 +128,15 @@ export class ModelAuthService<T extends ModelType> implements Authenticator<T>,
|
|
|
134
128
|
*/
|
|
135
129
|
async changePassword(userId: string, password: string, oldPassword?: string): Promise<T> {
|
|
136
130
|
const user = await this.#retrieve(userId);
|
|
137
|
-
const
|
|
131
|
+
const identity = this.toPrincipal(user);
|
|
138
132
|
|
|
139
|
-
if (oldPassword ===
|
|
140
|
-
if (
|
|
133
|
+
if (oldPassword === identity.resetToken) {
|
|
134
|
+
if (identity.resetExpires && identity.resetExpires.getTime() < Date.now()) {
|
|
141
135
|
throw new AuthenticationError('Reset token has expired', { category: 'data' });
|
|
142
136
|
}
|
|
143
137
|
} else if (oldPassword !== undefined) {
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
138
|
+
const oldPasswordHash = await AuthModelUtil.generateHash(oldPassword, identity.salt!);
|
|
139
|
+
if (oldPasswordHash !== identity.hash) {
|
|
146
140
|
throw new AuthenticationError('Old password is required to change');
|
|
147
141
|
}
|
|
148
142
|
}
|
|
@@ -158,15 +152,15 @@ export class ModelAuthService<T extends ModelType> implements Authenticator<T>,
|
|
|
158
152
|
*/
|
|
159
153
|
async generateResetToken(userId: string): Promise<RegisteredPrincipal> {
|
|
160
154
|
const user = await this.#retrieve(userId);
|
|
161
|
-
const
|
|
155
|
+
const identity = this.toPrincipal(user);
|
|
162
156
|
const salt = await Util.uuid();
|
|
163
157
|
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
identity.resetToken = await AuthModelUtil.generateHash(Util.uuid(), salt, 25000, 32);
|
|
159
|
+
identity.resetExpires = TimeUtil.fromNow(1, 'h');
|
|
166
160
|
|
|
167
|
-
const output: Partial<T> = { ...user, ...this.fromPrincipal(
|
|
161
|
+
const output: Partial<T> = { ...user, ...this.fromPrincipal(identity) };
|
|
168
162
|
await this.#modelService.update(this.#cls, this.#cls.from(castTo(output)));
|
|
169
|
-
return
|
|
163
|
+
return identity;
|
|
170
164
|
}
|
|
171
165
|
|
|
172
166
|
/**
|
package/src/util.ts
CHANGED
|
@@ -21,7 +21,7 @@ export class AuthModelUtil {
|
|
|
21
21
|
*/
|
|
22
22
|
static generateHash(value: string, salt: string, iterations = 25000, keylen = 256, digest = 'sha256'): Promise<string> {
|
|
23
23
|
const half = Math.trunc(Math.ceil(keylen / 2));
|
|
24
|
-
return pbkdf2(value, salt, iterations, half, digest).then(
|
|
24
|
+
return pbkdf2(value, salt, iterations, half, digest).then(buffer => buffer.toString('hex').substring(0, keylen));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
package/support/test/model.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { ModelAuthService, RegisteredPrincipal } from '../../src/model.ts';
|
|
|
11
11
|
|
|
12
12
|
export const TestModelSvcSymbol = Symbol.for('@travetto/auth:test-model-svc');
|
|
13
13
|
|
|
14
|
-
@Model({ autoCreate:
|
|
14
|
+
@Model({ autoCreate: 'production' })
|
|
15
15
|
class User implements RegisteredPrincipal {
|
|
16
16
|
id: string;
|
|
17
17
|
@Transient()
|