@aranzatech/aranza-auth 0.1.2 → 0.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/CHANGELOG.md +47 -24
- package/README.md +106 -10
- package/SECURITY.md +58 -0
- package/dist/{auth-repository.interface-BMlJc-98.d.cts → auth-repository.interface-9PpDVOs8.d.cts} +32 -2
- package/dist/{auth-repository.interface-BMlJc-98.d.ts → auth-repository.interface-9PpDVOs8.d.ts} +32 -2
- package/dist/{chunk-JLRBMDLH.js → chunk-QNEFN5ES.js} +5 -5
- package/dist/chunk-QNEFN5ES.js.map +1 -0
- package/dist/index.cjs +274 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.js +273 -69
- package/dist/index.js.map +1 -1
- package/dist/mongo/index.cjs +56 -5
- package/dist/mongo/index.cjs.map +1 -1
- package/dist/mongo/index.d.cts +7 -1
- package/dist/mongo/index.d.ts +7 -1
- package/dist/mongo/index.js +57 -3
- package/dist/mongo/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-JLRBMDLH.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var common = require('@nestjs/common');
|
|
4
|
+
var core = require('@nestjs/core');
|
|
4
5
|
var jwt = require('@nestjs/jwt');
|
|
5
6
|
var passport = require('@nestjs/passport');
|
|
6
7
|
var bcrypt2 = require('bcryptjs');
|
|
@@ -39,6 +40,29 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
39
40
|
return result;
|
|
40
41
|
};
|
|
41
42
|
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
43
|
+
|
|
44
|
+
// src/constants/rate-limit.presets.ts
|
|
45
|
+
var AUTH_RATE_LIMIT_PRESETS = {
|
|
46
|
+
/** General auth routes: 10 requests / minute / IP */
|
|
47
|
+
default: { name: "auth-default", ttl: 6e4, limit: 10 },
|
|
48
|
+
/** Login, register, refresh: 5 requests / minute / IP */
|
|
49
|
+
credentials: { name: "auth-credentials", ttl: 6e4, limit: 5 },
|
|
50
|
+
/** Forgot password: 3 requests / minute / IP */
|
|
51
|
+
passwordReset: { name: "auth-password-reset", ttl: 6e4, limit: 3 }
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/constants/auth-errors.ts
|
|
55
|
+
var AuthErrorCode = {
|
|
56
|
+
INVALID_CREDENTIALS: "Invalid credentials",
|
|
57
|
+
INVALID_REFRESH_TOKEN: "Invalid refresh token",
|
|
58
|
+
REFRESH_TOKEN_REUSE: "REFRESH_TOKEN_REUSE",
|
|
59
|
+
ACCOUNT_DISABLED: "ACCOUNT_DISABLED",
|
|
60
|
+
EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
|
|
61
|
+
TOKEN_INVALID_OR_EXPIRED: "TOKEN_INVALID_OR_EXPIRED",
|
|
62
|
+
ACCOUNT_LOCKED: "ACCOUNT_LOCKED",
|
|
63
|
+
INVALID_CURRENT_PASSWORD: "INVALID_CURRENT_PASSWORD",
|
|
64
|
+
PASSWORD_UNCHANGED: "PASSWORD_UNCHANGED"
|
|
65
|
+
};
|
|
42
66
|
var CurrentUser = common.createParamDecorator(
|
|
43
67
|
(_data, ctx) => {
|
|
44
68
|
const request = ctx.switchToHttp().getRequest();
|
|
@@ -61,6 +85,9 @@ exports.JwtAuthGuard = __decorateClass([
|
|
|
61
85
|
var AUTH_MODULE_OPTIONS = "AUTH_MODULE_OPTIONS";
|
|
62
86
|
var AUTH_HOOKS = "AUTH_HOOKS";
|
|
63
87
|
var AUTH_REPOSITORY = "AUTH_REPOSITORY";
|
|
88
|
+
|
|
89
|
+
// src/constants/password.constants.ts
|
|
90
|
+
var DUMMY_PASSWORD_HASH = "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy";
|
|
64
91
|
exports.DefaultAuthHooks = class DefaultAuthHooks {
|
|
65
92
|
async buildJwtPayload(account) {
|
|
66
93
|
return {
|
|
@@ -75,7 +102,9 @@ exports.DefaultAuthHooks = class DefaultAuthHooks {
|
|
|
75
102
|
email: account.email,
|
|
76
103
|
username: account.username,
|
|
77
104
|
emailVerified: account.emailVerified,
|
|
78
|
-
disabled: account.disabled
|
|
105
|
+
disabled: account.disabled,
|
|
106
|
+
...account.lastLoginAt != null ? { lastLoginAt: account.lastLoginAt } : {},
|
|
107
|
+
...account.passwordChangedAt != null ? { passwordChangedAt: account.passwordChangedAt } : {}
|
|
79
108
|
};
|
|
80
109
|
}
|
|
81
110
|
async onBeforeRegister(_input) {
|
|
@@ -96,15 +125,21 @@ exports.DefaultAuthHooks = __decorateClass([
|
|
|
96
125
|
function isDuplicateKeyError(error) {
|
|
97
126
|
return !!error && typeof error === "object" && "code" in error && error.code === 11e3;
|
|
98
127
|
}
|
|
99
|
-
|
|
100
|
-
|
|
128
|
+
var COMPLEXITY_PATTERN = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/;
|
|
129
|
+
function assertPasswordComplexity(password) {
|
|
130
|
+
if (!COMPLEXITY_PATTERN.test(password)) {
|
|
131
|
+
throw new common.BadRequestException(
|
|
132
|
+
"Password must contain at least one uppercase letter, one lowercase letter, and one digit"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
101
136
|
function normalizeIdentifier(value) {
|
|
102
137
|
return value.trim().toLowerCase();
|
|
103
138
|
}
|
|
104
139
|
function resolveRegisterIdentifier(input, field) {
|
|
105
140
|
const value = field === "email" ? input.email : input.username;
|
|
106
141
|
if (value == null || value.trim() === "") {
|
|
107
|
-
throw new
|
|
142
|
+
throw new common.BadRequestException(`Register input requires ${field}`);
|
|
108
143
|
}
|
|
109
144
|
return normalizeIdentifier(value);
|
|
110
145
|
}
|
|
@@ -123,11 +158,15 @@ function expiresAtFromTtlMs(ttlMs) {
|
|
|
123
158
|
}
|
|
124
159
|
var DEFAULT_EMAIL_VERIFICATION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
125
160
|
var DEFAULT_PASSWORD_RESET_TTL_MS = 15 * 60 * 1e3;
|
|
161
|
+
var JWT_ALGORITHM = "HS256";
|
|
126
162
|
exports.TokenService = class TokenService {
|
|
127
163
|
constructor(jwtService, options) {
|
|
128
164
|
this.jwtService = jwtService;
|
|
129
165
|
this.options = options;
|
|
130
166
|
}
|
|
167
|
+
get bcryptRounds() {
|
|
168
|
+
return this.options.bcryptRounds ?? 10;
|
|
169
|
+
}
|
|
131
170
|
async signTokens(payload) {
|
|
132
171
|
const accessExpiresIn = this.options.expiresIn ?? "1h";
|
|
133
172
|
const refreshExpiresIn = this.options.refreshExpiresIn ?? "7d";
|
|
@@ -136,14 +175,16 @@ exports.TokenService = class TokenService {
|
|
|
136
175
|
payload,
|
|
137
176
|
{
|
|
138
177
|
secret: this.options.secret,
|
|
139
|
-
expiresIn: accessExpiresIn
|
|
178
|
+
expiresIn: accessExpiresIn,
|
|
179
|
+
algorithm: JWT_ALGORITHM
|
|
140
180
|
}
|
|
141
181
|
),
|
|
142
182
|
this.jwtService.signAsync(
|
|
143
183
|
payload,
|
|
144
184
|
{
|
|
145
185
|
secret: this.options.refreshSecret,
|
|
146
|
-
expiresIn: refreshExpiresIn
|
|
186
|
+
expiresIn: refreshExpiresIn,
|
|
187
|
+
algorithm: JWT_ALGORITHM
|
|
147
188
|
}
|
|
148
189
|
)
|
|
149
190
|
]);
|
|
@@ -151,11 +192,12 @@ exports.TokenService = class TokenService {
|
|
|
151
192
|
}
|
|
152
193
|
async verifyRefreshToken(refreshToken) {
|
|
153
194
|
return this.jwtService.verifyAsync(refreshToken, {
|
|
154
|
-
secret: this.options.refreshSecret
|
|
195
|
+
secret: this.options.refreshSecret,
|
|
196
|
+
algorithms: [JWT_ALGORITHM]
|
|
155
197
|
});
|
|
156
198
|
}
|
|
157
199
|
async hashRefreshToken(refreshToken) {
|
|
158
|
-
return bcrypt2__namespace.hash(refreshToken,
|
|
200
|
+
return bcrypt2__namespace.hash(refreshToken, this.bcryptRounds);
|
|
159
201
|
}
|
|
160
202
|
async compareRefreshToken(refreshToken, hash3) {
|
|
161
203
|
return bcrypt2__namespace.compare(refreshToken, hash3);
|
|
@@ -187,6 +229,15 @@ exports.AuthService = class AuthService {
|
|
|
187
229
|
get rotateRefreshToken() {
|
|
188
230
|
return this.options.features?.refreshTokenRotation !== false;
|
|
189
231
|
}
|
|
232
|
+
get bcryptRounds() {
|
|
233
|
+
return this.options.bcryptRounds ?? 10;
|
|
234
|
+
}
|
|
235
|
+
get accountLockoutEnabled() {
|
|
236
|
+
return this.options.features?.accountLockout === true;
|
|
237
|
+
}
|
|
238
|
+
get lockoutOptions() {
|
|
239
|
+
return this.options.lockout;
|
|
240
|
+
}
|
|
190
241
|
resolveLoginIdentifier(dto) {
|
|
191
242
|
const value = this.identifierField === "email" ? dto.email : dto.username;
|
|
192
243
|
if (value == null || value.trim() === "") {
|
|
@@ -204,7 +255,8 @@ exports.AuthService = class AuthService {
|
|
|
204
255
|
await this.hooks.onBeforeRegister?.(input);
|
|
205
256
|
resolveRegisterIdentifier(input, this.identifierField);
|
|
206
257
|
this.assertRegisterEmailWhenVerificationEnabled(input);
|
|
207
|
-
|
|
258
|
+
this.assertPasswordPolicy(dto.password);
|
|
259
|
+
const passwordHash = await bcrypt2__namespace.hash(dto.password, this.bcryptRounds);
|
|
208
260
|
try {
|
|
209
261
|
const account = await this.authRepository.create({
|
|
210
262
|
...input,
|
|
@@ -217,7 +269,7 @@ exports.AuthService = class AuthService {
|
|
|
217
269
|
}
|
|
218
270
|
} catch (error) {
|
|
219
271
|
if (isDuplicateKeyError(error)) {
|
|
220
|
-
|
|
272
|
+
return { registered: true };
|
|
221
273
|
}
|
|
222
274
|
throw error;
|
|
223
275
|
}
|
|
@@ -228,17 +280,22 @@ exports.AuthService = class AuthService {
|
|
|
228
280
|
const account = await this.authRepository.findByIdentifierWithSecrets(
|
|
229
281
|
identifier
|
|
230
282
|
);
|
|
231
|
-
if (account
|
|
232
|
-
|
|
283
|
+
if (account != null) {
|
|
284
|
+
this.assertAccountNotLocked(account);
|
|
233
285
|
}
|
|
234
|
-
|
|
235
|
-
const passwordMatches = await bcrypt2__namespace.compare(
|
|
236
|
-
|
|
237
|
-
account.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
286
|
+
const passwordHash = account?.passwordHash ?? DUMMY_PASSWORD_HASH;
|
|
287
|
+
const passwordMatches = await bcrypt2__namespace.compare(dto.password, passwordHash);
|
|
288
|
+
if (account?.passwordHash == null || !passwordMatches) {
|
|
289
|
+
if (account != null && this.accountLockoutEnabled) {
|
|
290
|
+
await this.authRepository.recordLoginFailure(
|
|
291
|
+
account.id,
|
|
292
|
+
this.lockoutOptions
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
throw new common.UnauthorizedException(AuthErrorCode.INVALID_CREDENTIALS);
|
|
241
296
|
}
|
|
297
|
+
this.assertAccountActive(account);
|
|
298
|
+
await this.authRepository.recordLoginSuccess(account.id);
|
|
242
299
|
return this.issueTokens(account);
|
|
243
300
|
}
|
|
244
301
|
async refresh(refreshToken) {
|
|
@@ -246,19 +303,25 @@ exports.AuthService = class AuthService {
|
|
|
246
303
|
try {
|
|
247
304
|
payload = await this.tokenService.verifyRefreshToken(refreshToken);
|
|
248
305
|
} catch {
|
|
249
|
-
throw new common.UnauthorizedException(
|
|
306
|
+
throw new common.UnauthorizedException(AuthErrorCode.INVALID_REFRESH_TOKEN);
|
|
250
307
|
}
|
|
251
308
|
const account = await this.authRepository.findByIdWithSecrets(payload.sub);
|
|
252
|
-
if (account
|
|
253
|
-
throw new common.UnauthorizedException(
|
|
309
|
+
if (account == null) {
|
|
310
|
+
throw new common.UnauthorizedException(AuthErrorCode.INVALID_REFRESH_TOKEN);
|
|
254
311
|
}
|
|
255
312
|
this.assertAccountActive(account);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
313
|
+
if (this.rotateRefreshToken) {
|
|
314
|
+
if (account.refreshTokenHash == null) {
|
|
315
|
+
throw new common.UnauthorizedException(AuthErrorCode.INVALID_REFRESH_TOKEN);
|
|
316
|
+
}
|
|
317
|
+
const tokenMatches = await this.tokenService.compareRefreshToken(
|
|
318
|
+
refreshToken,
|
|
319
|
+
account.refreshTokenHash
|
|
320
|
+
);
|
|
321
|
+
if (!tokenMatches) {
|
|
322
|
+
await this.authRepository.updateRefreshTokenHash(account.id, null);
|
|
323
|
+
throw new common.UnauthorizedException(AuthErrorCode.REFRESH_TOKEN_REUSE);
|
|
324
|
+
}
|
|
262
325
|
}
|
|
263
326
|
return this.issueTokens(account);
|
|
264
327
|
}
|
|
@@ -281,7 +344,7 @@ exports.AuthService = class AuthService {
|
|
|
281
344
|
const tokenHash = hashToken(token);
|
|
282
345
|
const account = await this.authRepository.findByEmailVerificationTokenHash(tokenHash);
|
|
283
346
|
if (account == null) {
|
|
284
|
-
throw new common.BadRequestException(
|
|
347
|
+
throw new common.BadRequestException(AuthErrorCode.TOKEN_INVALID_OR_EXPIRED);
|
|
285
348
|
}
|
|
286
349
|
await this.authRepository.markEmailVerified(account.id);
|
|
287
350
|
return { verified: true };
|
|
@@ -307,20 +370,55 @@ exports.AuthService = class AuthService {
|
|
|
307
370
|
const tokenHash = hashToken(token);
|
|
308
371
|
const account = await this.authRepository.findByResetTokenHash(tokenHash);
|
|
309
372
|
if (account == null) {
|
|
310
|
-
throw new common.BadRequestException(
|
|
373
|
+
throw new common.BadRequestException(AuthErrorCode.TOKEN_INVALID_OR_EXPIRED);
|
|
311
374
|
}
|
|
312
|
-
|
|
375
|
+
this.assertPasswordPolicy(newPassword);
|
|
376
|
+
const passwordHash = await bcrypt2__namespace.hash(newPassword, this.bcryptRounds);
|
|
313
377
|
await this.authRepository.updatePasswordHash(account.id, passwordHash);
|
|
314
378
|
await this.authRepository.clearResetToken(account.id);
|
|
315
379
|
await this.authRepository.updateRefreshTokenHash(account.id, null);
|
|
316
380
|
return { reset: true };
|
|
317
381
|
}
|
|
382
|
+
async changePassword(authId, currentPassword, newPassword) {
|
|
383
|
+
const account = await this.authRepository.findByIdWithSecrets(authId);
|
|
384
|
+
if (account?.passwordHash == null) {
|
|
385
|
+
throw new common.UnauthorizedException(AuthErrorCode.INVALID_CURRENT_PASSWORD);
|
|
386
|
+
}
|
|
387
|
+
const currentMatches = await bcrypt2__namespace.compare(
|
|
388
|
+
currentPassword,
|
|
389
|
+
account.passwordHash
|
|
390
|
+
);
|
|
391
|
+
if (!currentMatches) {
|
|
392
|
+
throw new common.UnauthorizedException(AuthErrorCode.INVALID_CURRENT_PASSWORD);
|
|
393
|
+
}
|
|
394
|
+
if (currentPassword === newPassword) {
|
|
395
|
+
throw new common.BadRequestException(AuthErrorCode.PASSWORD_UNCHANGED);
|
|
396
|
+
}
|
|
397
|
+
this.assertPasswordPolicy(newPassword);
|
|
398
|
+
const passwordHash = await bcrypt2__namespace.hash(newPassword, this.bcryptRounds);
|
|
399
|
+
await this.authRepository.updatePasswordHash(account.id, passwordHash);
|
|
400
|
+
await this.authRepository.updateRefreshTokenHash(account.id, null);
|
|
401
|
+
return { changed: true };
|
|
402
|
+
}
|
|
403
|
+
assertAccountNotLocked(account) {
|
|
404
|
+
if (!this.accountLockoutEnabled || account.lockedUntil == null) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (account.lockedUntil > /* @__PURE__ */ new Date()) {
|
|
408
|
+
throw new common.UnauthorizedException(AuthErrorCode.ACCOUNT_LOCKED);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
318
411
|
assertAccountActive(account) {
|
|
319
412
|
if (account.disabled) {
|
|
320
|
-
throw new common.UnauthorizedException(
|
|
413
|
+
throw new common.UnauthorizedException(AuthErrorCode.ACCOUNT_DISABLED);
|
|
321
414
|
}
|
|
322
415
|
if (this.emailVerificationEnabled && !account.emailVerified) {
|
|
323
|
-
throw new common.UnauthorizedException(
|
|
416
|
+
throw new common.UnauthorizedException(AuthErrorCode.EMAIL_NOT_VERIFIED);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
assertPasswordPolicy(password) {
|
|
420
|
+
if (this.options.passwordComplexity === true) {
|
|
421
|
+
assertPasswordComplexity(password);
|
|
324
422
|
}
|
|
325
423
|
}
|
|
326
424
|
async issueTokens(account) {
|
|
@@ -436,6 +534,13 @@ var AuthController = class {
|
|
|
436
534
|
resetPassword(dto) {
|
|
437
535
|
return this.authService.resetPassword(dto.token, dto.newPassword);
|
|
438
536
|
}
|
|
537
|
+
changePassword(user, dto) {
|
|
538
|
+
return this.authService.changePassword(
|
|
539
|
+
user.sub,
|
|
540
|
+
dto.currentPassword,
|
|
541
|
+
dto.newPassword
|
|
542
|
+
);
|
|
543
|
+
}
|
|
439
544
|
};
|
|
440
545
|
__decorateClass([
|
|
441
546
|
common.Post("register"),
|
|
@@ -476,17 +581,39 @@ __decorateClass([
|
|
|
476
581
|
common.HttpCode(common.HttpStatus.OK),
|
|
477
582
|
__decorateParam(0, common.Body())
|
|
478
583
|
], AuthController.prototype, "resetPassword", 1);
|
|
584
|
+
__decorateClass([
|
|
585
|
+
common.Post("change-password"),
|
|
586
|
+
common.UseGuards(exports.JwtAuthGuard),
|
|
587
|
+
common.HttpCode(common.HttpStatus.OK),
|
|
588
|
+
__decorateParam(0, CurrentUser()),
|
|
589
|
+
__decorateParam(1, common.Body())
|
|
590
|
+
], AuthController.prototype, "changePassword", 1);
|
|
479
591
|
AuthController = __decorateClass([
|
|
480
592
|
common.Controller("auth"),
|
|
481
593
|
__decorateParam(0, common.Inject(exports.AuthService))
|
|
482
594
|
], AuthController);
|
|
595
|
+
|
|
596
|
+
// src/controllers/auth.controller.factory.ts
|
|
597
|
+
function createAuthController(routePrefix = "auth") {
|
|
598
|
+
let ConfiguredAuthController = class extends AuthController {
|
|
599
|
+
};
|
|
600
|
+
ConfiguredAuthController = __decorateClass([
|
|
601
|
+
common.Controller(routePrefix)
|
|
602
|
+
], ConfiguredAuthController);
|
|
603
|
+
Object.defineProperty(ConfiguredAuthController, "name", {
|
|
604
|
+
value: `AuthController_${routePrefix.replace(/\W+/g, "_")}`
|
|
605
|
+
});
|
|
606
|
+
return ConfiguredAuthController;
|
|
607
|
+
}
|
|
483
608
|
var JwtStrategy = class extends passport.PassportStrategy(passportJwt.Strategy) {
|
|
484
609
|
constructor(options, authRepository) {
|
|
485
610
|
super({
|
|
486
611
|
jwtFromRequest: passportJwt.ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
487
612
|
ignoreExpiration: false,
|
|
488
|
-
secretOrKey: options.secret
|
|
613
|
+
secretOrKey: options.secret,
|
|
614
|
+
algorithms: ["HS256"]
|
|
489
615
|
});
|
|
616
|
+
this.options = options;
|
|
490
617
|
this.authRepository = authRepository;
|
|
491
618
|
}
|
|
492
619
|
async validate(payload) {
|
|
@@ -494,6 +621,9 @@ var JwtStrategy = class extends passport.PassportStrategy(passportJwt.Strategy)
|
|
|
494
621
|
if (account == null || account.disabled) {
|
|
495
622
|
throw new common.UnauthorizedException("Account not found or inactive");
|
|
496
623
|
}
|
|
624
|
+
if (this.options.features?.emailVerification === true && !account.emailVerified) {
|
|
625
|
+
throw new common.UnauthorizedException(AuthErrorCode.EMAIL_NOT_VERIFIED);
|
|
626
|
+
}
|
|
497
627
|
return payload;
|
|
498
628
|
}
|
|
499
629
|
};
|
|
@@ -503,27 +633,82 @@ JwtStrategy = __decorateClass([
|
|
|
503
633
|
__decorateParam(1, common.Inject(AUTH_REPOSITORY))
|
|
504
634
|
], JwtStrategy);
|
|
505
635
|
|
|
636
|
+
// src/utils/hooks-provider.util.ts
|
|
637
|
+
function createHooksProvider(options) {
|
|
638
|
+
if (options.hooksProvider != null) {
|
|
639
|
+
return options.hooksProvider;
|
|
640
|
+
}
|
|
641
|
+
const HooksClass = options.hooks ?? exports.DefaultAuthHooks;
|
|
642
|
+
return {
|
|
643
|
+
provide: AUTH_HOOKS,
|
|
644
|
+
useClass: HooksClass
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/utils/validate-auth-config.util.ts
|
|
649
|
+
var MIN_SECRET_LENGTH = 32;
|
|
650
|
+
var MIN_BCRYPT_ROUNDS = 10;
|
|
651
|
+
var MAX_BCRYPT_ROUNDS = 14;
|
|
652
|
+
function validateAuthModuleOptions(options) {
|
|
653
|
+
if (options.secret.length < MIN_SECRET_LENGTH) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`AuthModule: secret must be at least ${MIN_SECRET_LENGTH} characters`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
if (options.refreshSecret.length < MIN_SECRET_LENGTH) {
|
|
659
|
+
throw new Error(
|
|
660
|
+
`AuthModule: refreshSecret must be at least ${MIN_SECRET_LENGTH} characters`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
if (options.secret === options.refreshSecret) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
"AuthModule: secret and refreshSecret must be different"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
const rounds = options.bcryptRounds ?? MIN_BCRYPT_ROUNDS;
|
|
669
|
+
if (rounds < MIN_BCRYPT_ROUNDS || rounds > MAX_BCRYPT_ROUNDS) {
|
|
670
|
+
throw new Error(
|
|
671
|
+
`AuthModule: bcryptRounds must be between ${MIN_BCRYPT_ROUNDS} and ${MAX_BCRYPT_ROUNDS}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
if (options.emailVerificationTokenTtlMs != null && options.emailVerificationTokenTtlMs < 6e4) {
|
|
675
|
+
throw new Error(
|
|
676
|
+
"AuthModule: emailVerificationTokenTtlMs must be at least 60000 (1 minute)"
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
if (options.passwordResetTokenTtlMs != null && options.passwordResetTokenTtlMs < 6e4) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
"AuthModule: passwordResetTokenTtlMs must be at least 60000 (1 minute)"
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
506
686
|
// src/auth.module.ts
|
|
507
|
-
function
|
|
687
|
+
function createCoreProviders(options) {
|
|
688
|
+
validateAuthModuleOptions(options);
|
|
508
689
|
return [
|
|
509
690
|
{
|
|
510
691
|
provide: AUTH_MODULE_OPTIONS,
|
|
511
692
|
useValue: options
|
|
512
693
|
},
|
|
513
|
-
|
|
514
|
-
provide: AUTH_HOOKS,
|
|
515
|
-
inject: [AUTH_MODULE_OPTIONS],
|
|
516
|
-
useFactory: (opts) => {
|
|
517
|
-
const HooksClass = opts.hooks ?? exports.DefaultAuthHooks;
|
|
518
|
-
return new HooksClass();
|
|
519
|
-
}
|
|
520
|
-
},
|
|
694
|
+
createHooksProvider(options),
|
|
521
695
|
exports.AuthService,
|
|
522
696
|
exports.TokenService,
|
|
523
697
|
JwtStrategy,
|
|
524
698
|
exports.JwtAuthGuard
|
|
525
699
|
];
|
|
526
700
|
}
|
|
701
|
+
function createAsyncHooksProvider(options) {
|
|
702
|
+
if (options.hooksProvider != null) {
|
|
703
|
+
return options.hooksProvider;
|
|
704
|
+
}
|
|
705
|
+
const HooksClass = options.hooks ?? exports.DefaultAuthHooks;
|
|
706
|
+
return {
|
|
707
|
+
provide: AUTH_HOOKS,
|
|
708
|
+
inject: [core.ModuleRef],
|
|
709
|
+
useFactory: (moduleRef) => moduleRef.create(HooksClass)
|
|
710
|
+
};
|
|
711
|
+
}
|
|
527
712
|
function createAuthImports() {
|
|
528
713
|
return [
|
|
529
714
|
passport.PassportModule.register({ defaultStrategy: "jwt" }),
|
|
@@ -531,7 +716,10 @@ function createAuthImports() {
|
|
|
531
716
|
inject: [AUTH_MODULE_OPTIONS],
|
|
532
717
|
useFactory: (opts) => ({
|
|
533
718
|
secret: opts.secret,
|
|
534
|
-
signOptions: {
|
|
719
|
+
signOptions: {
|
|
720
|
+
expiresIn: opts.expiresIn ?? "1h",
|
|
721
|
+
algorithm: "HS256"
|
|
722
|
+
}
|
|
535
723
|
})
|
|
536
724
|
})
|
|
537
725
|
];
|
|
@@ -543,14 +731,33 @@ function mergeImports(userImports) {
|
|
|
543
731
|
}
|
|
544
732
|
return merged;
|
|
545
733
|
}
|
|
734
|
+
function createAsyncProviders(options) {
|
|
735
|
+
return [
|
|
736
|
+
{
|
|
737
|
+
provide: AUTH_MODULE_OPTIONS,
|
|
738
|
+
inject: options.inject ?? [],
|
|
739
|
+
useFactory: async (...args) => {
|
|
740
|
+
const config = await options.useFactory(...args);
|
|
741
|
+
validateAuthModuleOptions(config);
|
|
742
|
+
return config;
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
createAsyncHooksProvider(options),
|
|
746
|
+
exports.AuthService,
|
|
747
|
+
exports.TokenService,
|
|
748
|
+
JwtStrategy,
|
|
749
|
+
exports.JwtAuthGuard
|
|
750
|
+
];
|
|
751
|
+
}
|
|
546
752
|
exports.AuthModule = class AuthModule {
|
|
547
753
|
static forRoot(options) {
|
|
754
|
+
const routePrefix = options.routePrefix ?? "auth";
|
|
548
755
|
return {
|
|
549
756
|
module: exports.AuthModule,
|
|
550
757
|
global: true,
|
|
551
758
|
imports: createAuthImports(),
|
|
552
|
-
controllers: [
|
|
553
|
-
providers:
|
|
759
|
+
controllers: [createAuthController(routePrefix)],
|
|
760
|
+
providers: createCoreProviders(options),
|
|
554
761
|
exports: [
|
|
555
762
|
AUTH_MODULE_OPTIONS,
|
|
556
763
|
AUTH_HOOKS,
|
|
@@ -563,30 +770,13 @@ exports.AuthModule = class AuthModule {
|
|
|
563
770
|
};
|
|
564
771
|
}
|
|
565
772
|
static forRootAsync(options) {
|
|
773
|
+
const routePrefix = options.routePrefix ?? "auth";
|
|
566
774
|
return {
|
|
567
775
|
module: exports.AuthModule,
|
|
568
776
|
global: true,
|
|
569
777
|
imports: mergeImports(options.imports),
|
|
570
|
-
controllers: [
|
|
571
|
-
providers:
|
|
572
|
-
{
|
|
573
|
-
provide: AUTH_MODULE_OPTIONS,
|
|
574
|
-
inject: options.inject ?? [],
|
|
575
|
-
useFactory: options.useFactory
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
provide: AUTH_HOOKS,
|
|
579
|
-
inject: [AUTH_MODULE_OPTIONS],
|
|
580
|
-
useFactory: (opts) => {
|
|
581
|
-
const HooksClass = opts.hooks ?? exports.DefaultAuthHooks;
|
|
582
|
-
return new HooksClass();
|
|
583
|
-
}
|
|
584
|
-
},
|
|
585
|
-
exports.AuthService,
|
|
586
|
-
exports.TokenService,
|
|
587
|
-
JwtStrategy,
|
|
588
|
-
exports.JwtAuthGuard
|
|
589
|
-
],
|
|
778
|
+
controllers: [createAuthController(routePrefix)],
|
|
779
|
+
providers: createAsyncProviders(options),
|
|
590
780
|
exports: [
|
|
591
781
|
AUTH_MODULE_OPTIONS,
|
|
592
782
|
AUTH_HOOKS,
|
|
@@ -606,6 +796,17 @@ exports.AuthModule = __decorateClass([
|
|
|
606
796
|
// src/dto/auth-tokens.dto.ts
|
|
607
797
|
var AuthTokensDto = class {
|
|
608
798
|
};
|
|
799
|
+
var ChangePasswordDto = class {
|
|
800
|
+
};
|
|
801
|
+
__decorateClass([
|
|
802
|
+
classValidator.IsString(),
|
|
803
|
+
classValidator.IsNotEmpty()
|
|
804
|
+
], ChangePasswordDto.prototype, "currentPassword", 2);
|
|
805
|
+
__decorateClass([
|
|
806
|
+
classValidator.IsString(),
|
|
807
|
+
classValidator.IsNotEmpty(),
|
|
808
|
+
classValidator.Length(8, 128)
|
|
809
|
+
], ChangePasswordDto.prototype, "newPassword", 2);
|
|
609
810
|
var ForgotPasswordDto = class {
|
|
610
811
|
};
|
|
611
812
|
__decorateClass([
|
|
@@ -615,7 +816,8 @@ var LoginDto = class {
|
|
|
615
816
|
};
|
|
616
817
|
__decorateClass([
|
|
617
818
|
classValidator.IsOptional(),
|
|
618
|
-
classValidator.
|
|
819
|
+
classValidator.ValidateIf((dto) => dto.email != null && dto.email.trim() !== ""),
|
|
820
|
+
classValidator.IsEmail(),
|
|
619
821
|
classValidator.Length(3, 255)
|
|
620
822
|
], LoginDto.prototype, "email", 2);
|
|
621
823
|
__decorateClass([
|
|
@@ -642,7 +844,8 @@ var RegisterDto = class {
|
|
|
642
844
|
};
|
|
643
845
|
__decorateClass([
|
|
644
846
|
classValidator.IsOptional(),
|
|
645
|
-
classValidator.
|
|
847
|
+
classValidator.ValidateIf((dto) => dto.email != null && dto.email.trim() !== ""),
|
|
848
|
+
classValidator.IsEmail(),
|
|
646
849
|
classValidator.Length(3, 255)
|
|
647
850
|
], RegisterDto.prototype, "email", 2);
|
|
648
851
|
__decorateClass([
|
|
@@ -676,8 +879,11 @@ __decorateClass([
|
|
|
676
879
|
|
|
677
880
|
exports.AUTH_HOOKS = AUTH_HOOKS;
|
|
678
881
|
exports.AUTH_MODULE_OPTIONS = AUTH_MODULE_OPTIONS;
|
|
882
|
+
exports.AUTH_RATE_LIMIT_PRESETS = AUTH_RATE_LIMIT_PRESETS;
|
|
679
883
|
exports.AUTH_REPOSITORY = AUTH_REPOSITORY;
|
|
884
|
+
exports.AuthErrorCode = AuthErrorCode;
|
|
680
885
|
exports.AuthTokensDto = AuthTokensDto;
|
|
886
|
+
exports.ChangePasswordDto = ChangePasswordDto;
|
|
681
887
|
exports.CurrentUser = CurrentUser;
|
|
682
888
|
exports.ForgotPasswordDto = ForgotPasswordDto;
|
|
683
889
|
exports.LoginDto = LoginDto;
|