@ackplus/nest-auth 1.1.17 → 1.1.19
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/package.json +1 -1
- package/src/lib/admin-console/static/index.html +697 -177
- package/src/lib/auth/controllers/mfa.controller.js +5 -5
- package/src/lib/auth/dto/responses/mfa-status.response.dto.d.ts +2 -2
- package/src/lib/auth/dto/responses/mfa-status.response.dto.d.ts.map +1 -1
- package/src/lib/auth/dto/responses/mfa-status.response.dto.js +5 -5
- package/src/lib/auth/guards/auth.guard.d.ts.map +1 -1
- package/src/lib/auth/guards/auth.guard.js +28 -13
- package/src/lib/auth/services/auth.service.d.ts.map +1 -1
- package/src/lib/auth/services/auth.service.js +188 -57
- package/src/lib/auth/services/mfa.service.d.ts.map +1 -1
- package/src/lib/auth/services/mfa.service.js +19 -8
- package/src/lib/auth.constants.d.ts +178 -8
- package/src/lib/auth.constants.d.ts.map +1 -1
- package/src/lib/auth.constants.js +139 -10
- package/src/lib/core/interfaces/auth-module-options.interface.d.ts +170 -0
- package/src/lib/core/interfaces/auth-module-options.interface.d.ts.map +1 -1
- package/src/lib/core/interfaces/session-options.interface.d.ts +52 -0
- package/src/lib/core/interfaces/session-options.interface.d.ts.map +1 -1
- package/src/lib/core/interfaces/token-payload.interface.d.ts +14 -6
- package/src/lib/core/interfaces/token-payload.interface.d.ts.map +1 -1
- package/src/lib/session/services/session-manager.service.d.ts +3 -3
- package/src/lib/session/services/session-manager.service.d.ts.map +1 -1
- package/src/lib/session/services/session-manager.service.js +27 -17
- package/src/lib/user/services/user.service.d.ts +3 -1
- package/src/lib/user/services/user.service.d.ts.map +1 -1
- package/src/lib/user/services/user.service.js +17 -4
|
@@ -64,7 +64,10 @@ let AuthService = class AuthService {
|
|
|
64
64
|
try {
|
|
65
65
|
const config = this.authConfigService.getConfig();
|
|
66
66
|
if (config.registration?.enabled === false) {
|
|
67
|
-
throw new common_1.ForbiddenException(
|
|
67
|
+
throw new common_1.ForbiddenException({
|
|
68
|
+
message: 'Registration is disabled',
|
|
69
|
+
code: auth_constants_1.ERROR_CODES.REGISTRATION_DISABLED,
|
|
70
|
+
});
|
|
68
71
|
}
|
|
69
72
|
const { email, phone, password } = input;
|
|
70
73
|
let { tenantId = null } = input;
|
|
@@ -73,7 +76,10 @@ let AuthService = class AuthService {
|
|
|
73
76
|
this.debugLogger.logAuthOperation('signup', 'email|phone', undefined, { email, phone, resolvedTenantId: tenantId });
|
|
74
77
|
if (!email && !phone) {
|
|
75
78
|
this.debugLogger.error('Signup failed: Neither email nor phone provided', 'AuthService');
|
|
76
|
-
throw new common_1.BadRequestException(
|
|
79
|
+
throw new common_1.BadRequestException({
|
|
80
|
+
message: 'Either email or phone must be provided',
|
|
81
|
+
code: auth_constants_1.ERROR_CODES.EMAIL_OR_PHONE_REQUIRED,
|
|
82
|
+
});
|
|
77
83
|
}
|
|
78
84
|
let provider = null;
|
|
79
85
|
let providerUserId = null;
|
|
@@ -87,17 +93,26 @@ let AuthService = class AuthService {
|
|
|
87
93
|
}
|
|
88
94
|
if (!provider) {
|
|
89
95
|
this.debugLogger.error('Provider not found for signup', 'AuthService', { email: !!email, phone: !!phone });
|
|
90
|
-
throw new common_1.InternalServerErrorException(
|
|
96
|
+
throw new common_1.InternalServerErrorException({
|
|
97
|
+
message: 'Phone or email authentication is not enabled',
|
|
98
|
+
code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
|
|
99
|
+
});
|
|
91
100
|
}
|
|
92
101
|
this.debugLogger.debug('Checking for existing identity', 'AuthService', { providerUserId });
|
|
93
102
|
const identity = await provider.findIdentity(providerUserId);
|
|
94
103
|
if (identity) {
|
|
95
104
|
this.debugLogger.warn('Identity already exists', 'AuthService', { email: !!email, phone: !!phone, tenantId });
|
|
96
105
|
if (email) {
|
|
97
|
-
throw new common_1.BadRequestException(
|
|
106
|
+
throw new common_1.BadRequestException({
|
|
107
|
+
message: 'Email already exists in this tenant',
|
|
108
|
+
code: auth_constants_1.ERROR_CODES.EMAIL_ALREADY_EXISTS,
|
|
109
|
+
});
|
|
98
110
|
}
|
|
99
111
|
if (phone) {
|
|
100
|
-
throw new common_1.BadRequestException(
|
|
112
|
+
throw new common_1.BadRequestException({
|
|
113
|
+
message: 'Phone number already exists in this tenant',
|
|
114
|
+
code: auth_constants_1.ERROR_CODES.PHONE_ALREADY_EXISTS,
|
|
115
|
+
});
|
|
101
116
|
}
|
|
102
117
|
}
|
|
103
118
|
this.debugLogger.debug('Creating new user', 'AuthService', { email: !!email, phone: !!phone, tenantId });
|
|
@@ -130,11 +145,17 @@ let AuthService = class AuthService {
|
|
|
130
145
|
isRequiresMfa
|
|
131
146
|
}));
|
|
132
147
|
this.debugLogger.logFunctionExit('signup', 'AuthService', { userId: user.id, isRequiresMfa });
|
|
133
|
-
|
|
148
|
+
// Build default response
|
|
149
|
+
let response = {
|
|
134
150
|
accessToken: tokens.accessToken,
|
|
135
151
|
refreshToken: tokens.refreshToken,
|
|
136
152
|
isRequiresMfa: isRequiresMfa,
|
|
137
153
|
};
|
|
154
|
+
// Apply auth.transformResponse hook if configured
|
|
155
|
+
if (config.auth?.transformResponse) {
|
|
156
|
+
response = await config.auth.transformResponse(response, user, session);
|
|
157
|
+
}
|
|
158
|
+
return response;
|
|
138
159
|
}
|
|
139
160
|
catch (error) {
|
|
140
161
|
this.debugLogger.logError(error, 'signup', { email: input.email, phone: input.phone });
|
|
@@ -150,18 +171,27 @@ let AuthService = class AuthService {
|
|
|
150
171
|
this.debugLogger.logAuthOperation('login', providerName, undefined, { resolvedTenantId: tenantId, createUserIfNotExists });
|
|
151
172
|
const provider = this.authProviderRegistry.getProvider(providerName);
|
|
152
173
|
if (!provider) {
|
|
153
|
-
throw new common_1.UnauthorizedException(
|
|
174
|
+
throw new common_1.UnauthorizedException({
|
|
175
|
+
message: 'Invalid authentication providerName or provider is not enabled',
|
|
176
|
+
code: auth_constants_1.ERROR_CODES.INVALID_PROVIDER,
|
|
177
|
+
});
|
|
154
178
|
}
|
|
155
179
|
const requiredFields = provider.getRequiredFields();
|
|
156
180
|
if (!requiredFields.every(field => credentials[field])) {
|
|
157
|
-
throw new common_1.BadRequestException(
|
|
181
|
+
throw new common_1.BadRequestException({
|
|
182
|
+
message: `Missing ${requiredFields.join(', ')} required fields`,
|
|
183
|
+
code: auth_constants_1.ERROR_CODES.MISSING_REQUIRED_FIELDS,
|
|
184
|
+
});
|
|
158
185
|
}
|
|
159
186
|
const authProviderUser = await provider.validate(credentials);
|
|
160
187
|
const identity = await provider.findIdentity(authProviderUser.userId);
|
|
161
188
|
let user = identity?.user || null;
|
|
162
189
|
if (!user) {
|
|
163
190
|
if (!createUserIfNotExists) {
|
|
164
|
-
throw new common_1.UnauthorizedException(
|
|
191
|
+
throw new common_1.UnauthorizedException({
|
|
192
|
+
message: 'Invalid credentials',
|
|
193
|
+
code: auth_constants_1.ERROR_CODES.INVALID_CREDENTIALS,
|
|
194
|
+
});
|
|
165
195
|
}
|
|
166
196
|
// Create new user if not exists and link to provider
|
|
167
197
|
user = await this.handleSocialLogin(provider, authProviderUser, tenantId);
|
|
@@ -169,13 +199,13 @@ let AuthService = class AuthService {
|
|
|
169
199
|
if (user.isActive === false) {
|
|
170
200
|
throw new common_1.UnauthorizedException({
|
|
171
201
|
message: 'Your account is suspended, please contact support',
|
|
172
|
-
code: auth_constants_1.
|
|
202
|
+
code: auth_constants_1.ERROR_CODES.ACCOUNT_INACTIVE,
|
|
173
203
|
});
|
|
174
204
|
}
|
|
175
|
-
user = await this.getUserWithRolesAndPermissions(user.id);
|
|
176
|
-
const session = await this.sessionManager.createSessionFromUser(user);
|
|
177
|
-
const tokens = await this.generateTokensFromSession(session);
|
|
178
205
|
let isRequiresMfa = await this.mfaService.isRequiresMfa(user.id);
|
|
206
|
+
user.isMfaEnabled = isRequiresMfa;
|
|
207
|
+
user = await this.getUserWithRolesAndPermissions(user.id);
|
|
208
|
+
let session = await this.sessionManager.createSessionFromUser(user);
|
|
179
209
|
// Check for trusted device cookie or header if MFA is required
|
|
180
210
|
if (isRequiresMfa) {
|
|
181
211
|
const trustCookieName = auth_config_service_1.AuthConfigService.getOptions().mfa?.trustDeviceStorageName || auth_constants_1.NEST_AUTH_TRUST_DEVICE_KEY;
|
|
@@ -190,12 +220,13 @@ let AuthService = class AuthService {
|
|
|
190
220
|
if (isTrusted) {
|
|
191
221
|
isRequiresMfa = false;
|
|
192
222
|
// Update session to indicate MFA is verified by trust
|
|
193
|
-
await this.sessionManager.updateSession(session.id, {
|
|
223
|
+
session = await this.sessionManager.updateSession(session.id, {
|
|
194
224
|
data: { ...session.data, isMfaVerified: true }
|
|
195
225
|
});
|
|
196
226
|
}
|
|
197
227
|
}
|
|
198
228
|
}
|
|
229
|
+
const tokens = await this.generateTokensFromSession(session);
|
|
199
230
|
// Emit login event
|
|
200
231
|
await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.LOGGED_IN, new user_logged_in_event_1.UserLoggedInEvent({
|
|
201
232
|
user,
|
|
@@ -206,11 +237,18 @@ let AuthService = class AuthService {
|
|
|
206
237
|
tokens,
|
|
207
238
|
isRequiresMfa
|
|
208
239
|
}));
|
|
209
|
-
|
|
240
|
+
// Build default response
|
|
241
|
+
let response = {
|
|
210
242
|
accessToken: tokens.accessToken,
|
|
211
243
|
refreshToken: tokens.refreshToken,
|
|
212
244
|
isRequiresMfa: isRequiresMfa,
|
|
213
245
|
};
|
|
246
|
+
// Apply auth.transformResponse hook if configured
|
|
247
|
+
const config = this.authConfigService.getConfig();
|
|
248
|
+
if (config.auth?.transformResponse) {
|
|
249
|
+
response = await config.auth.transformResponse(response, user, session);
|
|
250
|
+
}
|
|
251
|
+
return response;
|
|
214
252
|
}
|
|
215
253
|
async verify2fa(input) {
|
|
216
254
|
this.debugLogger.logFunctionEntry('verify2fa', 'AuthService', { method: input.method });
|
|
@@ -220,7 +258,7 @@ let AuthService = class AuthService {
|
|
|
220
258
|
this.debugLogger.error('Session not found for 2FA verification', 'AuthService');
|
|
221
259
|
throw new common_1.UnauthorizedException({
|
|
222
260
|
message: 'Session not found',
|
|
223
|
-
code: auth_constants_1.
|
|
261
|
+
code: auth_constants_1.ERROR_CODES.SESSION_NOT_FOUND,
|
|
224
262
|
});
|
|
225
263
|
}
|
|
226
264
|
this.debugLogger.debug('Verifying MFA code', 'AuthService', { userId: session.userId, method: input.method });
|
|
@@ -229,7 +267,7 @@ let AuthService = class AuthService {
|
|
|
229
267
|
this.debugLogger.warn('Invalid MFA code provided', 'AuthService', { userId: session.userId, method: input.method });
|
|
230
268
|
throw new common_1.UnauthorizedException({
|
|
231
269
|
message: 'Invalid MFA code',
|
|
232
|
-
code: auth_constants_1.
|
|
270
|
+
code: auth_constants_1.ERROR_CODES.MFA_CODE_INVALID,
|
|
233
271
|
});
|
|
234
272
|
}
|
|
235
273
|
this.debugLogger.debug('Updating session with MFA verification', 'AuthService', { sessionId: session.id });
|
|
@@ -272,7 +310,10 @@ let AuthService = class AuthService {
|
|
|
272
310
|
async send2faCode(userId, method) {
|
|
273
311
|
const user = await this.userRepository.findOne({ where: { id: userId } });
|
|
274
312
|
if (!user) {
|
|
275
|
-
throw new common_1.UnauthorizedException(
|
|
313
|
+
throw new common_1.UnauthorizedException({
|
|
314
|
+
message: 'User not found',
|
|
315
|
+
code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND,
|
|
316
|
+
});
|
|
276
317
|
}
|
|
277
318
|
await this.mfaService.sendMfaCode(user.id, method);
|
|
278
319
|
return true;
|
|
@@ -305,7 +346,7 @@ let AuthService = class AuthService {
|
|
|
305
346
|
this.debugLogger.error('No refresh token provided', 'AuthService');
|
|
306
347
|
throw new common_1.UnauthorizedException({
|
|
307
348
|
message: 'No refresh token provided',
|
|
308
|
-
code: auth_constants_1.REFRESH_TOKEN_INVALID,
|
|
349
|
+
code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
|
|
309
350
|
});
|
|
310
351
|
}
|
|
311
352
|
this.debugLogger.debug('Verifying refresh token', 'AuthService');
|
|
@@ -317,20 +358,18 @@ let AuthService = class AuthService {
|
|
|
317
358
|
this.debugLogger.warn('Invalid or expired refresh token', 'AuthService');
|
|
318
359
|
throw new common_1.UnauthorizedException({
|
|
319
360
|
message: 'Invalid or expired refresh token',
|
|
320
|
-
code: auth_constants_1.REFRESH_TOKEN_EXPIRED,
|
|
361
|
+
code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_EXPIRED,
|
|
321
362
|
});
|
|
322
363
|
}
|
|
323
364
|
const session = await this.sessionManager.getSession(payload.sessionId);
|
|
324
365
|
if (!session) {
|
|
325
366
|
throw new common_1.UnauthorizedException({
|
|
326
367
|
message: 'Invalid refresh token',
|
|
327
|
-
code: auth_constants_1.REFRESH_TOKEN_INVALID,
|
|
368
|
+
code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
|
|
328
369
|
});
|
|
329
370
|
}
|
|
330
|
-
//
|
|
331
|
-
const newSession = await this.sessionManager.
|
|
332
|
-
// Revoke old session
|
|
333
|
-
await this.sessionManager.revokeSession(session.id);
|
|
371
|
+
// Refresh existing session
|
|
372
|
+
const newSession = await this.sessionManager.refreshSession(session);
|
|
334
373
|
// Generate new tokens
|
|
335
374
|
this.debugLogger.debug('Generating new tokens from refreshed session', 'AuthService', { sessionId: newSession.id });
|
|
336
375
|
const tokens = await this.generateTokensFromSession(newSession);
|
|
@@ -354,20 +393,32 @@ let AuthService = class AuthService {
|
|
|
354
393
|
try {
|
|
355
394
|
const currentUser = request_context_1.RequestContext.currentUser();
|
|
356
395
|
if (!currentUser?.id) {
|
|
357
|
-
throw new common_1.UnauthorizedException(
|
|
396
|
+
throw new common_1.UnauthorizedException({
|
|
397
|
+
message: 'User not found',
|
|
398
|
+
code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND,
|
|
399
|
+
});
|
|
358
400
|
}
|
|
359
401
|
const user = await this.userRepository.findOne({
|
|
360
402
|
where: { id: currentUser.id },
|
|
361
403
|
});
|
|
362
404
|
if (!user) {
|
|
363
|
-
throw new common_1.UnauthorizedException(
|
|
405
|
+
throw new common_1.UnauthorizedException({
|
|
406
|
+
message: 'User not found',
|
|
407
|
+
code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND,
|
|
408
|
+
});
|
|
364
409
|
}
|
|
365
410
|
const isValid = await user.validatePassword(input.currentPassword);
|
|
366
411
|
if (!isValid) {
|
|
367
|
-
throw new common_1.BadRequestException(
|
|
412
|
+
throw new common_1.BadRequestException({
|
|
413
|
+
message: 'Current password is incorrect',
|
|
414
|
+
code: auth_constants_1.ERROR_CODES.CURRENT_PASSWORD_INCORRECT,
|
|
415
|
+
});
|
|
368
416
|
}
|
|
369
417
|
if (input.currentPassword === input.newPassword) {
|
|
370
|
-
throw new common_1.BadRequestException(
|
|
418
|
+
throw new common_1.BadRequestException({
|
|
419
|
+
message: 'New password must be different from the current password',
|
|
420
|
+
code: auth_constants_1.ERROR_CODES.NEW_PASSWORD_SAME_AS_CURRENT,
|
|
421
|
+
});
|
|
371
422
|
}
|
|
372
423
|
await user.setPassword(input.newPassword);
|
|
373
424
|
await this.userRepository.save(user);
|
|
@@ -403,17 +454,29 @@ let AuthService = class AuthService {
|
|
|
403
454
|
provider = this.authProviderRegistry.getProvider(auth_constants_1.EMAIL_AUTH_PROVIDER);
|
|
404
455
|
}
|
|
405
456
|
else {
|
|
406
|
-
throw new common_1.BadRequestException(
|
|
457
|
+
throw new common_1.BadRequestException({
|
|
458
|
+
message: 'Either email or phone must be provided',
|
|
459
|
+
code: auth_constants_1.ERROR_CODES.EMAIL_OR_PHONE_REQUIRED,
|
|
460
|
+
});
|
|
407
461
|
}
|
|
408
462
|
if (!provider) {
|
|
409
|
-
throw new common_1.BadRequestException(
|
|
463
|
+
throw new common_1.BadRequestException({
|
|
464
|
+
message: 'Phone or email authentication is not enabled',
|
|
465
|
+
code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
|
|
466
|
+
});
|
|
410
467
|
}
|
|
411
468
|
if (!provider.enabled) {
|
|
412
469
|
if (email) {
|
|
413
|
-
throw new common_1.BadRequestException(
|
|
470
|
+
throw new common_1.BadRequestException({
|
|
471
|
+
message: 'Email authentication is not enabled',
|
|
472
|
+
code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
|
|
473
|
+
});
|
|
414
474
|
}
|
|
415
475
|
else if (phone) {
|
|
416
|
-
throw new common_1.BadRequestException(
|
|
476
|
+
throw new common_1.BadRequestException({
|
|
477
|
+
message: 'Phone authentication is not enabled',
|
|
478
|
+
code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
|
|
479
|
+
});
|
|
417
480
|
}
|
|
418
481
|
}
|
|
419
482
|
const identity = await provider.findIdentity(email || phone);
|
|
@@ -456,7 +519,10 @@ let AuthService = class AuthService {
|
|
|
456
519
|
// Resolve tenant ID - use provided or default
|
|
457
520
|
tenantId = await this.tenantService.resolveTenantId(tenantId);
|
|
458
521
|
if (!email && !phone) {
|
|
459
|
-
throw new common_1.BadRequestException(
|
|
522
|
+
throw new common_1.BadRequestException({
|
|
523
|
+
message: 'Either email or phone must be provided',
|
|
524
|
+
code: auth_constants_1.ERROR_CODES.EMAIL_OR_PHONE_REQUIRED,
|
|
525
|
+
});
|
|
460
526
|
}
|
|
461
527
|
let provider = null;
|
|
462
528
|
if (phone) {
|
|
@@ -466,11 +532,17 @@ let AuthService = class AuthService {
|
|
|
466
532
|
provider = this.authProviderRegistry.getProvider(auth_constants_1.EMAIL_AUTH_PROVIDER);
|
|
467
533
|
}
|
|
468
534
|
if (!provider) {
|
|
469
|
-
throw new common_1.BadRequestException(
|
|
535
|
+
throw new common_1.BadRequestException({
|
|
536
|
+
message: 'Phone or email authentication is not enabled',
|
|
537
|
+
code: auth_constants_1.ERROR_CODES.PROVIDER_NOT_FOUND,
|
|
538
|
+
});
|
|
470
539
|
}
|
|
471
540
|
const identity = await provider.findIdentity(email || phone);
|
|
472
541
|
if (!identity) {
|
|
473
|
-
throw new common_1.BadRequestException(
|
|
542
|
+
throw new common_1.BadRequestException({
|
|
543
|
+
message: 'Invalid reset request',
|
|
544
|
+
code: auth_constants_1.ERROR_CODES.PASSWORD_RESET_INVALID_REQUEST,
|
|
545
|
+
});
|
|
474
546
|
}
|
|
475
547
|
const validOtp = await this.otpRepository.findOne({
|
|
476
548
|
where: {
|
|
@@ -482,10 +554,16 @@ let AuthService = class AuthService {
|
|
|
482
554
|
relations: ['user']
|
|
483
555
|
});
|
|
484
556
|
if (!validOtp) {
|
|
485
|
-
throw new common_1.BadRequestException(
|
|
557
|
+
throw new common_1.BadRequestException({
|
|
558
|
+
message: 'Invalid OTP code',
|
|
559
|
+
code: auth_constants_1.ERROR_CODES.OTP_INVALID,
|
|
560
|
+
});
|
|
486
561
|
}
|
|
487
562
|
if ((0, moment_1.default)(validOtp.expiresAt).isBefore(new Date())) {
|
|
488
|
-
throw new common_1.BadRequestException(
|
|
563
|
+
throw new common_1.BadRequestException({
|
|
564
|
+
message: 'OTP code expired',
|
|
565
|
+
code: auth_constants_1.ERROR_CODES.OTP_EXPIRED,
|
|
566
|
+
});
|
|
489
567
|
}
|
|
490
568
|
const user = validOtp.user;
|
|
491
569
|
// Generate JWT-based password reset token
|
|
@@ -517,7 +595,10 @@ let AuthService = class AuthService {
|
|
|
517
595
|
// Resolve tenant ID - use provided or default
|
|
518
596
|
tenantId = await this.tenantService.resolveTenantId(tenantId);
|
|
519
597
|
if (!email && !phone) {
|
|
520
|
-
throw new common_1.BadRequestException(
|
|
598
|
+
throw new common_1.BadRequestException({
|
|
599
|
+
message: 'Either email or phone must be provided',
|
|
600
|
+
code: auth_constants_1.ERROR_CODES.EMAIL_OR_PHONE_REQUIRED,
|
|
601
|
+
});
|
|
521
602
|
}
|
|
522
603
|
// Find user by email or phone
|
|
523
604
|
const user = await this.userRepository.findOne({
|
|
@@ -527,7 +608,10 @@ let AuthService = class AuthService {
|
|
|
527
608
|
]
|
|
528
609
|
});
|
|
529
610
|
if (!user) {
|
|
530
|
-
throw new common_1.BadRequestException(
|
|
611
|
+
throw new common_1.BadRequestException({
|
|
612
|
+
message: 'Invalid reset request',
|
|
613
|
+
code: auth_constants_1.ERROR_CODES.PASSWORD_RESET_INVALID_REQUEST,
|
|
614
|
+
});
|
|
531
615
|
}
|
|
532
616
|
// Find valid OTP
|
|
533
617
|
const validOtp = await this.otpRepository.findOne({
|
|
@@ -540,7 +624,10 @@ let AuthService = class AuthService {
|
|
|
540
624
|
}
|
|
541
625
|
});
|
|
542
626
|
if (!validOtp) {
|
|
543
|
-
throw new common_1.BadRequestException(
|
|
627
|
+
throw new common_1.BadRequestException({
|
|
628
|
+
message: 'Invalid or expired OTP',
|
|
629
|
+
code: auth_constants_1.ERROR_CODES.OTP_INVALID,
|
|
630
|
+
});
|
|
544
631
|
}
|
|
545
632
|
// Update password
|
|
546
633
|
await user.setPassword(newPassword);
|
|
@@ -572,23 +659,35 @@ let AuthService = class AuthService {
|
|
|
572
659
|
decoded = await this.jwtService.verifyPasswordResetToken(token);
|
|
573
660
|
}
|
|
574
661
|
catch (error) {
|
|
575
|
-
throw new common_1.BadRequestException(
|
|
662
|
+
throw new common_1.BadRequestException({
|
|
663
|
+
message: 'Invalid or expired reset token',
|
|
664
|
+
code: auth_constants_1.ERROR_CODES.PASSWORD_RESET_TOKEN_INVALID,
|
|
665
|
+
});
|
|
576
666
|
}
|
|
577
667
|
if (decoded.type !== 'password-reset') {
|
|
578
|
-
throw new common_1.BadRequestException(
|
|
668
|
+
throw new common_1.BadRequestException({
|
|
669
|
+
message: 'Invalid token type',
|
|
670
|
+
code: auth_constants_1.ERROR_CODES.PASSWORD_RESET_TOKEN_INVALID,
|
|
671
|
+
});
|
|
579
672
|
}
|
|
580
673
|
// Get user
|
|
581
674
|
const user = await this.userRepository.findOne({
|
|
582
675
|
where: { id: decoded.userId }
|
|
583
676
|
});
|
|
584
677
|
if (!user) {
|
|
585
|
-
throw new common_1.BadRequestException(
|
|
678
|
+
throw new common_1.BadRequestException({
|
|
679
|
+
message: 'User not found',
|
|
680
|
+
code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND,
|
|
681
|
+
});
|
|
586
682
|
}
|
|
587
683
|
// Verify password hasn't changed since token was issued
|
|
588
684
|
// This makes the token single-use in practice
|
|
589
685
|
const currentPasswordHashPrefix = user.passwordHash ? user.passwordHash.substring(0, 10) : '';
|
|
590
686
|
if (decoded.passwordHashPrefix !== currentPasswordHashPrefix) {
|
|
591
|
-
throw new common_1.BadRequestException(
|
|
687
|
+
throw new common_1.BadRequestException({
|
|
688
|
+
message: 'Reset token is no longer valid',
|
|
689
|
+
code: auth_constants_1.ERROR_CODES.PASSWORD_RESET_TOKEN_INVALID,
|
|
690
|
+
});
|
|
592
691
|
}
|
|
593
692
|
// Update password
|
|
594
693
|
await user.setPassword(newPassword);
|
|
@@ -626,7 +725,10 @@ let AuthService = class AuthService {
|
|
|
626
725
|
async logoutAll(userId, logoutType = 'user', reason) {
|
|
627
726
|
const session = request_context_1.RequestContext.currentSession();
|
|
628
727
|
if (!session) {
|
|
629
|
-
throw new common_1.UnauthorizedException(
|
|
728
|
+
throw new common_1.UnauthorizedException({
|
|
729
|
+
message: 'Session not found',
|
|
730
|
+
code: auth_constants_1.ERROR_CODES.SESSION_NOT_FOUND,
|
|
731
|
+
});
|
|
630
732
|
}
|
|
631
733
|
const sessions = await this.sessionManager.getUserSessions(userId);
|
|
632
734
|
await this.sessionManager.revokeAllUserSessions(userId);
|
|
@@ -647,14 +749,23 @@ let AuthService = class AuthService {
|
|
|
647
749
|
try {
|
|
648
750
|
const user = request_context_1.RequestContext.currentUser();
|
|
649
751
|
if (!user) {
|
|
650
|
-
throw new common_1.UnauthorizedException(
|
|
752
|
+
throw new common_1.UnauthorizedException({
|
|
753
|
+
message: 'User not authenticated',
|
|
754
|
+
code: auth_constants_1.ERROR_CODES.UNAUTHORIZED,
|
|
755
|
+
});
|
|
651
756
|
}
|
|
652
757
|
const fullUser = await this.getUserWithRolesAndPermissions(user.id);
|
|
653
758
|
if (!fullUser.email) {
|
|
654
|
-
throw new common_1.BadRequestException(
|
|
759
|
+
throw new common_1.BadRequestException({
|
|
760
|
+
message: 'User does not have an email address',
|
|
761
|
+
code: auth_constants_1.ERROR_CODES.NO_EMAIL_ADDRESS,
|
|
762
|
+
});
|
|
655
763
|
}
|
|
656
764
|
if (fullUser.emailVerifiedAt) {
|
|
657
|
-
throw new common_1.BadRequestException(
|
|
765
|
+
throw new common_1.BadRequestException({
|
|
766
|
+
message: 'Email is already verified',
|
|
767
|
+
code: auth_constants_1.ERROR_CODES.EMAIL_ALREADY_VERIFIED,
|
|
768
|
+
});
|
|
658
769
|
}
|
|
659
770
|
// Generate OTP
|
|
660
771
|
const otp = (0, otp_1.generateOtp)();
|
|
@@ -686,14 +797,23 @@ let AuthService = class AuthService {
|
|
|
686
797
|
try {
|
|
687
798
|
const user = request_context_1.RequestContext.currentUser();
|
|
688
799
|
if (!user) {
|
|
689
|
-
throw new common_1.UnauthorizedException(
|
|
800
|
+
throw new common_1.UnauthorizedException({
|
|
801
|
+
message: 'User not authenticated',
|
|
802
|
+
code: auth_constants_1.ERROR_CODES.UNAUTHORIZED,
|
|
803
|
+
});
|
|
690
804
|
}
|
|
691
805
|
const fullUser = await this.getUserWithRolesAndPermissions(user.id);
|
|
692
806
|
if (!fullUser.email) {
|
|
693
|
-
throw new common_1.BadRequestException(
|
|
807
|
+
throw new common_1.BadRequestException({
|
|
808
|
+
message: 'User does not have an email address',
|
|
809
|
+
code: auth_constants_1.ERROR_CODES.NO_EMAIL_ADDRESS,
|
|
810
|
+
});
|
|
694
811
|
}
|
|
695
812
|
if (fullUser.emailVerifiedAt) {
|
|
696
|
-
throw new common_1.BadRequestException(
|
|
813
|
+
throw new common_1.BadRequestException({
|
|
814
|
+
message: 'Email is already verified',
|
|
815
|
+
code: auth_constants_1.ERROR_CODES.EMAIL_ALREADY_VERIFIED,
|
|
816
|
+
});
|
|
697
817
|
}
|
|
698
818
|
// Find valid OTP
|
|
699
819
|
const validOtp = await this.otpRepository.findOne({
|
|
@@ -705,10 +825,16 @@ let AuthService = class AuthService {
|
|
|
705
825
|
}
|
|
706
826
|
});
|
|
707
827
|
if (!validOtp) {
|
|
708
|
-
throw new common_1.BadRequestException(
|
|
828
|
+
throw new common_1.BadRequestException({
|
|
829
|
+
message: 'Invalid verification code',
|
|
830
|
+
code: auth_constants_1.ERROR_CODES.VERIFICATION_CODE_INVALID,
|
|
831
|
+
});
|
|
709
832
|
}
|
|
710
833
|
if ((0, moment_1.default)(validOtp.expiresAt).isBefore(new Date())) {
|
|
711
|
-
throw new common_1.BadRequestException(
|
|
834
|
+
throw new common_1.BadRequestException({
|
|
835
|
+
message: 'Verification code has expired',
|
|
836
|
+
code: auth_constants_1.ERROR_CODES.VERIFICATION_CODE_EXPIRED,
|
|
837
|
+
});
|
|
712
838
|
}
|
|
713
839
|
// Mark OTP as used
|
|
714
840
|
validOtp.used = true;
|
|
@@ -730,8 +856,8 @@ let AuthService = class AuthService {
|
|
|
730
856
|
throw error;
|
|
731
857
|
}
|
|
732
858
|
}
|
|
733
|
-
generateTokensPayload(session, otherPayload = {}) {
|
|
734
|
-
|
|
859
|
+
async generateTokensPayload(session, otherPayload = {}) {
|
|
860
|
+
let payload = {
|
|
735
861
|
id: session.userId,
|
|
736
862
|
sub: session.userId,
|
|
737
863
|
sessionId: session.id,
|
|
@@ -744,10 +870,15 @@ let AuthService = class AuthService {
|
|
|
744
870
|
isMfaVerified: session.data?.isMfaVerified,
|
|
745
871
|
...otherPayload,
|
|
746
872
|
};
|
|
873
|
+
// Apply custom token payload hook if configured
|
|
874
|
+
const config = this.authConfigService.getConfig();
|
|
875
|
+
if (config.session?.customizeTokenPayload) {
|
|
876
|
+
payload = await config.session.customizeTokenPayload(payload, session);
|
|
877
|
+
}
|
|
747
878
|
return payload;
|
|
748
879
|
}
|
|
749
880
|
async generateTokensFromSession(session) {
|
|
750
|
-
const payload = this.generateTokensPayload(session);
|
|
881
|
+
const payload = await this.generateTokensPayload(session);
|
|
751
882
|
const tokens = await this.jwtService.generateTokens(payload);
|
|
752
883
|
return tokens;
|
|
753
884
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mfa.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/nest-auth/src/lib/auth/services/mfa.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,UAAU,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAG1E,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"mfa.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/nest-auth/src/lib/auth/services/mfa.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,6 @@ const otp_1 = require("../../utils/otp");
|
|
|
17
17
|
const ms_1 = tslib_1.__importDefault(require("ms"));
|
|
18
18
|
const auth_config_service_1 = require("../../core/services/auth-config.service");
|
|
19
19
|
const event_emitter_1 = require("@nestjs/event-emitter");
|
|
20
|
-
const auth_constants_2 = require("../../auth.constants");
|
|
21
20
|
const two_factor_code_sent_event_1 = require("../events/two-factor-code-sent.event");
|
|
22
21
|
const trusted_device_entity_1 = require("../entities/trusted-device.entity");
|
|
23
22
|
const crypto_1 = require("crypto");
|
|
@@ -33,7 +32,10 @@ let MfaService = class MfaService {
|
|
|
33
32
|
requireMfaEnabledForApp(throwError = true) {
|
|
34
33
|
if (!this.mfaConfig.enabled) {
|
|
35
34
|
if (throwError) {
|
|
36
|
-
throw new common_1.ForbiddenException(
|
|
35
|
+
throw new common_1.ForbiddenException({
|
|
36
|
+
message: 'MFA is not enabled for the application',
|
|
37
|
+
code: auth_constants_1.ERROR_CODES.MFA_NOT_ENABLED,
|
|
38
|
+
});
|
|
37
39
|
}
|
|
38
40
|
return false;
|
|
39
41
|
}
|
|
@@ -122,7 +124,7 @@ let MfaService = class MfaService {
|
|
|
122
124
|
if (method === mfa_options_interface_1.MFAMethodEnum.EMAIL || method === mfa_options_interface_1.MFAMethodEnum.SMS) {
|
|
123
125
|
const user = await this.userRepository.findOne({ where: { id: userId } });
|
|
124
126
|
if (user) {
|
|
125
|
-
await this.eventEmitter.emitAsync(
|
|
127
|
+
await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.TWO_FACTOR_CODE_SENT, new two_factor_code_sent_event_1.TwoFactorCodeSentEvent({
|
|
126
128
|
user,
|
|
127
129
|
tenantId: user.tenantId,
|
|
128
130
|
method,
|
|
@@ -263,18 +265,27 @@ let MfaService = class MfaService {
|
|
|
263
265
|
async enableMFA(userId) {
|
|
264
266
|
this.requireMfaEnabledForApp(true);
|
|
265
267
|
if (!this.mfaConfig.allowUserToggle) {
|
|
266
|
-
throw new
|
|
268
|
+
throw new common_1.ForbiddenException({
|
|
269
|
+
message: 'MFA toggling is not allowed',
|
|
270
|
+
code: auth_constants_1.ERROR_CODES.MFA_TOGGLING_NOT_ALLOWED,
|
|
271
|
+
});
|
|
267
272
|
}
|
|
268
273
|
const verifiedMethods = await this.getVerifiedMethods(userId);
|
|
269
274
|
if (verifiedMethods.length === 0) {
|
|
270
|
-
throw new common_1.ForbiddenException(
|
|
275
|
+
throw new common_1.ForbiddenException({
|
|
276
|
+
message: 'Cannot enable MFA without at least one verified method',
|
|
277
|
+
code: auth_constants_1.ERROR_CODES.MFA_CANNOT_ENABLE_WITHOUT_METHOD,
|
|
278
|
+
});
|
|
271
279
|
}
|
|
272
280
|
await this.userRepository.update(userId, { isMfaEnabled: true });
|
|
273
281
|
}
|
|
274
282
|
async disableMFA(userId) {
|
|
275
283
|
this.checkIsMfaEnabledForApp(true);
|
|
276
284
|
if (!this.mfaConfig.allowUserToggle) {
|
|
277
|
-
throw new
|
|
285
|
+
throw new common_1.ForbiddenException({
|
|
286
|
+
message: 'MFA toggling is not allowed',
|
|
287
|
+
code: auth_constants_1.ERROR_CODES.MFA_TOGGLING_NOT_ALLOWED,
|
|
288
|
+
});
|
|
278
289
|
}
|
|
279
290
|
await this.userRepository.update(userId, { isMfaEnabled: false });
|
|
280
291
|
}
|
|
@@ -296,7 +307,7 @@ let MfaService = class MfaService {
|
|
|
296
307
|
if (!user) {
|
|
297
308
|
throw new common_1.UnauthorizedException({
|
|
298
309
|
message: 'User not found',
|
|
299
|
-
code: auth_constants_1.
|
|
310
|
+
code: auth_constants_1.ERROR_CODES.USER_NOT_FOUND
|
|
300
311
|
});
|
|
301
312
|
}
|
|
302
313
|
if (user.mfaRecoveryCode === code) {
|
|
@@ -310,7 +321,7 @@ let MfaService = class MfaService {
|
|
|
310
321
|
}
|
|
311
322
|
throw new common_1.UnauthorizedException({
|
|
312
323
|
message: 'Invalid recovery code',
|
|
313
|
-
code: auth_constants_1.
|
|
324
|
+
code: auth_constants_1.ERROR_CODES.MFA_RECOVERY_CODE_INVALID
|
|
314
325
|
});
|
|
315
326
|
}
|
|
316
327
|
getAvailableMethods() {
|