@flusys/nestjs-auth 1.0.0-rc → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +530 -396
- package/cjs/controllers/authentication.controller.js +160 -28
- package/cjs/controllers/user-permission.controller.js +49 -136
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +0 -30
- package/cjs/dtos/index.js +0 -1
- package/cjs/interceptors/clear-token.interceptor.js +8 -18
- package/cjs/interceptors/set-token.interceptor.js +8 -55
- package/cjs/interfaces/auth-email-provider.interface.js +1 -1
- package/cjs/modules/auth.module.js +63 -129
- package/cjs/services/auth-config.service.js +6 -29
- package/cjs/services/auth-datasource.provider.js +8 -20
- package/cjs/services/auth-email.service.js +72 -87
- package/cjs/services/authentication.service.js +211 -355
- package/cjs/services/branch.service.js +9 -5
- package/cjs/services/user-permission.service.js +43 -71
- package/cjs/services/user.service.js +18 -47
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/user-permission.controller.d.ts +5 -4
- package/dtos/authentication.dto.d.ts +0 -4
- package/dtos/index.d.ts +0 -1
- package/entities/index.d.ts +3 -3
- package/fesm/controllers/authentication.controller.js +162 -30
- package/fesm/controllers/user-permission.controller.js +50 -137
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +0 -30
- package/fesm/dtos/index.js +0 -1
- package/fesm/interceptors/clear-token.interceptor.js +8 -18
- package/fesm/interceptors/set-token.interceptor.js +8 -55
- package/fesm/interfaces/auth-email-provider.interface.js +1 -1
- package/fesm/modules/auth.module.js +63 -129
- package/fesm/services/auth-config.service.js +6 -29
- package/fesm/services/auth-datasource.provider.js +8 -20
- package/fesm/services/auth-email.service.js +72 -87
- package/fesm/services/authentication.service.js +211 -355
- package/fesm/services/branch.service.js +9 -5
- package/fesm/services/user-permission.service.js +44 -72
- package/fesm/services/user.service.js +18 -47
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -2
- package/interfaces/authentication.interface.d.ts +0 -4
- package/modules/auth.module.d.ts +6 -2
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +2 -7
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +7 -4
- package/services/authentication.service.d.ts +14 -8
- package/services/user-permission.service.d.ts +6 -8
- package/services/user.service.d.ts +1 -1
- package/strategies/jwt.strategy.d.ts +4 -1
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/cjs/guards/auth-throttler.guard.js +0 -53
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
- package/fesm/guards/auth-throttler.guard.js +0 -43
- package/guards/auth-throttler.guard.d.ts +0 -7
|
@@ -8,20 +8,20 @@ Object.defineProperty(exports, "AuthenticationService", {
|
|
|
8
8
|
return AuthenticationService;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
+
const _utils = require("@flusys/nestjs-shared/utils");
|
|
11
13
|
const _common = require("@nestjs/common");
|
|
12
14
|
const _jwt = require("@nestjs/jwt");
|
|
13
15
|
const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
|
|
14
|
-
const _utils = require("@flusys/nestjs-shared/utils");
|
|
15
|
-
const _authconfigservice = require("./auth-config.service");
|
|
16
|
-
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
17
|
-
const _enums = require("../enums");
|
|
18
16
|
const _config = require("../config");
|
|
19
|
-
const _classes = require("@flusys/nestjs-shared/classes");
|
|
20
|
-
const _authemailservice = require("./auth-email.service");
|
|
21
17
|
const _companybranchentity = require("../entities/company-branch.entity");
|
|
22
18
|
const _companyentity = require("../entities/company.entity");
|
|
23
19
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
24
20
|
const _userentity = require("../entities/user.entity");
|
|
21
|
+
const _enums = require("../enums");
|
|
22
|
+
const _authconfigservice = require("./auth-config.service");
|
|
23
|
+
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
24
|
+
const _authemailservice = require("./auth-email.service");
|
|
25
25
|
const _companyselectionsessionservice = require("./company-selection-session.service");
|
|
26
26
|
function _define_property(obj, key, value) {
|
|
27
27
|
if (key in obj) {
|
|
@@ -92,6 +92,10 @@ function _ts_param(paramIndex, decorator) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
let AuthenticationService = class AuthenticationService {
|
|
95
|
+
// Feature Flags
|
|
96
|
+
/** Check if company feature is enabled (tenant-aware with global fallback) */ isCompanyFeatureEnabled() {
|
|
97
|
+
return this.dataSourceProvider?.isCompanyFeatureEnabled() ?? this.authConfig.isCompanyFeatureEnabled();
|
|
98
|
+
}
|
|
95
99
|
// Repository Getters
|
|
96
100
|
async getUserRepository() {
|
|
97
101
|
if (!this.dataSourceProvider) {
|
|
@@ -100,24 +104,31 @@ let AuthenticationService = class AuthenticationService {
|
|
|
100
104
|
return this.dataSourceProvider.getRepository(_userentity.User);
|
|
101
105
|
}
|
|
102
106
|
async getPermissionRepository() {
|
|
103
|
-
if (!this.
|
|
107
|
+
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
104
108
|
return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
|
|
105
109
|
}
|
|
106
110
|
async getCompanyRepository() {
|
|
107
|
-
if (!this.
|
|
111
|
+
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
108
112
|
return this.dataSourceProvider.getRepository(_companyentity.Company);
|
|
109
113
|
}
|
|
110
114
|
async getBranchRepository() {
|
|
111
|
-
if (!this.
|
|
115
|
+
if (!this.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
112
116
|
return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
|
|
113
117
|
}
|
|
114
118
|
// Login
|
|
115
|
-
|
|
119
|
+
async login(email, password) {
|
|
120
|
+
// Check for pending registration first (user registered but hasn't verified email yet)
|
|
121
|
+
if (await this.authEmailService?.hasPendingRegistration(email)) {
|
|
122
|
+
return {
|
|
123
|
+
requiresEmailVerification: true,
|
|
124
|
+
email,
|
|
125
|
+
message: 'Please verify your email address to complete registration.'
|
|
126
|
+
};
|
|
127
|
+
}
|
|
116
128
|
const user = await this.validateUser(email, password);
|
|
117
129
|
if (!user.isActive) {
|
|
118
130
|
throw new _common.UnauthorizedException('User account is deactivated');
|
|
119
131
|
}
|
|
120
|
-
// Check email verification if email service is enabled
|
|
121
132
|
if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
|
|
122
133
|
return {
|
|
123
134
|
requiresEmailVerification: true,
|
|
@@ -125,7 +136,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
125
136
|
message: 'Please verify your email address before logging in.'
|
|
126
137
|
};
|
|
127
138
|
}
|
|
128
|
-
if (!this.
|
|
139
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
129
140
|
return this.generateLoginResponse(user);
|
|
130
141
|
}
|
|
131
142
|
return this.handleCompanyBasedLogin(user);
|
|
@@ -157,7 +168,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
157
168
|
// If user has no company assignments yet, return simple login response
|
|
158
169
|
// This allows users to login and be assigned to companies later
|
|
159
170
|
if (companies.length === 0) {
|
|
160
|
-
return this.generateLoginResponse(user
|
|
171
|
+
return this.generateLoginResponse(user);
|
|
161
172
|
}
|
|
162
173
|
// Get branch permissions
|
|
163
174
|
const branchPermissions = await permissionRepository.find({
|
|
@@ -188,12 +199,17 @@ let AuthenticationService = class AuthenticationService {
|
|
|
188
199
|
email: dto.email
|
|
189
200
|
}
|
|
190
201
|
});
|
|
202
|
+
// When company feature is enabled and user exists, allow joining/creating company
|
|
203
|
+
if (existingUser && this.isCompanyFeatureEnabled()) {
|
|
204
|
+
return this.handleExistingUserCompanyRegistration(existingUser, dto);
|
|
205
|
+
}
|
|
206
|
+
// User exists but company feature is off - email must be unique
|
|
191
207
|
if (existingUser) {
|
|
192
|
-
throw new _common.BadRequestException('Registration failed. Please try again or contact support.');
|
|
208
|
+
throw new _common.BadRequestException('Registration failed due to email exists. Please try again or contact support.');
|
|
193
209
|
}
|
|
194
210
|
// If email verification is enabled, create pending registration instead of user
|
|
195
211
|
if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
|
|
196
|
-
const companyData = this.
|
|
212
|
+
const companyData = this.isCompanyFeatureEnabled() ? {
|
|
197
213
|
companySlug: dto.companySlug,
|
|
198
214
|
branchSlug: dto.branchSlug,
|
|
199
215
|
newCompanyName: dto.newCompanyName,
|
|
@@ -216,6 +232,38 @@ let AuthenticationService = class AuthenticationService {
|
|
|
216
232
|
// No email verification - create user directly
|
|
217
233
|
return this.createUserDirectly(dto);
|
|
218
234
|
}
|
|
235
|
+
/** Handle registration for existing user when company feature is enabled */ async handleExistingUserCompanyRegistration(existingUser, dto) {
|
|
236
|
+
if (!dto.companySlug && !dto.newCompanyName) {
|
|
237
|
+
throw new _common.BadRequestException('Email already registered. Please login or provide a company to join/create.');
|
|
238
|
+
}
|
|
239
|
+
const permissionRepository = await this.getPermissionRepository();
|
|
240
|
+
const companyRepository = await this.getCompanyRepository();
|
|
241
|
+
if (!permissionRepository || !companyRepository) {
|
|
242
|
+
throw new _common.BadRequestException('Company feature repositories not available');
|
|
243
|
+
}
|
|
244
|
+
// For existing users joining a company, check if already assigned
|
|
245
|
+
if (dto.companySlug) {
|
|
246
|
+
const company = await companyRepository.findOne({
|
|
247
|
+
where: {
|
|
248
|
+
slug: dto.companySlug,
|
|
249
|
+
isActive: true
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
if (company) {
|
|
253
|
+
const existingPermission = await permissionRepository.findOne({
|
|
254
|
+
where: {
|
|
255
|
+
userId: existingUser.id,
|
|
256
|
+
permissionType: _enums.PermissionType.COMPANY,
|
|
257
|
+
targetId: company.id
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
if (existingPermission) {
|
|
261
|
+
throw new _common.BadRequestException('You are already assigned to this company. Please login.');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return this.processCompanyRegistration(existingUser, dto, true);
|
|
266
|
+
}
|
|
219
267
|
/** Create user directly (no email verification) */ async createUserDirectly(dto) {
|
|
220
268
|
const userRepository = await this.getUserRepository();
|
|
221
269
|
const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
@@ -229,14 +277,13 @@ let AuthenticationService = class AuthenticationService {
|
|
|
229
277
|
phoneVerified: false
|
|
230
278
|
});
|
|
231
279
|
await userRepository.save(user);
|
|
232
|
-
if (!this.
|
|
233
|
-
return this.generateRegistrationResponse(user
|
|
280
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
281
|
+
return this.generateRegistrationResponse(user);
|
|
234
282
|
}
|
|
235
|
-
return this.
|
|
283
|
+
return this.processCompanyRegistration(user, dto);
|
|
236
284
|
}
|
|
237
285
|
/** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
|
|
238
286
|
const userRepository = await this.getUserRepository();
|
|
239
|
-
// Check if user already exists (race condition protection)
|
|
240
287
|
const existingUser = await userRepository.findOne({
|
|
241
288
|
where: {
|
|
242
289
|
email: pending.email
|
|
@@ -245,7 +292,6 @@ let AuthenticationService = class AuthenticationService {
|
|
|
245
292
|
if (existingUser) {
|
|
246
293
|
throw new _common.BadRequestException('This email has already been registered.');
|
|
247
294
|
}
|
|
248
|
-
// Create user with verified email
|
|
249
295
|
const user = userRepository.create({
|
|
250
296
|
name: pending.name,
|
|
251
297
|
email: pending.email,
|
|
@@ -256,107 +302,103 @@ let AuthenticationService = class AuthenticationService {
|
|
|
256
302
|
phoneVerified: false
|
|
257
303
|
});
|
|
258
304
|
await userRepository.save(user);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return this.handleCompanyRegistrationFromPending(user, pending.companyData);
|
|
305
|
+
if (this.isCompanyFeatureEnabled() && pending.companyData) {
|
|
306
|
+
return this.processCompanyRegistration(user, pending.companyData);
|
|
262
307
|
}
|
|
263
|
-
return this.generateRegistrationResponse(user
|
|
264
|
-
}
|
|
265
|
-
/** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
|
|
266
|
-
return this.processCompanyRegistration(user, companyData, false);
|
|
308
|
+
return this.generateRegistrationResponse(user);
|
|
267
309
|
}
|
|
268
|
-
/**
|
|
269
|
-
return this.processCompanyRegistration(user, dto, requiresEmailVerification);
|
|
270
|
-
}
|
|
271
|
-
/** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
|
|
310
|
+
/** Process company registration (join existing or create new) */ async processCompanyRegistration(user, data, throwOnUnavailable = false) {
|
|
272
311
|
const companyRepository = await this.getCompanyRepository();
|
|
273
312
|
const branchRepository = await this.getBranchRepository();
|
|
274
313
|
const permissionRepository = await this.getPermissionRepository();
|
|
275
314
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
276
|
-
|
|
315
|
+
if (throwOnUnavailable) throw new _common.BadRequestException('Company feature repositories not available');
|
|
316
|
+
return this.generateRegistrationResponse(user);
|
|
277
317
|
}
|
|
278
|
-
|
|
279
|
-
|
|
318
|
+
const { company, branch } = await this.findOrCreateCompanyAndBranch(data, companyRepository, branchRepository);
|
|
319
|
+
if (company) {
|
|
320
|
+
await this.assignUserPermissions(user.id, company.id, branch?.id, permissionRepository);
|
|
321
|
+
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined);
|
|
322
|
+
}
|
|
323
|
+
return this.generateRegistrationResponse(user);
|
|
324
|
+
}
|
|
325
|
+
/** Find existing company/branch or create new ones */ async findOrCreateCompanyAndBranch(data, companyRepo, branchRepo) {
|
|
280
326
|
if (data.companySlug) {
|
|
281
|
-
company = await
|
|
327
|
+
const company = await companyRepo.findOne({
|
|
282
328
|
where: {
|
|
283
329
|
slug: data.companySlug,
|
|
284
330
|
isActive: true
|
|
285
331
|
}
|
|
286
332
|
});
|
|
287
|
-
if (!company)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
slug: data.branchSlug,
|
|
294
|
-
companyId: company.id,
|
|
295
|
-
isActive: true
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
if (!branch) {
|
|
299
|
-
throw new _common.BadRequestException('Branch not found or inactive');
|
|
333
|
+
if (!company) throw new _common.BadRequestException('Company not found or inactive');
|
|
334
|
+
const branch = data.branchSlug ? await branchRepo.findOne({
|
|
335
|
+
where: {
|
|
336
|
+
slug: data.branchSlug,
|
|
337
|
+
companyId: company.id,
|
|
338
|
+
isActive: true
|
|
300
339
|
}
|
|
301
|
-
}
|
|
302
|
-
branch = await branchRepository.findOne({
|
|
303
|
-
where: {
|
|
304
|
-
companyId: company.id,
|
|
305
|
-
isActive: true
|
|
306
|
-
},
|
|
307
|
-
order: {
|
|
308
|
-
serial: 'ASC',
|
|
309
|
-
createdAt: 'ASC'
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
} else if (data.newCompanyName) {
|
|
314
|
-
const slug = this.generateSlug(data.newCompanyName);
|
|
315
|
-
const existingCompany = await companyRepository.findOne({
|
|
340
|
+
}) : await branchRepo.findOne({
|
|
316
341
|
where: {
|
|
317
|
-
|
|
342
|
+
companyId: company.id,
|
|
343
|
+
isActive: true
|
|
344
|
+
},
|
|
345
|
+
order: {
|
|
346
|
+
serial: 'ASC',
|
|
347
|
+
createdAt: 'ASC'
|
|
318
348
|
}
|
|
319
349
|
});
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
350
|
+
if (data.branchSlug && !branch) throw new _common.BadRequestException('Branch not found or inactive');
|
|
351
|
+
return {
|
|
352
|
+
company,
|
|
353
|
+
branch
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (data.newCompanyName) {
|
|
357
|
+
const slug = await (0, _utils.generateUniqueSlug)(data.newCompanyName, async (baseSlug)=>{
|
|
358
|
+
const rows = await companyRepo.createQueryBuilder('c').select('c.slug').where('c.slug = :base OR c.slug LIKE :pattern', {
|
|
359
|
+
base: baseSlug,
|
|
360
|
+
pattern: `${baseSlug}-%`
|
|
361
|
+
}).getMany();
|
|
362
|
+
return rows.map((r)=>r.slug);
|
|
363
|
+
});
|
|
364
|
+
const company = companyRepo.create({
|
|
324
365
|
name: data.newCompanyName,
|
|
325
366
|
slug,
|
|
326
367
|
phone: data.newCompanyPhone,
|
|
327
368
|
address: data.newCompanyAddress,
|
|
328
369
|
isActive: true
|
|
329
370
|
});
|
|
330
|
-
await
|
|
331
|
-
branch =
|
|
371
|
+
await companyRepo.save(company);
|
|
372
|
+
const branch = branchRepo.create({
|
|
332
373
|
name: 'Main Branch',
|
|
333
374
|
slug: 'main-branch',
|
|
334
375
|
companyId: company.id,
|
|
335
376
|
isActive: true
|
|
336
377
|
});
|
|
337
|
-
await
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
permissionType: _enums.PermissionType.COMPANY,
|
|
343
|
-
targetId: company.id
|
|
344
|
-
});
|
|
345
|
-
await permissionRepository.save(companyPermission);
|
|
346
|
-
if (branch) {
|
|
347
|
-
const branchPermission = permissionRepository.create({
|
|
348
|
-
userId: user.id,
|
|
349
|
-
permissionType: _enums.PermissionType.BRANCH,
|
|
350
|
-
targetId: branch.id
|
|
351
|
-
});
|
|
352
|
-
await permissionRepository.save(branchPermission);
|
|
353
|
-
}
|
|
354
|
-
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
|
|
378
|
+
await branchRepo.save(branch);
|
|
379
|
+
return {
|
|
380
|
+
company,
|
|
381
|
+
branch
|
|
382
|
+
};
|
|
355
383
|
}
|
|
356
|
-
return
|
|
384
|
+
return {
|
|
385
|
+
company: null,
|
|
386
|
+
branch: null
|
|
387
|
+
};
|
|
357
388
|
}
|
|
358
|
-
/**
|
|
359
|
-
|
|
389
|
+
/** Assign user to company and optionally branch */ async assignUserPermissions(userId, companyId, branchId, permissionRepo) {
|
|
390
|
+
await permissionRepo.save(permissionRepo.create({
|
|
391
|
+
userId,
|
|
392
|
+
permissionType: _enums.PermissionType.COMPANY,
|
|
393
|
+
targetId: companyId
|
|
394
|
+
}));
|
|
395
|
+
if (branchId) {
|
|
396
|
+
await permissionRepo.save(permissionRepo.create({
|
|
397
|
+
userId,
|
|
398
|
+
permissionType: _enums.PermissionType.BRANCH,
|
|
399
|
+
targetId: branchId
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
360
402
|
}
|
|
361
403
|
// Company/Branch Selection
|
|
362
404
|
/** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
|
|
@@ -368,66 +410,13 @@ let AuthenticationService = class AuthenticationService {
|
|
|
368
410
|
if (!session) {
|
|
369
411
|
throw new _common.UnauthorizedException('Invalid or expired session');
|
|
370
412
|
}
|
|
371
|
-
|
|
372
|
-
const user = await userRepository.findOne({
|
|
373
|
-
where: {
|
|
374
|
-
id: session.userId
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
if (!user) {
|
|
378
|
-
throw new _common.NotFoundException('User not found');
|
|
379
|
-
}
|
|
380
|
-
const permissionRepository = await this.getPermissionRepository();
|
|
381
|
-
const companyRepository = await this.getCompanyRepository();
|
|
382
|
-
const branchRepository = await this.getBranchRepository();
|
|
383
|
-
if (!permissionRepository || !companyRepository || !branchRepository) {
|
|
384
|
-
throw new _common.BadRequestException('Repositories not available');
|
|
385
|
-
}
|
|
386
|
-
// Validate company access
|
|
387
|
-
const companyPermission = await permissionRepository.findOne({
|
|
388
|
-
where: {
|
|
389
|
-
userId: user.id,
|
|
390
|
-
permissionType: _enums.PermissionType.COMPANY,
|
|
391
|
-
targetId: companyId
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
if (!companyPermission) {
|
|
395
|
-
throw new _common.UnauthorizedException('No access to this company');
|
|
396
|
-
}
|
|
397
|
-
const company = await companyRepository.findOne({
|
|
398
|
-
where: {
|
|
399
|
-
id: companyId,
|
|
400
|
-
isActive: true
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
if (!company) {
|
|
404
|
-
throw new _common.UnauthorizedException('Company not found or inactive');
|
|
405
|
-
}
|
|
406
|
-
// Validate branch access
|
|
407
|
-
const branchPermission = await permissionRepository.findOne({
|
|
408
|
-
where: {
|
|
409
|
-
userId: user.id,
|
|
410
|
-
permissionType: _enums.PermissionType.BRANCH,
|
|
411
|
-
targetId: branchId
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
if (!branchPermission) {
|
|
415
|
-
throw new _common.UnauthorizedException('No access to this branch');
|
|
416
|
-
}
|
|
417
|
-
const branch = await branchRepository.findOne({
|
|
418
|
-
where: {
|
|
419
|
-
id: branchId,
|
|
420
|
-
companyId,
|
|
421
|
-
isActive: true
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
if (!branch) {
|
|
425
|
-
throw new _common.UnauthorizedException('Branch not found or inactive');
|
|
426
|
-
}
|
|
427
|
-
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
413
|
+
return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
|
|
428
414
|
}
|
|
429
415
|
/** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
|
|
430
416
|
this.ensureCompanyFeatureEnabled();
|
|
417
|
+
return this.validateAndLoginWithCompany(userId, companyId, branchId);
|
|
418
|
+
}
|
|
419
|
+
/** Shared: Validate user access to company/branch and generate login response */ async validateAndLoginWithCompany(userId, companyId, branchId) {
|
|
431
420
|
const userRepository = await this.getUserRepository();
|
|
432
421
|
const user = await userRepository.findOne({
|
|
433
422
|
where: {
|
|
@@ -444,14 +433,14 @@ let AuthenticationService = class AuthenticationService {
|
|
|
444
433
|
throw new _common.BadRequestException('Repositories not available');
|
|
445
434
|
}
|
|
446
435
|
// Validate company access
|
|
447
|
-
const
|
|
436
|
+
const hasCompanyAccess = await permissionRepository.findOne({
|
|
448
437
|
where: {
|
|
449
438
|
userId,
|
|
450
439
|
permissionType: _enums.PermissionType.COMPANY,
|
|
451
440
|
targetId: companyId
|
|
452
441
|
}
|
|
453
442
|
});
|
|
454
|
-
if (!
|
|
443
|
+
if (!hasCompanyAccess) {
|
|
455
444
|
throw new _common.UnauthorizedException('No access to this company');
|
|
456
445
|
}
|
|
457
446
|
const company = await companyRepository.findOne({
|
|
@@ -463,14 +452,15 @@ let AuthenticationService = class AuthenticationService {
|
|
|
463
452
|
if (!company) {
|
|
464
453
|
throw new _common.UnauthorizedException('Company not found or inactive');
|
|
465
454
|
}
|
|
466
|
-
|
|
455
|
+
// Validate branch access
|
|
456
|
+
const hasBranchAccess = await permissionRepository.findOne({
|
|
467
457
|
where: {
|
|
468
458
|
userId,
|
|
469
459
|
permissionType: _enums.PermissionType.BRANCH,
|
|
470
460
|
targetId: branchId
|
|
471
461
|
}
|
|
472
462
|
});
|
|
473
|
-
if (!
|
|
463
|
+
if (!hasBranchAccess) {
|
|
474
464
|
throw new _common.UnauthorizedException('No access to this branch');
|
|
475
465
|
}
|
|
476
466
|
const branch = await branchRepository.findOne({
|
|
@@ -491,9 +481,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
491
481
|
const payload = this.jwtService.verify(refreshToken, {
|
|
492
482
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
493
483
|
});
|
|
494
|
-
|
|
495
|
-
const isRevoked = await this.isTokenRevoked(payload.id, payload.iat);
|
|
496
|
-
if (isRevoked) {
|
|
484
|
+
if (await this.isTokenRevoked(payload.id, payload.iat)) {
|
|
497
485
|
throw new _common.UnauthorizedException('Token has been revoked');
|
|
498
486
|
}
|
|
499
487
|
const userRepository = await this.getUserRepository();
|
|
@@ -505,7 +493,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
505
493
|
if (!user || !user.isActive) {
|
|
506
494
|
throw new _common.UnauthorizedException('Invalid refresh token');
|
|
507
495
|
}
|
|
508
|
-
|
|
496
|
+
return this.generateTokens({
|
|
509
497
|
id: user.id,
|
|
510
498
|
email: user.email,
|
|
511
499
|
type: 'access',
|
|
@@ -515,44 +503,9 @@ let AuthenticationService = class AuthenticationService {
|
|
|
515
503
|
},
|
|
516
504
|
...payload.branchId && {
|
|
517
505
|
branchId: payload.branchId
|
|
518
|
-
},
|
|
519
|
-
...payload.companyLogoId && {
|
|
520
|
-
companyLogoId: payload.companyLogoId
|
|
521
|
-
},
|
|
522
|
-
...payload.branchLogoId && {
|
|
523
|
-
branchLogoId: payload.branchLogoId
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
const refreshPayload = {
|
|
527
|
-
id: user.id,
|
|
528
|
-
email: user.email,
|
|
529
|
-
type: 'refresh',
|
|
530
|
-
profilePictureId: user.profilePictureId ?? undefined,
|
|
531
|
-
...payload.companyId && {
|
|
532
|
-
companyId: payload.companyId
|
|
533
|
-
},
|
|
534
|
-
...payload.branchId && {
|
|
535
|
-
branchId: payload.branchId
|
|
536
|
-
},
|
|
537
|
-
...payload.companyLogoId && {
|
|
538
|
-
companyLogoId: payload.companyLogoId
|
|
539
|
-
},
|
|
540
|
-
...payload.branchLogoId && {
|
|
541
|
-
branchLogoId: payload.branchLogoId
|
|
542
506
|
}
|
|
543
|
-
};
|
|
544
|
-
return {
|
|
545
|
-
accessToken: this.jwtService.sign(accessPayload, {
|
|
546
|
-
secret: this.authConfig.getJwtSecret(),
|
|
547
|
-
expiresIn: this.authConfig.getJwtExpiration()
|
|
548
|
-
}),
|
|
549
|
-
refreshToken: this.jwtService.sign(refreshPayload, {
|
|
550
|
-
secret: this.authConfig.getRefreshTokenSecret(),
|
|
551
|
-
expiresIn: this.authConfig.getRefreshTokenExpiration()
|
|
552
|
-
})
|
|
553
|
-
};
|
|
507
|
+
});
|
|
554
508
|
} catch (error) {
|
|
555
|
-
// Log error details for debugging (not exposed to client)
|
|
556
509
|
_utils.ErrorHandler.logError(this.logger, error, 'refreshToken');
|
|
557
510
|
throw new _common.UnauthorizedException('Invalid or expired refresh token');
|
|
558
511
|
}
|
|
@@ -564,11 +517,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
564
517
|
id: userId
|
|
565
518
|
}
|
|
566
519
|
});
|
|
567
|
-
if (!user)
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
const isPasswordValid = await _bcrypt.compare(currentPassword, user.password || '');
|
|
571
|
-
if (!isPasswordValid) {
|
|
520
|
+
if (!user) throw new _common.UnauthorizedException('User not found');
|
|
521
|
+
if (!await _bcrypt.compare(currentPassword, user.password || '')) {
|
|
572
522
|
throw new _common.UnauthorizedException('Current password is incorrect');
|
|
573
523
|
}
|
|
574
524
|
user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
|
|
@@ -704,54 +654,28 @@ let AuthenticationService = class AuthenticationService {
|
|
|
704
654
|
id: userId
|
|
705
655
|
}
|
|
706
656
|
});
|
|
707
|
-
if (!user)
|
|
708
|
-
throw new _common.UnauthorizedException('User not found');
|
|
709
|
-
}
|
|
710
|
-
// Build response (no tokens)
|
|
657
|
+
if (!user) throw new _common.UnauthorizedException('User not found');
|
|
711
658
|
const response = {
|
|
712
|
-
user:
|
|
713
|
-
id: user.id,
|
|
714
|
-
email: user.email,
|
|
715
|
-
name: user.name ?? undefined,
|
|
716
|
-
phone: user.phone ?? undefined,
|
|
717
|
-
profilePictureId: user.profilePictureId ?? undefined
|
|
718
|
-
}
|
|
659
|
+
user: this.toUserInfo(user)
|
|
719
660
|
};
|
|
720
|
-
|
|
721
|
-
if (this.authConfig.isCompanyFeatureEnabled()) {
|
|
722
|
-
// When company feature is enabled, companyId MUST be present
|
|
661
|
+
if (this.isCompanyFeatureEnabled()) {
|
|
723
662
|
if (!companyId) {
|
|
724
|
-
throw new _common.UnauthorizedException('Company feature is enabled but user has no company assigned.
|
|
663
|
+
throw new _common.UnauthorizedException('Company feature is enabled but user has no company assigned.');
|
|
725
664
|
}
|
|
726
665
|
const company = await this.getCompanyById(companyId);
|
|
727
|
-
if (!company)
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
response.company = {
|
|
731
|
-
id: company.id,
|
|
732
|
-
name: company.name,
|
|
733
|
-
slug: company.slug,
|
|
734
|
-
logoId: company.logoId ?? null
|
|
735
|
-
};
|
|
736
|
-
// Branch is optional in some configurations, but if provided it must exist
|
|
666
|
+
if (!company) throw new _common.UnauthorizedException('Company not found.');
|
|
667
|
+
response.company = this.toCompanyInfo(company);
|
|
737
668
|
if (branchId) {
|
|
738
669
|
const branch = await this.getBranchById(branchId);
|
|
739
|
-
if (!branch)
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
response.branch = {
|
|
743
|
-
id: branch.id,
|
|
744
|
-
name: branch.name,
|
|
745
|
-
slug: branch.slug,
|
|
746
|
-
logoId: branch.logoId ?? null
|
|
747
|
-
};
|
|
670
|
+
if (!branch) throw new _common.UnauthorizedException('Branch not found.');
|
|
671
|
+
response.branch = this.toBranchInfo(branch);
|
|
748
672
|
}
|
|
749
673
|
}
|
|
750
674
|
return response;
|
|
751
675
|
}
|
|
752
676
|
// Helper Methods
|
|
753
677
|
ensureCompanyFeatureEnabled() {
|
|
754
|
-
if (!this.
|
|
678
|
+
if (!this.isCompanyFeatureEnabled()) {
|
|
755
679
|
throw new _common.BadRequestException('Company feature is not enabled');
|
|
756
680
|
}
|
|
757
681
|
}
|
|
@@ -762,11 +686,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
762
686
|
email
|
|
763
687
|
}
|
|
764
688
|
});
|
|
765
|
-
if (!user) {
|
|
766
|
-
throw new _common.UnauthorizedException('Invalid credentials');
|
|
767
|
-
}
|
|
768
|
-
const isPasswordValid = await _bcrypt.compare(password, user.password || '');
|
|
769
|
-
if (!isPasswordValid) {
|
|
689
|
+
if (!user || !await _bcrypt.compare(password, user.password || '')) {
|
|
770
690
|
throw new _common.UnauthorizedException('Invalid credentials');
|
|
771
691
|
}
|
|
772
692
|
user.lastLoginAt = new Date();
|
|
@@ -774,84 +694,77 @@ let AuthenticationService = class AuthenticationService {
|
|
|
774
694
|
return user;
|
|
775
695
|
}
|
|
776
696
|
// Response Generators
|
|
777
|
-
|
|
778
|
-
|
|
697
|
+
/** Create user info object */ toUserInfo(user) {
|
|
698
|
+
return {
|
|
779
699
|
id: user.id,
|
|
780
700
|
email: user.email,
|
|
781
|
-
|
|
701
|
+
name: user.name ?? undefined,
|
|
702
|
+
phone: user.phone ?? null,
|
|
703
|
+
profilePictureId: user.profilePictureId ?? null
|
|
782
704
|
};
|
|
783
|
-
|
|
705
|
+
}
|
|
706
|
+
/** Create company info object */ toCompanyInfo(company) {
|
|
707
|
+
return {
|
|
708
|
+
id: company.id,
|
|
709
|
+
name: company.name,
|
|
710
|
+
slug: company.slug,
|
|
711
|
+
logoId: company.logoId ?? null
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
/** Create branch info object */ toBranchInfo(branch) {
|
|
715
|
+
return {
|
|
716
|
+
id: branch.id,
|
|
717
|
+
name: branch.name,
|
|
718
|
+
slug: branch.slug,
|
|
719
|
+
logoId: branch.logoId ?? null
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
/** Generate JWT tokens */ generateTokens(payload) {
|
|
723
|
+
const accessToken = this.jwtService.sign({
|
|
724
|
+
...payload,
|
|
725
|
+
type: 'access'
|
|
726
|
+
}, {
|
|
784
727
|
secret: this.authConfig.getJwtSecret(),
|
|
785
728
|
expiresIn: this.authConfig.getJwtExpiration()
|
|
786
729
|
});
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
email: user.email,
|
|
730
|
+
const refreshToken = this.jwtService.sign({
|
|
731
|
+
...payload,
|
|
790
732
|
type: 'refresh'
|
|
791
|
-
}
|
|
792
|
-
const refreshToken = this.jwtService.sign(refreshPayload, {
|
|
733
|
+
}, {
|
|
793
734
|
secret: this.authConfig.getRefreshTokenSecret(),
|
|
794
735
|
expiresIn: this.authConfig.getRefreshTokenExpiration()
|
|
795
736
|
});
|
|
796
737
|
return {
|
|
797
738
|
accessToken,
|
|
798
|
-
refreshToken
|
|
799
|
-
user: {
|
|
800
|
-
id: user.id,
|
|
801
|
-
email: user.email,
|
|
802
|
-
name: user.name ?? undefined,
|
|
803
|
-
phone: user.phone ?? null,
|
|
804
|
-
profilePictureId: user.profilePictureId ?? null
|
|
805
|
-
},
|
|
806
|
-
companyFeatureEnabled
|
|
739
|
+
refreshToken
|
|
807
740
|
};
|
|
808
741
|
}
|
|
809
|
-
|
|
810
|
-
const
|
|
742
|
+
generateLoginResponse(user) {
|
|
743
|
+
const { accessToken, refreshToken } = this.generateTokens({
|
|
811
744
|
id: user.id,
|
|
812
745
|
email: user.email,
|
|
813
|
-
type: 'access'
|
|
814
|
-
companyId: company.id,
|
|
815
|
-
branchId: branch?.id
|
|
816
|
-
};
|
|
817
|
-
const accessToken = this.jwtService.sign(payload, {
|
|
818
|
-
secret: this.authConfig.getJwtSecret(),
|
|
819
|
-
expiresIn: this.authConfig.getJwtExpiration()
|
|
746
|
+
type: 'access'
|
|
820
747
|
});
|
|
821
|
-
|
|
748
|
+
return {
|
|
749
|
+
accessToken,
|
|
750
|
+
refreshToken,
|
|
751
|
+
user: this.toUserInfo(user)
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
generateLoginResponseWithCompany(user, company, branch) {
|
|
755
|
+
const { accessToken, refreshToken } = this.generateTokens({
|
|
822
756
|
id: user.id,
|
|
823
757
|
email: user.email,
|
|
824
|
-
type: '
|
|
758
|
+
type: 'access',
|
|
825
759
|
companyId: company.id,
|
|
826
760
|
branchId: branch?.id
|
|
827
|
-
};
|
|
828
|
-
const refreshToken = this.jwtService.sign(refreshPayload, {
|
|
829
|
-
secret: this.authConfig.getRefreshTokenSecret(),
|
|
830
|
-
expiresIn: this.authConfig.getRefreshTokenExpiration()
|
|
831
761
|
});
|
|
832
762
|
return {
|
|
833
763
|
accessToken,
|
|
834
764
|
refreshToken,
|
|
835
|
-
user:
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
name: user.name ?? undefined,
|
|
839
|
-
phone: user.phone ?? null,
|
|
840
|
-
profilePictureId: user.profilePictureId ?? null
|
|
841
|
-
},
|
|
842
|
-
company: {
|
|
843
|
-
id: company.id,
|
|
844
|
-
name: company.name,
|
|
845
|
-
slug: company.slug,
|
|
846
|
-
logoId: company.logoId ?? null
|
|
847
|
-
},
|
|
848
|
-
branch: branch ? {
|
|
849
|
-
id: branch.id,
|
|
850
|
-
name: branch.name,
|
|
851
|
-
slug: branch.slug,
|
|
852
|
-
logoId: branch.logoId ?? null
|
|
853
|
-
} : undefined,
|
|
854
|
-
companyFeatureEnabled: true
|
|
765
|
+
user: this.toUserInfo(user),
|
|
766
|
+
company: this.toCompanyInfo(company),
|
|
767
|
+
branch: branch ? this.toBranchInfo(branch) : undefined
|
|
855
768
|
};
|
|
856
769
|
}
|
|
857
770
|
async generateCompanySelectionResponse(user, companies, branches) {
|
|
@@ -859,88 +772,31 @@ let AuthenticationService = class AuthenticationService {
|
|
|
859
772
|
throw new _common.BadRequestException('Session service not available');
|
|
860
773
|
}
|
|
861
774
|
const sessionId = await this.sessionService.createSession(user.id);
|
|
862
|
-
// Build companies with their branches
|
|
863
775
|
const companiesWithBranches = companies.map((company)=>({
|
|
864
|
-
|
|
865
|
-
name: company.name,
|
|
866
|
-
slug: company.slug,
|
|
776
|
+
...this.toCompanyInfo(company),
|
|
867
777
|
isActive: company.isActive,
|
|
868
|
-
logoId: company.logoId ?? null,
|
|
869
778
|
branches: branches.filter((b)=>b.companyId === company.id).map((b)=>({
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
slug: b.slug,
|
|
873
|
-
isActive: b.isActive,
|
|
874
|
-
logoId: b.logoId ?? null
|
|
779
|
+
...this.toBranchInfo(b),
|
|
780
|
+
isActive: b.isActive
|
|
875
781
|
}))
|
|
876
782
|
}));
|
|
877
783
|
return {
|
|
878
784
|
requiresSelection: true,
|
|
879
785
|
sessionId,
|
|
880
786
|
expiresAt: new Date(Date.now() + 5 * 60 * 1000),
|
|
881
|
-
user:
|
|
882
|
-
id: user.id,
|
|
883
|
-
email: user.email,
|
|
884
|
-
name: user.name ?? undefined,
|
|
885
|
-
phone: user.phone ?? null,
|
|
886
|
-
profilePictureId: user.profilePictureId ?? null
|
|
887
|
-
},
|
|
787
|
+
user: this.toUserInfo(user),
|
|
888
788
|
companies: companiesWithBranches
|
|
889
789
|
};
|
|
890
790
|
}
|
|
891
|
-
|
|
892
|
-
const userInfo = {
|
|
893
|
-
id: user.id,
|
|
894
|
-
email: user.email,
|
|
895
|
-
name: user.name ?? undefined,
|
|
896
|
-
phone: user.phone ?? null,
|
|
897
|
-
profilePictureId: user.profilePictureId ?? null
|
|
898
|
-
};
|
|
899
|
-
if (requiresEmailVerification) {
|
|
900
|
-
return {
|
|
901
|
-
user: userInfo,
|
|
902
|
-
companyFeatureEnabled,
|
|
903
|
-
requiresEmailVerification: true
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
|
|
791
|
+
generateRegistrationResponse(user) {
|
|
907
792
|
return {
|
|
908
|
-
...
|
|
793
|
+
...this.generateLoginResponse(user),
|
|
909
794
|
requiresEmailVerification: false
|
|
910
795
|
};
|
|
911
796
|
}
|
|
912
|
-
|
|
913
|
-
const userInfo = {
|
|
914
|
-
id: user.id,
|
|
915
|
-
email: user.email,
|
|
916
|
-
name: user.name ?? undefined,
|
|
917
|
-
phone: user.phone ?? null,
|
|
918
|
-
profilePictureId: user.profilePictureId ?? null
|
|
919
|
-
};
|
|
920
|
-
const companyInfo = {
|
|
921
|
-
id: company.id,
|
|
922
|
-
name: company.name,
|
|
923
|
-
slug: company.slug,
|
|
924
|
-
logoId: company.logoId ?? null
|
|
925
|
-
};
|
|
926
|
-
const branchInfo = branch ? {
|
|
927
|
-
id: branch.id,
|
|
928
|
-
name: branch.name,
|
|
929
|
-
slug: branch.slug,
|
|
930
|
-
logoId: branch.logoId ?? null
|
|
931
|
-
} : undefined;
|
|
932
|
-
if (requiresEmailVerification) {
|
|
933
|
-
return {
|
|
934
|
-
user: userInfo,
|
|
935
|
-
company: companyInfo,
|
|
936
|
-
branch: branchInfo,
|
|
937
|
-
companyFeatureEnabled: true,
|
|
938
|
-
requiresEmailVerification: true
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
|
|
797
|
+
generateRegistrationResponseWithCompany(user, company, branch) {
|
|
942
798
|
return {
|
|
943
|
-
...
|
|
799
|
+
...this.generateLoginResponseWithCompany(user, company, branch),
|
|
944
800
|
requiresEmailVerification: false
|
|
945
801
|
};
|
|
946
802
|
}
|