@flusys/nestjs-auth 3.0.0-rc → 3.0.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/README.md +610 -95
- package/cjs/controllers/authentication.controller.js +2 -173
- package/cjs/controllers/email-verification.controller.js +220 -0
- package/cjs/controllers/index.js +1 -0
- package/cjs/dtos/authentication.dto.js +12 -2
- package/cjs/entities/index.js +16 -17
- package/cjs/entities/pending-registration.entity.js +9 -0
- package/cjs/modules/auth.module.js +33 -24
- package/cjs/services/auth-config.service.js +11 -7
- package/cjs/services/auth-datasource.provider.js +15 -27
- package/cjs/services/auth-email.service.js +8 -33
- package/cjs/services/authentication.service.js +155 -110
- package/cjs/services/user.service.js +28 -83
- package/controllers/authentication.controller.d.ts +2 -10
- package/controllers/email-verification.controller.d.ts +20 -0
- package/controllers/index.d.ts +1 -0
- package/dtos/authentication.dto.d.ts +1 -0
- package/entities/index.d.ts +5 -10
- package/entities/pending-registration.entity.d.ts +1 -0
- package/fesm/controllers/authentication.controller.js +3 -174
- package/fesm/controllers/email-verification.controller.js +210 -0
- package/fesm/controllers/index.js +1 -0
- package/fesm/dtos/authentication.dto.js +32 -25
- package/fesm/entities/index.js +17 -9
- package/fesm/entities/pending-registration.entity.js +9 -0
- package/fesm/interfaces/user-enricher.interface.js +2 -24
- package/fesm/modules/auth.module.js +35 -26
- package/fesm/services/auth-config.service.js +11 -7
- package/fesm/services/auth-datasource.provider.js +15 -27
- package/fesm/services/auth-email.service.js +8 -33
- package/fesm/services/authentication.service.js +145 -100
- package/fesm/services/user.service.js +28 -83
- package/interfaces/user-enricher.interface.d.ts +3 -2
- package/modules/auth.module.d.ts +3 -1
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +1 -0
- package/services/auth-datasource.provider.d.ts +1 -0
- package/services/auth-email.service.d.ts +1 -0
- package/services/authentication.service.d.ts +7 -1
- package/services/user.service.d.ts +5 -0
|
@@ -97,23 +97,17 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
97
97
|
}
|
|
98
98
|
async ensureRepositoryInitialized() {
|
|
99
99
|
await super.ensureRepositoryInitialized();
|
|
100
|
-
// Initialize permission repository if company feature is enabled
|
|
101
100
|
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
102
101
|
if (isCompanyFeatureEnabled && !this.permissionRepository) {
|
|
103
102
|
[this.permissionRepository] = await this.initializeAdditionalRepositories([
|
|
104
103
|
_usercompanypermissionentity.UserCompanyPermission
|
|
105
104
|
]);
|
|
106
105
|
}
|
|
107
|
-
// Initialize DataSource for transaction support
|
|
108
106
|
if (!this.dataSource) {
|
|
109
107
|
this.dataSource = await this.getDataSourceForService();
|
|
110
108
|
}
|
|
111
109
|
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Validate profile ownership, ensure repository initialized, and fetch user.
|
|
115
|
-
* Throws ForbiddenException if not owner, NotFoundException if user not found.
|
|
116
|
-
*/ async getUserForProfile(userId, user) {
|
|
110
|
+
async getUserForProfile(userId, user) {
|
|
117
111
|
if (userId !== user.id) {
|
|
118
112
|
throw new _common.ForbiddenException('Cannot access another user\'s profile');
|
|
119
113
|
}
|
|
@@ -128,38 +122,32 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
128
122
|
}
|
|
129
123
|
return userEntity;
|
|
130
124
|
}
|
|
131
|
-
|
|
132
|
-
// Ensure repository is initialized for custom method
|
|
125
|
+
async verifyEmail(id) {
|
|
133
126
|
await this.ensureRepositoryInitialized();
|
|
134
127
|
await this.repository.update(id, {
|
|
135
128
|
emailVerified: true
|
|
136
129
|
});
|
|
137
|
-
// Clear cache for this user
|
|
138
130
|
await this.clearCacheForId([
|
|
139
131
|
{
|
|
140
132
|
id
|
|
141
133
|
}
|
|
142
134
|
]);
|
|
143
135
|
}
|
|
144
|
-
|
|
145
|
-
// Ensure repository is initialized for custom method
|
|
136
|
+
async verifyPhone(id) {
|
|
146
137
|
await this.ensureRepositoryInitialized();
|
|
147
138
|
await this.repository.update(id, {
|
|
148
139
|
phoneVerified: true
|
|
149
140
|
});
|
|
150
|
-
// Clear cache for this user
|
|
151
141
|
await this.clearCacheForId([
|
|
152
142
|
{
|
|
153
143
|
id
|
|
154
144
|
}
|
|
155
145
|
]);
|
|
156
146
|
}
|
|
157
|
-
|
|
158
|
-
// Ensure repository is initialized for custom method
|
|
147
|
+
async updateStatus(id, isActive, user) {
|
|
159
148
|
await this.ensureRepositoryInitialized();
|
|
160
149
|
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
161
150
|
if (isCompanyFeatureEnabled && user?.companyId && this.permissionRepository) {
|
|
162
|
-
// Company feature enabled - update permission isActive
|
|
163
151
|
const permission = await this.permissionRepository.findOne({
|
|
164
152
|
where: {
|
|
165
153
|
userId: id,
|
|
@@ -173,36 +161,30 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
173
161
|
permission.isActive = isActive;
|
|
174
162
|
await this.permissionRepository.save(permission);
|
|
175
163
|
} else {
|
|
176
|
-
// Company feature disabled - update User.isActive directly
|
|
177
164
|
await this.repository.update(id, {
|
|
178
165
|
isActive
|
|
179
166
|
});
|
|
180
167
|
}
|
|
181
|
-
// Clear cache for this user
|
|
182
168
|
await this.clearCacheForId([
|
|
183
169
|
{
|
|
184
170
|
id
|
|
185
171
|
}
|
|
186
172
|
]);
|
|
187
173
|
}
|
|
188
|
-
|
|
189
|
-
/** Convert DTO to entity (hash password before saving) */ async convertSingleDtoToEntity(dto, user) {
|
|
190
|
-
// Repository initialization is handled automatically by RequestScopedApiService
|
|
174
|
+
async convertSingleDtoToEntity(dto, user) {
|
|
191
175
|
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
192
|
-
// Hash password if provided (CreateUserDto has password field)
|
|
193
176
|
if ('password' in dto && dto.password) {
|
|
194
177
|
entity.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
195
178
|
}
|
|
196
179
|
return entity;
|
|
197
180
|
}
|
|
198
|
-
|
|
181
|
+
convertEntityToResponseDto(entity, isRaw) {
|
|
199
182
|
if (isRaw) {
|
|
200
183
|
return this.convertRawToResponseDto(entity);
|
|
201
184
|
}
|
|
202
185
|
return this.convertEntityToDto(entity);
|
|
203
186
|
}
|
|
204
|
-
|
|
205
|
-
// When raw mode is used with company feature, isActive comes from computed field
|
|
187
|
+
convertRawToResponseDto(raw) {
|
|
206
188
|
const isActive = raw.user_companyIsActive !== undefined ? Boolean(raw.user_companyIsActive) : raw.user_is_active;
|
|
207
189
|
return {
|
|
208
190
|
id: raw.user_id,
|
|
@@ -223,7 +205,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
223
205
|
deletedById: raw.user_deleted_by_id
|
|
224
206
|
};
|
|
225
207
|
}
|
|
226
|
-
|
|
208
|
+
convertEntityToDto(entity) {
|
|
227
209
|
return {
|
|
228
210
|
id: entity.id,
|
|
229
211
|
name: entity.name,
|
|
@@ -243,10 +225,10 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
243
225
|
deletedById: entity.deletedById
|
|
244
226
|
};
|
|
245
227
|
}
|
|
246
|
-
|
|
228
|
+
convertEntityListToResponseListDto(entities, isRaw) {
|
|
247
229
|
return entities.map((entity)=>this.convertEntityToResponseDto(entity, isRaw));
|
|
248
230
|
}
|
|
249
|
-
|
|
231
|
+
async getGlobalSearchQuery(query, search, _user) {
|
|
250
232
|
query.andWhere('(user.name LIKE :search OR user.email LIKE :search OR user.phone LIKE :search)', {
|
|
251
233
|
search: `%${search}%`
|
|
252
234
|
});
|
|
@@ -255,26 +237,16 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
255
237
|
isRaw: false
|
|
256
238
|
};
|
|
257
239
|
}
|
|
258
|
-
|
|
259
|
-
// Repository initialization is handled automatically by RequestScopedApiService
|
|
240
|
+
async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
|
|
260
241
|
let result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
|
|
261
|
-
// Apply user enricher query modifications if available
|
|
262
242
|
if (this.userEnricher?.enrichListQuery) {
|
|
263
243
|
await this.userEnricher.enrichListQuery(query, user);
|
|
264
244
|
}
|
|
265
|
-
// Only apply company filtering if:
|
|
266
|
-
// 1. Company feature is enabled at bootstrap
|
|
267
|
-
// 2. User has company context
|
|
268
|
-
// 3. Permission repository is available
|
|
269
245
|
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
270
246
|
if (!isCompanyFeatureEnabled) {
|
|
271
|
-
// Company feature disabled - return without any company-related modifications
|
|
272
|
-
// isActive will come from User entity directly
|
|
273
247
|
return result;
|
|
274
248
|
}
|
|
275
|
-
// Company feature is enabled - apply company-based filtering
|
|
276
249
|
if (user?.companyId && this.permissionRepository) {
|
|
277
|
-
// Add computed isActive field from permission table
|
|
278
250
|
query.addSelect(`(SELECT perm.is_active FROM user_company_permissions perm
|
|
279
251
|
WHERE perm.user_id = user.id
|
|
280
252
|
AND perm.permission_type = :permissionType
|
|
@@ -282,7 +254,6 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
282
254
|
LIMIT 1)`, 'user_companyIsActive');
|
|
283
255
|
query.setParameter('permissionType', _enums.PermissionType.COMPANY);
|
|
284
256
|
query.setParameter('companyId', user.companyId);
|
|
285
|
-
// Add filter for isActive if provided (filters by permission isActive)
|
|
286
257
|
if (filterAndPaginationDto.filter?.isActive !== undefined) {
|
|
287
258
|
query.andWhere(`EXISTS (
|
|
288
259
|
SELECT 1 FROM user_company_permissions perm
|
|
@@ -296,7 +267,6 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
296
267
|
isActiveValue: filterAndPaginationDto.filter.isActive
|
|
297
268
|
});
|
|
298
269
|
}
|
|
299
|
-
// Filter users to only show those with company permission
|
|
300
270
|
query.andWhere(`EXISTS (
|
|
301
271
|
SELECT 1 FROM user_company_permissions perm
|
|
302
272
|
WHERE perm.user_id = user.id
|
|
@@ -306,7 +276,6 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
306
276
|
permissionType: _enums.PermissionType.COMPANY,
|
|
307
277
|
companyId: user.companyId
|
|
308
278
|
});
|
|
309
|
-
// Return with isRaw: true to get the computed field
|
|
310
279
|
return {
|
|
311
280
|
query,
|
|
312
281
|
isRaw: true
|
|
@@ -314,11 +283,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
314
283
|
}
|
|
315
284
|
return result;
|
|
316
285
|
}
|
|
317
|
-
|
|
318
|
-
// Repository initialization is handled automatically by RequestScopedApiService
|
|
319
|
-
// Check if company feature is enabled
|
|
286
|
+
async insert(dto, user) {
|
|
320
287
|
const isCompanyFeatureEnabled = this.authConfig.isCompanyFeatureEnabled();
|
|
321
|
-
// If company feature is disabled OR missing dependencies, use parent implementation
|
|
322
288
|
if (!isCompanyFeatureEnabled || !this.dataSource || !user?.companyId || !this.permissionRepository) {
|
|
323
289
|
return super.insert(dto, user);
|
|
324
290
|
}
|
|
@@ -326,11 +292,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
326
292
|
await queryRunner.connect();
|
|
327
293
|
await queryRunner.startTransaction();
|
|
328
294
|
try {
|
|
329
|
-
// First, insert the user using parent method logic
|
|
330
295
|
const userEntity = await this.convertSingleDtoToEntity(dto, user);
|
|
331
296
|
const savedUser = await queryRunner.manager.save(this.repository.target, userEntity);
|
|
332
|
-
// Create company permission (repository is guaranteed to exist due to check above)
|
|
333
|
-
// Check if permission already exists
|
|
334
297
|
const existingPermission = await queryRunner.manager.findOne(this.permissionRepository.target, {
|
|
335
298
|
where: {
|
|
336
299
|
userId: savedUser.id,
|
|
@@ -347,7 +310,6 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
347
310
|
});
|
|
348
311
|
await queryRunner.manager.save(this.permissionRepository.target, permission);
|
|
349
312
|
}
|
|
350
|
-
// Also create branch permission if user has branch context
|
|
351
313
|
if (user.branchId) {
|
|
352
314
|
const existingBranchPermission = await queryRunner.manager.findOne(this.permissionRepository.target, {
|
|
353
315
|
where: {
|
|
@@ -381,13 +343,9 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
381
343
|
await queryRunner.release();
|
|
382
344
|
}
|
|
383
345
|
}
|
|
384
|
-
|
|
385
|
-
* Get user profile with extras from enricher
|
|
386
|
-
* Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
|
|
387
|
-
*/ async getProfileWithExtras(userId, user) {
|
|
346
|
+
async getProfileWithExtras(userId, user) {
|
|
388
347
|
const userEntity = await this.getUserForProfile(userId, user);
|
|
389
348
|
const userData = this.convertEntityToResponseDto(userEntity, false);
|
|
390
|
-
// Get extras from enricher if available
|
|
391
349
|
let extras;
|
|
392
350
|
if (this.userEnricher?.getProfileExtras) {
|
|
393
351
|
extras = await this.userEnricher.getProfileExtras(userId, user);
|
|
@@ -397,38 +355,34 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
397
355
|
extras
|
|
398
356
|
};
|
|
399
357
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
358
|
+
async getAll(search, filterAndPaginationDto, user) {
|
|
359
|
+
const result = await super.getAll(search, filterAndPaginationDto, user);
|
|
360
|
+
const enrichedData = await this.getEnrichedList(result.data, user);
|
|
361
|
+
return {
|
|
362
|
+
data: enrichedData,
|
|
363
|
+
total: result.total
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async getEnrichedList(users, user) {
|
|
404
367
|
if (this.userEnricher?.enrichListItems) {
|
|
405
368
|
return this.userEnricher.enrichListItems(users, user);
|
|
406
369
|
}
|
|
407
370
|
return users;
|
|
408
371
|
}
|
|
409
|
-
|
|
410
|
-
// Multi-Step Profile Methods (via Enricher)
|
|
411
|
-
// ============================================
|
|
412
|
-
/**
|
|
413
|
-
* Get profile section definitions from enricher
|
|
414
|
-
*/ async getProfileSections() {
|
|
372
|
+
async getProfileSections() {
|
|
415
373
|
if (this.userEnricher?.getProfileSections) {
|
|
416
374
|
return this.userEnricher.getProfileSections();
|
|
417
375
|
}
|
|
418
376
|
return [];
|
|
419
377
|
}
|
|
420
|
-
|
|
421
|
-
* Get specific profile section data
|
|
422
|
-
*/ async getProfileSectionData(userId, sectionId, user) {
|
|
378
|
+
async getProfileSectionData(userId, sectionId, user) {
|
|
423
379
|
await this.getUserForProfile(userId, user);
|
|
424
380
|
if (this.userEnricher?.getProfileSectionData) {
|
|
425
381
|
return this.userEnricher.getProfileSectionData(userId, sectionId, user);
|
|
426
382
|
}
|
|
427
383
|
return null;
|
|
428
384
|
}
|
|
429
|
-
|
|
430
|
-
* Update specific profile section
|
|
431
|
-
*/ async updateProfileSection(userId, sectionId, data, user) {
|
|
385
|
+
async updateProfileSection(userId, sectionId, data, user) {
|
|
432
386
|
const userEntity = await this.getUserForProfile(userId, user);
|
|
433
387
|
if (!this.dataSource) {
|
|
434
388
|
throw new _common.BadRequestException('DataSource not available');
|
|
@@ -456,23 +410,19 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
456
410
|
await queryRunner.release();
|
|
457
411
|
}
|
|
458
412
|
}
|
|
459
|
-
|
|
460
|
-
* Get profile completion percentage
|
|
461
|
-
*/ async getProfileCompletion(userId, user) {
|
|
413
|
+
async getProfileCompletion(userId, user) {
|
|
462
414
|
await this.getUserForProfile(userId, user);
|
|
463
415
|
if (this.userEnricher?.calculateProfileCompletion) {
|
|
464
416
|
return this.userEnricher.calculateProfileCompletion(userId, user);
|
|
465
417
|
}
|
|
466
|
-
return 100;
|
|
418
|
+
return 100;
|
|
467
419
|
}
|
|
468
|
-
|
|
469
|
-
// Ensure repository and dataSource are initialized
|
|
420
|
+
async profile(dto, user) {
|
|
470
421
|
await this.ensureRepositoryInitialized();
|
|
471
422
|
if (!this.dataSource) {
|
|
472
423
|
throw new _common.BadRequestException('DataSource not available');
|
|
473
424
|
}
|
|
474
425
|
const userId = dto.id ?? user.id;
|
|
475
|
-
// Validate extras if enricher provides validation
|
|
476
426
|
if (dto.extras && this.userEnricher?.validateProfileExtras) {
|
|
477
427
|
await this.userEnricher.validateProfileExtras(userId, dto.extras, user);
|
|
478
428
|
}
|
|
@@ -488,9 +438,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
488
438
|
if (!findUser) {
|
|
489
439
|
throw new _common.NotFoundException('User not found');
|
|
490
440
|
}
|
|
491
|
-
// Handle password update
|
|
492
441
|
if (dto.password) {
|
|
493
|
-
// If updating own profile, require old password verification
|
|
494
442
|
const isOwnProfile = !dto.id || dto.id === user.id;
|
|
495
443
|
if (isOwnProfile) {
|
|
496
444
|
if (!dto.oldPassword) {
|
|
@@ -503,12 +451,9 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
503
451
|
}
|
|
504
452
|
findUser.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
505
453
|
}
|
|
506
|
-
// Merge other fields (excluding password, oldPassword, extras, and sensitive flags)
|
|
507
|
-
// Users cannot self-modify emailVerified, phoneVerified, or isActive via profile update
|
|
508
454
|
const { password, oldPassword, extras, emailVerified, phoneVerified, isActive, ...restDto } = dto;
|
|
509
455
|
Object.assign(findUser, restDto);
|
|
510
456
|
await queryRunner.manager.save(this.repository.target, findUser);
|
|
511
|
-
// Handle extras update via enricher
|
|
512
457
|
if (extras && this.userEnricher?.updateProfileExtras) {
|
|
513
458
|
await this.userEnricher.updateProfileExtras(userId, extras, queryRunner);
|
|
514
459
|
}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
2
2
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
3
3
|
import { Request } from 'express';
|
|
4
|
-
import { ChangePasswordDto, CompanySelectionResponseDto,
|
|
4
|
+
import { ChangePasswordDto, CompanySelectionResponseDto, LoginDto, LoginResponseDto, LoginResponseRequiresEmailVerificationDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto } from '../dtos/authentication.dto';
|
|
5
5
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
6
|
-
import { AuthEmailService } from '../services/auth-email.service';
|
|
7
6
|
import { AuthenticationService } from '../services/authentication.service';
|
|
8
7
|
export declare class AuthenticationController {
|
|
9
8
|
private readonly authService;
|
|
10
9
|
private readonly authConfig?;
|
|
11
|
-
|
|
12
|
-
constructor(authService: AuthenticationService, authConfig?: AuthConfigService, authEmailService?: AuthEmailService);
|
|
10
|
+
constructor(authService: AuthenticationService, authConfig?: AuthConfigService);
|
|
13
11
|
private buildAuthUrl;
|
|
14
|
-
private get emailService();
|
|
15
|
-
private get isEmailEnabled();
|
|
16
12
|
login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto | LoginResponseRequiresEmailVerificationDto>;
|
|
17
13
|
register(req: Request, registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
|
|
18
14
|
refreshToken(req: Request, refreshTokenDto: RefreshTokenDto): Promise<{
|
|
@@ -21,8 +17,4 @@ export declare class AuthenticationController {
|
|
|
21
17
|
getCurrentUser(user: ILoggedUserInfo): Promise<MeResponseDto>;
|
|
22
18
|
logout(userId: string): Promise<MessageResponseDto>;
|
|
23
19
|
changePassword(userId: string, dto: ChangePasswordDto): Promise<MessageResponseDto>;
|
|
24
|
-
forgotPassword(req: Request, dto: ForgotPasswordDto): Promise<MessageResponseDto>;
|
|
25
|
-
resetPassword(dto: ResetPasswordDto): Promise<MessageResponseDto>;
|
|
26
|
-
resendVerification(req: Request, dto: ResendVerificationDto): Promise<MessageResponseDto>;
|
|
27
|
-
verifyEmail(dto: VerifyEmailDto): Promise<MessageResponseDto | RegistrationResponseDto>;
|
|
28
20
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
import { ForgotPasswordDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
|
|
4
|
+
import { AuthConfigService } from '../services/auth-config.service';
|
|
5
|
+
import { AuthDataSourceProvider } from '../services/auth-datasource.provider';
|
|
6
|
+
import { AuthEmailService } from '../services/auth-email.service';
|
|
7
|
+
import { AuthenticationService } from '../services/authentication.service';
|
|
8
|
+
export declare class EmailVerificationController {
|
|
9
|
+
private readonly authService;
|
|
10
|
+
private readonly authEmailService;
|
|
11
|
+
private readonly authConfig;
|
|
12
|
+
private readonly dataSourceProvider;
|
|
13
|
+
constructor(authService: AuthenticationService, authEmailService: AuthEmailService, authConfig: AuthConfigService, dataSourceProvider: AuthDataSourceProvider);
|
|
14
|
+
private ensureEmailVerificationEnabled;
|
|
15
|
+
private buildAuthUrl;
|
|
16
|
+
forgotPassword(req: Request, dto: ForgotPasswordDto): Promise<MessageResponseDto>;
|
|
17
|
+
resetPassword(dto: ResetPasswordDto): Promise<MessageResponseDto>;
|
|
18
|
+
resendVerification(req: Request, dto: ResendVerificationDto): Promise<MessageResponseDto>;
|
|
19
|
+
verifyEmail(dto: VerifyEmailDto): Promise<MessageResponseDto | RegistrationResponseDto>;
|
|
20
|
+
}
|
package/controllers/index.d.ts
CHANGED
package/entities/index.d.ts
CHANGED
|
@@ -4,13 +4,8 @@ export * from './pending-registration.entity';
|
|
|
4
4
|
export { PermissionType, UserCompanyPermission } from './user-company-permission.entity';
|
|
5
5
|
export * from './user.entity';
|
|
6
6
|
export * from './user-token.entity';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import { UserToken } from './user-token.entity';
|
|
13
|
-
export declare const CoreEntities: (typeof PendingRegistration | typeof UserToken | typeof User)[];
|
|
14
|
-
export declare const CompanyEntities: (typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
15
|
-
export declare const AllEntities: (typeof PendingRegistration | typeof UserToken | typeof User | typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
16
|
-
export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof PendingRegistration | typeof UserToken | typeof User | typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
7
|
+
export interface IAuthEntitiesConfig {
|
|
8
|
+
enableCompanyFeature?: boolean;
|
|
9
|
+
enableEmailVerification?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function getEntitiesByConfig(config: IAuthEntitiesConfig): any[];
|
|
@@ -34,26 +34,16 @@ import { ApiBearerAuth, ApiBody, ApiExtraModels, ApiHeader, ApiOperation, ApiRes
|
|
|
34
34
|
import { SkipThrottle, Throttle } from '@nestjs/throttler';
|
|
35
35
|
import { Request } from 'express';
|
|
36
36
|
import { DEFAULT_COOKIE_NAME } from '../config';
|
|
37
|
-
import { ChangePasswordDto, CompanySelectionResponseDto,
|
|
37
|
+
import { ChangePasswordDto, CompanySelectionResponseDto, LoginDto, LoginResponseDto, LoginResponseRequiresEmailVerificationDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto } from '../dtos/authentication.dto';
|
|
38
38
|
import { ClearToken } from '../interceptors/clear-token.interceptor';
|
|
39
39
|
import { SetToken } from '../interceptors/set-token.interceptor';
|
|
40
40
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
41
|
-
import { AuthEmailService } from '../services/auth-email.service';
|
|
42
41
|
import { AuthenticationService } from '../services/authentication.service';
|
|
43
42
|
export class AuthenticationController {
|
|
44
43
|
buildAuthUrl(req, path) {
|
|
45
44
|
const frontendUrl = this.authConfig?.getFrontendUrl();
|
|
46
45
|
return frontendUrl ? `${frontendUrl}${path}` : `${req.protocol}://${req.get('host')}${path}`;
|
|
47
46
|
}
|
|
48
|
-
get emailService() {
|
|
49
|
-
if (!this.authEmailService) {
|
|
50
|
-
throw new Error('Email service is not configured');
|
|
51
|
-
}
|
|
52
|
-
return this.authEmailService;
|
|
53
|
-
}
|
|
54
|
-
get isEmailEnabled() {
|
|
55
|
-
return !!this.authEmailService;
|
|
56
|
-
}
|
|
57
47
|
async login(loginDto) {
|
|
58
48
|
const result = await this.authService.login(loginDto.email, loginDto.password);
|
|
59
49
|
if ('requiresEmailVerification' in result) {
|
|
@@ -99,56 +89,11 @@ export class AuthenticationController {
|
|
|
99
89
|
message: 'Password changed successfully'
|
|
100
90
|
};
|
|
101
91
|
}
|
|
102
|
-
|
|
103
|
-
if (!this.isEmailEnabled) {
|
|
104
|
-
return {
|
|
105
|
-
success: false,
|
|
106
|
-
message: 'Email service is not configured'
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
return this.emailService.forgotPassword(dto.email, this.buildAuthUrl(req, '/auth/reset-password'));
|
|
110
|
-
}
|
|
111
|
-
async resetPassword(dto) {
|
|
112
|
-
if (!this.isEmailEnabled) {
|
|
113
|
-
return {
|
|
114
|
-
success: false,
|
|
115
|
-
message: 'Email service is not configured'
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
return this.emailService.resetPassword(dto.token, dto.newPassword);
|
|
119
|
-
}
|
|
120
|
-
async resendVerification(req, dto) {
|
|
121
|
-
if (!this.isEmailEnabled) {
|
|
122
|
-
return {
|
|
123
|
-
success: false,
|
|
124
|
-
message: 'Email service is not configured'
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return this.emailService.resendVerificationEmail(dto.email, this.buildAuthUrl(req, '/auth/verify-email'));
|
|
128
|
-
}
|
|
129
|
-
/** Verify email - Verify email using token. Handles both pending registration and existing user verification. */ async verifyEmail(dto) {
|
|
130
|
-
if (!this.isEmailEnabled) {
|
|
131
|
-
return {
|
|
132
|
-
success: false,
|
|
133
|
-
message: 'Email service is not configured'
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
const result = await this.emailService.verifyEmail(dto.token);
|
|
137
|
-
if (result.type === 'pending') {
|
|
138
|
-
return this.authService.createUserFromPendingRegistration(result.pending);
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
success: true,
|
|
142
|
-
message: result.message
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
constructor(authService, authConfig, authEmailService){
|
|
92
|
+
constructor(authService, authConfig){
|
|
146
93
|
_define_property(this, "authService", void 0);
|
|
147
94
|
_define_property(this, "authConfig", void 0);
|
|
148
|
-
_define_property(this, "authEmailService", void 0);
|
|
149
95
|
this.authService = authService;
|
|
150
96
|
this.authConfig = authConfig;
|
|
151
|
-
this.authEmailService = authEmailService;
|
|
152
97
|
}
|
|
153
98
|
}
|
|
154
99
|
_ts_decorate([
|
|
@@ -427,131 +372,15 @@ _ts_decorate([
|
|
|
427
372
|
]),
|
|
428
373
|
_ts_metadata("design:returntype", Promise)
|
|
429
374
|
], AuthenticationController.prototype, "changePassword", null);
|
|
430
|
-
_ts_decorate([
|
|
431
|
-
Post('forgot-password'),
|
|
432
|
-
HttpCode(HttpStatus.OK),
|
|
433
|
-
Throttle({
|
|
434
|
-
default: {
|
|
435
|
-
limit: 3,
|
|
436
|
-
ttl: 300000
|
|
437
|
-
}
|
|
438
|
-
}),
|
|
439
|
-
ApiOperation({
|
|
440
|
-
summary: 'Request password reset email'
|
|
441
|
-
}),
|
|
442
|
-
ApiBody({
|
|
443
|
-
type: ForgotPasswordDto
|
|
444
|
-
}),
|
|
445
|
-
ApiResponse({
|
|
446
|
-
status: 200,
|
|
447
|
-
type: MessageResponseDto
|
|
448
|
-
}),
|
|
449
|
-
_ts_param(0, Req()),
|
|
450
|
-
_ts_param(1, Body()),
|
|
451
|
-
_ts_metadata("design:type", Function),
|
|
452
|
-
_ts_metadata("design:paramtypes", [
|
|
453
|
-
typeof Request === "undefined" ? Object : Request,
|
|
454
|
-
typeof ForgotPasswordDto === "undefined" ? Object : ForgotPasswordDto
|
|
455
|
-
]),
|
|
456
|
-
_ts_metadata("design:returntype", Promise)
|
|
457
|
-
], AuthenticationController.prototype, "forgotPassword", null);
|
|
458
|
-
_ts_decorate([
|
|
459
|
-
Post('reset-password'),
|
|
460
|
-
HttpCode(HttpStatus.OK),
|
|
461
|
-
Throttle({
|
|
462
|
-
default: {
|
|
463
|
-
limit: 5,
|
|
464
|
-
ttl: 900000
|
|
465
|
-
}
|
|
466
|
-
}),
|
|
467
|
-
ApiOperation({
|
|
468
|
-
summary: 'Reset password using token'
|
|
469
|
-
}),
|
|
470
|
-
ApiBody({
|
|
471
|
-
type: ResetPasswordDto
|
|
472
|
-
}),
|
|
473
|
-
ApiResponse({
|
|
474
|
-
status: 200,
|
|
475
|
-
type: MessageResponseDto
|
|
476
|
-
}),
|
|
477
|
-
_ts_param(0, Body()),
|
|
478
|
-
_ts_metadata("design:type", Function),
|
|
479
|
-
_ts_metadata("design:paramtypes", [
|
|
480
|
-
typeof ResetPasswordDto === "undefined" ? Object : ResetPasswordDto
|
|
481
|
-
]),
|
|
482
|
-
_ts_metadata("design:returntype", Promise)
|
|
483
|
-
], AuthenticationController.prototype, "resetPassword", null);
|
|
484
|
-
_ts_decorate([
|
|
485
|
-
Post('resend-verification'),
|
|
486
|
-
HttpCode(HttpStatus.OK),
|
|
487
|
-
Throttle({
|
|
488
|
-
default: {
|
|
489
|
-
limit: 3,
|
|
490
|
-
ttl: 300000
|
|
491
|
-
}
|
|
492
|
-
}),
|
|
493
|
-
ApiOperation({
|
|
494
|
-
summary: 'Resend email verification link'
|
|
495
|
-
}),
|
|
496
|
-
ApiBody({
|
|
497
|
-
type: ResendVerificationDto
|
|
498
|
-
}),
|
|
499
|
-
ApiResponse({
|
|
500
|
-
status: 200,
|
|
501
|
-
type: MessageResponseDto
|
|
502
|
-
}),
|
|
503
|
-
_ts_param(0, Req()),
|
|
504
|
-
_ts_param(1, Body()),
|
|
505
|
-
_ts_metadata("design:type", Function),
|
|
506
|
-
_ts_metadata("design:paramtypes", [
|
|
507
|
-
typeof Request === "undefined" ? Object : Request,
|
|
508
|
-
typeof ResendVerificationDto === "undefined" ? Object : ResendVerificationDto
|
|
509
|
-
]),
|
|
510
|
-
_ts_metadata("design:returntype", Promise)
|
|
511
|
-
], AuthenticationController.prototype, "resendVerification", null);
|
|
512
|
-
_ts_decorate([
|
|
513
|
-
Post('verify-email'),
|
|
514
|
-
HttpCode(HttpStatus.OK),
|
|
515
|
-
Throttle({
|
|
516
|
-
default: {
|
|
517
|
-
limit: 10,
|
|
518
|
-
ttl: 60000
|
|
519
|
-
}
|
|
520
|
-
}),
|
|
521
|
-
UseInterceptors(SetToken),
|
|
522
|
-
ApiOperation({
|
|
523
|
-
summary: 'Verify email using token'
|
|
524
|
-
}),
|
|
525
|
-
ApiBody({
|
|
526
|
-
type: VerifyEmailDto
|
|
527
|
-
}),
|
|
528
|
-
ApiResponse({
|
|
529
|
-
status: 200,
|
|
530
|
-
type: MessageResponseDto
|
|
531
|
-
}),
|
|
532
|
-
ApiResponse({
|
|
533
|
-
status: 200,
|
|
534
|
-
type: RegistrationResponseDto
|
|
535
|
-
}),
|
|
536
|
-
_ts_param(0, Body()),
|
|
537
|
-
_ts_metadata("design:type", Function),
|
|
538
|
-
_ts_metadata("design:paramtypes", [
|
|
539
|
-
typeof VerifyEmailDto === "undefined" ? Object : VerifyEmailDto
|
|
540
|
-
]),
|
|
541
|
-
_ts_metadata("design:returntype", Promise)
|
|
542
|
-
], AuthenticationController.prototype, "verifyEmail", null);
|
|
543
375
|
AuthenticationController = _ts_decorate([
|
|
544
376
|
ApiTags('Authentication'),
|
|
545
377
|
Controller('auth'),
|
|
546
378
|
_ts_param(0, Inject(AuthenticationService)),
|
|
547
379
|
_ts_param(1, Optional()),
|
|
548
380
|
_ts_param(1, Inject(AuthConfigService)),
|
|
549
|
-
_ts_param(2, Optional()),
|
|
550
|
-
_ts_param(2, Inject(AuthEmailService)),
|
|
551
381
|
_ts_metadata("design:type", Function),
|
|
552
382
|
_ts_metadata("design:paramtypes", [
|
|
553
383
|
typeof AuthenticationService === "undefined" ? Object : AuthenticationService,
|
|
554
|
-
typeof AuthConfigService === "undefined" ? Object : AuthConfigService
|
|
555
|
-
typeof AuthEmailService === "undefined" ? Object : AuthEmailService
|
|
384
|
+
typeof AuthConfigService === "undefined" ? Object : AuthConfigService
|
|
556
385
|
])
|
|
557
386
|
], AuthenticationController);
|