@ackplus/nest-auth 1.1.18 → 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 -61
- 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,19 +199,15 @@ 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
|
-
// Set Mfa enbale if requred for all user, set in properly in session
|
|
182
|
-
await this.sessionManager.updateSession(session.id, {
|
|
183
|
-
data: { ...session.data, isMfaEnabled: true }
|
|
184
|
-
});
|
|
185
211
|
const trustCookieName = auth_config_service_1.AuthConfigService.getOptions().mfa?.trustDeviceStorageName || auth_constants_1.NEST_AUTH_TRUST_DEVICE_KEY;
|
|
186
212
|
const req = request_context_1.RequestContext.currentRequest();
|
|
187
213
|
let trustToken = cookie_helper_1.CookieHelper.get(req, trustCookieName);
|
|
@@ -194,12 +220,13 @@ let AuthService = class AuthService {
|
|
|
194
220
|
if (isTrusted) {
|
|
195
221
|
isRequiresMfa = false;
|
|
196
222
|
// Update session to indicate MFA is verified by trust
|
|
197
|
-
await this.sessionManager.updateSession(session.id, {
|
|
223
|
+
session = await this.sessionManager.updateSession(session.id, {
|
|
198
224
|
data: { ...session.data, isMfaVerified: true }
|
|
199
225
|
});
|
|
200
226
|
}
|
|
201
227
|
}
|
|
202
228
|
}
|
|
229
|
+
const tokens = await this.generateTokensFromSession(session);
|
|
203
230
|
// Emit login event
|
|
204
231
|
await this.eventEmitter.emitAsync(auth_constants_1.NestAuthEvents.LOGGED_IN, new user_logged_in_event_1.UserLoggedInEvent({
|
|
205
232
|
user,
|
|
@@ -210,11 +237,18 @@ let AuthService = class AuthService {
|
|
|
210
237
|
tokens,
|
|
211
238
|
isRequiresMfa
|
|
212
239
|
}));
|
|
213
|
-
|
|
240
|
+
// Build default response
|
|
241
|
+
let response = {
|
|
214
242
|
accessToken: tokens.accessToken,
|
|
215
243
|
refreshToken: tokens.refreshToken,
|
|
216
244
|
isRequiresMfa: isRequiresMfa,
|
|
217
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;
|
|
218
252
|
}
|
|
219
253
|
async verify2fa(input) {
|
|
220
254
|
this.debugLogger.logFunctionEntry('verify2fa', 'AuthService', { method: input.method });
|
|
@@ -224,7 +258,7 @@ let AuthService = class AuthService {
|
|
|
224
258
|
this.debugLogger.error('Session not found for 2FA verification', 'AuthService');
|
|
225
259
|
throw new common_1.UnauthorizedException({
|
|
226
260
|
message: 'Session not found',
|
|
227
|
-
code: auth_constants_1.
|
|
261
|
+
code: auth_constants_1.ERROR_CODES.SESSION_NOT_FOUND,
|
|
228
262
|
});
|
|
229
263
|
}
|
|
230
264
|
this.debugLogger.debug('Verifying MFA code', 'AuthService', { userId: session.userId, method: input.method });
|
|
@@ -233,7 +267,7 @@ let AuthService = class AuthService {
|
|
|
233
267
|
this.debugLogger.warn('Invalid MFA code provided', 'AuthService', { userId: session.userId, method: input.method });
|
|
234
268
|
throw new common_1.UnauthorizedException({
|
|
235
269
|
message: 'Invalid MFA code',
|
|
236
|
-
code: auth_constants_1.
|
|
270
|
+
code: auth_constants_1.ERROR_CODES.MFA_CODE_INVALID,
|
|
237
271
|
});
|
|
238
272
|
}
|
|
239
273
|
this.debugLogger.debug('Updating session with MFA verification', 'AuthService', { sessionId: session.id });
|
|
@@ -276,7 +310,10 @@ let AuthService = class AuthService {
|
|
|
276
310
|
async send2faCode(userId, method) {
|
|
277
311
|
const user = await this.userRepository.findOne({ where: { id: userId } });
|
|
278
312
|
if (!user) {
|
|
279
|
-
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
|
+
});
|
|
280
317
|
}
|
|
281
318
|
await this.mfaService.sendMfaCode(user.id, method);
|
|
282
319
|
return true;
|
|
@@ -309,7 +346,7 @@ let AuthService = class AuthService {
|
|
|
309
346
|
this.debugLogger.error('No refresh token provided', 'AuthService');
|
|
310
347
|
throw new common_1.UnauthorizedException({
|
|
311
348
|
message: 'No refresh token provided',
|
|
312
|
-
code: auth_constants_1.REFRESH_TOKEN_INVALID,
|
|
349
|
+
code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
|
|
313
350
|
});
|
|
314
351
|
}
|
|
315
352
|
this.debugLogger.debug('Verifying refresh token', 'AuthService');
|
|
@@ -321,20 +358,18 @@ let AuthService = class AuthService {
|
|
|
321
358
|
this.debugLogger.warn('Invalid or expired refresh token', 'AuthService');
|
|
322
359
|
throw new common_1.UnauthorizedException({
|
|
323
360
|
message: 'Invalid or expired refresh token',
|
|
324
|
-
code: auth_constants_1.REFRESH_TOKEN_EXPIRED,
|
|
361
|
+
code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_EXPIRED,
|
|
325
362
|
});
|
|
326
363
|
}
|
|
327
364
|
const session = await this.sessionManager.getSession(payload.sessionId);
|
|
328
365
|
if (!session) {
|
|
329
366
|
throw new common_1.UnauthorizedException({
|
|
330
367
|
message: 'Invalid refresh token',
|
|
331
|
-
code: auth_constants_1.REFRESH_TOKEN_INVALID,
|
|
368
|
+
code: auth_constants_1.ERROR_CODES.REFRESH_TOKEN_INVALID,
|
|
332
369
|
});
|
|
333
370
|
}
|
|
334
|
-
//
|
|
335
|
-
const newSession = await this.sessionManager.
|
|
336
|
-
// Revoke old session
|
|
337
|
-
await this.sessionManager.revokeSession(session.id);
|
|
371
|
+
// Refresh existing session
|
|
372
|
+
const newSession = await this.sessionManager.refreshSession(session);
|
|
338
373
|
// Generate new tokens
|
|
339
374
|
this.debugLogger.debug('Generating new tokens from refreshed session', 'AuthService', { sessionId: newSession.id });
|
|
340
375
|
const tokens = await this.generateTokensFromSession(newSession);
|
|
@@ -358,20 +393,32 @@ let AuthService = class AuthService {
|
|
|
358
393
|
try {
|
|
359
394
|
const currentUser = request_context_1.RequestContext.currentUser();
|
|
360
395
|
if (!currentUser?.id) {
|
|
361
|
-
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
|
+
});
|
|
362
400
|
}
|
|
363
401
|
const user = await this.userRepository.findOne({
|
|
364
402
|
where: { id: currentUser.id },
|
|
365
403
|
});
|
|
366
404
|
if (!user) {
|
|
367
|
-
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
|
+
});
|
|
368
409
|
}
|
|
369
410
|
const isValid = await user.validatePassword(input.currentPassword);
|
|
370
411
|
if (!isValid) {
|
|
371
|
-
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
|
+
});
|
|
372
416
|
}
|
|
373
417
|
if (input.currentPassword === input.newPassword) {
|
|
374
|
-
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
|
+
});
|
|
375
422
|
}
|
|
376
423
|
await user.setPassword(input.newPassword);
|
|
377
424
|
await this.userRepository.save(user);
|
|
@@ -407,17 +454,29 @@ let AuthService = class AuthService {
|
|
|
407
454
|
provider = this.authProviderRegistry.getProvider(auth_constants_1.EMAIL_AUTH_PROVIDER);
|
|
408
455
|
}
|
|
409
456
|
else {
|
|
410
|
-
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
|
+
});
|
|
411
461
|
}
|
|
412
462
|
if (!provider) {
|
|
413
|
-
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
|
+
});
|
|
414
467
|
}
|
|
415
468
|
if (!provider.enabled) {
|
|
416
469
|
if (email) {
|
|
417
|
-
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
|
+
});
|
|
418
474
|
}
|
|
419
475
|
else if (phone) {
|
|
420
|
-
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
|
+
});
|
|
421
480
|
}
|
|
422
481
|
}
|
|
423
482
|
const identity = await provider.findIdentity(email || phone);
|
|
@@ -460,7 +519,10 @@ let AuthService = class AuthService {
|
|
|
460
519
|
// Resolve tenant ID - use provided or default
|
|
461
520
|
tenantId = await this.tenantService.resolveTenantId(tenantId);
|
|
462
521
|
if (!email && !phone) {
|
|
463
|
-
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
|
+
});
|
|
464
526
|
}
|
|
465
527
|
let provider = null;
|
|
466
528
|
if (phone) {
|
|
@@ -470,11 +532,17 @@ let AuthService = class AuthService {
|
|
|
470
532
|
provider = this.authProviderRegistry.getProvider(auth_constants_1.EMAIL_AUTH_PROVIDER);
|
|
471
533
|
}
|
|
472
534
|
if (!provider) {
|
|
473
|
-
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
|
+
});
|
|
474
539
|
}
|
|
475
540
|
const identity = await provider.findIdentity(email || phone);
|
|
476
541
|
if (!identity) {
|
|
477
|
-
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
|
+
});
|
|
478
546
|
}
|
|
479
547
|
const validOtp = await this.otpRepository.findOne({
|
|
480
548
|
where: {
|
|
@@ -486,10 +554,16 @@ let AuthService = class AuthService {
|
|
|
486
554
|
relations: ['user']
|
|
487
555
|
});
|
|
488
556
|
if (!validOtp) {
|
|
489
|
-
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
|
+
});
|
|
490
561
|
}
|
|
491
562
|
if ((0, moment_1.default)(validOtp.expiresAt).isBefore(new Date())) {
|
|
492
|
-
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
|
+
});
|
|
493
567
|
}
|
|
494
568
|
const user = validOtp.user;
|
|
495
569
|
// Generate JWT-based password reset token
|
|
@@ -521,7 +595,10 @@ let AuthService = class AuthService {
|
|
|
521
595
|
// Resolve tenant ID - use provided or default
|
|
522
596
|
tenantId = await this.tenantService.resolveTenantId(tenantId);
|
|
523
597
|
if (!email && !phone) {
|
|
524
|
-
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
|
+
});
|
|
525
602
|
}
|
|
526
603
|
// Find user by email or phone
|
|
527
604
|
const user = await this.userRepository.findOne({
|
|
@@ -531,7 +608,10 @@ let AuthService = class AuthService {
|
|
|
531
608
|
]
|
|
532
609
|
});
|
|
533
610
|
if (!user) {
|
|
534
|
-
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
|
+
});
|
|
535
615
|
}
|
|
536
616
|
// Find valid OTP
|
|
537
617
|
const validOtp = await this.otpRepository.findOne({
|
|
@@ -544,7 +624,10 @@ let AuthService = class AuthService {
|
|
|
544
624
|
}
|
|
545
625
|
});
|
|
546
626
|
if (!validOtp) {
|
|
547
|
-
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
|
+
});
|
|
548
631
|
}
|
|
549
632
|
// Update password
|
|
550
633
|
await user.setPassword(newPassword);
|
|
@@ -576,23 +659,35 @@ let AuthService = class AuthService {
|
|
|
576
659
|
decoded = await this.jwtService.verifyPasswordResetToken(token);
|
|
577
660
|
}
|
|
578
661
|
catch (error) {
|
|
579
|
-
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
|
+
});
|
|
580
666
|
}
|
|
581
667
|
if (decoded.type !== 'password-reset') {
|
|
582
|
-
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
|
+
});
|
|
583
672
|
}
|
|
584
673
|
// Get user
|
|
585
674
|
const user = await this.userRepository.findOne({
|
|
586
675
|
where: { id: decoded.userId }
|
|
587
676
|
});
|
|
588
677
|
if (!user) {
|
|
589
|
-
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
|
+
});
|
|
590
682
|
}
|
|
591
683
|
// Verify password hasn't changed since token was issued
|
|
592
684
|
// This makes the token single-use in practice
|
|
593
685
|
const currentPasswordHashPrefix = user.passwordHash ? user.passwordHash.substring(0, 10) : '';
|
|
594
686
|
if (decoded.passwordHashPrefix !== currentPasswordHashPrefix) {
|
|
595
|
-
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
|
+
});
|
|
596
691
|
}
|
|
597
692
|
// Update password
|
|
598
693
|
await user.setPassword(newPassword);
|
|
@@ -630,7 +725,10 @@ let AuthService = class AuthService {
|
|
|
630
725
|
async logoutAll(userId, logoutType = 'user', reason) {
|
|
631
726
|
const session = request_context_1.RequestContext.currentSession();
|
|
632
727
|
if (!session) {
|
|
633
|
-
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
|
+
});
|
|
634
732
|
}
|
|
635
733
|
const sessions = await this.sessionManager.getUserSessions(userId);
|
|
636
734
|
await this.sessionManager.revokeAllUserSessions(userId);
|
|
@@ -651,14 +749,23 @@ let AuthService = class AuthService {
|
|
|
651
749
|
try {
|
|
652
750
|
const user = request_context_1.RequestContext.currentUser();
|
|
653
751
|
if (!user) {
|
|
654
|
-
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
|
+
});
|
|
655
756
|
}
|
|
656
757
|
const fullUser = await this.getUserWithRolesAndPermissions(user.id);
|
|
657
758
|
if (!fullUser.email) {
|
|
658
|
-
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
|
+
});
|
|
659
763
|
}
|
|
660
764
|
if (fullUser.emailVerifiedAt) {
|
|
661
|
-
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
|
+
});
|
|
662
769
|
}
|
|
663
770
|
// Generate OTP
|
|
664
771
|
const otp = (0, otp_1.generateOtp)();
|
|
@@ -690,14 +797,23 @@ let AuthService = class AuthService {
|
|
|
690
797
|
try {
|
|
691
798
|
const user = request_context_1.RequestContext.currentUser();
|
|
692
799
|
if (!user) {
|
|
693
|
-
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
|
+
});
|
|
694
804
|
}
|
|
695
805
|
const fullUser = await this.getUserWithRolesAndPermissions(user.id);
|
|
696
806
|
if (!fullUser.email) {
|
|
697
|
-
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
|
+
});
|
|
698
811
|
}
|
|
699
812
|
if (fullUser.emailVerifiedAt) {
|
|
700
|
-
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
|
+
});
|
|
701
817
|
}
|
|
702
818
|
// Find valid OTP
|
|
703
819
|
const validOtp = await this.otpRepository.findOne({
|
|
@@ -709,10 +825,16 @@ let AuthService = class AuthService {
|
|
|
709
825
|
}
|
|
710
826
|
});
|
|
711
827
|
if (!validOtp) {
|
|
712
|
-
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
|
+
});
|
|
713
832
|
}
|
|
714
833
|
if ((0, moment_1.default)(validOtp.expiresAt).isBefore(new Date())) {
|
|
715
|
-
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
|
+
});
|
|
716
838
|
}
|
|
717
839
|
// Mark OTP as used
|
|
718
840
|
validOtp.used = true;
|
|
@@ -734,8 +856,8 @@ let AuthService = class AuthService {
|
|
|
734
856
|
throw error;
|
|
735
857
|
}
|
|
736
858
|
}
|
|
737
|
-
generateTokensPayload(session, otherPayload = {}) {
|
|
738
|
-
|
|
859
|
+
async generateTokensPayload(session, otherPayload = {}) {
|
|
860
|
+
let payload = {
|
|
739
861
|
id: session.userId,
|
|
740
862
|
sub: session.userId,
|
|
741
863
|
sessionId: session.id,
|
|
@@ -748,10 +870,15 @@ let AuthService = class AuthService {
|
|
|
748
870
|
isMfaVerified: session.data?.isMfaVerified,
|
|
749
871
|
...otherPayload,
|
|
750
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
|
+
}
|
|
751
878
|
return payload;
|
|
752
879
|
}
|
|
753
880
|
async generateTokensFromSession(session) {
|
|
754
|
-
const payload = this.generateTokensPayload(session);
|
|
881
|
+
const payload = await this.generateTokensPayload(session);
|
|
755
882
|
const tokens = await this.jwtService.generateTokens(payload);
|
|
756
883
|
return tokens;
|
|
757
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() {
|