@flusys/nestjs-auth 1.0.0-beta → 1.0.0-rc
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 +274 -291
- package/cjs/config/auth.constants.js +12 -0
- package/cjs/controllers/authentication.controller.js +219 -10
- package/cjs/controllers/branch.controller.js +44 -1
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +44 -1
- package/cjs/controllers/user-permission.controller.js +40 -25
- package/cjs/controllers/user.controller.js +211 -7
- package/cjs/dtos/authentication.dto.js +119 -13
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/index.js +5 -1
- package/cjs/entities/pending-registration.entity.js +125 -0
- package/cjs/entities/user-company-permission.entity.js +0 -6
- package/cjs/entities/user-token.entity.js +129 -0
- package/cjs/guards/auth-throttler.guard.js +53 -0
- package/cjs/interceptors/clear-token.interceptor.js +3 -6
- package/cjs/interceptors/set-token.interceptor.js +38 -31
- package/cjs/interfaces/auth-email-provider.interface.js +45 -0
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/user-enricher.interface.js +4 -0
- package/cjs/modules/auth.module.js +8 -20
- package/cjs/services/auth-config.service.js +23 -2
- package/cjs/services/auth-datasource.provider.js +5 -3
- package/cjs/services/auth-email.service.js +471 -0
- package/cjs/services/authentication.service.js +169 -77
- package/cjs/services/branch.service.js +6 -5
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +0 -2
- package/cjs/services/user.service.js +213 -34
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +3 -0
- package/controllers/authentication.controller.d.ts +13 -9
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user.controller.d.ts +33 -0
- package/dtos/authentication.dto.d.ts +21 -3
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -2
- package/entities/index.d.ts +7 -3
- package/entities/pending-registration.entity.d.ts +17 -0
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/entities/user-token.entity.d.ts +15 -0
- package/fesm/config/auth.constants.js +6 -2
- package/fesm/controllers/authentication.controller.js +221 -12
- package/fesm/controllers/branch.controller.js +45 -2
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +45 -2
- package/fesm/controllers/user-permission.controller.js +41 -26
- package/fesm/controllers/user.controller.js +214 -10
- package/fesm/dtos/authentication.dto.js +106 -14
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/index.js +7 -1
- package/fesm/entities/pending-registration.entity.js +115 -0
- package/fesm/entities/user-company-permission.entity.js +0 -6
- package/fesm/entities/user-token.entity.js +113 -0
- package/fesm/guards/auth-throttler.guard.js +43 -0
- package/fesm/interceptors/clear-token.interceptor.js +2 -5
- package/fesm/interceptors/set-token.interceptor.js +37 -30
- package/fesm/interfaces/auth-email-provider.interface.js +38 -0
- package/fesm/interfaces/authentication.interface.js +1 -1
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/user-enricher.interface.js +26 -0
- package/fesm/modules/auth.module.js +10 -22
- package/fesm/services/auth-config.service.js +24 -3
- package/fesm/services/auth-datasource.provider.js +6 -4
- package/fesm/services/auth-email.service.js +420 -0
- package/fesm/services/authentication.service.js +169 -77
- package/fesm/services/branch.service.js +6 -5
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +1 -3
- package/fesm/services/user.service.js +215 -36
- package/fesm/validators/password.validator.js +48 -0
- package/guards/auth-throttler.guard.d.ts +7 -0
- package/interceptors/set-token.interceptor.d.ts +1 -0
- package/interfaces/auth-email-provider.interface.d.ts +6 -0
- package/interfaces/auth-module-options.interface.d.ts +4 -1
- package/interfaces/authentication.interface.d.ts +14 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/user-enricher.interface.d.ts +64 -0
- package/interfaces/user.interface.d.ts +1 -0
- package/modules/auth.module.d.ts +0 -1
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +5 -1
- package/services/auth-email.service.d.ts +51 -0
- package/services/authentication.service.d.ts +16 -11
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/index.d.ts +1 -0
- package/services/user-permission.service.d.ts +0 -1
- package/services/user.service.d.ts +19 -3
- package/validators/password.validator.d.ts +10 -0
|
@@ -17,6 +17,7 @@ const _authconfigservice = require("./auth-config.service");
|
|
|
17
17
|
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
18
18
|
const _config = require("../config");
|
|
19
19
|
const _enums = require("../enums");
|
|
20
|
+
const _userenricherinterface = require("../interfaces/user-enricher.interface");
|
|
20
21
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
21
22
|
const _userentity = require("../entities/user.entity");
|
|
22
23
|
function _define_property(obj, key, value) {
|
|
@@ -109,6 +110,14 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
// Custom Methods
|
|
113
|
+
/**
|
|
114
|
+
* Validate that the requesting user owns the target profile.
|
|
115
|
+
* Users can only access/modify their own profile data.
|
|
116
|
+
*/ validateProfileOwnership(targetUserId, requestingUser) {
|
|
117
|
+
if (targetUserId !== requestingUser.id) {
|
|
118
|
+
throw new _common.ForbiddenException('Cannot access another user\'s profile');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
112
121
|
/** Verify email */ async verifyEmail(id) {
|
|
113
122
|
// Ensure repository is initialized for custom method
|
|
114
123
|
await this.ensureRepositoryInitialized();
|
|
@@ -170,33 +179,58 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
170
179
|
/** Convert DTO to entity (hash password before saving) */ async convertSingleDtoToEntity(dto, user) {
|
|
171
180
|
// Repository initialization is handled automatically by RequestScopedApiService
|
|
172
181
|
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
173
|
-
// Hash password if provided
|
|
174
|
-
if (dto.password) {
|
|
182
|
+
// Hash password if provided (CreateUserDto has password field)
|
|
183
|
+
if ('password' in dto && dto.password) {
|
|
175
184
|
entity.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
176
185
|
}
|
|
177
186
|
return entity;
|
|
178
187
|
}
|
|
179
188
|
/** Convert entity to response DTO (removes password). In raw mode with company feature, isActive comes from permission table. */ convertEntityToResponseDto(entity, isRaw) {
|
|
189
|
+
if (isRaw) {
|
|
190
|
+
return this.convertRawToResponseDto(entity);
|
|
191
|
+
}
|
|
192
|
+
return this.convertEntityToDto(entity);
|
|
193
|
+
}
|
|
194
|
+
/** Convert raw query result to response DTO */ convertRawToResponseDto(raw) {
|
|
180
195
|
// When raw mode is used with company feature, isActive comes from computed field
|
|
181
|
-
|
|
182
|
-
const isActive = isRaw && entity.user_companyIsActive !== undefined ? Boolean(entity.user_companyIsActive) : isRaw ? entity.user_is_active : entity.isActive;
|
|
196
|
+
const isActive = raw.user_companyIsActive !== undefined ? Boolean(raw.user_companyIsActive) : raw.user_is_active;
|
|
183
197
|
return {
|
|
184
|
-
id:
|
|
185
|
-
name:
|
|
186
|
-
phone:
|
|
187
|
-
email:
|
|
188
|
-
profilePictureId:
|
|
198
|
+
id: raw.user_id,
|
|
199
|
+
name: raw.user_name,
|
|
200
|
+
phone: raw.user_phone,
|
|
201
|
+
email: raw.user_email,
|
|
202
|
+
profilePictureId: raw.user_profile_picture_id,
|
|
189
203
|
isActive,
|
|
190
|
-
emailVerified:
|
|
191
|
-
phoneVerified:
|
|
192
|
-
lastLoginAt:
|
|
193
|
-
additionalFields:
|
|
194
|
-
createdAt:
|
|
195
|
-
updatedAt:
|
|
196
|
-
deletedAt:
|
|
197
|
-
createdById:
|
|
198
|
-
updatedById:
|
|
199
|
-
deletedById:
|
|
204
|
+
emailVerified: raw.user_email_verified,
|
|
205
|
+
phoneVerified: raw.user_phone_verified,
|
|
206
|
+
lastLoginAt: raw.user_last_login_at,
|
|
207
|
+
additionalFields: raw.user_additional_fields,
|
|
208
|
+
createdAt: raw.user_created_at,
|
|
209
|
+
updatedAt: raw.user_updated_at,
|
|
210
|
+
deletedAt: raw.user_deleted_at,
|
|
211
|
+
createdById: raw.user_created_by_id,
|
|
212
|
+
updatedById: raw.user_updated_by_id,
|
|
213
|
+
deletedById: raw.user_deleted_by_id
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** Convert entity to response DTO */ convertEntityToDto(entity) {
|
|
217
|
+
return {
|
|
218
|
+
id: entity.id,
|
|
219
|
+
name: entity.name,
|
|
220
|
+
phone: entity.phone,
|
|
221
|
+
email: entity.email,
|
|
222
|
+
profilePictureId: entity.profilePictureId,
|
|
223
|
+
isActive: entity.isActive,
|
|
224
|
+
emailVerified: entity.emailVerified,
|
|
225
|
+
phoneVerified: entity.phoneVerified,
|
|
226
|
+
lastLoginAt: entity.lastLoginAt,
|
|
227
|
+
additionalFields: entity.additionalFields,
|
|
228
|
+
createdAt: entity.createdAt,
|
|
229
|
+
updatedAt: entity.updatedAt,
|
|
230
|
+
deletedAt: entity.deletedAt,
|
|
231
|
+
createdById: entity.createdById,
|
|
232
|
+
updatedById: entity.updatedById,
|
|
233
|
+
deletedById: entity.deletedById
|
|
200
234
|
};
|
|
201
235
|
}
|
|
202
236
|
/** Convert entity list to response DTO list */ convertEntityListToResponseListDto(entities, isRaw) {
|
|
@@ -213,7 +247,11 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
213
247
|
}
|
|
214
248
|
/** Add company permission-based isActive check when company feature enabled. Returns isRaw: true for computed fields. */ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
|
|
215
249
|
// Repository initialization is handled automatically by RequestScopedApiService
|
|
216
|
-
|
|
250
|
+
let result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
|
|
251
|
+
// Apply user enricher query modifications if available
|
|
252
|
+
if (this.userEnricher?.enrichListQuery) {
|
|
253
|
+
await this.userEnricher.enrichListQuery(query, user);
|
|
254
|
+
}
|
|
217
255
|
// Only apply company filtering if:
|
|
218
256
|
// 1. Company feature is enabled at bootstrap
|
|
219
257
|
// 2. User has company context
|
|
@@ -333,16 +371,144 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
333
371
|
await queryRunner.release();
|
|
334
372
|
}
|
|
335
373
|
}
|
|
336
|
-
/**
|
|
337
|
-
|
|
374
|
+
/**
|
|
375
|
+
* Get user profile with extras from enricher
|
|
376
|
+
* Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
|
|
377
|
+
*/ async getProfileWithExtras(userId, user) {
|
|
378
|
+
this.validateProfileOwnership(userId, user);
|
|
379
|
+
await this.ensureRepositoryInitialized();
|
|
380
|
+
const userEntity = await this.repository.findOne({
|
|
381
|
+
where: {
|
|
382
|
+
id: userId
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
if (!userEntity) {
|
|
386
|
+
throw new _common.NotFoundException('User not found');
|
|
387
|
+
}
|
|
388
|
+
const userData = this.convertEntityToResponseDto(userEntity, false);
|
|
389
|
+
// Get extras from enricher if available
|
|
390
|
+
let extras;
|
|
391
|
+
if (this.userEnricher?.getProfileExtras) {
|
|
392
|
+
extras = await this.userEnricher.getProfileExtras(userId, user);
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
user: userData,
|
|
396
|
+
extras
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get enriched user list
|
|
401
|
+
* Applies enricher transformations after query execution
|
|
402
|
+
*/ async getEnrichedList(users, user) {
|
|
403
|
+
if (this.userEnricher?.enrichListItems) {
|
|
404
|
+
return this.userEnricher.enrichListItems(users, user);
|
|
405
|
+
}
|
|
406
|
+
return users;
|
|
407
|
+
}
|
|
408
|
+
// ============================================
|
|
409
|
+
// Multi-Step Profile Methods (via Enricher)
|
|
410
|
+
// ============================================
|
|
411
|
+
/**
|
|
412
|
+
* Get profile section definitions from enricher
|
|
413
|
+
*/ async getProfileSections() {
|
|
414
|
+
if (this.userEnricher?.getProfileSections) {
|
|
415
|
+
return this.userEnricher.getProfileSections();
|
|
416
|
+
}
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Get specific profile section data
|
|
421
|
+
*/ async getProfileSectionData(userId, sectionId, user) {
|
|
422
|
+
this.validateProfileOwnership(userId, user);
|
|
423
|
+
await this.ensureRepositoryInitialized();
|
|
424
|
+
// Verify user exists
|
|
425
|
+
const userEntity = await this.repository.findOne({
|
|
426
|
+
where: {
|
|
427
|
+
id: userId
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
if (!userEntity) {
|
|
431
|
+
throw new _common.NotFoundException('User not found');
|
|
432
|
+
}
|
|
433
|
+
if (this.userEnricher?.getProfileSectionData) {
|
|
434
|
+
return this.userEnricher.getProfileSectionData(userId, sectionId, user);
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Update specific profile section
|
|
440
|
+
*/ async updateProfileSection(userId, sectionId, data, user) {
|
|
441
|
+
this.validateProfileOwnership(userId, user);
|
|
442
|
+
await this.ensureRepositoryInitialized();
|
|
338
443
|
if (!this.dataSource) {
|
|
339
444
|
throw new _common.BadRequestException('DataSource not available');
|
|
340
445
|
}
|
|
446
|
+
// Verify user exists
|
|
447
|
+
const userEntity = await this.repository.findOne({
|
|
448
|
+
where: {
|
|
449
|
+
id: userId
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
if (!userEntity) {
|
|
453
|
+
throw new _common.NotFoundException('User not found');
|
|
454
|
+
}
|
|
455
|
+
if (!this.userEnricher?.updateProfileSection) {
|
|
456
|
+
throw new _common.BadRequestException('Profile sections not supported');
|
|
457
|
+
}
|
|
458
|
+
const queryRunner = this.dataSource.createQueryRunner();
|
|
459
|
+
await queryRunner.connect();
|
|
460
|
+
await queryRunner.startTransaction();
|
|
461
|
+
try {
|
|
462
|
+
await this.userEnricher.updateProfileSection(userId, sectionId, data, queryRunner);
|
|
463
|
+
await queryRunner.commitTransaction();
|
|
464
|
+
await this.clearCacheForId([
|
|
465
|
+
userEntity
|
|
466
|
+
]);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
await queryRunner.rollbackTransaction();
|
|
469
|
+
_utils.ErrorHandler.logError(this.logger, error, 'updateProfileSection', {
|
|
470
|
+
entity: this.entityName,
|
|
471
|
+
sectionId
|
|
472
|
+
});
|
|
473
|
+
_utils.ErrorHandler.rethrowError(error);
|
|
474
|
+
} finally{
|
|
475
|
+
await queryRunner.release();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get profile completion percentage
|
|
480
|
+
*/ async getProfileCompletion(userId, user) {
|
|
481
|
+
this.validateProfileOwnership(userId, user);
|
|
482
|
+
await this.ensureRepositoryInitialized();
|
|
483
|
+
// Verify user exists
|
|
484
|
+
const userEntity = await this.repository.findOne({
|
|
485
|
+
where: {
|
|
486
|
+
id: userId
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
if (!userEntity) {
|
|
490
|
+
throw new _common.NotFoundException('User not found');
|
|
491
|
+
}
|
|
492
|
+
if (this.userEnricher?.calculateProfileCompletion) {
|
|
493
|
+
return this.userEnricher.calculateProfileCompletion(userId, user);
|
|
494
|
+
}
|
|
495
|
+
return 100; // Default to 100% if no enricher
|
|
496
|
+
}
|
|
497
|
+
/** Update user profile with password handling and extras */ async profile(dto, user) {
|
|
498
|
+
// Ensure repository and dataSource are initialized
|
|
499
|
+
await this.ensureRepositoryInitialized();
|
|
500
|
+
if (!this.dataSource) {
|
|
501
|
+
throw new _common.BadRequestException('DataSource not available');
|
|
502
|
+
}
|
|
503
|
+
const userId = dto.id ?? user.id;
|
|
504
|
+
// Validate extras if enricher provides validation
|
|
505
|
+
if (dto.extras && this.userEnricher?.validateProfileExtras) {
|
|
506
|
+
await this.userEnricher.validateProfileExtras(userId, dto.extras, user);
|
|
507
|
+
}
|
|
341
508
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
342
509
|
await queryRunner.connect();
|
|
343
510
|
await queryRunner.startTransaction();
|
|
344
511
|
try {
|
|
345
|
-
const userId = dto.id ?? user.id;
|
|
346
512
|
const findUser = await queryRunner.manager.findOne(this.repository.target, {
|
|
347
513
|
where: {
|
|
348
514
|
id: userId
|
|
@@ -353,18 +519,28 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
353
519
|
}
|
|
354
520
|
// Handle password update
|
|
355
521
|
if (dto.password) {
|
|
356
|
-
// If updating own profile, require old password
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
522
|
+
// If updating own profile, require old password verification
|
|
523
|
+
const isOwnProfile = !dto.id || dto.id === user.id;
|
|
524
|
+
if (isOwnProfile) {
|
|
525
|
+
if (!dto.oldPassword) {
|
|
526
|
+
throw new _common.BadRequestException('Current password is required to change your password');
|
|
527
|
+
}
|
|
528
|
+
const isPasswordValid = await _bcrypt.compare(dto.oldPassword, findUser.password);
|
|
529
|
+
if (!isPasswordValid) {
|
|
530
|
+
throw new _common.BadRequestException('Current password is incorrect');
|
|
531
|
+
}
|
|
360
532
|
}
|
|
361
|
-
|
|
362
|
-
findUser.password = await _bcrypt.hash(dto.password, salt);
|
|
533
|
+
findUser.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
363
534
|
}
|
|
364
|
-
// Merge other fields (excluding password)
|
|
365
|
-
|
|
535
|
+
// Merge other fields (excluding password, oldPassword, extras, and sensitive flags)
|
|
536
|
+
// Users cannot self-modify emailVerified, phoneVerified, or isActive via profile update
|
|
537
|
+
const { password, oldPassword, extras, emailVerified, phoneVerified, isActive, ...restDto } = dto;
|
|
366
538
|
Object.assign(findUser, restDto);
|
|
367
539
|
await queryRunner.manager.save(this.repository.target, findUser);
|
|
540
|
+
// Handle extras update via enricher
|
|
541
|
+
if (extras && this.userEnricher?.updateProfileExtras) {
|
|
542
|
+
await this.userEnricher.updateProfileExtras(userId, extras, queryRunner);
|
|
543
|
+
}
|
|
368
544
|
await queryRunner.commitTransaction();
|
|
369
545
|
await this.clearCacheForId([
|
|
370
546
|
findUser
|
|
@@ -380,8 +556,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
380
556
|
await queryRunner.release();
|
|
381
557
|
}
|
|
382
558
|
}
|
|
383
|
-
constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
|
|
384
|
-
super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig;
|
|
559
|
+
constructor(cacheManager, utilsService, dataSourceProvider, authConfig, userEnricher){
|
|
560
|
+
super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "userEnricher", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig, this.userEnricher = userEnricher;
|
|
385
561
|
}
|
|
386
562
|
};
|
|
387
563
|
UserService = _ts_decorate([
|
|
@@ -392,11 +568,14 @@ UserService = _ts_decorate([
|
|
|
392
568
|
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
393
569
|
_ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
|
|
394
570
|
_ts_param(3, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
|
|
571
|
+
_ts_param(4, (0, _common.Optional)()),
|
|
572
|
+
_ts_param(4, (0, _common.Inject)(_config.USER_ENRICHER)),
|
|
395
573
|
_ts_metadata("design:type", Function),
|
|
396
574
|
_ts_metadata("design:paramtypes", [
|
|
397
575
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
398
576
|
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
399
577
|
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
400
|
-
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
|
|
578
|
+
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
|
|
579
|
+
typeof _userenricherinterface.IUserEnricher === "undefined" ? Object : _userenricherinterface.IUserEnricher
|
|
401
580
|
])
|
|
402
581
|
], UserService);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "IsStrongPassword", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return IsStrongPassword;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _classvalidator = require("class-validator");
|
|
12
|
+
function IsStrongPassword(options = {}, validationOptions) {
|
|
13
|
+
const { minLength = 8, maxLength = 128, requireUppercase = true, requireLowercase = true, requireNumber = true, requireSpecialChar = true } = options;
|
|
14
|
+
return function(object, propertyName) {
|
|
15
|
+
(0, _classvalidator.registerDecorator)({
|
|
16
|
+
name: 'isStrongPassword',
|
|
17
|
+
target: object.constructor,
|
|
18
|
+
propertyName: propertyName,
|
|
19
|
+
options: validationOptions,
|
|
20
|
+
validator: {
|
|
21
|
+
validate (value) {
|
|
22
|
+
if (typeof value !== 'string') return false;
|
|
23
|
+
const checks = [
|
|
24
|
+
value.length >= minLength,
|
|
25
|
+
value.length <= maxLength,
|
|
26
|
+
!requireUppercase || /[A-Z]/.test(value),
|
|
27
|
+
!requireLowercase || /[a-z]/.test(value),
|
|
28
|
+
!requireNumber || /[0-9]/.test(value),
|
|
29
|
+
!requireSpecialChar || /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~`]/.test(value)
|
|
30
|
+
];
|
|
31
|
+
return checks.every(Boolean);
|
|
32
|
+
},
|
|
33
|
+
defaultMessage (args) {
|
|
34
|
+
const requirements = [];
|
|
35
|
+
if (minLength > 0) requirements.push(`at least ${minLength} characters`);
|
|
36
|
+
if (requireUppercase) requirements.push('one uppercase letter');
|
|
37
|
+
if (requireLowercase) requirements.push('one lowercase letter');
|
|
38
|
+
if (requireNumber) requirements.push('one number');
|
|
39
|
+
if (requireSpecialChar) requirements.push('one special character (!@#$%^&*...)');
|
|
40
|
+
return `Password must contain ${requirements.join(', ')}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export declare const AUTH_MODULE_OPTIONS = "AUTH_MODULE_OPTIONS";
|
|
2
|
+
export declare const USER_ENRICHER = "USER_ENRICHER";
|
|
2
3
|
export declare const BCRYPT_SALT_ROUNDS = 12;
|
|
4
|
+
export declare const DEFAULT_COOKIE_NAME = "fsn_refresh_token";
|
|
5
|
+
export declare const MAX_COOKIE_BYTES = 3500;
|
|
3
6
|
export declare const SESSION_CACHE_PREFIX = "auth:session";
|
|
4
7
|
export declare const TOKEN_REVOKED_PREFIX = "auth:revoked";
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
2
2
|
import { Request } from 'express';
|
|
3
|
-
import { ChangePasswordDto, CompanySelectionResponseDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto } from '../dtos/authentication.dto';
|
|
3
|
+
import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
|
|
4
|
+
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
4
5
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
6
|
+
import { AuthEmailService } from '../services/auth-email.service';
|
|
5
7
|
import { AuthenticationService } from '../services/authentication.service';
|
|
6
8
|
export declare class AuthenticationController {
|
|
7
9
|
private readonly authService;
|
|
8
10
|
private readonly authConfig?;
|
|
9
|
-
|
|
11
|
+
private readonly authEmailService?;
|
|
12
|
+
constructor(authService: AuthenticationService, authConfig?: AuthConfigService, authEmailService?: AuthEmailService);
|
|
13
|
+
private buildAuthUrl;
|
|
10
14
|
login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto>;
|
|
11
|
-
register(registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
|
|
15
|
+
register(req: Request, registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
|
|
12
16
|
refreshToken(req: Request, refreshTokenDto: RefreshTokenDto): Promise<{
|
|
13
17
|
accessToken: string;
|
|
14
18
|
}>;
|
|
15
19
|
getCurrentUser(user: ILoggedUserInfo): Promise<MeResponseDto>;
|
|
16
|
-
logout(userId: string): Promise<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
logout(userId: string): Promise<MessageResponseDto>;
|
|
21
|
+
changePassword(userId: string, dto: ChangePasswordDto): Promise<MessageResponseDto>;
|
|
22
|
+
forgotPassword(req: Request, dto: ForgotPasswordDto): Promise<MessageResponseDto>;
|
|
23
|
+
resetPassword(dto: ResetPasswordDto): Promise<MessageResponseDto>;
|
|
24
|
+
resendVerification(req: Request, dto: ResendVerificationDto): Promise<MessageResponseDto>;
|
|
25
|
+
verifyEmail(dto: VerifyEmailDto): Promise<MessageResponseDto | RegistrationResponseDto>;
|
|
22
26
|
}
|
|
@@ -6,9 +6,13 @@ export declare class CompanySelectionController {
|
|
|
6
6
|
select(dto: SelectDto): Promise<LoginResponseDto>;
|
|
7
7
|
switchCompany(userId: string, dto: SwitchCompanyDto): Promise<LoginResponseDto>;
|
|
8
8
|
getUserCompanies(userId: string): Promise<{
|
|
9
|
-
|
|
9
|
+
success: boolean;
|
|
10
|
+
message: string;
|
|
11
|
+
data: import("..").Company[];
|
|
10
12
|
}>;
|
|
11
13
|
getUserBranches(userId: string, companyId: string): Promise<{
|
|
12
|
-
|
|
14
|
+
success: boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
data: import("..").CompanyBranch[];
|
|
13
17
|
}>;
|
|
14
18
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
2
2
|
import { CreateUserDto, UpdateUserDto, UserResponseDto } from '../dtos/user.dto';
|
|
3
|
+
import { IUser } from '../interfaces/user.interface';
|
|
3
4
|
import { UserService } from '../services/user.service';
|
|
4
5
|
declare const UserController_base: abstract new (service: UserService) => {
|
|
5
6
|
service: UserService;
|
|
@@ -14,18 +15,50 @@ declare const UserController_base: abstract new (service: UserService) => {
|
|
|
14
15
|
export declare class UserController extends UserController_base {
|
|
15
16
|
service: UserService;
|
|
16
17
|
constructor(service: UserService);
|
|
18
|
+
getProfileWithExtras(id: string, user: ILoggedUserInfo): Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
data: {
|
|
21
|
+
user: IUser;
|
|
22
|
+
extras?: import("..").IProfileExtras;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
17
25
|
verifyEmail(id: string): Promise<{
|
|
26
|
+
success: boolean;
|
|
18
27
|
message: string;
|
|
19
28
|
}>;
|
|
20
29
|
verifyPhone(id: string): Promise<{
|
|
30
|
+
success: boolean;
|
|
21
31
|
message: string;
|
|
22
32
|
}>;
|
|
23
33
|
updateStatus(id: string, isActive: boolean, user: ILoggedUserInfo): Promise<{
|
|
34
|
+
success: boolean;
|
|
24
35
|
message: string;
|
|
25
36
|
}>;
|
|
26
37
|
profile(dto: UpdateUserDto, user: ILoggedUserInfo): Promise<{
|
|
38
|
+
success: boolean;
|
|
27
39
|
message: string;
|
|
28
40
|
data: string;
|
|
29
41
|
}>;
|
|
42
|
+
getProfileSections(): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
message: string;
|
|
45
|
+
data: import("..").IProfileSection[];
|
|
46
|
+
}>;
|
|
47
|
+
getProfileSectionData(id: string, sectionId: string, user: ILoggedUserInfo): Promise<{
|
|
48
|
+
success: boolean;
|
|
49
|
+
message: string;
|
|
50
|
+
data: import("..").IProfileSectionData;
|
|
51
|
+
}>;
|
|
52
|
+
updateProfileSection(id: string, sectionId: string, data: Record<string, any>, user: ILoggedUserInfo): Promise<{
|
|
53
|
+
success: boolean;
|
|
54
|
+
message: string;
|
|
55
|
+
}>;
|
|
56
|
+
getProfileCompletion(id: string, user: ILoggedUserInfo): Promise<{
|
|
57
|
+
success: boolean;
|
|
58
|
+
message: string;
|
|
59
|
+
data: {
|
|
60
|
+
completionPercentage: number;
|
|
61
|
+
};
|
|
62
|
+
}>;
|
|
30
63
|
}
|
|
31
64
|
export {};
|
|
@@ -71,12 +71,13 @@ export declare class LoginResponseDto {
|
|
|
71
71
|
companyFeatureEnabled?: boolean;
|
|
72
72
|
}
|
|
73
73
|
export declare class RegistrationResponseDto {
|
|
74
|
-
accessToken
|
|
75
|
-
refreshToken
|
|
74
|
+
accessToken?: string;
|
|
75
|
+
refreshToken?: string;
|
|
76
76
|
user: UserInfoDto;
|
|
77
77
|
company?: CompanyInfoDto;
|
|
78
78
|
branch?: BranchInfoDto;
|
|
79
|
-
companyFeatureEnabled
|
|
79
|
+
companyFeatureEnabled?: boolean;
|
|
80
|
+
requiresEmailVerification?: boolean;
|
|
80
81
|
}
|
|
81
82
|
export declare class MeResponseDto {
|
|
82
83
|
user: UserInfoDto;
|
|
@@ -90,3 +91,20 @@ export declare class CompanySelectionResponseDto {
|
|
|
90
91
|
user: UserInfoDto;
|
|
91
92
|
companies: CompanyWithBranchesDto[];
|
|
92
93
|
}
|
|
94
|
+
export declare class LoginResponseRequiresEmailVerificationDto {
|
|
95
|
+
requiresEmailVerification: true;
|
|
96
|
+
email: string;
|
|
97
|
+
message: string;
|
|
98
|
+
}
|
|
99
|
+
export declare class ForgotPasswordDto {
|
|
100
|
+
email: string;
|
|
101
|
+
}
|
|
102
|
+
export declare class ResetPasswordDto {
|
|
103
|
+
token: string;
|
|
104
|
+
newPassword: string;
|
|
105
|
+
}
|
|
106
|
+
export declare class VerifyEmailDto {
|
|
107
|
+
token: string;
|
|
108
|
+
}
|
|
109
|
+
export declare class ResendVerificationDto extends ForgotPasswordDto {
|
|
110
|
+
}
|
package/dtos/company.dto.d.ts
CHANGED
|
@@ -26,8 +26,12 @@ export declare class UserBranchPermissionDto {
|
|
|
26
26
|
companyId: string;
|
|
27
27
|
}
|
|
28
28
|
export declare class UserCompaniesResponseDto {
|
|
29
|
+
success: boolean;
|
|
30
|
+
message: string;
|
|
29
31
|
data: UserCompanyPermissionDto[];
|
|
30
32
|
}
|
|
31
33
|
export declare class UserBranchesResponseDto {
|
|
34
|
+
success: boolean;
|
|
35
|
+
message: string;
|
|
32
36
|
data: UserBranchPermissionDto[];
|
|
33
37
|
}
|
package/dtos/user.dto.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export declare class CreateUserDto {
|
|
2
|
-
[key: string]: unknown;
|
|
3
2
|
name: string;
|
|
4
3
|
phone?: string;
|
|
5
4
|
email: string;
|
|
@@ -16,6 +15,7 @@ export declare class UpdateUserDto {
|
|
|
16
15
|
phone?: string;
|
|
17
16
|
email?: string;
|
|
18
17
|
password?: string;
|
|
18
|
+
oldPassword?: string;
|
|
19
19
|
profilePictureId?: string;
|
|
20
20
|
isActive?: boolean;
|
|
21
21
|
emailVerified?: boolean;
|
|
@@ -23,7 +23,6 @@ export declare class UpdateUserDto {
|
|
|
23
23
|
additionalFields?: Record<string, any>;
|
|
24
24
|
}
|
|
25
25
|
export declare class UserResponseDto {
|
|
26
|
-
[key: string]: unknown;
|
|
27
26
|
id: string;
|
|
28
27
|
name: string;
|
|
29
28
|
phone?: string | null;
|
package/entities/index.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
export * from './company-branch.entity';
|
|
2
2
|
export * from './company.entity';
|
|
3
|
+
export * from './pending-registration.entity';
|
|
3
4
|
export { PermissionType, UserCompanyPermission } from './user-company-permission.entity';
|
|
4
5
|
export * from './user.entity';
|
|
6
|
+
export * from './user-token.entity';
|
|
5
7
|
import { CompanyBranch } from './company-branch.entity';
|
|
6
8
|
import { Company } from './company.entity';
|
|
9
|
+
import { PendingRegistration } from './pending-registration.entity';
|
|
7
10
|
import { UserCompanyPermission } from './user-company-permission.entity';
|
|
8
11
|
import { User } from './user.entity';
|
|
9
|
-
|
|
12
|
+
import { UserToken } from './user-token.entity';
|
|
13
|
+
export declare const CoreEntities: (typeof PendingRegistration | typeof User | typeof UserToken)[];
|
|
10
14
|
export declare const CompanyEntities: (typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
11
|
-
export declare const AllEntities: (typeof Company | typeof CompanyBranch | typeof User | typeof UserCompanyPermission)[];
|
|
12
|
-
export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof Company | typeof CompanyBranch | typeof User | typeof UserCompanyPermission)[];
|
|
15
|
+
export declare const AllEntities: (typeof Company | typeof CompanyBranch | typeof PendingRegistration | typeof User | typeof UserCompanyPermission | typeof UserToken)[];
|
|
16
|
+
export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof Company | typeof CompanyBranch | typeof PendingRegistration | typeof User | typeof UserCompanyPermission | typeof UserToken)[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class PendingRegistration {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string | null;
|
|
5
|
+
phone: string | null;
|
|
6
|
+
passwordHash: string;
|
|
7
|
+
token: string;
|
|
8
|
+
expiresAt: Date;
|
|
9
|
+
companyData: {
|
|
10
|
+
companySlug?: string;
|
|
11
|
+
branchSlug?: string;
|
|
12
|
+
newCompanyName?: string;
|
|
13
|
+
newCompanyPhone?: string;
|
|
14
|
+
newCompanyAddress?: string;
|
|
15
|
+
} | null;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare enum TokenType {
|
|
2
|
+
PASSWORD_RESET = "password_reset",
|
|
3
|
+
EMAIL_VERIFICATION = "email_verification",
|
|
4
|
+
EMAIL_CHANGE = "email_change"
|
|
5
|
+
}
|
|
6
|
+
export declare class UserToken {
|
|
7
|
+
id: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
type: TokenType;
|
|
10
|
+
token: string;
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
usedAt: Date | null;
|
|
13
|
+
metadata: string | null;
|
|
14
|
+
createdAt: Date;
|
|
15
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// Injection Tokens
|
|
2
2
|
export const AUTH_MODULE_OPTIONS = 'AUTH_MODULE_OPTIONS';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
export const USER_ENRICHER = 'USER_ENRICHER';
|
|
4
|
+
// Security
|
|
5
|
+
export const BCRYPT_SALT_ROUNDS = 12;
|
|
6
|
+
// Cookie
|
|
7
|
+
export const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
|
|
8
|
+
export const MAX_COOKIE_BYTES = 3500;
|
|
5
9
|
// Cache Key Prefixes
|
|
6
10
|
export const SESSION_CACHE_PREFIX = 'auth:session';
|
|
7
11
|
export const TOKEN_REVOKED_PREFIX = 'auth:revoked';
|