@aranzatech/aranza-auth 0.1.2 → 0.2.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/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.js
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
|
-
import { __decorateClass, __decorateParam, AUTH_MODULE_OPTIONS, AUTH_REPOSITORY, AUTH_HOOKS, normalizeIdentifier, resolveRegisterIdentifier, readAccountIdentifier } from './chunk-
|
|
2
|
-
export { AUTH_HOOKS, AUTH_MODULE_OPTIONS, AUTH_REPOSITORY } from './chunk-
|
|
1
|
+
import { __decorateClass, __decorateParam, AUTH_MODULE_OPTIONS, AUTH_REPOSITORY, AUTH_HOOKS, normalizeIdentifier, resolveRegisterIdentifier, readAccountIdentifier } from './chunk-QNEFN5ES.js';
|
|
2
|
+
export { AUTH_HOOKS, AUTH_MODULE_OPTIONS, AUTH_REPOSITORY } from './chunk-QNEFN5ES.js';
|
|
3
3
|
import { createParamDecorator, UnauthorizedException, Injectable, Inject, Post, Body, HttpCode, HttpStatus, UseGuards, Get, Controller, Module, BadRequestException, NotFoundException } from '@nestjs/common';
|
|
4
|
+
import { ModuleRef } from '@nestjs/core';
|
|
4
5
|
import { JwtService, JwtModule } from '@nestjs/jwt';
|
|
5
6
|
import { AuthGuard, PassportStrategy, PassportModule } from '@nestjs/passport';
|
|
6
7
|
import * as bcrypt2 from 'bcryptjs';
|
|
7
8
|
import { createHash, randomBytes } from 'crypto';
|
|
8
9
|
import { Strategy, ExtractJwt } from 'passport-jwt';
|
|
9
|
-
import {
|
|
10
|
+
import { IsString, IsNotEmpty, Length, IsEmail, IsOptional, ValidateIf, Matches } from 'class-validator';
|
|
10
11
|
|
|
12
|
+
// src/constants/rate-limit.presets.ts
|
|
13
|
+
var AUTH_RATE_LIMIT_PRESETS = {
|
|
14
|
+
/** General auth routes: 10 requests / minute / IP */
|
|
15
|
+
default: { name: "auth-default", ttl: 6e4, limit: 10 },
|
|
16
|
+
/** Login, register, refresh: 5 requests / minute / IP */
|
|
17
|
+
credentials: { name: "auth-credentials", ttl: 6e4, limit: 5 },
|
|
18
|
+
/** Forgot password: 3 requests / minute / IP */
|
|
19
|
+
passwordReset: { name: "auth-password-reset", ttl: 6e4, limit: 3 }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/constants/auth-errors.ts
|
|
23
|
+
var AuthErrorCode = {
|
|
24
|
+
INVALID_CREDENTIALS: "Invalid credentials",
|
|
25
|
+
INVALID_REFRESH_TOKEN: "Invalid refresh token",
|
|
26
|
+
REFRESH_TOKEN_REUSE: "REFRESH_TOKEN_REUSE",
|
|
27
|
+
ACCOUNT_DISABLED: "ACCOUNT_DISABLED",
|
|
28
|
+
EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
|
|
29
|
+
TOKEN_INVALID_OR_EXPIRED: "TOKEN_INVALID_OR_EXPIRED",
|
|
30
|
+
ACCOUNT_LOCKED: "ACCOUNT_LOCKED",
|
|
31
|
+
INVALID_CURRENT_PASSWORD: "INVALID_CURRENT_PASSWORD",
|
|
32
|
+
PASSWORD_UNCHANGED: "PASSWORD_UNCHANGED"
|
|
33
|
+
};
|
|
11
34
|
var CurrentUser = createParamDecorator(
|
|
12
35
|
(_data, ctx) => {
|
|
13
36
|
const request = ctx.switchToHttp().getRequest();
|
|
@@ -25,6 +48,9 @@ var JwtAuthGuard = class extends AuthGuard("jwt") {
|
|
|
25
48
|
JwtAuthGuard = __decorateClass([
|
|
26
49
|
Injectable()
|
|
27
50
|
], JwtAuthGuard);
|
|
51
|
+
|
|
52
|
+
// src/constants/password.constants.ts
|
|
53
|
+
var DUMMY_PASSWORD_HASH = "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy";
|
|
28
54
|
var DefaultAuthHooks = class {
|
|
29
55
|
async buildJwtPayload(account) {
|
|
30
56
|
return {
|
|
@@ -39,7 +65,9 @@ var DefaultAuthHooks = class {
|
|
|
39
65
|
email: account.email,
|
|
40
66
|
username: account.username,
|
|
41
67
|
emailVerified: account.emailVerified,
|
|
42
|
-
disabled: account.disabled
|
|
68
|
+
disabled: account.disabled,
|
|
69
|
+
...account.lastLoginAt != null ? { lastLoginAt: account.lastLoginAt } : {},
|
|
70
|
+
...account.passwordChangedAt != null ? { passwordChangedAt: account.passwordChangedAt } : {}
|
|
43
71
|
};
|
|
44
72
|
}
|
|
45
73
|
async onBeforeRegister(_input) {
|
|
@@ -60,6 +88,14 @@ DefaultAuthHooks = __decorateClass([
|
|
|
60
88
|
function isDuplicateKeyError(error) {
|
|
61
89
|
return !!error && typeof error === "object" && "code" in error && error.code === 11e3;
|
|
62
90
|
}
|
|
91
|
+
var COMPLEXITY_PATTERN = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/;
|
|
92
|
+
function assertPasswordComplexity(password) {
|
|
93
|
+
if (!COMPLEXITY_PATTERN.test(password)) {
|
|
94
|
+
throw new BadRequestException(
|
|
95
|
+
"Password must contain at least one uppercase letter, one lowercase letter, and one digit"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
63
99
|
function generateRawToken(byteLength = 32) {
|
|
64
100
|
return randomBytes(byteLength).toString("hex");
|
|
65
101
|
}
|
|
@@ -71,11 +107,15 @@ function expiresAtFromTtlMs(ttlMs) {
|
|
|
71
107
|
}
|
|
72
108
|
var DEFAULT_EMAIL_VERIFICATION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
73
109
|
var DEFAULT_PASSWORD_RESET_TTL_MS = 15 * 60 * 1e3;
|
|
110
|
+
var JWT_ALGORITHM = "HS256";
|
|
74
111
|
var TokenService = class {
|
|
75
112
|
constructor(jwtService, options) {
|
|
76
113
|
this.jwtService = jwtService;
|
|
77
114
|
this.options = options;
|
|
78
115
|
}
|
|
116
|
+
get bcryptRounds() {
|
|
117
|
+
return this.options.bcryptRounds ?? 10;
|
|
118
|
+
}
|
|
79
119
|
async signTokens(payload) {
|
|
80
120
|
const accessExpiresIn = this.options.expiresIn ?? "1h";
|
|
81
121
|
const refreshExpiresIn = this.options.refreshExpiresIn ?? "7d";
|
|
@@ -84,14 +124,16 @@ var TokenService = class {
|
|
|
84
124
|
payload,
|
|
85
125
|
{
|
|
86
126
|
secret: this.options.secret,
|
|
87
|
-
expiresIn: accessExpiresIn
|
|
127
|
+
expiresIn: accessExpiresIn,
|
|
128
|
+
algorithm: JWT_ALGORITHM
|
|
88
129
|
}
|
|
89
130
|
),
|
|
90
131
|
this.jwtService.signAsync(
|
|
91
132
|
payload,
|
|
92
133
|
{
|
|
93
134
|
secret: this.options.refreshSecret,
|
|
94
|
-
expiresIn: refreshExpiresIn
|
|
135
|
+
expiresIn: refreshExpiresIn,
|
|
136
|
+
algorithm: JWT_ALGORITHM
|
|
95
137
|
}
|
|
96
138
|
)
|
|
97
139
|
]);
|
|
@@ -99,11 +141,12 @@ var TokenService = class {
|
|
|
99
141
|
}
|
|
100
142
|
async verifyRefreshToken(refreshToken) {
|
|
101
143
|
return this.jwtService.verifyAsync(refreshToken, {
|
|
102
|
-
secret: this.options.refreshSecret
|
|
144
|
+
secret: this.options.refreshSecret,
|
|
145
|
+
algorithms: [JWT_ALGORITHM]
|
|
103
146
|
});
|
|
104
147
|
}
|
|
105
148
|
async hashRefreshToken(refreshToken) {
|
|
106
|
-
return bcrypt2.hash(refreshToken,
|
|
149
|
+
return bcrypt2.hash(refreshToken, this.bcryptRounds);
|
|
107
150
|
}
|
|
108
151
|
async compareRefreshToken(refreshToken, hash3) {
|
|
109
152
|
return bcrypt2.compare(refreshToken, hash3);
|
|
@@ -135,6 +178,15 @@ var AuthService = class {
|
|
|
135
178
|
get rotateRefreshToken() {
|
|
136
179
|
return this.options.features?.refreshTokenRotation !== false;
|
|
137
180
|
}
|
|
181
|
+
get bcryptRounds() {
|
|
182
|
+
return this.options.bcryptRounds ?? 10;
|
|
183
|
+
}
|
|
184
|
+
get accountLockoutEnabled() {
|
|
185
|
+
return this.options.features?.accountLockout === true;
|
|
186
|
+
}
|
|
187
|
+
get lockoutOptions() {
|
|
188
|
+
return this.options.lockout;
|
|
189
|
+
}
|
|
138
190
|
resolveLoginIdentifier(dto) {
|
|
139
191
|
const value = this.identifierField === "email" ? dto.email : dto.username;
|
|
140
192
|
if (value == null || value.trim() === "") {
|
|
@@ -152,7 +204,8 @@ var AuthService = class {
|
|
|
152
204
|
await this.hooks.onBeforeRegister?.(input);
|
|
153
205
|
resolveRegisterIdentifier(input, this.identifierField);
|
|
154
206
|
this.assertRegisterEmailWhenVerificationEnabled(input);
|
|
155
|
-
|
|
207
|
+
this.assertPasswordPolicy(dto.password);
|
|
208
|
+
const passwordHash = await bcrypt2.hash(dto.password, this.bcryptRounds);
|
|
156
209
|
try {
|
|
157
210
|
const account = await this.authRepository.create({
|
|
158
211
|
...input,
|
|
@@ -165,7 +218,7 @@ var AuthService = class {
|
|
|
165
218
|
}
|
|
166
219
|
} catch (error) {
|
|
167
220
|
if (isDuplicateKeyError(error)) {
|
|
168
|
-
|
|
221
|
+
return { registered: true };
|
|
169
222
|
}
|
|
170
223
|
throw error;
|
|
171
224
|
}
|
|
@@ -176,17 +229,22 @@ var AuthService = class {
|
|
|
176
229
|
const account = await this.authRepository.findByIdentifierWithSecrets(
|
|
177
230
|
identifier
|
|
178
231
|
);
|
|
179
|
-
if (account
|
|
180
|
-
|
|
232
|
+
if (account != null) {
|
|
233
|
+
this.assertAccountNotLocked(account);
|
|
181
234
|
}
|
|
182
|
-
|
|
183
|
-
const passwordMatches = await bcrypt2.compare(
|
|
184
|
-
|
|
185
|
-
account.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
235
|
+
const passwordHash = account?.passwordHash ?? DUMMY_PASSWORD_HASH;
|
|
236
|
+
const passwordMatches = await bcrypt2.compare(dto.password, passwordHash);
|
|
237
|
+
if (account?.passwordHash == null || !passwordMatches) {
|
|
238
|
+
if (account != null && this.accountLockoutEnabled) {
|
|
239
|
+
await this.authRepository.recordLoginFailure(
|
|
240
|
+
account.id,
|
|
241
|
+
this.lockoutOptions
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
throw new UnauthorizedException(AuthErrorCode.INVALID_CREDENTIALS);
|
|
189
245
|
}
|
|
246
|
+
this.assertAccountActive(account);
|
|
247
|
+
await this.authRepository.recordLoginSuccess(account.id);
|
|
190
248
|
return this.issueTokens(account);
|
|
191
249
|
}
|
|
192
250
|
async refresh(refreshToken) {
|
|
@@ -194,19 +252,25 @@ var AuthService = class {
|
|
|
194
252
|
try {
|
|
195
253
|
payload = await this.tokenService.verifyRefreshToken(refreshToken);
|
|
196
254
|
} catch {
|
|
197
|
-
throw new UnauthorizedException(
|
|
255
|
+
throw new UnauthorizedException(AuthErrorCode.INVALID_REFRESH_TOKEN);
|
|
198
256
|
}
|
|
199
257
|
const account = await this.authRepository.findByIdWithSecrets(payload.sub);
|
|
200
|
-
if (account
|
|
201
|
-
throw new UnauthorizedException(
|
|
258
|
+
if (account == null) {
|
|
259
|
+
throw new UnauthorizedException(AuthErrorCode.INVALID_REFRESH_TOKEN);
|
|
202
260
|
}
|
|
203
261
|
this.assertAccountActive(account);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
262
|
+
if (this.rotateRefreshToken) {
|
|
263
|
+
if (account.refreshTokenHash == null) {
|
|
264
|
+
throw new UnauthorizedException(AuthErrorCode.INVALID_REFRESH_TOKEN);
|
|
265
|
+
}
|
|
266
|
+
const tokenMatches = await this.tokenService.compareRefreshToken(
|
|
267
|
+
refreshToken,
|
|
268
|
+
account.refreshTokenHash
|
|
269
|
+
);
|
|
270
|
+
if (!tokenMatches) {
|
|
271
|
+
await this.authRepository.updateRefreshTokenHash(account.id, null);
|
|
272
|
+
throw new UnauthorizedException(AuthErrorCode.REFRESH_TOKEN_REUSE);
|
|
273
|
+
}
|
|
210
274
|
}
|
|
211
275
|
return this.issueTokens(account);
|
|
212
276
|
}
|
|
@@ -229,7 +293,7 @@ var AuthService = class {
|
|
|
229
293
|
const tokenHash = hashToken(token);
|
|
230
294
|
const account = await this.authRepository.findByEmailVerificationTokenHash(tokenHash);
|
|
231
295
|
if (account == null) {
|
|
232
|
-
throw new BadRequestException(
|
|
296
|
+
throw new BadRequestException(AuthErrorCode.TOKEN_INVALID_OR_EXPIRED);
|
|
233
297
|
}
|
|
234
298
|
await this.authRepository.markEmailVerified(account.id);
|
|
235
299
|
return { verified: true };
|
|
@@ -255,20 +319,55 @@ var AuthService = class {
|
|
|
255
319
|
const tokenHash = hashToken(token);
|
|
256
320
|
const account = await this.authRepository.findByResetTokenHash(tokenHash);
|
|
257
321
|
if (account == null) {
|
|
258
|
-
throw new BadRequestException(
|
|
322
|
+
throw new BadRequestException(AuthErrorCode.TOKEN_INVALID_OR_EXPIRED);
|
|
259
323
|
}
|
|
260
|
-
|
|
324
|
+
this.assertPasswordPolicy(newPassword);
|
|
325
|
+
const passwordHash = await bcrypt2.hash(newPassword, this.bcryptRounds);
|
|
261
326
|
await this.authRepository.updatePasswordHash(account.id, passwordHash);
|
|
262
327
|
await this.authRepository.clearResetToken(account.id);
|
|
263
328
|
await this.authRepository.updateRefreshTokenHash(account.id, null);
|
|
264
329
|
return { reset: true };
|
|
265
330
|
}
|
|
331
|
+
async changePassword(authId, currentPassword, newPassword) {
|
|
332
|
+
const account = await this.authRepository.findByIdWithSecrets(authId);
|
|
333
|
+
if (account?.passwordHash == null) {
|
|
334
|
+
throw new UnauthorizedException(AuthErrorCode.INVALID_CURRENT_PASSWORD);
|
|
335
|
+
}
|
|
336
|
+
const currentMatches = await bcrypt2.compare(
|
|
337
|
+
currentPassword,
|
|
338
|
+
account.passwordHash
|
|
339
|
+
);
|
|
340
|
+
if (!currentMatches) {
|
|
341
|
+
throw new UnauthorizedException(AuthErrorCode.INVALID_CURRENT_PASSWORD);
|
|
342
|
+
}
|
|
343
|
+
if (currentPassword === newPassword) {
|
|
344
|
+
throw new BadRequestException(AuthErrorCode.PASSWORD_UNCHANGED);
|
|
345
|
+
}
|
|
346
|
+
this.assertPasswordPolicy(newPassword);
|
|
347
|
+
const passwordHash = await bcrypt2.hash(newPassword, this.bcryptRounds);
|
|
348
|
+
await this.authRepository.updatePasswordHash(account.id, passwordHash);
|
|
349
|
+
await this.authRepository.updateRefreshTokenHash(account.id, null);
|
|
350
|
+
return { changed: true };
|
|
351
|
+
}
|
|
352
|
+
assertAccountNotLocked(account) {
|
|
353
|
+
if (!this.accountLockoutEnabled || account.lockedUntil == null) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (account.lockedUntil > /* @__PURE__ */ new Date()) {
|
|
357
|
+
throw new UnauthorizedException(AuthErrorCode.ACCOUNT_LOCKED);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
266
360
|
assertAccountActive(account) {
|
|
267
361
|
if (account.disabled) {
|
|
268
|
-
throw new UnauthorizedException(
|
|
362
|
+
throw new UnauthorizedException(AuthErrorCode.ACCOUNT_DISABLED);
|
|
269
363
|
}
|
|
270
364
|
if (this.emailVerificationEnabled && !account.emailVerified) {
|
|
271
|
-
throw new UnauthorizedException(
|
|
365
|
+
throw new UnauthorizedException(AuthErrorCode.EMAIL_NOT_VERIFIED);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
assertPasswordPolicy(password) {
|
|
369
|
+
if (this.options.passwordComplexity === true) {
|
|
370
|
+
assertPasswordComplexity(password);
|
|
272
371
|
}
|
|
273
372
|
}
|
|
274
373
|
async issueTokens(account) {
|
|
@@ -384,6 +483,13 @@ var AuthController = class {
|
|
|
384
483
|
resetPassword(dto) {
|
|
385
484
|
return this.authService.resetPassword(dto.token, dto.newPassword);
|
|
386
485
|
}
|
|
486
|
+
changePassword(user, dto) {
|
|
487
|
+
return this.authService.changePassword(
|
|
488
|
+
user.sub,
|
|
489
|
+
dto.currentPassword,
|
|
490
|
+
dto.newPassword
|
|
491
|
+
);
|
|
492
|
+
}
|
|
387
493
|
};
|
|
388
494
|
__decorateClass([
|
|
389
495
|
Post("register"),
|
|
@@ -424,17 +530,39 @@ __decorateClass([
|
|
|
424
530
|
HttpCode(HttpStatus.OK),
|
|
425
531
|
__decorateParam(0, Body())
|
|
426
532
|
], AuthController.prototype, "resetPassword", 1);
|
|
533
|
+
__decorateClass([
|
|
534
|
+
Post("change-password"),
|
|
535
|
+
UseGuards(JwtAuthGuard),
|
|
536
|
+
HttpCode(HttpStatus.OK),
|
|
537
|
+
__decorateParam(0, CurrentUser()),
|
|
538
|
+
__decorateParam(1, Body())
|
|
539
|
+
], AuthController.prototype, "changePassword", 1);
|
|
427
540
|
AuthController = __decorateClass([
|
|
428
541
|
Controller("auth"),
|
|
429
542
|
__decorateParam(0, Inject(AuthService))
|
|
430
543
|
], AuthController);
|
|
544
|
+
|
|
545
|
+
// src/controllers/auth.controller.factory.ts
|
|
546
|
+
function createAuthController(routePrefix = "auth") {
|
|
547
|
+
let ConfiguredAuthController = class extends AuthController {
|
|
548
|
+
};
|
|
549
|
+
ConfiguredAuthController = __decorateClass([
|
|
550
|
+
Controller(routePrefix)
|
|
551
|
+
], ConfiguredAuthController);
|
|
552
|
+
Object.defineProperty(ConfiguredAuthController, "name", {
|
|
553
|
+
value: `AuthController_${routePrefix.replace(/\W+/g, "_")}`
|
|
554
|
+
});
|
|
555
|
+
return ConfiguredAuthController;
|
|
556
|
+
}
|
|
431
557
|
var JwtStrategy = class extends PassportStrategy(Strategy) {
|
|
432
558
|
constructor(options, authRepository) {
|
|
433
559
|
super({
|
|
434
560
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
435
561
|
ignoreExpiration: false,
|
|
436
|
-
secretOrKey: options.secret
|
|
562
|
+
secretOrKey: options.secret,
|
|
563
|
+
algorithms: ["HS256"]
|
|
437
564
|
});
|
|
565
|
+
this.options = options;
|
|
438
566
|
this.authRepository = authRepository;
|
|
439
567
|
}
|
|
440
568
|
async validate(payload) {
|
|
@@ -442,6 +570,9 @@ var JwtStrategy = class extends PassportStrategy(Strategy) {
|
|
|
442
570
|
if (account == null || account.disabled) {
|
|
443
571
|
throw new UnauthorizedException("Account not found or inactive");
|
|
444
572
|
}
|
|
573
|
+
if (this.options.features?.emailVerification === true && !account.emailVerified) {
|
|
574
|
+
throw new UnauthorizedException(AuthErrorCode.EMAIL_NOT_VERIFIED);
|
|
575
|
+
}
|
|
445
576
|
return payload;
|
|
446
577
|
}
|
|
447
578
|
};
|
|
@@ -451,27 +582,82 @@ JwtStrategy = __decorateClass([
|
|
|
451
582
|
__decorateParam(1, Inject(AUTH_REPOSITORY))
|
|
452
583
|
], JwtStrategy);
|
|
453
584
|
|
|
585
|
+
// src/utils/hooks-provider.util.ts
|
|
586
|
+
function createHooksProvider(options) {
|
|
587
|
+
if (options.hooksProvider != null) {
|
|
588
|
+
return options.hooksProvider;
|
|
589
|
+
}
|
|
590
|
+
const HooksClass = options.hooks ?? DefaultAuthHooks;
|
|
591
|
+
return {
|
|
592
|
+
provide: AUTH_HOOKS,
|
|
593
|
+
useClass: HooksClass
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/utils/validate-auth-config.util.ts
|
|
598
|
+
var MIN_SECRET_LENGTH = 32;
|
|
599
|
+
var MIN_BCRYPT_ROUNDS = 10;
|
|
600
|
+
var MAX_BCRYPT_ROUNDS = 14;
|
|
601
|
+
function validateAuthModuleOptions(options) {
|
|
602
|
+
if (options.secret.length < MIN_SECRET_LENGTH) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`AuthModule: secret must be at least ${MIN_SECRET_LENGTH} characters`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
if (options.refreshSecret.length < MIN_SECRET_LENGTH) {
|
|
608
|
+
throw new Error(
|
|
609
|
+
`AuthModule: refreshSecret must be at least ${MIN_SECRET_LENGTH} characters`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (options.secret === options.refreshSecret) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
"AuthModule: secret and refreshSecret must be different"
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
const rounds = options.bcryptRounds ?? MIN_BCRYPT_ROUNDS;
|
|
618
|
+
if (rounds < MIN_BCRYPT_ROUNDS || rounds > MAX_BCRYPT_ROUNDS) {
|
|
619
|
+
throw new Error(
|
|
620
|
+
`AuthModule: bcryptRounds must be between ${MIN_BCRYPT_ROUNDS} and ${MAX_BCRYPT_ROUNDS}`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
if (options.emailVerificationTokenTtlMs != null && options.emailVerificationTokenTtlMs < 6e4) {
|
|
624
|
+
throw new Error(
|
|
625
|
+
"AuthModule: emailVerificationTokenTtlMs must be at least 60000 (1 minute)"
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
if (options.passwordResetTokenTtlMs != null && options.passwordResetTokenTtlMs < 6e4) {
|
|
629
|
+
throw new Error(
|
|
630
|
+
"AuthModule: passwordResetTokenTtlMs must be at least 60000 (1 minute)"
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
454
635
|
// src/auth.module.ts
|
|
455
|
-
function
|
|
636
|
+
function createCoreProviders(options) {
|
|
637
|
+
validateAuthModuleOptions(options);
|
|
456
638
|
return [
|
|
457
639
|
{
|
|
458
640
|
provide: AUTH_MODULE_OPTIONS,
|
|
459
641
|
useValue: options
|
|
460
642
|
},
|
|
461
|
-
|
|
462
|
-
provide: AUTH_HOOKS,
|
|
463
|
-
inject: [AUTH_MODULE_OPTIONS],
|
|
464
|
-
useFactory: (opts) => {
|
|
465
|
-
const HooksClass = opts.hooks ?? DefaultAuthHooks;
|
|
466
|
-
return new HooksClass();
|
|
467
|
-
}
|
|
468
|
-
},
|
|
643
|
+
createHooksProvider(options),
|
|
469
644
|
AuthService,
|
|
470
645
|
TokenService,
|
|
471
646
|
JwtStrategy,
|
|
472
647
|
JwtAuthGuard
|
|
473
648
|
];
|
|
474
649
|
}
|
|
650
|
+
function createAsyncHooksProvider(options) {
|
|
651
|
+
if (options.hooksProvider != null) {
|
|
652
|
+
return options.hooksProvider;
|
|
653
|
+
}
|
|
654
|
+
const HooksClass = options.hooks ?? DefaultAuthHooks;
|
|
655
|
+
return {
|
|
656
|
+
provide: AUTH_HOOKS,
|
|
657
|
+
inject: [ModuleRef],
|
|
658
|
+
useFactory: (moduleRef) => moduleRef.create(HooksClass)
|
|
659
|
+
};
|
|
660
|
+
}
|
|
475
661
|
function createAuthImports() {
|
|
476
662
|
return [
|
|
477
663
|
PassportModule.register({ defaultStrategy: "jwt" }),
|
|
@@ -479,7 +665,10 @@ function createAuthImports() {
|
|
|
479
665
|
inject: [AUTH_MODULE_OPTIONS],
|
|
480
666
|
useFactory: (opts) => ({
|
|
481
667
|
secret: opts.secret,
|
|
482
|
-
signOptions: {
|
|
668
|
+
signOptions: {
|
|
669
|
+
expiresIn: opts.expiresIn ?? "1h",
|
|
670
|
+
algorithm: "HS256"
|
|
671
|
+
}
|
|
483
672
|
})
|
|
484
673
|
})
|
|
485
674
|
];
|
|
@@ -491,14 +680,33 @@ function mergeImports(userImports) {
|
|
|
491
680
|
}
|
|
492
681
|
return merged;
|
|
493
682
|
}
|
|
683
|
+
function createAsyncProviders(options) {
|
|
684
|
+
return [
|
|
685
|
+
{
|
|
686
|
+
provide: AUTH_MODULE_OPTIONS,
|
|
687
|
+
inject: options.inject ?? [],
|
|
688
|
+
useFactory: async (...args) => {
|
|
689
|
+
const config = await options.useFactory(...args);
|
|
690
|
+
validateAuthModuleOptions(config);
|
|
691
|
+
return config;
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
createAsyncHooksProvider(options),
|
|
695
|
+
AuthService,
|
|
696
|
+
TokenService,
|
|
697
|
+
JwtStrategy,
|
|
698
|
+
JwtAuthGuard
|
|
699
|
+
];
|
|
700
|
+
}
|
|
494
701
|
var AuthModule = class {
|
|
495
702
|
static forRoot(options) {
|
|
703
|
+
const routePrefix = options.routePrefix ?? "auth";
|
|
496
704
|
return {
|
|
497
705
|
module: AuthModule,
|
|
498
706
|
global: true,
|
|
499
707
|
imports: createAuthImports(),
|
|
500
|
-
controllers: [
|
|
501
|
-
providers:
|
|
708
|
+
controllers: [createAuthController(routePrefix)],
|
|
709
|
+
providers: createCoreProviders(options),
|
|
502
710
|
exports: [
|
|
503
711
|
AUTH_MODULE_OPTIONS,
|
|
504
712
|
AUTH_HOOKS,
|
|
@@ -511,30 +719,13 @@ var AuthModule = class {
|
|
|
511
719
|
};
|
|
512
720
|
}
|
|
513
721
|
static forRootAsync(options) {
|
|
722
|
+
const routePrefix = options.routePrefix ?? "auth";
|
|
514
723
|
return {
|
|
515
724
|
module: AuthModule,
|
|
516
725
|
global: true,
|
|
517
726
|
imports: mergeImports(options.imports),
|
|
518
|
-
controllers: [
|
|
519
|
-
providers:
|
|
520
|
-
{
|
|
521
|
-
provide: AUTH_MODULE_OPTIONS,
|
|
522
|
-
inject: options.inject ?? [],
|
|
523
|
-
useFactory: options.useFactory
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
provide: AUTH_HOOKS,
|
|
527
|
-
inject: [AUTH_MODULE_OPTIONS],
|
|
528
|
-
useFactory: (opts) => {
|
|
529
|
-
const HooksClass = opts.hooks ?? DefaultAuthHooks;
|
|
530
|
-
return new HooksClass();
|
|
531
|
-
}
|
|
532
|
-
},
|
|
533
|
-
AuthService,
|
|
534
|
-
TokenService,
|
|
535
|
-
JwtStrategy,
|
|
536
|
-
JwtAuthGuard
|
|
537
|
-
],
|
|
727
|
+
controllers: [createAuthController(routePrefix)],
|
|
728
|
+
providers: createAsyncProviders(options),
|
|
538
729
|
exports: [
|
|
539
730
|
AUTH_MODULE_OPTIONS,
|
|
540
731
|
AUTH_HOOKS,
|
|
@@ -554,6 +745,17 @@ AuthModule = __decorateClass([
|
|
|
554
745
|
// src/dto/auth-tokens.dto.ts
|
|
555
746
|
var AuthTokensDto = class {
|
|
556
747
|
};
|
|
748
|
+
var ChangePasswordDto = class {
|
|
749
|
+
};
|
|
750
|
+
__decorateClass([
|
|
751
|
+
IsString(),
|
|
752
|
+
IsNotEmpty()
|
|
753
|
+
], ChangePasswordDto.prototype, "currentPassword", 2);
|
|
754
|
+
__decorateClass([
|
|
755
|
+
IsString(),
|
|
756
|
+
IsNotEmpty(),
|
|
757
|
+
Length(8, 128)
|
|
758
|
+
], ChangePasswordDto.prototype, "newPassword", 2);
|
|
557
759
|
var ForgotPasswordDto = class {
|
|
558
760
|
};
|
|
559
761
|
__decorateClass([
|
|
@@ -563,7 +765,8 @@ var LoginDto = class {
|
|
|
563
765
|
};
|
|
564
766
|
__decorateClass([
|
|
565
767
|
IsOptional(),
|
|
566
|
-
|
|
768
|
+
ValidateIf((dto) => dto.email != null && dto.email.trim() !== ""),
|
|
769
|
+
IsEmail(),
|
|
567
770
|
Length(3, 255)
|
|
568
771
|
], LoginDto.prototype, "email", 2);
|
|
569
772
|
__decorateClass([
|
|
@@ -590,7 +793,8 @@ var RegisterDto = class {
|
|
|
590
793
|
};
|
|
591
794
|
__decorateClass([
|
|
592
795
|
IsOptional(),
|
|
593
|
-
|
|
796
|
+
ValidateIf((dto) => dto.email != null && dto.email.trim() !== ""),
|
|
797
|
+
IsEmail(),
|
|
594
798
|
Length(3, 255)
|
|
595
799
|
], RegisterDto.prototype, "email", 2);
|
|
596
800
|
__decorateClass([
|
|
@@ -622,6 +826,6 @@ __decorateClass([
|
|
|
622
826
|
IsNotEmpty()
|
|
623
827
|
], VerifyEmailDto.prototype, "token", 2);
|
|
624
828
|
|
|
625
|
-
export { AuthModule, AuthService, AuthTokensDto, CurrentUser, DefaultAuthHooks, ForgotPasswordDto, JwtAuthGuard, LoginDto, RefreshTokenDto, RegisterAckDto, RegisterDto, ResetPasswordDto, TokenService, VerifyEmailDto };
|
|
829
|
+
export { AUTH_RATE_LIMIT_PRESETS, AuthErrorCode, AuthModule, AuthService, AuthTokensDto, ChangePasswordDto, CurrentUser, DefaultAuthHooks, ForgotPasswordDto, JwtAuthGuard, LoginDto, RefreshTokenDto, RegisterAckDto, RegisterDto, ResetPasswordDto, TokenService, VerifyEmailDto };
|
|
626
830
|
//# sourceMappingURL=index.js.map
|
|
627
831
|
//# sourceMappingURL=index.js.map
|