@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
|
@@ -9,16 +9,19 @@ Object.defineProperty(exports, "AuthenticationService", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
+
const _interfaces = require("@flusys/nestjs-shared/interfaces");
|
|
13
|
+
const _enums = require("@flusys/nestjs-shared/enums");
|
|
12
14
|
const _utils = require("@flusys/nestjs-shared/utils");
|
|
13
15
|
const _common = require("@nestjs/common");
|
|
14
16
|
const _jwt = require("@nestjs/jwt");
|
|
15
17
|
const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
|
|
16
18
|
const _config = require("../config");
|
|
19
|
+
const _userenricherinterface = require("../interfaces/user-enricher.interface");
|
|
17
20
|
const _companybranchentity = require("../entities/company-branch.entity");
|
|
18
21
|
const _companyentity = require("../entities/company.entity");
|
|
19
22
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
20
23
|
const _userentity = require("../entities/user.entity");
|
|
21
|
-
const
|
|
24
|
+
const _enums1 = require("../enums");
|
|
22
25
|
const _authconfigservice = require("./auth-config.service");
|
|
23
26
|
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
24
27
|
const _authemailservice = require("./auth-email.service");
|
|
@@ -92,11 +95,14 @@ function _ts_param(paramIndex, decorator) {
|
|
|
92
95
|
};
|
|
93
96
|
}
|
|
94
97
|
let AuthenticationService = class AuthenticationService {
|
|
95
|
-
|
|
96
|
-
/** Check if company feature is enabled (tenant-aware with global fallback) */ isCompanyFeatureEnabled() {
|
|
98
|
+
isCompanyFeatureEnabled() {
|
|
97
99
|
return this.dataSourceProvider?.isCompanyFeatureEnabled() ?? this.authConfig.isCompanyFeatureEnabled();
|
|
98
100
|
}
|
|
99
|
-
|
|
101
|
+
isEmailVerificationEnabled() {
|
|
102
|
+
const featureEnabled = this.dataSourceProvider?.isEmailVerificationEnabled() ?? this.authConfig.isEmailVerificationEnabled();
|
|
103
|
+
const providerAvailable = this.authEmailService?.isEmailEnabled() ?? false;
|
|
104
|
+
return featureEnabled && providerAvailable;
|
|
105
|
+
}
|
|
100
106
|
async getUserRepository() {
|
|
101
107
|
if (!this.dataSourceProvider) {
|
|
102
108
|
throw new Error('User repository not available');
|
|
@@ -115,10 +121,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
115
121
|
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
116
122
|
return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
|
|
117
123
|
}
|
|
118
|
-
// Login
|
|
119
124
|
async login(email, password) {
|
|
120
|
-
|
|
121
|
-
if (await this.authEmailService?.hasPendingRegistration(email)) {
|
|
125
|
+
if (this.isEmailVerificationEnabled() && await this.authEmailService.hasPendingRegistration(email)) {
|
|
122
126
|
return {
|
|
123
127
|
requiresEmailVerification: true,
|
|
124
128
|
email,
|
|
@@ -129,7 +133,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
129
133
|
if (!user.isActive) {
|
|
130
134
|
throw new _common.UnauthorizedException('User account is deactivated');
|
|
131
135
|
}
|
|
132
|
-
if (this.
|
|
136
|
+
if (this.isEmailVerificationEnabled() && !user.emailVerified) {
|
|
133
137
|
return {
|
|
134
138
|
requiresEmailVerification: true,
|
|
135
139
|
email: user.email,
|
|
@@ -141,20 +145,18 @@ let AuthenticationService = class AuthenticationService {
|
|
|
141
145
|
}
|
|
142
146
|
return this.handleCompanyBasedLogin(user);
|
|
143
147
|
}
|
|
144
|
-
|
|
148
|
+
async handleCompanyBasedLogin(user) {
|
|
145
149
|
const permissionRepository = await this.getPermissionRepository();
|
|
146
150
|
const branchRepository = await this.getBranchRepository();
|
|
147
151
|
if (!permissionRepository || !branchRepository) {
|
|
148
152
|
return this.generateLoginResponse(user);
|
|
149
153
|
}
|
|
150
|
-
// Get company permissions
|
|
151
154
|
const companyPermissions = await permissionRepository.find({
|
|
152
155
|
where: {
|
|
153
156
|
userId: user.id,
|
|
154
|
-
permissionType:
|
|
157
|
+
permissionType: _enums1.PermissionType.COMPANY
|
|
155
158
|
}
|
|
156
159
|
});
|
|
157
|
-
// Load companies
|
|
158
160
|
const companyRepository = await this.getCompanyRepository();
|
|
159
161
|
if (!companyRepository) {
|
|
160
162
|
return this.generateLoginResponse(user);
|
|
@@ -165,16 +167,13 @@ let AuthenticationService = class AuthenticationService {
|
|
|
165
167
|
}).andWhere('c.is_active = :active', {
|
|
166
168
|
active: true
|
|
167
169
|
}).getMany() : [];
|
|
168
|
-
// If user has no company assignments yet, return simple login response
|
|
169
|
-
// This allows users to login and be assigned to companies later
|
|
170
170
|
if (companies.length === 0) {
|
|
171
171
|
return this.generateLoginResponse(user);
|
|
172
172
|
}
|
|
173
|
-
// Get branch permissions
|
|
174
173
|
const branchPermissions = await permissionRepository.find({
|
|
175
174
|
where: {
|
|
176
175
|
userId: user.id,
|
|
177
|
-
permissionType:
|
|
176
|
+
permissionType: _enums1.PermissionType.BRANCH
|
|
178
177
|
}
|
|
179
178
|
});
|
|
180
179
|
const branchIds = branchPermissions.map((p)=>p.targetId);
|
|
@@ -183,32 +182,25 @@ let AuthenticationService = class AuthenticationService {
|
|
|
183
182
|
}).andWhere('b.is_active = :active', {
|
|
184
183
|
active: true
|
|
185
184
|
}).getMany() : [];
|
|
186
|
-
// Case 1: Single company, single branch → Direct login
|
|
187
185
|
if (companies.length === 1 && branches.length === 1) {
|
|
188
186
|
return this.generateLoginResponseWithCompany(user, companies[0], branches[0]);
|
|
189
187
|
}
|
|
190
|
-
// Case 2: Multiple options → Selection required (frontend handles UI)
|
|
191
|
-
// Returns all companies with their branches for frontend to display
|
|
192
188
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
193
189
|
}
|
|
194
|
-
|
|
195
|
-
/** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
|
|
190
|
+
async register(dto, verifyUrlBase) {
|
|
196
191
|
const userRepository = await this.getUserRepository();
|
|
197
192
|
const existingUser = await userRepository.findOne({
|
|
198
193
|
where: {
|
|
199
194
|
email: dto.email
|
|
200
195
|
}
|
|
201
196
|
});
|
|
202
|
-
// When company feature is enabled and user exists, allow joining/creating company
|
|
203
197
|
if (existingUser && this.isCompanyFeatureEnabled()) {
|
|
204
198
|
return this.handleExistingUserCompanyRegistration(existingUser, dto);
|
|
205
199
|
}
|
|
206
|
-
// User exists but company feature is off - email must be unique
|
|
207
200
|
if (existingUser) {
|
|
208
201
|
throw new _common.BadRequestException('Registration failed due to email exists. Please try again or contact support.');
|
|
209
202
|
}
|
|
210
|
-
|
|
211
|
-
if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
|
|
203
|
+
if (this.isEmailVerificationEnabled() && verifyUrlBase) {
|
|
212
204
|
const companyData = this.isCompanyFeatureEnabled() ? {
|
|
213
205
|
companySlug: dto.companySlug,
|
|
214
206
|
branchSlug: dto.branchSlug,
|
|
@@ -222,6 +214,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
222
214
|
phone: dto.phone,
|
|
223
215
|
password: dto.password,
|
|
224
216
|
companyData,
|
|
217
|
+
additionalFields: dto.additionalFields,
|
|
225
218
|
verifyUrlBase
|
|
226
219
|
});
|
|
227
220
|
return {
|
|
@@ -229,10 +222,9 @@ let AuthenticationService = class AuthenticationService {
|
|
|
229
222
|
message: result.message
|
|
230
223
|
};
|
|
231
224
|
}
|
|
232
|
-
// No email verification - create user directly
|
|
233
225
|
return this.createUserDirectly(dto);
|
|
234
226
|
}
|
|
235
|
-
|
|
227
|
+
async handleExistingUserCompanyRegistration(existingUser, dto) {
|
|
236
228
|
if (!dto.companySlug && !dto.newCompanyName) {
|
|
237
229
|
throw new _common.BadRequestException('Email already registered. Please login or provide a company to join/create.');
|
|
238
230
|
}
|
|
@@ -241,7 +233,6 @@ let AuthenticationService = class AuthenticationService {
|
|
|
241
233
|
if (!permissionRepository || !companyRepository) {
|
|
242
234
|
throw new _common.BadRequestException('Company feature repositories not available');
|
|
243
235
|
}
|
|
244
|
-
// For existing users joining a company, check if already assigned
|
|
245
236
|
if (dto.companySlug) {
|
|
246
237
|
const company = await companyRepository.findOne({
|
|
247
238
|
where: {
|
|
@@ -253,7 +244,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
253
244
|
const existingPermission = await permissionRepository.findOne({
|
|
254
245
|
where: {
|
|
255
246
|
userId: existingUser.id,
|
|
256
|
-
permissionType:
|
|
247
|
+
permissionType: _enums1.PermissionType.COMPANY,
|
|
257
248
|
targetId: company.id
|
|
258
249
|
}
|
|
259
250
|
});
|
|
@@ -264,50 +255,92 @@ let AuthenticationService = class AuthenticationService {
|
|
|
264
255
|
}
|
|
265
256
|
return this.processCompanyRegistration(existingUser, dto, true);
|
|
266
257
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
258
|
+
async createUserDirectly(dto) {
|
|
259
|
+
if (!this.dataSourceProvider) {
|
|
260
|
+
throw new _common.BadRequestException('Data source not available');
|
|
261
|
+
}
|
|
262
|
+
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
263
|
+
const queryRunner = dataSource.createQueryRunner();
|
|
264
|
+
await queryRunner.connect();
|
|
265
|
+
await queryRunner.startTransaction();
|
|
266
|
+
try {
|
|
267
|
+
const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
268
|
+
const user = queryRunner.manager.create(_userentity.User, {
|
|
269
|
+
name: dto.name,
|
|
270
|
+
email: dto.email,
|
|
271
|
+
phone: dto.phone,
|
|
272
|
+
password: hashedPassword,
|
|
273
|
+
isActive: true,
|
|
274
|
+
emailVerified: false,
|
|
275
|
+
phoneVerified: false
|
|
276
|
+
});
|
|
277
|
+
await queryRunner.manager.save(_userentity.User, user);
|
|
278
|
+
if (this.userEnricher?.onUserCreated) {
|
|
279
|
+
await this.userEnricher.onUserCreated(user.id, dto.additionalFields ?? null, queryRunner);
|
|
280
|
+
}
|
|
281
|
+
await queryRunner.commitTransaction();
|
|
282
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
283
|
+
return this.generateRegistrationResponse(user);
|
|
284
|
+
}
|
|
285
|
+
return this.processCompanyRegistration(user, dto);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
await queryRunner.rollbackTransaction();
|
|
288
|
+
if (error instanceof _common.BadRequestException) {
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
_utils.ErrorHandler.logError(this.logger, error, 'createUserDirectly');
|
|
292
|
+
throw new _common.BadRequestException('Registration failed. Please try again.');
|
|
293
|
+
} finally{
|
|
294
|
+
await queryRunner.release();
|
|
282
295
|
}
|
|
283
|
-
return this.processCompanyRegistration(user, dto);
|
|
284
296
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
297
|
+
async createUserFromPendingRegistration(pending) {
|
|
298
|
+
if (!this.dataSourceProvider) {
|
|
299
|
+
throw new _common.BadRequestException('Data source not available');
|
|
300
|
+
}
|
|
301
|
+
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
302
|
+
const queryRunner = dataSource.createQueryRunner();
|
|
303
|
+
await queryRunner.connect();
|
|
304
|
+
await queryRunner.startTransaction();
|
|
305
|
+
try {
|
|
306
|
+
const existingUser = await queryRunner.manager.findOne(_userentity.User, {
|
|
307
|
+
where: {
|
|
308
|
+
email: pending.email
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
if (existingUser) {
|
|
312
|
+
throw new _common.BadRequestException('This email has already been registered.');
|
|
290
313
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
314
|
+
const user = queryRunner.manager.create(_userentity.User, {
|
|
315
|
+
name: pending.name,
|
|
316
|
+
email: pending.email,
|
|
317
|
+
phone: pending.phone,
|
|
318
|
+
password: pending.passwordHash,
|
|
319
|
+
isActive: true,
|
|
320
|
+
emailVerified: true,
|
|
321
|
+
phoneVerified: false
|
|
322
|
+
});
|
|
323
|
+
await queryRunner.manager.save(_userentity.User, user);
|
|
324
|
+
if (this.userEnricher?.onUserCreated) {
|
|
325
|
+
await this.userEnricher.onUserCreated(user.id, pending.additionalFields ?? null, queryRunner);
|
|
326
|
+
}
|
|
327
|
+
await queryRunner.commitTransaction();
|
|
328
|
+
if (this.isCompanyFeatureEnabled() && pending.companyData) {
|
|
329
|
+
return this.processCompanyRegistration(user, pending.companyData);
|
|
330
|
+
}
|
|
331
|
+
return this.generateRegistrationResponse(user);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
await queryRunner.rollbackTransaction();
|
|
334
|
+
if (error instanceof _common.BadRequestException) {
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
_utils.ErrorHandler.logError(this.logger, error, 'createUserFromPendingRegistration');
|
|
338
|
+
throw new _common.BadRequestException('Registration failed. Please try again.');
|
|
339
|
+
} finally{
|
|
340
|
+
await queryRunner.release();
|
|
307
341
|
}
|
|
308
|
-
return this.generateRegistrationResponse(user);
|
|
309
342
|
}
|
|
310
|
-
|
|
343
|
+
async processCompanyRegistration(user, data, throwOnUnavailable = false) {
|
|
311
344
|
const companyRepository = await this.getCompanyRepository();
|
|
312
345
|
const branchRepository = await this.getBranchRepository();
|
|
313
346
|
const permissionRepository = await this.getPermissionRepository();
|
|
@@ -322,7 +355,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
322
355
|
}
|
|
323
356
|
return this.generateRegistrationResponse(user);
|
|
324
357
|
}
|
|
325
|
-
|
|
358
|
+
async findOrCreateCompanyAndBranch(data, companyRepo, branchRepo) {
|
|
326
359
|
if (data.companySlug) {
|
|
327
360
|
const company = await companyRepo.findOne({
|
|
328
361
|
where: {
|
|
@@ -386,22 +419,21 @@ let AuthenticationService = class AuthenticationService {
|
|
|
386
419
|
branch: null
|
|
387
420
|
};
|
|
388
421
|
}
|
|
389
|
-
|
|
422
|
+
async assignUserPermissions(userId, companyId, branchId, permissionRepo) {
|
|
390
423
|
await permissionRepo.save(permissionRepo.create({
|
|
391
424
|
userId,
|
|
392
|
-
permissionType:
|
|
425
|
+
permissionType: _enums1.PermissionType.COMPANY,
|
|
393
426
|
targetId: companyId
|
|
394
427
|
}));
|
|
395
428
|
if (branchId) {
|
|
396
429
|
await permissionRepo.save(permissionRepo.create({
|
|
397
430
|
userId,
|
|
398
|
-
permissionType:
|
|
431
|
+
permissionType: _enums1.PermissionType.BRANCH,
|
|
399
432
|
targetId: branchId
|
|
400
433
|
}));
|
|
401
434
|
}
|
|
402
435
|
}
|
|
403
|
-
|
|
404
|
-
/** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
|
|
436
|
+
async select(sessionId, companyId, branchId) {
|
|
405
437
|
this.ensureCompanyFeatureEnabled();
|
|
406
438
|
if (!this.sessionService) {
|
|
407
439
|
throw new _common.BadRequestException('Session service not available');
|
|
@@ -412,11 +444,11 @@ let AuthenticationService = class AuthenticationService {
|
|
|
412
444
|
}
|
|
413
445
|
return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
|
|
414
446
|
}
|
|
415
|
-
|
|
447
|
+
async switchCompany(userId, companyId, branchId) {
|
|
416
448
|
this.ensureCompanyFeatureEnabled();
|
|
417
449
|
return this.validateAndLoginWithCompany(userId, companyId, branchId);
|
|
418
450
|
}
|
|
419
|
-
|
|
451
|
+
async validateAndLoginWithCompany(userId, companyId, branchId) {
|
|
420
452
|
const userRepository = await this.getUserRepository();
|
|
421
453
|
const user = await userRepository.findOne({
|
|
422
454
|
where: {
|
|
@@ -432,11 +464,10 @@ let AuthenticationService = class AuthenticationService {
|
|
|
432
464
|
if (!permissionRepository || !companyRepository || !branchRepository) {
|
|
433
465
|
throw new _common.BadRequestException('Repositories not available');
|
|
434
466
|
}
|
|
435
|
-
// Validate company access
|
|
436
467
|
const hasCompanyAccess = await permissionRepository.findOne({
|
|
437
468
|
where: {
|
|
438
469
|
userId,
|
|
439
|
-
permissionType:
|
|
470
|
+
permissionType: _enums1.PermissionType.COMPANY,
|
|
440
471
|
targetId: companyId
|
|
441
472
|
}
|
|
442
473
|
});
|
|
@@ -452,11 +483,10 @@ let AuthenticationService = class AuthenticationService {
|
|
|
452
483
|
if (!company) {
|
|
453
484
|
throw new _common.UnauthorizedException('Company not found or inactive');
|
|
454
485
|
}
|
|
455
|
-
// Validate branch access
|
|
456
486
|
const hasBranchAccess = await permissionRepository.findOne({
|
|
457
487
|
where: {
|
|
458
488
|
userId,
|
|
459
|
-
permissionType:
|
|
489
|
+
permissionType: _enums1.PermissionType.BRANCH,
|
|
460
490
|
targetId: branchId
|
|
461
491
|
}
|
|
462
492
|
});
|
|
@@ -475,8 +505,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
475
505
|
}
|
|
476
506
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
477
507
|
}
|
|
478
|
-
|
|
479
|
-
/** Refresh access token (with token rotation). Returns new access token and new refresh token. */ async refreshToken(refreshToken) {
|
|
508
|
+
async refreshToken(refreshToken) {
|
|
480
509
|
try {
|
|
481
510
|
const payload = this.jwtService.verify(refreshToken, {
|
|
482
511
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
@@ -510,7 +539,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
510
539
|
throw new _common.UnauthorizedException('Invalid or expired refresh token');
|
|
511
540
|
}
|
|
512
541
|
}
|
|
513
|
-
|
|
542
|
+
async changePassword(userId, currentPassword, newPassword) {
|
|
514
543
|
const userRepository = await this.getUserRepository();
|
|
515
544
|
const user = await userRepository.findOne({
|
|
516
545
|
where: {
|
|
@@ -524,21 +553,19 @@ let AuthenticationService = class AuthenticationService {
|
|
|
524
553
|
user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
|
|
525
554
|
await userRepository.save(user);
|
|
526
555
|
}
|
|
527
|
-
|
|
556
|
+
async logout(userId) {
|
|
528
557
|
if (!this.cacheManager) {
|
|
529
|
-
// Cache is required for secure logout - tokens cannot be revoked without it
|
|
530
558
|
this.logger.error(`SECURITY: Cache not available for logout (userId: ${userId}). ` + 'Token revocation failed - tokens will remain valid until expiry. ' + 'Configure cache (Redis) to enable proper token revocation.');
|
|
531
559
|
throw new _common.BadRequestException('Logout service temporarily unavailable. Please try again later.');
|
|
532
560
|
}
|
|
533
|
-
const revokedAt = Math.floor(Date.now() / 1000);
|
|
561
|
+
const revokedAt = Math.floor(Date.now() / 1000);
|
|
534
562
|
const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
535
|
-
// TTL matches refresh token expiry (tokens older than this are already expired)
|
|
536
563
|
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
537
564
|
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
538
565
|
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
539
566
|
this.logger.log(`User ${userId} logged out - all tokens revoked`);
|
|
540
567
|
}
|
|
541
|
-
|
|
568
|
+
async isTokenRevoked(userId, tokenIssuedAt) {
|
|
542
569
|
if (!this.cacheManager || !tokenIssuedAt) {
|
|
543
570
|
return false;
|
|
544
571
|
}
|
|
@@ -547,12 +574,11 @@ let AuthenticationService = class AuthenticationService {
|
|
|
547
574
|
if (!revokedAt) {
|
|
548
575
|
return false;
|
|
549
576
|
}
|
|
550
|
-
// Token is revoked if it was issued before the revocation timestamp
|
|
551
577
|
return tokenIssuedAt < revokedAt;
|
|
552
578
|
}
|
|
553
|
-
|
|
579
|
+
parseExpiryToMs(expiry) {
|
|
554
580
|
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
555
|
-
if (!match) return 7 * 24 * 60 * 60 * 1000;
|
|
581
|
+
if (!match) return 7 * 24 * 60 * 60 * 1000;
|
|
556
582
|
const value = parseInt(match[1], 10);
|
|
557
583
|
const unit = match[2];
|
|
558
584
|
switch(unit){
|
|
@@ -568,8 +594,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
568
594
|
return 7 * 24 * 60 * 60 * 1000;
|
|
569
595
|
}
|
|
570
596
|
}
|
|
571
|
-
|
|
572
|
-
/** Get user's companies */ async getUserCompanies(userId) {
|
|
597
|
+
async getUserCompanies(userId) {
|
|
573
598
|
this.ensureCompanyFeatureEnabled();
|
|
574
599
|
const permissionRepository = await this.getPermissionRepository();
|
|
575
600
|
const companyRepository = await this.getCompanyRepository();
|
|
@@ -579,7 +604,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
579
604
|
const permissions = await permissionRepository.find({
|
|
580
605
|
where: {
|
|
581
606
|
userId,
|
|
582
|
-
permissionType:
|
|
607
|
+
permissionType: _enums1.PermissionType.COMPANY
|
|
583
608
|
}
|
|
584
609
|
});
|
|
585
610
|
const companyIds = permissions.map((p)=>p.targetId);
|
|
@@ -590,29 +615,27 @@ let AuthenticationService = class AuthenticationService {
|
|
|
590
615
|
active: true
|
|
591
616
|
}).orderBy('c.name', 'ASC').getMany();
|
|
592
617
|
}
|
|
593
|
-
|
|
618
|
+
async getUserBranches(userId, companyId) {
|
|
594
619
|
this.ensureCompanyFeatureEnabled();
|
|
595
620
|
const permissionRepository = await this.getPermissionRepository();
|
|
596
621
|
const branchRepository = await this.getBranchRepository();
|
|
597
622
|
if (!permissionRepository || !branchRepository) {
|
|
598
623
|
throw new _common.BadRequestException('Repositories not available');
|
|
599
624
|
}
|
|
600
|
-
// Verify company access
|
|
601
625
|
const companyPermission = await permissionRepository.findOne({
|
|
602
626
|
where: {
|
|
603
627
|
userId,
|
|
604
|
-
permissionType:
|
|
628
|
+
permissionType: _enums1.PermissionType.COMPANY,
|
|
605
629
|
targetId: companyId
|
|
606
630
|
}
|
|
607
631
|
});
|
|
608
632
|
if (!companyPermission) {
|
|
609
633
|
throw new _common.UnauthorizedException('No access to this company');
|
|
610
634
|
}
|
|
611
|
-
// Get branch permissions
|
|
612
635
|
const branchPermissions = await permissionRepository.find({
|
|
613
636
|
where: {
|
|
614
637
|
userId,
|
|
615
|
-
permissionType:
|
|
638
|
+
permissionType: _enums1.PermissionType.BRANCH
|
|
616
639
|
}
|
|
617
640
|
});
|
|
618
641
|
const branchIds = branchPermissions.map((p)=>p.targetId);
|
|
@@ -625,7 +648,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
625
648
|
active: true
|
|
626
649
|
}).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
|
|
627
650
|
}
|
|
628
|
-
|
|
651
|
+
async getCompanyById(companyId) {
|
|
629
652
|
const companyRepository = await this.getCompanyRepository();
|
|
630
653
|
if (!companyRepository) {
|
|
631
654
|
return null;
|
|
@@ -636,7 +659,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
636
659
|
}
|
|
637
660
|
});
|
|
638
661
|
}
|
|
639
|
-
|
|
662
|
+
async getBranchById(branchId) {
|
|
640
663
|
const branchRepository = await this.getBranchRepository();
|
|
641
664
|
if (!branchRepository) {
|
|
642
665
|
return null;
|
|
@@ -647,7 +670,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
647
670
|
}
|
|
648
671
|
});
|
|
649
672
|
}
|
|
650
|
-
|
|
673
|
+
async getUserInfo(userId, companyId, branchId) {
|
|
651
674
|
const userRepository = await this.getUserRepository();
|
|
652
675
|
const user = await userRepository.findOne({
|
|
653
676
|
where: {
|
|
@@ -673,7 +696,6 @@ let AuthenticationService = class AuthenticationService {
|
|
|
673
696
|
}
|
|
674
697
|
return response;
|
|
675
698
|
}
|
|
676
|
-
// Helper Methods
|
|
677
699
|
ensureCompanyFeatureEnabled() {
|
|
678
700
|
if (!this.isCompanyFeatureEnabled()) {
|
|
679
701
|
throw new _common.BadRequestException('Company feature is not enabled');
|
|
@@ -693,8 +715,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
693
715
|
await userRepository.save(user);
|
|
694
716
|
return user;
|
|
695
717
|
}
|
|
696
|
-
|
|
697
|
-
/** Create user info object */ toUserInfo(user) {
|
|
718
|
+
toUserInfo(user) {
|
|
698
719
|
return {
|
|
699
720
|
id: user.id,
|
|
700
721
|
email: user.email,
|
|
@@ -703,7 +724,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
703
724
|
profilePictureId: user.profilePictureId ?? null
|
|
704
725
|
};
|
|
705
726
|
}
|
|
706
|
-
|
|
727
|
+
toCompanyInfo(company) {
|
|
707
728
|
return {
|
|
708
729
|
id: company.id,
|
|
709
730
|
name: company.name,
|
|
@@ -711,7 +732,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
711
732
|
logoId: company.logoId ?? null
|
|
712
733
|
};
|
|
713
734
|
}
|
|
714
|
-
|
|
735
|
+
toBranchInfo(branch) {
|
|
715
736
|
return {
|
|
716
737
|
id: branch.id,
|
|
717
738
|
name: branch.name,
|
|
@@ -719,7 +740,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
719
740
|
logoId: branch.logoId ?? null
|
|
720
741
|
};
|
|
721
742
|
}
|
|
722
|
-
|
|
743
|
+
generateTokens(payload) {
|
|
723
744
|
const accessToken = this.jwtService.sign({
|
|
724
745
|
...payload,
|
|
725
746
|
type: 'access'
|
|
@@ -745,6 +766,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
745
766
|
email: user.email,
|
|
746
767
|
type: 'access'
|
|
747
768
|
});
|
|
769
|
+
this.sendLoginNotification(user.id);
|
|
748
770
|
return {
|
|
749
771
|
accessToken,
|
|
750
772
|
refreshToken,
|
|
@@ -759,6 +781,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
759
781
|
companyId: company.id,
|
|
760
782
|
branchId: branch?.id
|
|
761
783
|
});
|
|
784
|
+
this.sendLoginNotification(user.id, company.id);
|
|
762
785
|
return {
|
|
763
786
|
accessToken,
|
|
764
787
|
refreshToken,
|
|
@@ -767,6 +790,18 @@ let AuthenticationService = class AuthenticationService {
|
|
|
767
790
|
branch: branch ? this.toBranchInfo(branch) : undefined
|
|
768
791
|
};
|
|
769
792
|
}
|
|
793
|
+
sendLoginNotification(userId, companyId) {
|
|
794
|
+
if (!this.notificationAdapter) return;
|
|
795
|
+
this.notificationAdapter.send({
|
|
796
|
+
userId,
|
|
797
|
+
title: 'Login successful',
|
|
798
|
+
message: 'You have successfully logged in to your account.',
|
|
799
|
+
type: _enums.NotificationType.INFO,
|
|
800
|
+
companyId
|
|
801
|
+
}).catch((error)=>{
|
|
802
|
+
this.logger.warn(`Failed to send login notification: ${error.message}`);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
770
805
|
async generateCompanySelectionResponse(user, companies, branches) {
|
|
771
806
|
if (!this.sessionService) {
|
|
772
807
|
throw new _common.BadRequestException('Session service not available');
|
|
@@ -801,13 +836,15 @@ let AuthenticationService = class AuthenticationService {
|
|
|
801
836
|
};
|
|
802
837
|
}
|
|
803
838
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
804
|
-
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
|
|
839
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, userEnricher, notificationAdapter){
|
|
805
840
|
_define_property(this, "jwtService", void 0);
|
|
806
841
|
_define_property(this, "authConfig", void 0);
|
|
807
842
|
_define_property(this, "cacheManager", void 0);
|
|
808
843
|
_define_property(this, "dataSourceProvider", void 0);
|
|
809
844
|
_define_property(this, "sessionService", void 0);
|
|
810
845
|
_define_property(this, "authEmailService", void 0);
|
|
846
|
+
_define_property(this, "userEnricher", void 0);
|
|
847
|
+
_define_property(this, "notificationAdapter", void 0);
|
|
811
848
|
_define_property(this, "logger", void 0);
|
|
812
849
|
this.jwtService = jwtService;
|
|
813
850
|
this.authConfig = authConfig;
|
|
@@ -815,6 +852,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
815
852
|
this.dataSourceProvider = dataSourceProvider;
|
|
816
853
|
this.sessionService = sessionService;
|
|
817
854
|
this.authEmailService = authEmailService;
|
|
855
|
+
this.userEnricher = userEnricher;
|
|
856
|
+
this.notificationAdapter = notificationAdapter;
|
|
818
857
|
this.logger = new _common.Logger(AuthenticationService.name);
|
|
819
858
|
}
|
|
820
859
|
};
|
|
@@ -832,6 +871,10 @@ AuthenticationService = _ts_decorate([
|
|
|
832
871
|
_ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
|
|
833
872
|
_ts_param(5, (0, _common.Optional)()),
|
|
834
873
|
_ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
|
|
874
|
+
_ts_param(6, (0, _common.Optional)()),
|
|
875
|
+
_ts_param(6, (0, _common.Inject)(_config.USER_ENRICHER)),
|
|
876
|
+
_ts_param(7, (0, _common.Optional)()),
|
|
877
|
+
_ts_param(7, (0, _common.Inject)(_interfaces.NOTIFICATION_ADAPTER)),
|
|
835
878
|
_ts_metadata("design:type", Function),
|
|
836
879
|
_ts_metadata("design:paramtypes", [
|
|
837
880
|
typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
|
|
@@ -839,6 +882,8 @@ AuthenticationService = _ts_decorate([
|
|
|
839
882
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
840
883
|
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
841
884
|
typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
|
|
842
|
-
typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService
|
|
885
|
+
typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService,
|
|
886
|
+
typeof _userenricherinterface.IUserEnricher === "undefined" ? Object : _userenricherinterface.IUserEnricher,
|
|
887
|
+
typeof _interfaces.INotificationAdapter === "undefined" ? Object : _interfaces.INotificationAdapter
|
|
843
888
|
])
|
|
844
889
|
], AuthenticationService);
|