@flusys/nestjs-auth 1.1.0-beta → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +590 -473
- package/cjs/config/auth.constants.js +8 -0
- package/cjs/controllers/authentication.controller.js +218 -39
- 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 +82 -154
- package/cjs/controllers/user.controller.js +68 -15
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +16 -43
- package/cjs/dtos/index.js +0 -1
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/user-company-permission.entity.js +0 -6
- package/cjs/interceptors/clear-token.interceptor.js +10 -23
- package/cjs/interceptors/set-token.interceptor.js +15 -55
- package/cjs/interfaces/auth-email-provider.interface.js +1 -1
- package/cjs/modules/auth.module.js +63 -147
- package/cjs/services/auth-config.service.js +22 -28
- package/cjs/services/auth-datasource.provider.js +9 -21
- package/cjs/services/auth-email.service.js +89 -85
- package/cjs/services/authentication.service.js +225 -396
- package/cjs/services/branch.service.js +9 -4
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/user-permission.service.js +43 -73
- package/cjs/services/user.service.js +78 -64
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +2 -0
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user-permission.controller.d.ts +5 -4
- package/controllers/user.controller.d.ts +6 -0
- package/dtos/authentication.dto.d.ts +0 -4
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/index.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -3
- package/entities/index.d.ts +3 -3
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/fesm/config/auth.constants.js +6 -13
- package/fesm/controllers/authentication.controller.js +221 -42
- 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 +84 -156
- package/fesm/controllers/user.controller.js +71 -18
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +17 -44
- package/fesm/dtos/index.js +0 -1
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/user-company-permission.entity.js +0 -6
- package/fesm/interceptors/clear-token.interceptor.js +9 -22
- package/fesm/interceptors/set-token.interceptor.js +14 -54
- package/fesm/interfaces/auth-email-provider.interface.js +1 -1
- package/fesm/modules/auth.module.js +64 -148
- package/fesm/services/auth-config.service.js +23 -29
- package/fesm/services/auth-datasource.provider.js +10 -22
- package/fesm/services/auth-email.service.js +90 -86
- package/fesm/services/authentication.service.js +225 -396
- package/fesm/services/branch.service.js +9 -4
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/user-permission.service.js +44 -74
- package/fesm/services/user.service.js +79 -65
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/fesm/validators/password.validator.js +48 -0
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -1
- package/interfaces/authentication.interface.d.ts +0 -4
- package/interfaces/user-enricher.interface.d.ts +1 -4
- package/modules/auth.module.d.ts +6 -3
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +6 -8
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +8 -4
- package/services/authentication.service.d.ts +15 -16
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/user-permission.service.d.ts +6 -9
- package/services/user.service.d.ts +3 -0
- package/strategies/jwt.strategy.d.ts +4 -1
- package/validators/password.validator.d.ts +10 -0
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "BranchService", {
|
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
12
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
|
+
const _utils = require("@flusys/nestjs-shared/utils");
|
|
13
14
|
const _common = require("@nestjs/common");
|
|
14
15
|
const _companybranchentity = require("../entities/company-branch.entity");
|
|
15
16
|
const _authconfigservice = require("./auth-config.service");
|
|
@@ -76,10 +77,14 @@ let BranchService = class BranchService extends _classes.RequestScopedApiService
|
|
|
76
77
|
}
|
|
77
78
|
async getExtraManipulateQuery(query, filterDto, user) {
|
|
78
79
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
// Only apply company filter if companyId is NOT explicitly provided in filter
|
|
81
|
+
// This prevents dual filtering when user selects a specific company
|
|
82
|
+
if (!filterDto?.filter?.companyId) {
|
|
83
|
+
(0, _utils.applyCompanyFilter)(query, {
|
|
84
|
+
isCompanyFeatureEnabled: this.authConfig.isCompanyFeatureEnabled(),
|
|
85
|
+
entityAlias: 'company_branch',
|
|
86
|
+
columnName: 'company_id'
|
|
87
|
+
}, user);
|
|
83
88
|
}
|
|
84
89
|
return result;
|
|
85
90
|
}
|
|
@@ -43,7 +43,7 @@ const SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
43
43
|
let CompanySelectionSessionService = class CompanySelectionSessionService {
|
|
44
44
|
/**
|
|
45
45
|
* Create a new session for company/branch selection
|
|
46
|
-
*/ async createSession(userId
|
|
46
|
+
*/ async createSession(userId) {
|
|
47
47
|
const sessionId = (0, _crypto.randomUUID)();
|
|
48
48
|
const now = new Date();
|
|
49
49
|
const expiresAt = new Date(now.getTime() + SESSION_TTL_MS);
|
|
@@ -39,122 +39,92 @@ function _ts_param(paramIndex, decorator) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
let UserPermissionService = class UserPermissionService {
|
|
42
|
-
|
|
42
|
+
async getRepository() {
|
|
43
43
|
if (!this._repository) {
|
|
44
44
|
this._repository = await this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
|
|
45
45
|
}
|
|
46
46
|
return this._repository;
|
|
47
47
|
}
|
|
48
|
-
//
|
|
49
|
-
/** Find all
|
|
48
|
+
// Generic Permission Operations
|
|
49
|
+
/** Find all permissions of a specific type for a user */ async findUserPermissions(userId, type) {
|
|
50
50
|
const repository = await this.getRepository();
|
|
51
51
|
return repository.find({
|
|
52
52
|
where: {
|
|
53
53
|
userId,
|
|
54
|
-
permissionType:
|
|
54
|
+
permissionType: type
|
|
55
55
|
},
|
|
56
56
|
relations: [
|
|
57
|
-
'company'
|
|
57
|
+
type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch'
|
|
58
58
|
],
|
|
59
59
|
order: {
|
|
60
60
|
createdAt: 'DESC'
|
|
61
61
|
}
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
-
/** Assign
|
|
64
|
+
/** Assign permission to user */ async assignPermission(userId, targetId, type, metadata) {
|
|
65
65
|
const repository = await this.getRepository();
|
|
66
|
-
|
|
66
|
+
const typeName = type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch';
|
|
67
67
|
const existing = await repository.findOne({
|
|
68
68
|
where: {
|
|
69
|
-
userId
|
|
70
|
-
targetId
|
|
71
|
-
permissionType:
|
|
69
|
+
userId,
|
|
70
|
+
targetId,
|
|
71
|
+
permissionType: type
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
if (existing) {
|
|
75
|
-
throw new _common.
|
|
75
|
+
throw new _common.ConflictException(`User already assigned to this ${typeName}`);
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
permissionType: _usercompanypermissionentity.PermissionType.COMPANY,
|
|
80
|
-
targetId: dto.companyId,
|
|
81
|
-
metadata: dto.metadata
|
|
82
|
-
});
|
|
83
|
-
await repository.save(permission);
|
|
84
|
-
}
|
|
85
|
-
/** Revoke user access to a company */ async revokeCompanyAccess(userId, companyId) {
|
|
86
|
-
const repository = await this.getRepository();
|
|
87
|
-
const permission = await repository.findOne({
|
|
88
|
-
where: {
|
|
77
|
+
try {
|
|
78
|
+
await repository.save(repository.create({
|
|
89
79
|
userId,
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
permissionType: type,
|
|
81
|
+
targetId,
|
|
82
|
+
metadata
|
|
83
|
+
}));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
|
|
86
|
+
throw new _common.ConflictException(`User already assigned to this ${typeName}. Please refresh and try again.`);
|
|
92
87
|
}
|
|
93
|
-
|
|
94
|
-
if (!permission) {
|
|
95
|
-
throw new _common.NotFoundException('Company permission not found');
|
|
88
|
+
throw error;
|
|
96
89
|
}
|
|
97
|
-
await repository.remove(permission);
|
|
98
90
|
}
|
|
99
|
-
|
|
100
|
-
/** Find all branches a user has access to */ async findUserBranches(userId) {
|
|
101
|
-
const repository = await this.getRepository();
|
|
102
|
-
return repository.find({
|
|
103
|
-
where: {
|
|
104
|
-
userId,
|
|
105
|
-
permissionType: _usercompanypermissionentity.PermissionType.BRANCH
|
|
106
|
-
},
|
|
107
|
-
relations: [
|
|
108
|
-
'branch'
|
|
109
|
-
],
|
|
110
|
-
order: {
|
|
111
|
-
createdAt: 'DESC'
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
/** Assign user to a branch */ async assignUserToBranch(dto) {
|
|
116
|
-
const repository = await this.getRepository();
|
|
117
|
-
// Check if already exists
|
|
118
|
-
const existing = await repository.findOne({
|
|
119
|
-
where: {
|
|
120
|
-
userId: dto.userId,
|
|
121
|
-
targetId: dto.branchId,
|
|
122
|
-
permissionType: _usercompanypermissionentity.PermissionType.BRANCH
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
if (existing) {
|
|
126
|
-
throw new _common.BadRequestException('User already assigned to this branch');
|
|
127
|
-
}
|
|
128
|
-
const permission = repository.create({
|
|
129
|
-
userId: dto.userId,
|
|
130
|
-
permissionType: _usercompanypermissionentity.PermissionType.BRANCH,
|
|
131
|
-
targetId: dto.branchId,
|
|
132
|
-
metadata: dto.metadata
|
|
133
|
-
});
|
|
134
|
-
await repository.save(permission);
|
|
135
|
-
}
|
|
136
|
-
/** Revoke user access to a branch */ async revokeBranchAccess(userId, branchId) {
|
|
91
|
+
/** Revoke permission from user */ async revokePermission(userId, targetId, type) {
|
|
137
92
|
const repository = await this.getRepository();
|
|
93
|
+
const typeName = type === _usercompanypermissionentity.PermissionType.COMPANY ? 'company' : 'branch';
|
|
138
94
|
const permission = await repository.findOne({
|
|
139
95
|
where: {
|
|
140
96
|
userId,
|
|
141
|
-
targetId
|
|
142
|
-
permissionType:
|
|
97
|
+
targetId,
|
|
98
|
+
permissionType: type
|
|
143
99
|
}
|
|
144
100
|
});
|
|
145
101
|
if (!permission) {
|
|
146
|
-
throw new _common.NotFoundException(
|
|
102
|
+
throw new _common.NotFoundException(`${typeName.charAt(0).toUpperCase() + typeName.slice(1)} permission not found`);
|
|
147
103
|
}
|
|
148
104
|
await repository.remove(permission);
|
|
149
105
|
}
|
|
150
|
-
|
|
106
|
+
/** Find user branch permissions with optional company filter */ async findUserBranches(userId, companyId) {
|
|
107
|
+
const repository = await this.getRepository();
|
|
108
|
+
const queryBuilder = repository.createQueryBuilder('permission').leftJoinAndSelect('permission.branch', 'branch').where('permission.userId = :userId', {
|
|
109
|
+
userId
|
|
110
|
+
}).andWhere('permission.permissionType = :type', {
|
|
111
|
+
type: _usercompanypermissionentity.PermissionType.BRANCH
|
|
112
|
+
}).orderBy('permission.createdAt', 'DESC');
|
|
113
|
+
if (companyId) {
|
|
114
|
+
queryBuilder.andWhere('branch.companyId = :companyId', {
|
|
115
|
+
companyId
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return queryBuilder.getMany();
|
|
119
|
+
}
|
|
151
120
|
constructor(dataSourceProvider){
|
|
152
121
|
_define_property(this, "dataSourceProvider", void 0);
|
|
153
|
-
_define_property(this, "logger", void 0);
|
|
154
122
|
_define_property(this, "_repository", void 0);
|
|
123
|
+
// Convenience Methods
|
|
124
|
+
_define_property(this, "findUserCompanies", void 0);
|
|
155
125
|
this.dataSourceProvider = dataSourceProvider;
|
|
156
|
-
this.logger = new _common.Logger(UserPermissionService.name);
|
|
157
126
|
this._repository = null;
|
|
127
|
+
this.findUserCompanies = (userId)=>this.findUserPermissions(userId, _usercompanypermissionentity.PermissionType.COMPANY);
|
|
158
128
|
}
|
|
159
129
|
};
|
|
160
130
|
UserPermissionService = _ts_decorate([
|
|
@@ -110,6 +110,24 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
// Custom Methods
|
|
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) {
|
|
117
|
+
if (userId !== user.id) {
|
|
118
|
+
throw new _common.ForbiddenException('Cannot access another user\'s profile');
|
|
119
|
+
}
|
|
120
|
+
await this.ensureRepositoryInitialized();
|
|
121
|
+
const userEntity = await this.repository.findOne({
|
|
122
|
+
where: {
|
|
123
|
+
id: userId
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (!userEntity) {
|
|
127
|
+
throw new _common.NotFoundException('User not found');
|
|
128
|
+
}
|
|
129
|
+
return userEntity;
|
|
130
|
+
}
|
|
113
131
|
/** Verify email */ async verifyEmail(id) {
|
|
114
132
|
// Ensure repository is initialized for custom method
|
|
115
133
|
await this.ensureRepositoryInitialized();
|
|
@@ -178,26 +196,51 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
178
196
|
return entity;
|
|
179
197
|
}
|
|
180
198
|
/** Convert entity to response DTO (removes password). In raw mode with company feature, isActive comes from permission table. */ convertEntityToResponseDto(entity, isRaw) {
|
|
199
|
+
if (isRaw) {
|
|
200
|
+
return this.convertRawToResponseDto(entity);
|
|
201
|
+
}
|
|
202
|
+
return this.convertEntityToDto(entity);
|
|
203
|
+
}
|
|
204
|
+
/** Convert raw query result to response DTO */ convertRawToResponseDto(raw) {
|
|
181
205
|
// When raw mode is used with company feature, isActive comes from computed field
|
|
182
|
-
|
|
183
|
-
const isActive = isRaw && entity.user_companyIsActive !== undefined ? Boolean(entity.user_companyIsActive) : isRaw ? entity.user_is_active : entity.isActive;
|
|
206
|
+
const isActive = raw.user_companyIsActive !== undefined ? Boolean(raw.user_companyIsActive) : raw.user_is_active;
|
|
184
207
|
return {
|
|
185
|
-
id:
|
|
186
|
-
name:
|
|
187
|
-
phone:
|
|
188
|
-
email:
|
|
189
|
-
profilePictureId:
|
|
208
|
+
id: raw.user_id,
|
|
209
|
+
name: raw.user_name,
|
|
210
|
+
phone: raw.user_phone,
|
|
211
|
+
email: raw.user_email,
|
|
212
|
+
profilePictureId: raw.user_profile_picture_id,
|
|
190
213
|
isActive,
|
|
191
|
-
emailVerified:
|
|
192
|
-
phoneVerified:
|
|
193
|
-
lastLoginAt:
|
|
194
|
-
additionalFields:
|
|
195
|
-
createdAt:
|
|
196
|
-
updatedAt:
|
|
197
|
-
deletedAt:
|
|
198
|
-
createdById:
|
|
199
|
-
updatedById:
|
|
200
|
-
deletedById:
|
|
214
|
+
emailVerified: raw.user_email_verified,
|
|
215
|
+
phoneVerified: raw.user_phone_verified,
|
|
216
|
+
lastLoginAt: raw.user_last_login_at,
|
|
217
|
+
additionalFields: raw.user_additional_fields,
|
|
218
|
+
createdAt: raw.user_created_at,
|
|
219
|
+
updatedAt: raw.user_updated_at,
|
|
220
|
+
deletedAt: raw.user_deleted_at,
|
|
221
|
+
createdById: raw.user_created_by_id,
|
|
222
|
+
updatedById: raw.user_updated_by_id,
|
|
223
|
+
deletedById: raw.user_deleted_by_id
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** Convert entity to response DTO */ convertEntityToDto(entity) {
|
|
227
|
+
return {
|
|
228
|
+
id: entity.id,
|
|
229
|
+
name: entity.name,
|
|
230
|
+
phone: entity.phone,
|
|
231
|
+
email: entity.email,
|
|
232
|
+
profilePictureId: entity.profilePictureId,
|
|
233
|
+
isActive: entity.isActive,
|
|
234
|
+
emailVerified: entity.emailVerified,
|
|
235
|
+
phoneVerified: entity.phoneVerified,
|
|
236
|
+
lastLoginAt: entity.lastLoginAt,
|
|
237
|
+
additionalFields: entity.additionalFields,
|
|
238
|
+
createdAt: entity.createdAt,
|
|
239
|
+
updatedAt: entity.updatedAt,
|
|
240
|
+
deletedAt: entity.deletedAt,
|
|
241
|
+
createdById: entity.createdById,
|
|
242
|
+
updatedById: entity.updatedById,
|
|
243
|
+
deletedById: entity.deletedById
|
|
201
244
|
};
|
|
202
245
|
}
|
|
203
246
|
/** Convert entity list to response DTO list */ convertEntityListToResponseListDto(entities, isRaw) {
|
|
@@ -342,15 +385,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
342
385
|
* Get user profile with extras from enricher
|
|
343
386
|
* Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
|
|
344
387
|
*/ async getProfileWithExtras(userId, user) {
|
|
345
|
-
await this.
|
|
346
|
-
const userEntity = await this.repository.findOne({
|
|
347
|
-
where: {
|
|
348
|
-
id: userId
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
if (!userEntity) {
|
|
352
|
-
throw new _common.NotFoundException('User not found');
|
|
353
|
-
}
|
|
388
|
+
const userEntity = await this.getUserForProfile(userId, user);
|
|
354
389
|
const userData = this.convertEntityToResponseDto(userEntity, false);
|
|
355
390
|
// Get extras from enricher if available
|
|
356
391
|
let extras;
|
|
@@ -385,16 +420,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
385
420
|
/**
|
|
386
421
|
* Get specific profile section data
|
|
387
422
|
*/ async getProfileSectionData(userId, sectionId, user) {
|
|
388
|
-
await this.
|
|
389
|
-
// Verify user exists
|
|
390
|
-
const userEntity = await this.repository.findOne({
|
|
391
|
-
where: {
|
|
392
|
-
id: userId
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
if (!userEntity) {
|
|
396
|
-
throw new _common.NotFoundException('User not found');
|
|
397
|
-
}
|
|
423
|
+
await this.getUserForProfile(userId, user);
|
|
398
424
|
if (this.userEnricher?.getProfileSectionData) {
|
|
399
425
|
return this.userEnricher.getProfileSectionData(userId, sectionId, user);
|
|
400
426
|
}
|
|
@@ -403,19 +429,10 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
403
429
|
/**
|
|
404
430
|
* Update specific profile section
|
|
405
431
|
*/ async updateProfileSection(userId, sectionId, data, user) {
|
|
406
|
-
await this.
|
|
432
|
+
const userEntity = await this.getUserForProfile(userId, user);
|
|
407
433
|
if (!this.dataSource) {
|
|
408
434
|
throw new _common.BadRequestException('DataSource not available');
|
|
409
435
|
}
|
|
410
|
-
// Verify user exists
|
|
411
|
-
const userEntity = await this.repository.findOne({
|
|
412
|
-
where: {
|
|
413
|
-
id: userId
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
if (!userEntity) {
|
|
417
|
-
throw new _common.NotFoundException('User not found');
|
|
418
|
-
}
|
|
419
436
|
if (!this.userEnricher?.updateProfileSection) {
|
|
420
437
|
throw new _common.BadRequestException('Profile sections not supported');
|
|
421
438
|
}
|
|
@@ -442,16 +459,7 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
442
459
|
/**
|
|
443
460
|
* Get profile completion percentage
|
|
444
461
|
*/ async getProfileCompletion(userId, user) {
|
|
445
|
-
await this.
|
|
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
|
-
}
|
|
462
|
+
await this.getUserForProfile(userId, user);
|
|
455
463
|
if (this.userEnricher?.calculateProfileCompletion) {
|
|
456
464
|
return this.userEnricher.calculateProfileCompletion(userId, user);
|
|
457
465
|
}
|
|
@@ -482,16 +490,22 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
482
490
|
}
|
|
483
491
|
// Handle password update
|
|
484
492
|
if (dto.password) {
|
|
485
|
-
// If updating own profile, require old password
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
493
|
+
// If updating own profile, require old password verification
|
|
494
|
+
const isOwnProfile = !dto.id || dto.id === user.id;
|
|
495
|
+
if (isOwnProfile) {
|
|
496
|
+
if (!dto.oldPassword) {
|
|
497
|
+
throw new _common.BadRequestException('Current password is required to change your password');
|
|
498
|
+
}
|
|
499
|
+
const isPasswordValid = await _bcrypt.compare(dto.oldPassword, findUser.password);
|
|
500
|
+
if (!isPasswordValid) {
|
|
501
|
+
throw new _common.BadRequestException('Current password is incorrect');
|
|
502
|
+
}
|
|
489
503
|
}
|
|
490
|
-
|
|
491
|
-
findUser.password = await _bcrypt.hash(dto.password, salt);
|
|
504
|
+
findUser.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
492
505
|
}
|
|
493
|
-
// Merge other fields (excluding password and
|
|
494
|
-
|
|
506
|
+
// Merge other fields (excluding password, oldPassword, extras, and sensitive flags)
|
|
507
|
+
// Users cannot self-modify emailVerified, phoneVerified, or isActive via profile update
|
|
508
|
+
const { password, oldPassword, extras, emailVerified, phoneVerified, isActive, ...restDto } = dto;
|
|
495
509
|
Object.assign(findUser, restDto);
|
|
496
510
|
await queryRunner.manager.save(this.repository.target, findUser);
|
|
497
511
|
// Handle extras update via enricher
|
|
@@ -12,10 +12,10 @@ const _common = require("@nestjs/common");
|
|
|
12
12
|
const _core = require("@nestjs/core");
|
|
13
13
|
const _passport = require("@nestjs/passport");
|
|
14
14
|
const _passportjwt = require("passport-jwt");
|
|
15
|
+
const _userentity = require("../entities/user.entity");
|
|
15
16
|
const _authconfigservice = require("../services/auth-config.service");
|
|
16
17
|
const _authdatasourceprovider = require("../services/auth-datasource.provider");
|
|
17
18
|
const _authenticationservice = require("../services/authentication.service");
|
|
18
|
-
const _userentity = require("../entities/user.entity");
|
|
19
19
|
function _define_property(obj, key, value) {
|
|
20
20
|
if (key in obj) {
|
|
21
21
|
Object.defineProperty(obj, key, {
|
|
@@ -38,71 +38,71 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
38
38
|
function _ts_metadata(k, v) {
|
|
39
39
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
* Type guard to check if payload includes company context
|
|
43
|
-
*/ function isTokenPayloadWithCompany(payload) {
|
|
41
|
+
function hasCompanyContext(payload) {
|
|
44
42
|
return 'companyId' in payload && 'branchId' in payload;
|
|
45
43
|
}
|
|
46
44
|
let JwtStrategy = class JwtStrategy extends (0, _passport.PassportStrategy)(_passportjwt.Strategy, 'jwt') {
|
|
47
45
|
async validate(_req, payload) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const userRepo = await this.resolveUserRepository();
|
|
47
|
+
await this.checkTokenRevocation(payload);
|
|
48
|
+
const user = await userRepo.findOne({
|
|
49
|
+
where: {
|
|
50
|
+
id: payload.id,
|
|
51
|
+
isActive: true
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (!user) {
|
|
55
|
+
throw new _common.UnauthorizedException('User not found or inactive');
|
|
56
|
+
}
|
|
57
|
+
return this.buildUserInfo(payload);
|
|
58
|
+
}
|
|
59
|
+
async resolveUserRepository() {
|
|
51
60
|
try {
|
|
52
|
-
const
|
|
61
|
+
const provider = await this.moduleRef.resolve(_authdatasourceprovider.AuthDataSourceProvider, undefined, {
|
|
53
62
|
strict: false
|
|
54
63
|
});
|
|
55
|
-
if (
|
|
56
|
-
|
|
64
|
+
if (provider) {
|
|
65
|
+
return provider.getRepository(_userentity.User);
|
|
57
66
|
}
|
|
58
|
-
|
|
59
|
-
authService = await this.moduleRef.resolve(_authenticationservice.AuthenticationService, undefined, {
|
|
60
|
-
strict: false
|
|
61
|
-
});
|
|
62
|
-
} catch (error) {
|
|
67
|
+
} catch {
|
|
63
68
|
throw new _common.UnauthorizedException('Unable to access user data');
|
|
64
69
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
70
|
+
throw new _common.UnauthorizedException('User repository not available');
|
|
71
|
+
}
|
|
72
|
+
async checkTokenRevocation(payload) {
|
|
73
|
+
if (!payload.iat) return;
|
|
74
|
+
try {
|
|
75
|
+
const authService = await this.moduleRef.resolve(_authenticationservice.AuthenticationService, undefined, {
|
|
76
|
+
strict: false
|
|
77
|
+
});
|
|
78
|
+
if (authService && await authService.isTokenRevoked(payload.id, payload.iat)) {
|
|
72
79
|
throw new _common.UnauthorizedException('Token has been revoked');
|
|
73
80
|
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err instanceof _common.UnauthorizedException) throw err;
|
|
74
83
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
id: payload.id,
|
|
79
|
-
isActive: true
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
if (!user) {
|
|
83
|
-
throw new _common.UnauthorizedException('User not found or inactive');
|
|
84
|
-
}
|
|
85
|
-
const result = {
|
|
84
|
+
}
|
|
85
|
+
buildUserInfo(payload) {
|
|
86
|
+
const base = {
|
|
86
87
|
id: payload.id,
|
|
87
88
|
email: payload.email
|
|
88
89
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
result.companyId = payload.companyId;
|
|
92
|
-
// Only set branchId if it's not empty string
|
|
93
|
-
if (payload.branchId) {
|
|
94
|
-
result.branchId = payload.branchId;
|
|
95
|
-
}
|
|
90
|
+
if (!hasCompanyContext(payload)) {
|
|
91
|
+
return base;
|
|
96
92
|
}
|
|
97
|
-
return
|
|
93
|
+
return {
|
|
94
|
+
...base,
|
|
95
|
+
companyId: payload.companyId,
|
|
96
|
+
...payload.branchId && {
|
|
97
|
+
branchId: payload.branchId
|
|
98
|
+
}
|
|
99
|
+
};
|
|
98
100
|
}
|
|
99
101
|
constructor(authConfig, moduleRef){
|
|
100
102
|
super({
|
|
101
103
|
jwtFromRequest: _passportjwt.ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
102
104
|
ignoreExpiration: false,
|
|
103
|
-
|
|
104
|
-
// This ensures AuthConfigService is fully initialized when using forRootAsync
|
|
105
|
-
secretOrKeyProvider: (_request, _rawJwtToken, done)=>{
|
|
105
|
+
secretOrKeyProvider: (_req, _token, done)=>{
|
|
106
106
|
done(null, this.authConfig.getJwtSecret());
|
|
107
107
|
},
|
|
108
108
|
passReqToCallback: true
|
|
@@ -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,5 +1,7 @@
|
|
|
1
1
|
export declare const AUTH_MODULE_OPTIONS = "AUTH_MODULE_OPTIONS";
|
|
2
2
|
export declare const USER_ENRICHER = "USER_ENRICHER";
|
|
3
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;
|
|
4
6
|
export declare const SESSION_CACHE_PREFIX = "auth:session";
|
|
5
7
|
export declare const TOKEN_REVOKED_PREFIX = "auth:revoked";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
1
2
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
2
3
|
import { Request } from 'express';
|
|
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
|
+
import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, LoginResponseRequiresEmailVerificationDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
|
|
5
5
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
6
6
|
import { AuthEmailService } from '../services/auth-email.service';
|
|
7
7
|
import { AuthenticationService } from '../services/authentication.service';
|
|
@@ -11,7 +11,9 @@ export declare class AuthenticationController {
|
|
|
11
11
|
private readonly authEmailService?;
|
|
12
12
|
constructor(authService: AuthenticationService, authConfig?: AuthConfigService, authEmailService?: AuthEmailService);
|
|
13
13
|
private buildAuthUrl;
|
|
14
|
-
|
|
14
|
+
private get emailService();
|
|
15
|
+
private get isEmailEnabled();
|
|
16
|
+
login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto | LoginResponseRequiresEmailVerificationDto>;
|
|
15
17
|
register(req: Request, registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
|
|
16
18
|
refreshToken(req: Request, refreshTokenDto: RefreshTokenDto): Promise<{
|
|
17
19
|
accessToken: string;
|
|
@@ -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,8 +1,8 @@
|
|
|
1
|
-
import { AssignUserCompanyDto,
|
|
1
|
+
import { AssignUserBranchDto, AssignUserCompanyDto, UserBranchesResponseDto, UserCompaniesResponseDto } from '../dtos/user-permission.dto';
|
|
2
2
|
import { UserPermissionService } from '../services/user-permission.service';
|
|
3
3
|
export declare class UserPermissionController {
|
|
4
|
-
private readonly
|
|
5
|
-
constructor(
|
|
4
|
+
private readonly permissionService;
|
|
5
|
+
constructor(permissionService: UserPermissionService);
|
|
6
6
|
assignUserCompany(dto: AssignUserCompanyDto): Promise<{
|
|
7
7
|
success: boolean;
|
|
8
8
|
message: string;
|
|
@@ -12,5 +12,6 @@ export declare class UserPermissionController {
|
|
|
12
12
|
success: boolean;
|
|
13
13
|
message: string;
|
|
14
14
|
}>;
|
|
15
|
-
getUserBranches(userId: string): Promise<UserBranchesResponseDto>;
|
|
15
|
+
getUserBranches(userId: string, companyId?: string): Promise<UserBranchesResponseDto>;
|
|
16
|
+
private processBatchPermissions;
|
|
16
17
|
}
|