@flusys/nestjs-auth 2.0.0 → 3.0.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 +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/entities/user-company-permission.entity.js +3 -3
- 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 +123 -99
- 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/entities/user-company-permission.entity.d.ts +1 -1
- 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/entities/user-company-permission.entity.js +3 -3
- 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 +124 -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 +4 -1
- package/services/user.service.d.ts +5 -0
|
@@ -30,7 +30,8 @@ import { ErrorHandler, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
|
|
|
30
30
|
import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Optional, Scope, UnauthorizedException } from '@nestjs/common';
|
|
31
31
|
import { JwtService } from '@nestjs/jwt';
|
|
32
32
|
import * as bcrypt from 'bcrypt';
|
|
33
|
-
import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX } from '../config';
|
|
33
|
+
import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX, USER_ENRICHER } from '../config';
|
|
34
|
+
import { IUserEnricher } from '../interfaces/user-enricher.interface';
|
|
34
35
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
35
36
|
import { Company } from '../entities/company.entity';
|
|
36
37
|
import { UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
@@ -41,11 +42,14 @@ import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
|
41
42
|
import { AuthEmailService } from './auth-email.service';
|
|
42
43
|
import { CompanySelectionSessionService } from './company-selection-session.service';
|
|
43
44
|
export class AuthenticationService {
|
|
44
|
-
|
|
45
|
-
/** Check if company feature is enabled (tenant-aware with global fallback) */ isCompanyFeatureEnabled() {
|
|
45
|
+
isCompanyFeatureEnabled() {
|
|
46
46
|
return this.dataSourceProvider?.isCompanyFeatureEnabled() ?? this.authConfig.isCompanyFeatureEnabled();
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
isEmailVerificationEnabled() {
|
|
49
|
+
const featureEnabled = this.dataSourceProvider?.isEmailVerificationEnabled() ?? this.authConfig.isEmailVerificationEnabled();
|
|
50
|
+
const providerAvailable = this.authEmailService?.isEmailEnabled() ?? false;
|
|
51
|
+
return featureEnabled && providerAvailable;
|
|
52
|
+
}
|
|
49
53
|
async getUserRepository() {
|
|
50
54
|
if (!this.dataSourceProvider) {
|
|
51
55
|
throw new Error('User repository not available');
|
|
@@ -64,10 +68,8 @@ export class AuthenticationService {
|
|
|
64
68
|
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
65
69
|
return this.dataSourceProvider.getRepository(CompanyBranch);
|
|
66
70
|
}
|
|
67
|
-
// Login
|
|
68
71
|
async login(email, password) {
|
|
69
|
-
|
|
70
|
-
if (await this.authEmailService?.hasPendingRegistration(email)) {
|
|
72
|
+
if (this.isEmailVerificationEnabled() && await this.authEmailService.hasPendingRegistration(email)) {
|
|
71
73
|
return {
|
|
72
74
|
requiresEmailVerification: true,
|
|
73
75
|
email,
|
|
@@ -78,7 +80,7 @@ export class AuthenticationService {
|
|
|
78
80
|
if (!user.isActive) {
|
|
79
81
|
throw new UnauthorizedException('User account is deactivated');
|
|
80
82
|
}
|
|
81
|
-
if (this.
|
|
83
|
+
if (this.isEmailVerificationEnabled() && !user.emailVerified) {
|
|
82
84
|
return {
|
|
83
85
|
requiresEmailVerification: true,
|
|
84
86
|
email: user.email,
|
|
@@ -90,20 +92,18 @@ export class AuthenticationService {
|
|
|
90
92
|
}
|
|
91
93
|
return this.handleCompanyBasedLogin(user);
|
|
92
94
|
}
|
|
93
|
-
|
|
95
|
+
async handleCompanyBasedLogin(user) {
|
|
94
96
|
const permissionRepository = await this.getPermissionRepository();
|
|
95
97
|
const branchRepository = await this.getBranchRepository();
|
|
96
98
|
if (!permissionRepository || !branchRepository) {
|
|
97
99
|
return this.generateLoginResponse(user);
|
|
98
100
|
}
|
|
99
|
-
// Get company permissions
|
|
100
101
|
const companyPermissions = await permissionRepository.find({
|
|
101
102
|
where: {
|
|
102
103
|
userId: user.id,
|
|
103
104
|
permissionType: PermissionType.COMPANY
|
|
104
105
|
}
|
|
105
106
|
});
|
|
106
|
-
// Load companies
|
|
107
107
|
const companyRepository = await this.getCompanyRepository();
|
|
108
108
|
if (!companyRepository) {
|
|
109
109
|
return this.generateLoginResponse(user);
|
|
@@ -114,12 +114,9 @@ export class AuthenticationService {
|
|
|
114
114
|
}).andWhere('c.is_active = :active', {
|
|
115
115
|
active: true
|
|
116
116
|
}).getMany() : [];
|
|
117
|
-
// If user has no company assignments yet, return simple login response
|
|
118
|
-
// This allows users to login and be assigned to companies later
|
|
119
117
|
if (companies.length === 0) {
|
|
120
118
|
return this.generateLoginResponse(user);
|
|
121
119
|
}
|
|
122
|
-
// Get branch permissions
|
|
123
120
|
const branchPermissions = await permissionRepository.find({
|
|
124
121
|
where: {
|
|
125
122
|
userId: user.id,
|
|
@@ -132,32 +129,25 @@ export class AuthenticationService {
|
|
|
132
129
|
}).andWhere('b.is_active = :active', {
|
|
133
130
|
active: true
|
|
134
131
|
}).getMany() : [];
|
|
135
|
-
// Case 1: Single company, single branch → Direct login
|
|
136
132
|
if (companies.length === 1 && branches.length === 1) {
|
|
137
133
|
return this.generateLoginResponseWithCompany(user, companies[0], branches[0]);
|
|
138
134
|
}
|
|
139
|
-
// Case 2: Multiple options → Selection required (frontend handles UI)
|
|
140
|
-
// Returns all companies with their branches for frontend to display
|
|
141
135
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
142
136
|
}
|
|
143
|
-
|
|
144
|
-
/** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
|
|
137
|
+
async register(dto, verifyUrlBase) {
|
|
145
138
|
const userRepository = await this.getUserRepository();
|
|
146
139
|
const existingUser = await userRepository.findOne({
|
|
147
140
|
where: {
|
|
148
141
|
email: dto.email
|
|
149
142
|
}
|
|
150
143
|
});
|
|
151
|
-
// When company feature is enabled and user exists, allow joining/creating company
|
|
152
144
|
if (existingUser && this.isCompanyFeatureEnabled()) {
|
|
153
145
|
return this.handleExistingUserCompanyRegistration(existingUser, dto);
|
|
154
146
|
}
|
|
155
|
-
// User exists but company feature is off - email must be unique
|
|
156
147
|
if (existingUser) {
|
|
157
148
|
throw new BadRequestException('Registration failed due to email exists. Please try again or contact support.');
|
|
158
149
|
}
|
|
159
|
-
|
|
160
|
-
if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
|
|
150
|
+
if (this.isEmailVerificationEnabled() && verifyUrlBase) {
|
|
161
151
|
const companyData = this.isCompanyFeatureEnabled() ? {
|
|
162
152
|
companySlug: dto.companySlug,
|
|
163
153
|
branchSlug: dto.branchSlug,
|
|
@@ -171,6 +161,7 @@ export class AuthenticationService {
|
|
|
171
161
|
phone: dto.phone,
|
|
172
162
|
password: dto.password,
|
|
173
163
|
companyData,
|
|
164
|
+
additionalFields: dto.additionalFields,
|
|
174
165
|
verifyUrlBase
|
|
175
166
|
});
|
|
176
167
|
return {
|
|
@@ -178,10 +169,9 @@ export class AuthenticationService {
|
|
|
178
169
|
message: result.message
|
|
179
170
|
};
|
|
180
171
|
}
|
|
181
|
-
// No email verification - create user directly
|
|
182
172
|
return this.createUserDirectly(dto);
|
|
183
173
|
}
|
|
184
|
-
|
|
174
|
+
async handleExistingUserCompanyRegistration(existingUser, dto) {
|
|
185
175
|
if (!dto.companySlug && !dto.newCompanyName) {
|
|
186
176
|
throw new BadRequestException('Email already registered. Please login or provide a company to join/create.');
|
|
187
177
|
}
|
|
@@ -190,7 +180,6 @@ export class AuthenticationService {
|
|
|
190
180
|
if (!permissionRepository || !companyRepository) {
|
|
191
181
|
throw new BadRequestException('Company feature repositories not available');
|
|
192
182
|
}
|
|
193
|
-
// For existing users joining a company, check if already assigned
|
|
194
183
|
if (dto.companySlug) {
|
|
195
184
|
const company = await companyRepository.findOne({
|
|
196
185
|
where: {
|
|
@@ -213,50 +202,92 @@ export class AuthenticationService {
|
|
|
213
202
|
}
|
|
214
203
|
return this.processCompanyRegistration(existingUser, dto, true);
|
|
215
204
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
205
|
+
async createUserDirectly(dto) {
|
|
206
|
+
if (!this.dataSourceProvider) {
|
|
207
|
+
throw new BadRequestException('Data source not available');
|
|
208
|
+
}
|
|
209
|
+
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
210
|
+
const queryRunner = dataSource.createQueryRunner();
|
|
211
|
+
await queryRunner.connect();
|
|
212
|
+
await queryRunner.startTransaction();
|
|
213
|
+
try {
|
|
214
|
+
const hashedPassword = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS);
|
|
215
|
+
const user = queryRunner.manager.create(User, {
|
|
216
|
+
name: dto.name,
|
|
217
|
+
email: dto.email,
|
|
218
|
+
phone: dto.phone,
|
|
219
|
+
password: hashedPassword,
|
|
220
|
+
isActive: true,
|
|
221
|
+
emailVerified: false,
|
|
222
|
+
phoneVerified: false
|
|
223
|
+
});
|
|
224
|
+
await queryRunner.manager.save(User, user);
|
|
225
|
+
if (this.userEnricher?.onUserCreated) {
|
|
226
|
+
await this.userEnricher.onUserCreated(user.id, dto.additionalFields ?? null, queryRunner);
|
|
227
|
+
}
|
|
228
|
+
await queryRunner.commitTransaction();
|
|
229
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
230
|
+
return this.generateRegistrationResponse(user);
|
|
231
|
+
}
|
|
232
|
+
return this.processCompanyRegistration(user, dto);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
await queryRunner.rollbackTransaction();
|
|
235
|
+
if (error instanceof BadRequestException) {
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
ErrorHandler.logError(this.logger, error, 'createUserDirectly');
|
|
239
|
+
throw new BadRequestException('Registration failed. Please try again.');
|
|
240
|
+
} finally{
|
|
241
|
+
await queryRunner.release();
|
|
231
242
|
}
|
|
232
|
-
return this.processCompanyRegistration(user, dto);
|
|
233
243
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
244
|
+
async createUserFromPendingRegistration(pending) {
|
|
245
|
+
if (!this.dataSourceProvider) {
|
|
246
|
+
throw new BadRequestException('Data source not available');
|
|
247
|
+
}
|
|
248
|
+
const dataSource = await this.dataSourceProvider.getDataSource();
|
|
249
|
+
const queryRunner = dataSource.createQueryRunner();
|
|
250
|
+
await queryRunner.connect();
|
|
251
|
+
await queryRunner.startTransaction();
|
|
252
|
+
try {
|
|
253
|
+
const existingUser = await queryRunner.manager.findOne(User, {
|
|
254
|
+
where: {
|
|
255
|
+
email: pending.email
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
if (existingUser) {
|
|
259
|
+
throw new BadRequestException('This email has already been registered.');
|
|
239
260
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
const user = queryRunner.manager.create(User, {
|
|
262
|
+
name: pending.name,
|
|
263
|
+
email: pending.email,
|
|
264
|
+
phone: pending.phone,
|
|
265
|
+
password: pending.passwordHash,
|
|
266
|
+
isActive: true,
|
|
267
|
+
emailVerified: true,
|
|
268
|
+
phoneVerified: false
|
|
269
|
+
});
|
|
270
|
+
await queryRunner.manager.save(User, user);
|
|
271
|
+
if (this.userEnricher?.onUserCreated) {
|
|
272
|
+
await this.userEnricher.onUserCreated(user.id, pending.additionalFields ?? null, queryRunner);
|
|
273
|
+
}
|
|
274
|
+
await queryRunner.commitTransaction();
|
|
275
|
+
if (this.isCompanyFeatureEnabled() && pending.companyData) {
|
|
276
|
+
return this.processCompanyRegistration(user, pending.companyData);
|
|
277
|
+
}
|
|
278
|
+
return this.generateRegistrationResponse(user);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
await queryRunner.rollbackTransaction();
|
|
281
|
+
if (error instanceof BadRequestException) {
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
ErrorHandler.logError(this.logger, error, 'createUserFromPendingRegistration');
|
|
285
|
+
throw new BadRequestException('Registration failed. Please try again.');
|
|
286
|
+
} finally{
|
|
287
|
+
await queryRunner.release();
|
|
256
288
|
}
|
|
257
|
-
return this.generateRegistrationResponse(user);
|
|
258
289
|
}
|
|
259
|
-
|
|
290
|
+
async processCompanyRegistration(user, data, throwOnUnavailable = false) {
|
|
260
291
|
const companyRepository = await this.getCompanyRepository();
|
|
261
292
|
const branchRepository = await this.getBranchRepository();
|
|
262
293
|
const permissionRepository = await this.getPermissionRepository();
|
|
@@ -271,7 +302,7 @@ export class AuthenticationService {
|
|
|
271
302
|
}
|
|
272
303
|
return this.generateRegistrationResponse(user);
|
|
273
304
|
}
|
|
274
|
-
|
|
305
|
+
async findOrCreateCompanyAndBranch(data, companyRepo, branchRepo) {
|
|
275
306
|
if (data.companySlug) {
|
|
276
307
|
const company = await companyRepo.findOne({
|
|
277
308
|
where: {
|
|
@@ -335,7 +366,7 @@ export class AuthenticationService {
|
|
|
335
366
|
branch: null
|
|
336
367
|
};
|
|
337
368
|
}
|
|
338
|
-
|
|
369
|
+
async assignUserPermissions(userId, companyId, branchId, permissionRepo) {
|
|
339
370
|
await permissionRepo.save(permissionRepo.create({
|
|
340
371
|
userId,
|
|
341
372
|
permissionType: PermissionType.COMPANY,
|
|
@@ -349,8 +380,7 @@ export class AuthenticationService {
|
|
|
349
380
|
}));
|
|
350
381
|
}
|
|
351
382
|
}
|
|
352
|
-
|
|
353
|
-
/** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
|
|
383
|
+
async select(sessionId, companyId, branchId) {
|
|
354
384
|
this.ensureCompanyFeatureEnabled();
|
|
355
385
|
if (!this.sessionService) {
|
|
356
386
|
throw new BadRequestException('Session service not available');
|
|
@@ -361,11 +391,11 @@ export class AuthenticationService {
|
|
|
361
391
|
}
|
|
362
392
|
return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
|
|
363
393
|
}
|
|
364
|
-
|
|
394
|
+
async switchCompany(userId, companyId, branchId) {
|
|
365
395
|
this.ensureCompanyFeatureEnabled();
|
|
366
396
|
return this.validateAndLoginWithCompany(userId, companyId, branchId);
|
|
367
397
|
}
|
|
368
|
-
|
|
398
|
+
async validateAndLoginWithCompany(userId, companyId, branchId) {
|
|
369
399
|
const userRepository = await this.getUserRepository();
|
|
370
400
|
const user = await userRepository.findOne({
|
|
371
401
|
where: {
|
|
@@ -381,7 +411,6 @@ export class AuthenticationService {
|
|
|
381
411
|
if (!permissionRepository || !companyRepository || !branchRepository) {
|
|
382
412
|
throw new BadRequestException('Repositories not available');
|
|
383
413
|
}
|
|
384
|
-
// Validate company access
|
|
385
414
|
const hasCompanyAccess = await permissionRepository.findOne({
|
|
386
415
|
where: {
|
|
387
416
|
userId,
|
|
@@ -401,7 +430,6 @@ export class AuthenticationService {
|
|
|
401
430
|
if (!company) {
|
|
402
431
|
throw new UnauthorizedException('Company not found or inactive');
|
|
403
432
|
}
|
|
404
|
-
// Validate branch access
|
|
405
433
|
const hasBranchAccess = await permissionRepository.findOne({
|
|
406
434
|
where: {
|
|
407
435
|
userId,
|
|
@@ -424,8 +452,7 @@ export class AuthenticationService {
|
|
|
424
452
|
}
|
|
425
453
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
426
454
|
}
|
|
427
|
-
|
|
428
|
-
/** Refresh access token (with token rotation). Returns new access token and new refresh token. */ async refreshToken(refreshToken) {
|
|
455
|
+
async refreshToken(refreshToken) {
|
|
429
456
|
try {
|
|
430
457
|
const payload = this.jwtService.verify(refreshToken, {
|
|
431
458
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
@@ -459,7 +486,7 @@ export class AuthenticationService {
|
|
|
459
486
|
throw new UnauthorizedException('Invalid or expired refresh token');
|
|
460
487
|
}
|
|
461
488
|
}
|
|
462
|
-
|
|
489
|
+
async changePassword(userId, currentPassword, newPassword) {
|
|
463
490
|
const userRepository = await this.getUserRepository();
|
|
464
491
|
const user = await userRepository.findOne({
|
|
465
492
|
where: {
|
|
@@ -473,21 +500,19 @@ export class AuthenticationService {
|
|
|
473
500
|
user.password = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
|
|
474
501
|
await userRepository.save(user);
|
|
475
502
|
}
|
|
476
|
-
|
|
503
|
+
async logout(userId) {
|
|
477
504
|
if (!this.cacheManager) {
|
|
478
|
-
// Cache is required for secure logout - tokens cannot be revoked without it
|
|
479
505
|
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.');
|
|
480
506
|
throw new BadRequestException('Logout service temporarily unavailable. Please try again later.');
|
|
481
507
|
}
|
|
482
|
-
const revokedAt = Math.floor(Date.now() / 1000);
|
|
508
|
+
const revokedAt = Math.floor(Date.now() / 1000);
|
|
483
509
|
const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
484
|
-
// TTL matches refresh token expiry (tokens older than this are already expired)
|
|
485
510
|
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
486
511
|
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
487
512
|
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
488
513
|
this.logger.log(`User ${userId} logged out - all tokens revoked`);
|
|
489
514
|
}
|
|
490
|
-
|
|
515
|
+
async isTokenRevoked(userId, tokenIssuedAt) {
|
|
491
516
|
if (!this.cacheManager || !tokenIssuedAt) {
|
|
492
517
|
return false;
|
|
493
518
|
}
|
|
@@ -496,12 +521,11 @@ export class AuthenticationService {
|
|
|
496
521
|
if (!revokedAt) {
|
|
497
522
|
return false;
|
|
498
523
|
}
|
|
499
|
-
// Token is revoked if it was issued before the revocation timestamp
|
|
500
524
|
return tokenIssuedAt < revokedAt;
|
|
501
525
|
}
|
|
502
|
-
|
|
526
|
+
parseExpiryToMs(expiry) {
|
|
503
527
|
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
504
|
-
if (!match) return 7 * 24 * 60 * 60 * 1000;
|
|
528
|
+
if (!match) return 7 * 24 * 60 * 60 * 1000;
|
|
505
529
|
const value = parseInt(match[1], 10);
|
|
506
530
|
const unit = match[2];
|
|
507
531
|
switch(unit){
|
|
@@ -517,8 +541,7 @@ export class AuthenticationService {
|
|
|
517
541
|
return 7 * 24 * 60 * 60 * 1000;
|
|
518
542
|
}
|
|
519
543
|
}
|
|
520
|
-
|
|
521
|
-
/** Get user's companies */ async getUserCompanies(userId) {
|
|
544
|
+
async getUserCompanies(userId) {
|
|
522
545
|
this.ensureCompanyFeatureEnabled();
|
|
523
546
|
const permissionRepository = await this.getPermissionRepository();
|
|
524
547
|
const companyRepository = await this.getCompanyRepository();
|
|
@@ -539,14 +562,13 @@ export class AuthenticationService {
|
|
|
539
562
|
active: true
|
|
540
563
|
}).orderBy('c.name', 'ASC').getMany();
|
|
541
564
|
}
|
|
542
|
-
|
|
565
|
+
async getUserBranches(userId, companyId) {
|
|
543
566
|
this.ensureCompanyFeatureEnabled();
|
|
544
567
|
const permissionRepository = await this.getPermissionRepository();
|
|
545
568
|
const branchRepository = await this.getBranchRepository();
|
|
546
569
|
if (!permissionRepository || !branchRepository) {
|
|
547
570
|
throw new BadRequestException('Repositories not available');
|
|
548
571
|
}
|
|
549
|
-
// Verify company access
|
|
550
572
|
const companyPermission = await permissionRepository.findOne({
|
|
551
573
|
where: {
|
|
552
574
|
userId,
|
|
@@ -557,7 +579,6 @@ export class AuthenticationService {
|
|
|
557
579
|
if (!companyPermission) {
|
|
558
580
|
throw new UnauthorizedException('No access to this company');
|
|
559
581
|
}
|
|
560
|
-
// Get branch permissions
|
|
561
582
|
const branchPermissions = await permissionRepository.find({
|
|
562
583
|
where: {
|
|
563
584
|
userId,
|
|
@@ -574,7 +595,7 @@ export class AuthenticationService {
|
|
|
574
595
|
active: true
|
|
575
596
|
}).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
|
|
576
597
|
}
|
|
577
|
-
|
|
598
|
+
async getCompanyById(companyId) {
|
|
578
599
|
const companyRepository = await this.getCompanyRepository();
|
|
579
600
|
if (!companyRepository) {
|
|
580
601
|
return null;
|
|
@@ -585,7 +606,7 @@ export class AuthenticationService {
|
|
|
585
606
|
}
|
|
586
607
|
});
|
|
587
608
|
}
|
|
588
|
-
|
|
609
|
+
async getBranchById(branchId) {
|
|
589
610
|
const branchRepository = await this.getBranchRepository();
|
|
590
611
|
if (!branchRepository) {
|
|
591
612
|
return null;
|
|
@@ -596,7 +617,7 @@ export class AuthenticationService {
|
|
|
596
617
|
}
|
|
597
618
|
});
|
|
598
619
|
}
|
|
599
|
-
|
|
620
|
+
async getUserInfo(userId, companyId, branchId) {
|
|
600
621
|
const userRepository = await this.getUserRepository();
|
|
601
622
|
const user = await userRepository.findOne({
|
|
602
623
|
where: {
|
|
@@ -622,7 +643,6 @@ export class AuthenticationService {
|
|
|
622
643
|
}
|
|
623
644
|
return response;
|
|
624
645
|
}
|
|
625
|
-
// Helper Methods
|
|
626
646
|
ensureCompanyFeatureEnabled() {
|
|
627
647
|
if (!this.isCompanyFeatureEnabled()) {
|
|
628
648
|
throw new BadRequestException('Company feature is not enabled');
|
|
@@ -642,8 +662,7 @@ export class AuthenticationService {
|
|
|
642
662
|
await userRepository.save(user);
|
|
643
663
|
return user;
|
|
644
664
|
}
|
|
645
|
-
|
|
646
|
-
/** Create user info object */ toUserInfo(user) {
|
|
665
|
+
toUserInfo(user) {
|
|
647
666
|
return {
|
|
648
667
|
id: user.id,
|
|
649
668
|
email: user.email,
|
|
@@ -652,7 +671,7 @@ export class AuthenticationService {
|
|
|
652
671
|
profilePictureId: user.profilePictureId ?? null
|
|
653
672
|
};
|
|
654
673
|
}
|
|
655
|
-
|
|
674
|
+
toCompanyInfo(company) {
|
|
656
675
|
return {
|
|
657
676
|
id: company.id,
|
|
658
677
|
name: company.name,
|
|
@@ -660,7 +679,7 @@ export class AuthenticationService {
|
|
|
660
679
|
logoId: company.logoId ?? null
|
|
661
680
|
};
|
|
662
681
|
}
|
|
663
|
-
|
|
682
|
+
toBranchInfo(branch) {
|
|
664
683
|
return {
|
|
665
684
|
id: branch.id,
|
|
666
685
|
name: branch.name,
|
|
@@ -668,7 +687,7 @@ export class AuthenticationService {
|
|
|
668
687
|
logoId: branch.logoId ?? null
|
|
669
688
|
};
|
|
670
689
|
}
|
|
671
|
-
|
|
690
|
+
generateTokens(payload) {
|
|
672
691
|
const accessToken = this.jwtService.sign({
|
|
673
692
|
...payload,
|
|
674
693
|
type: 'access'
|
|
@@ -750,13 +769,14 @@ export class AuthenticationService {
|
|
|
750
769
|
};
|
|
751
770
|
}
|
|
752
771
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
753
|
-
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
|
|
772
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, userEnricher){
|
|
754
773
|
_define_property(this, "jwtService", void 0);
|
|
755
774
|
_define_property(this, "authConfig", void 0);
|
|
756
775
|
_define_property(this, "cacheManager", void 0);
|
|
757
776
|
_define_property(this, "dataSourceProvider", void 0);
|
|
758
777
|
_define_property(this, "sessionService", void 0);
|
|
759
778
|
_define_property(this, "authEmailService", void 0);
|
|
779
|
+
_define_property(this, "userEnricher", void 0);
|
|
760
780
|
_define_property(this, "logger", void 0);
|
|
761
781
|
this.jwtService = jwtService;
|
|
762
782
|
this.authConfig = authConfig;
|
|
@@ -764,6 +784,7 @@ export class AuthenticationService {
|
|
|
764
784
|
this.dataSourceProvider = dataSourceProvider;
|
|
765
785
|
this.sessionService = sessionService;
|
|
766
786
|
this.authEmailService = authEmailService;
|
|
787
|
+
this.userEnricher = userEnricher;
|
|
767
788
|
this.logger = new Logger(AuthenticationService.name);
|
|
768
789
|
}
|
|
769
790
|
}
|
|
@@ -781,6 +802,8 @@ AuthenticationService = _ts_decorate([
|
|
|
781
802
|
_ts_param(4, Inject(CompanySelectionSessionService)),
|
|
782
803
|
_ts_param(5, Optional()),
|
|
783
804
|
_ts_param(5, Inject(AuthEmailService)),
|
|
805
|
+
_ts_param(6, Optional()),
|
|
806
|
+
_ts_param(6, Inject(USER_ENRICHER)),
|
|
784
807
|
_ts_metadata("design:type", Function),
|
|
785
808
|
_ts_metadata("design:paramtypes", [
|
|
786
809
|
typeof JwtService === "undefined" ? Object : JwtService,
|
|
@@ -788,6 +811,7 @@ AuthenticationService = _ts_decorate([
|
|
|
788
811
|
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
789
812
|
typeof AuthDataSourceProvider === "undefined" ? Object : AuthDataSourceProvider,
|
|
790
813
|
typeof CompanySelectionSessionService === "undefined" ? Object : CompanySelectionSessionService,
|
|
791
|
-
typeof AuthEmailService === "undefined" ? Object : AuthEmailService
|
|
814
|
+
typeof AuthEmailService === "undefined" ? Object : AuthEmailService,
|
|
815
|
+
typeof IUserEnricher === "undefined" ? Object : IUserEnricher
|
|
792
816
|
])
|
|
793
817
|
], AuthenticationService);
|