@flusys/nestjs-auth 1.1.0-beta → 1.1.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 +633 -468
- package/cjs/config/auth.constants.js +8 -0
- package/cjs/controllers/authentication.controller.js +175 -167
- 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/email-verification.controller.js +220 -0
- package/cjs/controllers/index.js +1 -0
- 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/index.js +16 -17
- package/cjs/entities/user-company-permission.entity.js +3 -9
- 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 +79 -154
- package/cjs/services/auth-config.service.js +33 -35
- package/cjs/services/auth-datasource.provider.js +18 -42
- package/cjs/services/auth-email.service.js +89 -85
- package/cjs/services/authentication.service.js +248 -414
- 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 +4 -10
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/email-verification.controller.d.ts +20 -0
- package/controllers/index.d.ts +1 -0
- 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 +5 -10
- package/entities/user-company-permission.entity.d.ts +1 -3
- package/fesm/config/auth.constants.js +6 -13
- package/fesm/controllers/authentication.controller.js +178 -170
- 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/email-verification.controller.js +210 -0
- package/fesm/controllers/index.js +1 -0
- 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/index.js +17 -9
- package/fesm/entities/user-company-permission.entity.js +3 -9
- 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 +82 -157
- package/fesm/services/auth-config.service.js +34 -36
- package/fesm/services/auth-datasource.provider.js +19 -43
- package/fesm/services/auth-email.service.js +90 -86
- package/fesm/services/authentication.service.js +248 -414
- 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 +9 -4
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +7 -8
- package/services/auth-datasource.provider.d.ts +3 -4
- package/services/auth-email.service.d.ts +8 -4
- package/services/authentication.service.d.ts +16 -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
|
@@ -25,75 +25,77 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
|
+
import { ErrorHandler, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
|
|
28
30
|
import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Optional, Scope, UnauthorizedException } from '@nestjs/common';
|
|
29
31
|
import { JwtService } from '@nestjs/jwt';
|
|
30
|
-
import { InjectRepository } from '@nestjs/typeorm';
|
|
31
32
|
import * as bcrypt from 'bcrypt';
|
|
32
|
-
import { Repository } from 'typeorm';
|
|
33
|
-
import { ErrorHandler } from '@flusys/nestjs-shared/utils';
|
|
34
|
-
import { AuthConfigService } from './auth-config.service';
|
|
35
|
-
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
36
|
-
import { PermissionType } from '../enums';
|
|
37
33
|
import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX } from '../config';
|
|
38
|
-
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
39
|
-
import { AuthEmailService } from './auth-email.service';
|
|
40
34
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
41
35
|
import { Company } from '../entities/company.entity';
|
|
42
36
|
import { UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
43
37
|
import { User } from '../entities/user.entity';
|
|
38
|
+
import { PermissionType } from '../enums';
|
|
39
|
+
import { AuthConfigService } from './auth-config.service';
|
|
40
|
+
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
41
|
+
import { AuthEmailService } from './auth-email.service';
|
|
44
42
|
import { CompanySelectionSessionService } from './company-selection-session.service';
|
|
45
43
|
export class AuthenticationService {
|
|
44
|
+
// Feature Flags
|
|
45
|
+
isCompanyFeatureEnabled() {
|
|
46
|
+
return this.dataSourceProvider?.isCompanyFeatureEnabled() ?? this.authConfig.isCompanyFeatureEnabled();
|
|
47
|
+
}
|
|
48
|
+
isEmailVerificationEnabled() {
|
|
49
|
+
const featureEnabled = this.dataSourceProvider?.isEmailVerificationEnabled() ?? this.authConfig.isEmailVerificationEnabled();
|
|
50
|
+
const providerAvailable = this.authEmailService?.isEmailEnabled() ?? false;
|
|
51
|
+
return featureEnabled && providerAvailable;
|
|
52
|
+
}
|
|
46
53
|
// Repository Getters
|
|
47
54
|
async getUserRepository() {
|
|
48
|
-
if (this.dataSourceProvider) {
|
|
49
|
-
|
|
55
|
+
if (!this.dataSourceProvider) {
|
|
56
|
+
throw new Error('User repository not available');
|
|
50
57
|
}
|
|
51
|
-
|
|
52
|
-
return this.injectedUserRepository;
|
|
53
|
-
}
|
|
54
|
-
throw new Error('User repository not available');
|
|
58
|
+
return this.dataSourceProvider.getRepository(User);
|
|
55
59
|
}
|
|
56
60
|
async getPermissionRepository() {
|
|
57
|
-
if (!this.
|
|
58
|
-
|
|
59
|
-
return this.dataSourceProvider.getRepository(UserCompanyPermission);
|
|
60
|
-
}
|
|
61
|
-
return this.injectedPermissionRepository ?? null;
|
|
61
|
+
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
62
|
+
return this.dataSourceProvider.getRepository(UserCompanyPermission);
|
|
62
63
|
}
|
|
63
64
|
async getCompanyRepository() {
|
|
64
|
-
if (!this.
|
|
65
|
-
|
|
66
|
-
return this.dataSourceProvider.getRepository(Company);
|
|
67
|
-
}
|
|
68
|
-
return this.injectedCompanyRepository ?? null;
|
|
65
|
+
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
66
|
+
return this.dataSourceProvider.getRepository(Company);
|
|
69
67
|
}
|
|
70
68
|
async getBranchRepository() {
|
|
71
|
-
if (!this.
|
|
72
|
-
|
|
73
|
-
return this.dataSourceProvider.getRepository(CompanyBranch);
|
|
74
|
-
}
|
|
75
|
-
return this.injectedBranchRepository ?? null;
|
|
69
|
+
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
70
|
+
return this.dataSourceProvider.getRepository(CompanyBranch);
|
|
76
71
|
}
|
|
77
72
|
// Login
|
|
78
|
-
|
|
73
|
+
async login(email, password) {
|
|
74
|
+
// Check for pending registration first (user registered but hasn't verified email yet)
|
|
75
|
+
if (this.isEmailVerificationEnabled() && await this.authEmailService.hasPendingRegistration(email)) {
|
|
76
|
+
return {
|
|
77
|
+
requiresEmailVerification: true,
|
|
78
|
+
email,
|
|
79
|
+
message: 'Please verify your email address to complete registration.'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
79
82
|
const user = await this.validateUser(email, password);
|
|
80
83
|
if (!user.isActive) {
|
|
81
84
|
throw new UnauthorizedException('User account is deactivated');
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
|
|
86
|
+
if (this.isEmailVerificationEnabled() && !user.emailVerified) {
|
|
85
87
|
return {
|
|
86
88
|
requiresEmailVerification: true,
|
|
87
89
|
email: user.email,
|
|
88
90
|
message: 'Please verify your email address before logging in.'
|
|
89
91
|
};
|
|
90
92
|
}
|
|
91
|
-
if (!this.
|
|
93
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
92
94
|
return this.generateLoginResponse(user);
|
|
93
95
|
}
|
|
94
96
|
return this.handleCompanyBasedLogin(user);
|
|
95
97
|
}
|
|
96
|
-
|
|
98
|
+
async handleCompanyBasedLogin(user) {
|
|
97
99
|
const permissionRepository = await this.getPermissionRepository();
|
|
98
100
|
const branchRepository = await this.getBranchRepository();
|
|
99
101
|
if (!permissionRepository || !branchRepository) {
|
|
@@ -120,7 +122,7 @@ export class AuthenticationService {
|
|
|
120
122
|
// If user has no company assignments yet, return simple login response
|
|
121
123
|
// This allows users to login and be assigned to companies later
|
|
122
124
|
if (companies.length === 0) {
|
|
123
|
-
return this.generateLoginResponse(user
|
|
125
|
+
return this.generateLoginResponse(user);
|
|
124
126
|
}
|
|
125
127
|
// Get branch permissions
|
|
126
128
|
const branchPermissions = await permissionRepository.find({
|
|
@@ -144,19 +146,24 @@ export class AuthenticationService {
|
|
|
144
146
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
145
147
|
}
|
|
146
148
|
// Registration
|
|
147
|
-
|
|
149
|
+
async register(dto, verifyUrlBase) {
|
|
148
150
|
const userRepository = await this.getUserRepository();
|
|
149
151
|
const existingUser = await userRepository.findOne({
|
|
150
152
|
where: {
|
|
151
153
|
email: dto.email
|
|
152
154
|
}
|
|
153
155
|
});
|
|
156
|
+
// When company feature is enabled and user exists, allow joining/creating company
|
|
157
|
+
if (existingUser && this.isCompanyFeatureEnabled()) {
|
|
158
|
+
return this.handleExistingUserCompanyRegistration(existingUser, dto);
|
|
159
|
+
}
|
|
160
|
+
// User exists but company feature is off - email must be unique
|
|
154
161
|
if (existingUser) {
|
|
155
|
-
throw new BadRequestException('Registration failed. Please try again or contact support.');
|
|
162
|
+
throw new BadRequestException('Registration failed due to email exists. Please try again or contact support.');
|
|
156
163
|
}
|
|
157
164
|
// If email verification is enabled, create pending registration instead of user
|
|
158
|
-
if (this.
|
|
159
|
-
const companyData = this.
|
|
165
|
+
if (this.isEmailVerificationEnabled() && verifyUrlBase) {
|
|
166
|
+
const companyData = this.isCompanyFeatureEnabled() ? {
|
|
160
167
|
companySlug: dto.companySlug,
|
|
161
168
|
branchSlug: dto.branchSlug,
|
|
162
169
|
newCompanyName: dto.newCompanyName,
|
|
@@ -179,7 +186,39 @@ export class AuthenticationService {
|
|
|
179
186
|
// No email verification - create user directly
|
|
180
187
|
return this.createUserDirectly(dto);
|
|
181
188
|
}
|
|
182
|
-
|
|
189
|
+
async handleExistingUserCompanyRegistration(existingUser, dto) {
|
|
190
|
+
if (!dto.companySlug && !dto.newCompanyName) {
|
|
191
|
+
throw new BadRequestException('Email already registered. Please login or provide a company to join/create.');
|
|
192
|
+
}
|
|
193
|
+
const permissionRepository = await this.getPermissionRepository();
|
|
194
|
+
const companyRepository = await this.getCompanyRepository();
|
|
195
|
+
if (!permissionRepository || !companyRepository) {
|
|
196
|
+
throw new BadRequestException('Company feature repositories not available');
|
|
197
|
+
}
|
|
198
|
+
// For existing users joining a company, check if already assigned
|
|
199
|
+
if (dto.companySlug) {
|
|
200
|
+
const company = await companyRepository.findOne({
|
|
201
|
+
where: {
|
|
202
|
+
slug: dto.companySlug,
|
|
203
|
+
isActive: true
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
if (company) {
|
|
207
|
+
const existingPermission = await permissionRepository.findOne({
|
|
208
|
+
where: {
|
|
209
|
+
userId: existingUser.id,
|
|
210
|
+
permissionType: PermissionType.COMPANY,
|
|
211
|
+
targetId: company.id
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
if (existingPermission) {
|
|
215
|
+
throw new BadRequestException('You are already assigned to this company. Please login.');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return this.processCompanyRegistration(existingUser, dto, true);
|
|
220
|
+
}
|
|
221
|
+
async createUserDirectly(dto) {
|
|
183
222
|
const userRepository = await this.getUserRepository();
|
|
184
223
|
const hashedPassword = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS);
|
|
185
224
|
const user = userRepository.create({
|
|
@@ -192,14 +231,13 @@ export class AuthenticationService {
|
|
|
192
231
|
phoneVerified: false
|
|
193
232
|
});
|
|
194
233
|
await userRepository.save(user);
|
|
195
|
-
if (!this.
|
|
196
|
-
return this.generateRegistrationResponse(user
|
|
234
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
235
|
+
return this.generateRegistrationResponse(user);
|
|
197
236
|
}
|
|
198
|
-
return this.
|
|
237
|
+
return this.processCompanyRegistration(user, dto);
|
|
199
238
|
}
|
|
200
|
-
|
|
239
|
+
async createUserFromPendingRegistration(pending) {
|
|
201
240
|
const userRepository = await this.getUserRepository();
|
|
202
|
-
// Check if user already exists (race condition protection)
|
|
203
241
|
const existingUser = await userRepository.findOne({
|
|
204
242
|
where: {
|
|
205
243
|
email: pending.email
|
|
@@ -208,7 +246,6 @@ export class AuthenticationService {
|
|
|
208
246
|
if (existingUser) {
|
|
209
247
|
throw new BadRequestException('This email has already been registered.');
|
|
210
248
|
}
|
|
211
|
-
// Create user with verified email
|
|
212
249
|
const user = userRepository.create({
|
|
213
250
|
name: pending.name,
|
|
214
251
|
email: pending.email,
|
|
@@ -219,110 +256,106 @@ export class AuthenticationService {
|
|
|
219
256
|
phoneVerified: false
|
|
220
257
|
});
|
|
221
258
|
await userRepository.save(user);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return this.handleCompanyRegistrationFromPending(user, pending.companyData);
|
|
259
|
+
if (this.isCompanyFeatureEnabled() && pending.companyData) {
|
|
260
|
+
return this.processCompanyRegistration(user, pending.companyData);
|
|
225
261
|
}
|
|
226
|
-
return this.generateRegistrationResponse(user
|
|
227
|
-
}
|
|
228
|
-
/** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
|
|
229
|
-
return this.processCompanyRegistration(user, companyData, false);
|
|
262
|
+
return this.generateRegistrationResponse(user);
|
|
230
263
|
}
|
|
231
|
-
|
|
232
|
-
return this.processCompanyRegistration(user, dto, requiresEmailVerification);
|
|
233
|
-
}
|
|
234
|
-
/** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
|
|
264
|
+
async processCompanyRegistration(user, data, throwOnUnavailable = false) {
|
|
235
265
|
const companyRepository = await this.getCompanyRepository();
|
|
236
266
|
const branchRepository = await this.getBranchRepository();
|
|
237
267
|
const permissionRepository = await this.getPermissionRepository();
|
|
238
268
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
239
|
-
|
|
269
|
+
if (throwOnUnavailable) throw new BadRequestException('Company feature repositories not available');
|
|
270
|
+
return this.generateRegistrationResponse(user);
|
|
271
|
+
}
|
|
272
|
+
const { company, branch } = await this.findOrCreateCompanyAndBranch(data, companyRepository, branchRepository);
|
|
273
|
+
if (company) {
|
|
274
|
+
await this.assignUserPermissions(user.id, company.id, branch?.id, permissionRepository);
|
|
275
|
+
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined);
|
|
240
276
|
}
|
|
241
|
-
|
|
242
|
-
|
|
277
|
+
return this.generateRegistrationResponse(user);
|
|
278
|
+
}
|
|
279
|
+
async findOrCreateCompanyAndBranch(data, companyRepo, branchRepo) {
|
|
243
280
|
if (data.companySlug) {
|
|
244
|
-
company = await
|
|
281
|
+
const company = await companyRepo.findOne({
|
|
245
282
|
where: {
|
|
246
283
|
slug: data.companySlug,
|
|
247
284
|
isActive: true
|
|
248
285
|
}
|
|
249
286
|
});
|
|
250
|
-
if (!company)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
slug: data.branchSlug,
|
|
257
|
-
companyId: company.id,
|
|
258
|
-
isActive: true
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
if (!branch) {
|
|
262
|
-
throw new BadRequestException('Branch not found or inactive');
|
|
287
|
+
if (!company) throw new BadRequestException('Company not found or inactive');
|
|
288
|
+
const branch = data.branchSlug ? await branchRepo.findOne({
|
|
289
|
+
where: {
|
|
290
|
+
slug: data.branchSlug,
|
|
291
|
+
companyId: company.id,
|
|
292
|
+
isActive: true
|
|
263
293
|
}
|
|
264
|
-
}
|
|
265
|
-
branch = await branchRepository.findOne({
|
|
266
|
-
where: {
|
|
267
|
-
companyId: company.id,
|
|
268
|
-
isActive: true
|
|
269
|
-
},
|
|
270
|
-
order: {
|
|
271
|
-
serial: 'ASC',
|
|
272
|
-
createdAt: 'ASC'
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
} else if (data.newCompanyName) {
|
|
277
|
-
const slug = this.generateSlug(data.newCompanyName);
|
|
278
|
-
const existingCompany = await companyRepository.findOne({
|
|
294
|
+
}) : await branchRepo.findOne({
|
|
279
295
|
where: {
|
|
280
|
-
|
|
296
|
+
companyId: company.id,
|
|
297
|
+
isActive: true
|
|
298
|
+
},
|
|
299
|
+
order: {
|
|
300
|
+
serial: 'ASC',
|
|
301
|
+
createdAt: 'ASC'
|
|
281
302
|
}
|
|
282
303
|
});
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
304
|
+
if (data.branchSlug && !branch) throw new BadRequestException('Branch not found or inactive');
|
|
305
|
+
return {
|
|
306
|
+
company,
|
|
307
|
+
branch
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (data.newCompanyName) {
|
|
311
|
+
const slug = await generateUniqueSlug(data.newCompanyName, async (baseSlug)=>{
|
|
312
|
+
const rows = await companyRepo.createQueryBuilder('c').select('c.slug').where('c.slug = :base OR c.slug LIKE :pattern', {
|
|
313
|
+
base: baseSlug,
|
|
314
|
+
pattern: `${baseSlug}-%`
|
|
315
|
+
}).getMany();
|
|
316
|
+
return rows.map((r)=>r.slug);
|
|
317
|
+
});
|
|
318
|
+
const company = companyRepo.create({
|
|
287
319
|
name: data.newCompanyName,
|
|
288
320
|
slug,
|
|
289
321
|
phone: data.newCompanyPhone,
|
|
290
322
|
address: data.newCompanyAddress,
|
|
291
323
|
isActive: true
|
|
292
324
|
});
|
|
293
|
-
await
|
|
294
|
-
branch =
|
|
325
|
+
await companyRepo.save(company);
|
|
326
|
+
const branch = branchRepo.create({
|
|
295
327
|
name: 'Main Branch',
|
|
296
328
|
slug: 'main-branch',
|
|
297
329
|
companyId: company.id,
|
|
298
330
|
isActive: true
|
|
299
331
|
});
|
|
300
|
-
await
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
permissionType: PermissionType.COMPANY,
|
|
306
|
-
targetId: company.id
|
|
307
|
-
});
|
|
308
|
-
await permissionRepository.save(companyPermission);
|
|
309
|
-
if (branch) {
|
|
310
|
-
const branchPermission = permissionRepository.create({
|
|
311
|
-
userId: user.id,
|
|
312
|
-
permissionType: PermissionType.BRANCH,
|
|
313
|
-
targetId: branch.id
|
|
314
|
-
});
|
|
315
|
-
await permissionRepository.save(branchPermission);
|
|
316
|
-
}
|
|
317
|
-
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
|
|
332
|
+
await branchRepo.save(branch);
|
|
333
|
+
return {
|
|
334
|
+
company,
|
|
335
|
+
branch
|
|
336
|
+
};
|
|
318
337
|
}
|
|
319
|
-
return
|
|
338
|
+
return {
|
|
339
|
+
company: null,
|
|
340
|
+
branch: null
|
|
341
|
+
};
|
|
320
342
|
}
|
|
321
|
-
|
|
322
|
-
|
|
343
|
+
async assignUserPermissions(userId, companyId, branchId, permissionRepo) {
|
|
344
|
+
await permissionRepo.save(permissionRepo.create({
|
|
345
|
+
userId,
|
|
346
|
+
permissionType: PermissionType.COMPANY,
|
|
347
|
+
targetId: companyId
|
|
348
|
+
}));
|
|
349
|
+
if (branchId) {
|
|
350
|
+
await permissionRepo.save(permissionRepo.create({
|
|
351
|
+
userId,
|
|
352
|
+
permissionType: PermissionType.BRANCH,
|
|
353
|
+
targetId: branchId
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
323
356
|
}
|
|
324
357
|
// Company/Branch Selection
|
|
325
|
-
|
|
358
|
+
async select(sessionId, companyId, branchId) {
|
|
326
359
|
this.ensureCompanyFeatureEnabled();
|
|
327
360
|
if (!this.sessionService) {
|
|
328
361
|
throw new BadRequestException('Session service not available');
|
|
@@ -331,66 +364,13 @@ export class AuthenticationService {
|
|
|
331
364
|
if (!session) {
|
|
332
365
|
throw new UnauthorizedException('Invalid or expired session');
|
|
333
366
|
}
|
|
334
|
-
|
|
335
|
-
const user = await userRepository.findOne({
|
|
336
|
-
where: {
|
|
337
|
-
id: session.userId
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
if (!user) {
|
|
341
|
-
throw new NotFoundException('User not found');
|
|
342
|
-
}
|
|
343
|
-
const permissionRepository = await this.getPermissionRepository();
|
|
344
|
-
const companyRepository = await this.getCompanyRepository();
|
|
345
|
-
const branchRepository = await this.getBranchRepository();
|
|
346
|
-
if (!permissionRepository || !companyRepository || !branchRepository) {
|
|
347
|
-
throw new BadRequestException('Repositories not available');
|
|
348
|
-
}
|
|
349
|
-
// Validate company access
|
|
350
|
-
const companyPermission = await permissionRepository.findOne({
|
|
351
|
-
where: {
|
|
352
|
-
userId: user.id,
|
|
353
|
-
permissionType: PermissionType.COMPANY,
|
|
354
|
-
targetId: companyId
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
if (!companyPermission) {
|
|
358
|
-
throw new UnauthorizedException('No access to this company');
|
|
359
|
-
}
|
|
360
|
-
const company = await companyRepository.findOne({
|
|
361
|
-
where: {
|
|
362
|
-
id: companyId,
|
|
363
|
-
isActive: true
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
if (!company) {
|
|
367
|
-
throw new UnauthorizedException('Company not found or inactive');
|
|
368
|
-
}
|
|
369
|
-
// Validate branch access
|
|
370
|
-
const branchPermission = await permissionRepository.findOne({
|
|
371
|
-
where: {
|
|
372
|
-
userId: user.id,
|
|
373
|
-
permissionType: PermissionType.BRANCH,
|
|
374
|
-
targetId: branchId
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
if (!branchPermission) {
|
|
378
|
-
throw new UnauthorizedException('No access to this branch');
|
|
379
|
-
}
|
|
380
|
-
const branch = await branchRepository.findOne({
|
|
381
|
-
where: {
|
|
382
|
-
id: branchId,
|
|
383
|
-
companyId,
|
|
384
|
-
isActive: true
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
if (!branch) {
|
|
388
|
-
throw new UnauthorizedException('Branch not found or inactive');
|
|
389
|
-
}
|
|
390
|
-
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
367
|
+
return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
|
|
391
368
|
}
|
|
392
|
-
|
|
369
|
+
async switchCompany(userId, companyId, branchId) {
|
|
393
370
|
this.ensureCompanyFeatureEnabled();
|
|
371
|
+
return this.validateAndLoginWithCompany(userId, companyId, branchId);
|
|
372
|
+
}
|
|
373
|
+
async validateAndLoginWithCompany(userId, companyId, branchId) {
|
|
394
374
|
const userRepository = await this.getUserRepository();
|
|
395
375
|
const user = await userRepository.findOne({
|
|
396
376
|
where: {
|
|
@@ -407,14 +387,14 @@ export class AuthenticationService {
|
|
|
407
387
|
throw new BadRequestException('Repositories not available');
|
|
408
388
|
}
|
|
409
389
|
// Validate company access
|
|
410
|
-
const
|
|
390
|
+
const hasCompanyAccess = await permissionRepository.findOne({
|
|
411
391
|
where: {
|
|
412
392
|
userId,
|
|
413
393
|
permissionType: PermissionType.COMPANY,
|
|
414
394
|
targetId: companyId
|
|
415
395
|
}
|
|
416
396
|
});
|
|
417
|
-
if (!
|
|
397
|
+
if (!hasCompanyAccess) {
|
|
418
398
|
throw new UnauthorizedException('No access to this company');
|
|
419
399
|
}
|
|
420
400
|
const company = await companyRepository.findOne({
|
|
@@ -426,14 +406,15 @@ export class AuthenticationService {
|
|
|
426
406
|
if (!company) {
|
|
427
407
|
throw new UnauthorizedException('Company not found or inactive');
|
|
428
408
|
}
|
|
429
|
-
|
|
409
|
+
// Validate branch access
|
|
410
|
+
const hasBranchAccess = await permissionRepository.findOne({
|
|
430
411
|
where: {
|
|
431
412
|
userId,
|
|
432
413
|
permissionType: PermissionType.BRANCH,
|
|
433
414
|
targetId: branchId
|
|
434
415
|
}
|
|
435
416
|
});
|
|
436
|
-
if (!
|
|
417
|
+
if (!hasBranchAccess) {
|
|
437
418
|
throw new UnauthorizedException('No access to this branch');
|
|
438
419
|
}
|
|
439
420
|
const branch = await branchRepository.findOne({
|
|
@@ -449,11 +430,14 @@ export class AuthenticationService {
|
|
|
449
430
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
450
431
|
}
|
|
451
432
|
// Token Management
|
|
452
|
-
|
|
433
|
+
async refreshToken(refreshToken) {
|
|
453
434
|
try {
|
|
454
435
|
const payload = this.jwtService.verify(refreshToken, {
|
|
455
436
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
456
437
|
});
|
|
438
|
+
if (await this.isTokenRevoked(payload.id, payload.iat)) {
|
|
439
|
+
throw new UnauthorizedException('Token has been revoked');
|
|
440
|
+
}
|
|
457
441
|
const userRepository = await this.getUserRepository();
|
|
458
442
|
const user = await userRepository.findOne({
|
|
459
443
|
where: {
|
|
@@ -463,7 +447,7 @@ export class AuthenticationService {
|
|
|
463
447
|
if (!user || !user.isActive) {
|
|
464
448
|
throw new UnauthorizedException('Invalid refresh token');
|
|
465
449
|
}
|
|
466
|
-
|
|
450
|
+
return this.generateTokens({
|
|
467
451
|
id: user.id,
|
|
468
452
|
email: user.email,
|
|
469
453
|
type: 'access',
|
|
@@ -473,69 +457,32 @@ export class AuthenticationService {
|
|
|
473
457
|
},
|
|
474
458
|
...payload.branchId && {
|
|
475
459
|
branchId: payload.branchId
|
|
476
|
-
},
|
|
477
|
-
...payload.companyLogoId && {
|
|
478
|
-
companyLogoId: payload.companyLogoId
|
|
479
|
-
},
|
|
480
|
-
...payload.branchLogoId && {
|
|
481
|
-
branchLogoId: payload.branchLogoId
|
|
482
460
|
}
|
|
483
|
-
};
|
|
484
|
-
const refreshPayload = {
|
|
485
|
-
id: user.id,
|
|
486
|
-
email: user.email,
|
|
487
|
-
type: 'refresh',
|
|
488
|
-
profilePictureId: user.profilePictureId ?? undefined,
|
|
489
|
-
...payload.companyId && {
|
|
490
|
-
companyId: payload.companyId
|
|
491
|
-
},
|
|
492
|
-
...payload.branchId && {
|
|
493
|
-
branchId: payload.branchId
|
|
494
|
-
},
|
|
495
|
-
...payload.companyLogoId && {
|
|
496
|
-
companyLogoId: payload.companyLogoId
|
|
497
|
-
},
|
|
498
|
-
...payload.branchLogoId && {
|
|
499
|
-
branchLogoId: payload.branchLogoId
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
return {
|
|
503
|
-
accessToken: this.jwtService.sign(accessPayload, {
|
|
504
|
-
secret: this.authConfig.getJwtSecret(),
|
|
505
|
-
expiresIn: this.authConfig.getJwtExpiration()
|
|
506
|
-
}),
|
|
507
|
-
refreshToken: this.jwtService.sign(refreshPayload, {
|
|
508
|
-
secret: this.authConfig.getRefreshTokenSecret(),
|
|
509
|
-
expiresIn: this.authConfig.getRefreshTokenExpiration()
|
|
510
|
-
})
|
|
511
|
-
};
|
|
461
|
+
});
|
|
512
462
|
} catch (error) {
|
|
513
|
-
// Log error details for debugging (not exposed to client)
|
|
514
463
|
ErrorHandler.logError(this.logger, error, 'refreshToken');
|
|
515
464
|
throw new UnauthorizedException('Invalid or expired refresh token');
|
|
516
465
|
}
|
|
517
466
|
}
|
|
518
|
-
|
|
467
|
+
async changePassword(userId, currentPassword, newPassword) {
|
|
519
468
|
const userRepository = await this.getUserRepository();
|
|
520
469
|
const user = await userRepository.findOne({
|
|
521
470
|
where: {
|
|
522
471
|
id: userId
|
|
523
472
|
}
|
|
524
473
|
});
|
|
525
|
-
if (!user)
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
const isPasswordValid = await bcrypt.compare(currentPassword, user.password || '');
|
|
529
|
-
if (!isPasswordValid) {
|
|
474
|
+
if (!user) throw new UnauthorizedException('User not found');
|
|
475
|
+
if (!await bcrypt.compare(currentPassword, user.password || '')) {
|
|
530
476
|
throw new UnauthorizedException('Current password is incorrect');
|
|
531
477
|
}
|
|
532
478
|
user.password = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
|
|
533
479
|
await userRepository.save(user);
|
|
534
480
|
}
|
|
535
|
-
|
|
481
|
+
async logout(userId) {
|
|
536
482
|
if (!this.cacheManager) {
|
|
537
|
-
// Cache
|
|
538
|
-
|
|
483
|
+
// Cache is required for secure logout - tokens cannot be revoked without it
|
|
484
|
+
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.');
|
|
485
|
+
throw new BadRequestException('Logout service temporarily unavailable. Please try again later.');
|
|
539
486
|
}
|
|
540
487
|
const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
|
|
541
488
|
const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
@@ -543,8 +490,9 @@ export class AuthenticationService {
|
|
|
543
490
|
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
544
491
|
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
545
492
|
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
493
|
+
this.logger.log(`User ${userId} logged out - all tokens revoked`);
|
|
546
494
|
}
|
|
547
|
-
|
|
495
|
+
async isTokenRevoked(userId, tokenIssuedAt) {
|
|
548
496
|
if (!this.cacheManager || !tokenIssuedAt) {
|
|
549
497
|
return false;
|
|
550
498
|
}
|
|
@@ -556,7 +504,7 @@ export class AuthenticationService {
|
|
|
556
504
|
// Token is revoked if it was issued before the revocation timestamp
|
|
557
505
|
return tokenIssuedAt < revokedAt;
|
|
558
506
|
}
|
|
559
|
-
|
|
507
|
+
parseExpiryToMs(expiry) {
|
|
560
508
|
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
561
509
|
if (!match) return 7 * 24 * 60 * 60 * 1000; // Default 7 days
|
|
562
510
|
const value = parseInt(match[1], 10);
|
|
@@ -575,7 +523,7 @@ export class AuthenticationService {
|
|
|
575
523
|
}
|
|
576
524
|
}
|
|
577
525
|
// Query Methods
|
|
578
|
-
|
|
526
|
+
async getUserCompanies(userId) {
|
|
579
527
|
this.ensureCompanyFeatureEnabled();
|
|
580
528
|
const permissionRepository = await this.getPermissionRepository();
|
|
581
529
|
const companyRepository = await this.getCompanyRepository();
|
|
@@ -596,7 +544,7 @@ export class AuthenticationService {
|
|
|
596
544
|
active: true
|
|
597
545
|
}).orderBy('c.name', 'ASC').getMany();
|
|
598
546
|
}
|
|
599
|
-
|
|
547
|
+
async getUserBranches(userId, companyId) {
|
|
600
548
|
this.ensureCompanyFeatureEnabled();
|
|
601
549
|
const permissionRepository = await this.getPermissionRepository();
|
|
602
550
|
const branchRepository = await this.getBranchRepository();
|
|
@@ -631,7 +579,7 @@ export class AuthenticationService {
|
|
|
631
579
|
active: true
|
|
632
580
|
}).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
|
|
633
581
|
}
|
|
634
|
-
|
|
582
|
+
async getCompanyById(companyId) {
|
|
635
583
|
const companyRepository = await this.getCompanyRepository();
|
|
636
584
|
if (!companyRepository) {
|
|
637
585
|
return null;
|
|
@@ -642,7 +590,7 @@ export class AuthenticationService {
|
|
|
642
590
|
}
|
|
643
591
|
});
|
|
644
592
|
}
|
|
645
|
-
|
|
593
|
+
async getBranchById(branchId) {
|
|
646
594
|
const branchRepository = await this.getBranchRepository();
|
|
647
595
|
if (!branchRepository) {
|
|
648
596
|
return null;
|
|
@@ -653,61 +601,35 @@ export class AuthenticationService {
|
|
|
653
601
|
}
|
|
654
602
|
});
|
|
655
603
|
}
|
|
656
|
-
|
|
604
|
+
async getUserInfo(userId, companyId, branchId) {
|
|
657
605
|
const userRepository = await this.getUserRepository();
|
|
658
606
|
const user = await userRepository.findOne({
|
|
659
607
|
where: {
|
|
660
608
|
id: userId
|
|
661
609
|
}
|
|
662
610
|
});
|
|
663
|
-
if (!user)
|
|
664
|
-
throw new UnauthorizedException('User not found');
|
|
665
|
-
}
|
|
666
|
-
// Build response (no tokens)
|
|
611
|
+
if (!user) throw new UnauthorizedException('User not found');
|
|
667
612
|
const response = {
|
|
668
|
-
user:
|
|
669
|
-
id: user.id,
|
|
670
|
-
email: user.email,
|
|
671
|
-
name: user.name ?? undefined,
|
|
672
|
-
phone: user.phone ?? undefined,
|
|
673
|
-
profilePictureId: user.profilePictureId ?? undefined
|
|
674
|
-
}
|
|
613
|
+
user: this.toUserInfo(user)
|
|
675
614
|
};
|
|
676
|
-
|
|
677
|
-
if (this.authConfig.isCompanyFeatureEnabled()) {
|
|
678
|
-
// When company feature is enabled, companyId MUST be present
|
|
615
|
+
if (this.isCompanyFeatureEnabled()) {
|
|
679
616
|
if (!companyId) {
|
|
680
|
-
throw new UnauthorizedException('Company feature is enabled but user has no company assigned.
|
|
617
|
+
throw new UnauthorizedException('Company feature is enabled but user has no company assigned.');
|
|
681
618
|
}
|
|
682
619
|
const company = await this.getCompanyById(companyId);
|
|
683
|
-
if (!company)
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
response.company = {
|
|
687
|
-
id: company.id,
|
|
688
|
-
name: company.name,
|
|
689
|
-
slug: company.slug,
|
|
690
|
-
logoId: company.logoId ?? null
|
|
691
|
-
};
|
|
692
|
-
// Branch is optional in some configurations, but if provided it must exist
|
|
620
|
+
if (!company) throw new UnauthorizedException('Company not found.');
|
|
621
|
+
response.company = this.toCompanyInfo(company);
|
|
693
622
|
if (branchId) {
|
|
694
623
|
const branch = await this.getBranchById(branchId);
|
|
695
|
-
if (!branch)
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
response.branch = {
|
|
699
|
-
id: branch.id,
|
|
700
|
-
name: branch.name,
|
|
701
|
-
slug: branch.slug,
|
|
702
|
-
logoId: branch.logoId ?? null
|
|
703
|
-
};
|
|
624
|
+
if (!branch) throw new UnauthorizedException('Branch not found.');
|
|
625
|
+
response.branch = this.toBranchInfo(branch);
|
|
704
626
|
}
|
|
705
627
|
}
|
|
706
628
|
return response;
|
|
707
629
|
}
|
|
708
630
|
// Helper Methods
|
|
709
631
|
ensureCompanyFeatureEnabled() {
|
|
710
|
-
if (!this.
|
|
632
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
711
633
|
throw new BadRequestException('Company feature is not enabled');
|
|
712
634
|
}
|
|
713
635
|
}
|
|
@@ -718,11 +640,7 @@ export class AuthenticationService {
|
|
|
718
640
|
email
|
|
719
641
|
}
|
|
720
642
|
});
|
|
721
|
-
if (!user) {
|
|
722
|
-
throw new UnauthorizedException('Invalid credentials');
|
|
723
|
-
}
|
|
724
|
-
const isPasswordValid = await bcrypt.compare(password, user.password || '');
|
|
725
|
-
if (!isPasswordValid) {
|
|
643
|
+
if (!user || !await bcrypt.compare(password, user.password || '')) {
|
|
726
644
|
throw new UnauthorizedException('Invalid credentials');
|
|
727
645
|
}
|
|
728
646
|
user.lastLoginAt = new Date();
|
|
@@ -730,84 +648,77 @@ export class AuthenticationService {
|
|
|
730
648
|
return user;
|
|
731
649
|
}
|
|
732
650
|
// Response Generators
|
|
733
|
-
|
|
734
|
-
|
|
651
|
+
toUserInfo(user) {
|
|
652
|
+
return {
|
|
735
653
|
id: user.id,
|
|
736
654
|
email: user.email,
|
|
737
|
-
|
|
655
|
+
name: user.name ?? undefined,
|
|
656
|
+
phone: user.phone ?? null,
|
|
657
|
+
profilePictureId: user.profilePictureId ?? null
|
|
738
658
|
};
|
|
739
|
-
|
|
659
|
+
}
|
|
660
|
+
toCompanyInfo(company) {
|
|
661
|
+
return {
|
|
662
|
+
id: company.id,
|
|
663
|
+
name: company.name,
|
|
664
|
+
slug: company.slug,
|
|
665
|
+
logoId: company.logoId ?? null
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
toBranchInfo(branch) {
|
|
669
|
+
return {
|
|
670
|
+
id: branch.id,
|
|
671
|
+
name: branch.name,
|
|
672
|
+
slug: branch.slug,
|
|
673
|
+
logoId: branch.logoId ?? null
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
generateTokens(payload) {
|
|
677
|
+
const accessToken = this.jwtService.sign({
|
|
678
|
+
...payload,
|
|
679
|
+
type: 'access'
|
|
680
|
+
}, {
|
|
740
681
|
secret: this.authConfig.getJwtSecret(),
|
|
741
682
|
expiresIn: this.authConfig.getJwtExpiration()
|
|
742
683
|
});
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
email: user.email,
|
|
684
|
+
const refreshToken = this.jwtService.sign({
|
|
685
|
+
...payload,
|
|
746
686
|
type: 'refresh'
|
|
747
|
-
}
|
|
748
|
-
const refreshToken = this.jwtService.sign(refreshPayload, {
|
|
687
|
+
}, {
|
|
749
688
|
secret: this.authConfig.getRefreshTokenSecret(),
|
|
750
689
|
expiresIn: this.authConfig.getRefreshTokenExpiration()
|
|
751
690
|
});
|
|
752
691
|
return {
|
|
753
692
|
accessToken,
|
|
754
|
-
refreshToken
|
|
755
|
-
user: {
|
|
756
|
-
id: user.id,
|
|
757
|
-
email: user.email,
|
|
758
|
-
name: user.name ?? undefined,
|
|
759
|
-
phone: user.phone ?? null,
|
|
760
|
-
profilePictureId: user.profilePictureId ?? null
|
|
761
|
-
},
|
|
762
|
-
companyFeatureEnabled
|
|
693
|
+
refreshToken
|
|
763
694
|
};
|
|
764
695
|
}
|
|
765
|
-
|
|
766
|
-
const
|
|
696
|
+
generateLoginResponse(user) {
|
|
697
|
+
const { accessToken, refreshToken } = this.generateTokens({
|
|
767
698
|
id: user.id,
|
|
768
699
|
email: user.email,
|
|
769
|
-
type: 'access'
|
|
770
|
-
companyId: company.id,
|
|
771
|
-
branchId: branch?.id
|
|
772
|
-
};
|
|
773
|
-
const accessToken = this.jwtService.sign(payload, {
|
|
774
|
-
secret: this.authConfig.getJwtSecret(),
|
|
775
|
-
expiresIn: this.authConfig.getJwtExpiration()
|
|
700
|
+
type: 'access'
|
|
776
701
|
});
|
|
777
|
-
|
|
702
|
+
return {
|
|
703
|
+
accessToken,
|
|
704
|
+
refreshToken,
|
|
705
|
+
user: this.toUserInfo(user)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
generateLoginResponseWithCompany(user, company, branch) {
|
|
709
|
+
const { accessToken, refreshToken } = this.generateTokens({
|
|
778
710
|
id: user.id,
|
|
779
711
|
email: user.email,
|
|
780
|
-
type: '
|
|
712
|
+
type: 'access',
|
|
781
713
|
companyId: company.id,
|
|
782
714
|
branchId: branch?.id
|
|
783
|
-
};
|
|
784
|
-
const refreshToken = this.jwtService.sign(refreshPayload, {
|
|
785
|
-
secret: this.authConfig.getRefreshTokenSecret(),
|
|
786
|
-
expiresIn: this.authConfig.getRefreshTokenExpiration()
|
|
787
715
|
});
|
|
788
716
|
return {
|
|
789
717
|
accessToken,
|
|
790
718
|
refreshToken,
|
|
791
|
-
user:
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
name: user.name ?? undefined,
|
|
795
|
-
phone: user.phone ?? null,
|
|
796
|
-
profilePictureId: user.profilePictureId ?? null
|
|
797
|
-
},
|
|
798
|
-
company: {
|
|
799
|
-
id: company.id,
|
|
800
|
-
name: company.name,
|
|
801
|
-
slug: company.slug,
|
|
802
|
-
logoId: company.logoId ?? null
|
|
803
|
-
},
|
|
804
|
-
branch: branch ? {
|
|
805
|
-
id: branch.id,
|
|
806
|
-
name: branch.name,
|
|
807
|
-
slug: branch.slug,
|
|
808
|
-
logoId: branch.logoId ?? null
|
|
809
|
-
} : undefined,
|
|
810
|
-
companyFeatureEnabled: true
|
|
719
|
+
user: this.toUserInfo(user),
|
|
720
|
+
company: this.toCompanyInfo(company),
|
|
721
|
+
branch: branch ? this.toBranchInfo(branch) : undefined
|
|
811
722
|
};
|
|
812
723
|
}
|
|
813
724
|
async generateCompanySelectionResponse(user, companies, branches) {
|
|
@@ -815,103 +726,42 @@ export class AuthenticationService {
|
|
|
815
726
|
throw new BadRequestException('Session service not available');
|
|
816
727
|
}
|
|
817
728
|
const sessionId = await this.sessionService.createSession(user.id);
|
|
818
|
-
// Build companies with their branches
|
|
819
729
|
const companiesWithBranches = companies.map((company)=>({
|
|
820
|
-
|
|
821
|
-
name: company.name,
|
|
822
|
-
slug: company.slug,
|
|
730
|
+
...this.toCompanyInfo(company),
|
|
823
731
|
isActive: company.isActive,
|
|
824
|
-
logoId: company.logoId ?? null,
|
|
825
732
|
branches: branches.filter((b)=>b.companyId === company.id).map((b)=>({
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
slug: b.slug,
|
|
829
|
-
isActive: b.isActive,
|
|
830
|
-
logoId: b.logoId ?? null
|
|
733
|
+
...this.toBranchInfo(b),
|
|
734
|
+
isActive: b.isActive
|
|
831
735
|
}))
|
|
832
736
|
}));
|
|
833
737
|
return {
|
|
834
738
|
requiresSelection: true,
|
|
835
739
|
sessionId,
|
|
836
740
|
expiresAt: new Date(Date.now() + 5 * 60 * 1000),
|
|
837
|
-
user:
|
|
838
|
-
id: user.id,
|
|
839
|
-
email: user.email,
|
|
840
|
-
name: user.name ?? undefined,
|
|
841
|
-
phone: user.phone ?? null,
|
|
842
|
-
profilePictureId: user.profilePictureId ?? null
|
|
843
|
-
},
|
|
741
|
+
user: this.toUserInfo(user),
|
|
844
742
|
companies: companiesWithBranches
|
|
845
743
|
};
|
|
846
744
|
}
|
|
847
|
-
|
|
848
|
-
const userInfo = {
|
|
849
|
-
id: user.id,
|
|
850
|
-
email: user.email,
|
|
851
|
-
name: user.name ?? undefined,
|
|
852
|
-
phone: user.phone ?? null,
|
|
853
|
-
profilePictureId: user.profilePictureId ?? null
|
|
854
|
-
};
|
|
855
|
-
if (requiresEmailVerification) {
|
|
856
|
-
return {
|
|
857
|
-
user: userInfo,
|
|
858
|
-
companyFeatureEnabled,
|
|
859
|
-
requiresEmailVerification: true
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
|
|
745
|
+
generateRegistrationResponse(user) {
|
|
863
746
|
return {
|
|
864
|
-
...
|
|
747
|
+
...this.generateLoginResponse(user),
|
|
865
748
|
requiresEmailVerification: false
|
|
866
749
|
};
|
|
867
750
|
}
|
|
868
|
-
|
|
869
|
-
const userInfo = {
|
|
870
|
-
id: user.id,
|
|
871
|
-
email: user.email,
|
|
872
|
-
name: user.name ?? undefined,
|
|
873
|
-
phone: user.phone ?? null,
|
|
874
|
-
profilePictureId: user.profilePictureId ?? null
|
|
875
|
-
};
|
|
876
|
-
const companyInfo = {
|
|
877
|
-
id: company.id,
|
|
878
|
-
name: company.name,
|
|
879
|
-
slug: company.slug,
|
|
880
|
-
logoId: company.logoId ?? null
|
|
881
|
-
};
|
|
882
|
-
const branchInfo = branch ? {
|
|
883
|
-
id: branch.id,
|
|
884
|
-
name: branch.name,
|
|
885
|
-
slug: branch.slug,
|
|
886
|
-
logoId: branch.logoId ?? null
|
|
887
|
-
} : undefined;
|
|
888
|
-
if (requiresEmailVerification) {
|
|
889
|
-
return {
|
|
890
|
-
user: userInfo,
|
|
891
|
-
company: companyInfo,
|
|
892
|
-
branch: branchInfo,
|
|
893
|
-
companyFeatureEnabled: true,
|
|
894
|
-
requiresEmailVerification: true
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
|
|
751
|
+
generateRegistrationResponseWithCompany(user, company, branch) {
|
|
898
752
|
return {
|
|
899
|
-
...
|
|
753
|
+
...this.generateLoginResponseWithCompany(user, company, branch),
|
|
900
754
|
requiresEmailVerification: false
|
|
901
755
|
};
|
|
902
756
|
}
|
|
903
757
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
904
|
-
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService
|
|
758
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
|
|
905
759
|
_define_property(this, "jwtService", void 0);
|
|
906
760
|
_define_property(this, "authConfig", void 0);
|
|
907
761
|
_define_property(this, "cacheManager", void 0);
|
|
908
762
|
_define_property(this, "dataSourceProvider", void 0);
|
|
909
763
|
_define_property(this, "sessionService", void 0);
|
|
910
764
|
_define_property(this, "authEmailService", void 0);
|
|
911
|
-
_define_property(this, "injectedUserRepository", void 0);
|
|
912
|
-
_define_property(this, "injectedPermissionRepository", void 0);
|
|
913
|
-
_define_property(this, "injectedCompanyRepository", void 0);
|
|
914
|
-
_define_property(this, "injectedBranchRepository", void 0);
|
|
915
765
|
_define_property(this, "logger", void 0);
|
|
916
766
|
this.jwtService = jwtService;
|
|
917
767
|
this.authConfig = authConfig;
|
|
@@ -919,10 +769,6 @@ export class AuthenticationService {
|
|
|
919
769
|
this.dataSourceProvider = dataSourceProvider;
|
|
920
770
|
this.sessionService = sessionService;
|
|
921
771
|
this.authEmailService = authEmailService;
|
|
922
|
-
this.injectedUserRepository = injectedUserRepository;
|
|
923
|
-
this.injectedPermissionRepository = injectedPermissionRepository;
|
|
924
|
-
this.injectedCompanyRepository = injectedCompanyRepository;
|
|
925
|
-
this.injectedBranchRepository = injectedBranchRepository;
|
|
926
772
|
this.logger = new Logger(AuthenticationService.name);
|
|
927
773
|
}
|
|
928
774
|
}
|
|
@@ -940,14 +786,6 @@ AuthenticationService = _ts_decorate([
|
|
|
940
786
|
_ts_param(4, Inject(CompanySelectionSessionService)),
|
|
941
787
|
_ts_param(5, Optional()),
|
|
942
788
|
_ts_param(5, Inject(AuthEmailService)),
|
|
943
|
-
_ts_param(6, Optional()),
|
|
944
|
-
_ts_param(6, InjectRepository(User)),
|
|
945
|
-
_ts_param(7, Optional()),
|
|
946
|
-
_ts_param(7, InjectRepository(UserCompanyPermission)),
|
|
947
|
-
_ts_param(8, Optional()),
|
|
948
|
-
_ts_param(8, InjectRepository(Company)),
|
|
949
|
-
_ts_param(9, Optional()),
|
|
950
|
-
_ts_param(9, InjectRepository(CompanyBranch)),
|
|
951
789
|
_ts_metadata("design:type", Function),
|
|
952
790
|
_ts_metadata("design:paramtypes", [
|
|
953
791
|
typeof JwtService === "undefined" ? Object : JwtService,
|
|
@@ -955,10 +793,6 @@ AuthenticationService = _ts_decorate([
|
|
|
955
793
|
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
956
794
|
typeof AuthDataSourceProvider === "undefined" ? Object : AuthDataSourceProvider,
|
|
957
795
|
typeof CompanySelectionSessionService === "undefined" ? Object : CompanySelectionSessionService,
|
|
958
|
-
typeof AuthEmailService === "undefined" ? Object : AuthEmailService
|
|
959
|
-
typeof Repository === "undefined" ? Object : Repository,
|
|
960
|
-
typeof Repository === "undefined" ? Object : Repository,
|
|
961
|
-
typeof Repository === "undefined" ? Object : Repository,
|
|
962
|
-
typeof Repository === "undefined" ? Object : Repository
|
|
796
|
+
typeof AuthEmailService === "undefined" ? Object : AuthEmailService
|
|
963
797
|
])
|
|
964
798
|
], AuthenticationService);
|