@flusys/nestjs-auth 1.0.0-beta → 1.0.0-rc
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 +274 -291
- package/cjs/config/auth.constants.js +12 -0
- package/cjs/controllers/authentication.controller.js +219 -10
- package/cjs/controllers/branch.controller.js +44 -1
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +44 -1
- package/cjs/controllers/user-permission.controller.js +40 -25
- package/cjs/controllers/user.controller.js +211 -7
- package/cjs/dtos/authentication.dto.js +119 -13
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/index.js +5 -1
- package/cjs/entities/pending-registration.entity.js +125 -0
- package/cjs/entities/user-company-permission.entity.js +0 -6
- package/cjs/entities/user-token.entity.js +129 -0
- package/cjs/guards/auth-throttler.guard.js +53 -0
- package/cjs/interceptors/clear-token.interceptor.js +3 -6
- package/cjs/interceptors/set-token.interceptor.js +38 -31
- package/cjs/interfaces/auth-email-provider.interface.js +45 -0
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/user-enricher.interface.js +4 -0
- package/cjs/modules/auth.module.js +8 -20
- package/cjs/services/auth-config.service.js +23 -2
- package/cjs/services/auth-datasource.provider.js +5 -3
- package/cjs/services/auth-email.service.js +471 -0
- package/cjs/services/authentication.service.js +169 -77
- package/cjs/services/branch.service.js +6 -5
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +0 -2
- package/cjs/services/user.service.js +213 -34
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +3 -0
- package/controllers/authentication.controller.d.ts +13 -9
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user.controller.d.ts +33 -0
- package/dtos/authentication.dto.d.ts +21 -3
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -2
- package/entities/index.d.ts +7 -3
- package/entities/pending-registration.entity.d.ts +17 -0
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/entities/user-token.entity.d.ts +15 -0
- package/fesm/config/auth.constants.js +6 -2
- package/fesm/controllers/authentication.controller.js +221 -12
- package/fesm/controllers/branch.controller.js +45 -2
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +45 -2
- package/fesm/controllers/user-permission.controller.js +41 -26
- package/fesm/controllers/user.controller.js +214 -10
- package/fesm/dtos/authentication.dto.js +106 -14
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/index.js +7 -1
- package/fesm/entities/pending-registration.entity.js +115 -0
- package/fesm/entities/user-company-permission.entity.js +0 -6
- package/fesm/entities/user-token.entity.js +113 -0
- package/fesm/guards/auth-throttler.guard.js +43 -0
- package/fesm/interceptors/clear-token.interceptor.js +2 -5
- package/fesm/interceptors/set-token.interceptor.js +37 -30
- package/fesm/interfaces/auth-email-provider.interface.js +38 -0
- package/fesm/interfaces/authentication.interface.js +1 -1
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/user-enricher.interface.js +26 -0
- package/fesm/modules/auth.module.js +10 -22
- package/fesm/services/auth-config.service.js +24 -3
- package/fesm/services/auth-datasource.provider.js +6 -4
- package/fesm/services/auth-email.service.js +420 -0
- package/fesm/services/authentication.service.js +169 -77
- package/fesm/services/branch.service.js +6 -5
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +1 -3
- package/fesm/services/user.service.js +215 -36
- package/fesm/validators/password.validator.js +48 -0
- package/guards/auth-throttler.guard.d.ts +7 -0
- package/interceptors/set-token.interceptor.d.ts +1 -0
- package/interfaces/auth-email-provider.interface.d.ts +6 -0
- package/interfaces/auth-module-options.interface.d.ts +4 -1
- package/interfaces/authentication.interface.d.ts +14 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/user-enricher.interface.d.ts +64 -0
- package/interfaces/user.interface.d.ts +1 -0
- package/modules/auth.module.d.ts +0 -1
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +5 -1
- package/services/auth-email.service.d.ts +51 -0
- package/services/authentication.service.d.ts +16 -11
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/index.d.ts +1 -0
- package/services/user-permission.service.d.ts +0 -1
- package/services/user.service.d.ts +19 -3
- package/validators/password.validator.d.ts +10 -0
|
@@ -27,15 +27,14 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Optional, Scope, UnauthorizedException } from '@nestjs/common';
|
|
29
29
|
import { JwtService } from '@nestjs/jwt';
|
|
30
|
-
import { InjectRepository } from '@nestjs/typeorm';
|
|
31
30
|
import * as bcrypt from 'bcrypt';
|
|
32
|
-
import { Repository } from 'typeorm';
|
|
33
31
|
import { ErrorHandler } from '@flusys/nestjs-shared/utils';
|
|
34
32
|
import { AuthConfigService } from './auth-config.service';
|
|
35
33
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
36
34
|
import { PermissionType } from '../enums';
|
|
37
35
|
import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX } from '../config';
|
|
38
36
|
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
37
|
+
import { AuthEmailService } from './auth-email.service';
|
|
39
38
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
40
39
|
import { Company } from '../entities/company.entity';
|
|
41
40
|
import { UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
@@ -44,34 +43,22 @@ import { CompanySelectionSessionService } from './company-selection-session.serv
|
|
|
44
43
|
export class AuthenticationService {
|
|
45
44
|
// Repository Getters
|
|
46
45
|
async getUserRepository() {
|
|
47
|
-
if (this.dataSourceProvider) {
|
|
48
|
-
|
|
46
|
+
if (!this.dataSourceProvider) {
|
|
47
|
+
throw new Error('User repository not available');
|
|
49
48
|
}
|
|
50
|
-
|
|
51
|
-
return this.injectedUserRepository;
|
|
52
|
-
}
|
|
53
|
-
throw new Error('User repository not available');
|
|
49
|
+
return this.dataSourceProvider.getRepository(User);
|
|
54
50
|
}
|
|
55
51
|
async getPermissionRepository() {
|
|
56
|
-
if (!this.authConfig.isCompanyFeatureEnabled()) return null;
|
|
57
|
-
|
|
58
|
-
return this.dataSourceProvider.getRepository(UserCompanyPermission);
|
|
59
|
-
}
|
|
60
|
-
return this.injectedPermissionRepository ?? null;
|
|
52
|
+
if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
53
|
+
return this.dataSourceProvider.getRepository(UserCompanyPermission);
|
|
61
54
|
}
|
|
62
55
|
async getCompanyRepository() {
|
|
63
|
-
if (!this.authConfig.isCompanyFeatureEnabled()) return null;
|
|
64
|
-
|
|
65
|
-
return this.dataSourceProvider.getRepository(Company);
|
|
66
|
-
}
|
|
67
|
-
return this.injectedCompanyRepository ?? null;
|
|
56
|
+
if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
57
|
+
return this.dataSourceProvider.getRepository(Company);
|
|
68
58
|
}
|
|
69
59
|
async getBranchRepository() {
|
|
70
|
-
if (!this.authConfig.isCompanyFeatureEnabled()) return null;
|
|
71
|
-
|
|
72
|
-
return this.dataSourceProvider.getRepository(CompanyBranch);
|
|
73
|
-
}
|
|
74
|
-
return this.injectedBranchRepository ?? null;
|
|
60
|
+
if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
61
|
+
return this.dataSourceProvider.getRepository(CompanyBranch);
|
|
75
62
|
}
|
|
76
63
|
// Login
|
|
77
64
|
/** Login with email and password. Returns tokens or requires company/branch selection. */ async login(email, password) {
|
|
@@ -79,7 +66,14 @@ export class AuthenticationService {
|
|
|
79
66
|
if (!user.isActive) {
|
|
80
67
|
throw new UnauthorizedException('User account is deactivated');
|
|
81
68
|
}
|
|
82
|
-
//
|
|
69
|
+
// Check email verification if email service is enabled
|
|
70
|
+
if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
|
|
71
|
+
return {
|
|
72
|
+
requiresEmailVerification: true,
|
|
73
|
+
email: user.email,
|
|
74
|
+
message: 'Please verify your email address before logging in.'
|
|
75
|
+
};
|
|
76
|
+
}
|
|
83
77
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
84
78
|
return this.generateLoginResponse(user);
|
|
85
79
|
}
|
|
@@ -136,10 +130,8 @@ export class AuthenticationService {
|
|
|
136
130
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
137
131
|
}
|
|
138
132
|
// Registration
|
|
139
|
-
/** Register a new user. If
|
|
133
|
+
/** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
|
|
140
134
|
const userRepository = await this.getUserRepository();
|
|
141
|
-
// Check if user exists
|
|
142
|
-
// Note: Generic error message to prevent email enumeration attacks
|
|
143
135
|
const existingUser = await userRepository.findOne({
|
|
144
136
|
where: {
|
|
145
137
|
email: dto.email
|
|
@@ -148,7 +140,33 @@ export class AuthenticationService {
|
|
|
148
140
|
if (existingUser) {
|
|
149
141
|
throw new BadRequestException('Registration failed. Please try again or contact support.');
|
|
150
142
|
}
|
|
151
|
-
//
|
|
143
|
+
// If email verification is enabled, create pending registration instead of user
|
|
144
|
+
if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
|
|
145
|
+
const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
|
|
146
|
+
companySlug: dto.companySlug,
|
|
147
|
+
branchSlug: dto.branchSlug,
|
|
148
|
+
newCompanyName: dto.newCompanyName,
|
|
149
|
+
newCompanyPhone: dto.newCompanyPhone,
|
|
150
|
+
newCompanyAddress: dto.newCompanyAddress
|
|
151
|
+
} : undefined;
|
|
152
|
+
const result = await this.authEmailService.createPendingRegistration({
|
|
153
|
+
email: dto.email,
|
|
154
|
+
name: dto.name,
|
|
155
|
+
phone: dto.phone,
|
|
156
|
+
password: dto.password,
|
|
157
|
+
companyData,
|
|
158
|
+
verifyUrlBase
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
requiresEmailVerification: true,
|
|
162
|
+
message: result.message
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// No email verification - create user directly
|
|
166
|
+
return this.createUserDirectly(dto);
|
|
167
|
+
}
|
|
168
|
+
/** Create user directly (no email verification) */ async createUserDirectly(dto) {
|
|
169
|
+
const userRepository = await this.getUserRepository();
|
|
152
170
|
const hashedPassword = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS);
|
|
153
171
|
const user = userRepository.create({
|
|
154
172
|
name: dto.name,
|
|
@@ -160,38 +178,68 @@ export class AuthenticationService {
|
|
|
160
178
|
phoneVerified: false
|
|
161
179
|
});
|
|
162
180
|
await userRepository.save(user);
|
|
163
|
-
// If company feature not enabled, return simple response
|
|
164
181
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
165
|
-
return this.
|
|
182
|
+
return this.generateRegistrationResponse(user, false, false);
|
|
166
183
|
}
|
|
167
|
-
|
|
168
|
-
return this.handleCompanyRegistration(user, dto);
|
|
184
|
+
return this.handleCompanyRegistration(user, dto, false);
|
|
169
185
|
}
|
|
170
|
-
/**
|
|
186
|
+
/** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
|
|
187
|
+
const userRepository = await this.getUserRepository();
|
|
188
|
+
// Check if user already exists (race condition protection)
|
|
189
|
+
const existingUser = await userRepository.findOne({
|
|
190
|
+
where: {
|
|
191
|
+
email: pending.email
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
if (existingUser) {
|
|
195
|
+
throw new BadRequestException('This email has already been registered.');
|
|
196
|
+
}
|
|
197
|
+
// Create user with verified email
|
|
198
|
+
const user = userRepository.create({
|
|
199
|
+
name: pending.name,
|
|
200
|
+
email: pending.email,
|
|
201
|
+
phone: pending.phone,
|
|
202
|
+
password: pending.passwordHash,
|
|
203
|
+
isActive: true,
|
|
204
|
+
emailVerified: true,
|
|
205
|
+
phoneVerified: false
|
|
206
|
+
});
|
|
207
|
+
await userRepository.save(user);
|
|
208
|
+
// Handle company feature if enabled
|
|
209
|
+
if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
|
|
210
|
+
return this.handleCompanyRegistrationFromPending(user, pending.companyData);
|
|
211
|
+
}
|
|
212
|
+
return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
|
|
213
|
+
}
|
|
214
|
+
/** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
|
|
215
|
+
return this.processCompanyRegistration(user, companyData, false);
|
|
216
|
+
}
|
|
217
|
+
/** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
|
|
218
|
+
return this.processCompanyRegistration(user, dto, requiresEmailVerification);
|
|
219
|
+
}
|
|
220
|
+
/** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
|
|
171
221
|
const companyRepository = await this.getCompanyRepository();
|
|
172
222
|
const branchRepository = await this.getBranchRepository();
|
|
173
223
|
const permissionRepository = await this.getPermissionRepository();
|
|
174
224
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
175
|
-
return this.
|
|
225
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
176
226
|
}
|
|
177
227
|
let company = null;
|
|
178
228
|
let branch = null;
|
|
179
|
-
|
|
180
|
-
if (dto.companySlug) {
|
|
229
|
+
if (data.companySlug) {
|
|
181
230
|
company = await companyRepository.findOne({
|
|
182
231
|
where: {
|
|
183
|
-
slug:
|
|
232
|
+
slug: data.companySlug,
|
|
184
233
|
isActive: true
|
|
185
234
|
}
|
|
186
235
|
});
|
|
187
236
|
if (!company) {
|
|
188
237
|
throw new BadRequestException('Company not found or inactive');
|
|
189
238
|
}
|
|
190
|
-
|
|
191
|
-
if (dto.branchSlug) {
|
|
239
|
+
if (data.branchSlug) {
|
|
192
240
|
branch = await branchRepository.findOne({
|
|
193
241
|
where: {
|
|
194
|
-
slug:
|
|
242
|
+
slug: data.branchSlug,
|
|
195
243
|
companyId: company.id,
|
|
196
244
|
isActive: true
|
|
197
245
|
}
|
|
@@ -200,7 +248,6 @@ export class AuthenticationService {
|
|
|
200
248
|
throw new BadRequestException('Branch not found or inactive');
|
|
201
249
|
}
|
|
202
250
|
} else {
|
|
203
|
-
// Get first active branch
|
|
204
251
|
branch = await branchRepository.findOne({
|
|
205
252
|
where: {
|
|
206
253
|
companyId: company.id,
|
|
@@ -212,9 +259,8 @@ export class AuthenticationService {
|
|
|
212
259
|
}
|
|
213
260
|
});
|
|
214
261
|
}
|
|
215
|
-
} else if (
|
|
216
|
-
const slug = this.generateSlug(
|
|
217
|
-
// Check if slug exists
|
|
262
|
+
} else if (data.newCompanyName) {
|
|
263
|
+
const slug = this.generateSlug(data.newCompanyName);
|
|
218
264
|
const existingCompany = await companyRepository.findOne({
|
|
219
265
|
where: {
|
|
220
266
|
slug
|
|
@@ -223,16 +269,14 @@ export class AuthenticationService {
|
|
|
223
269
|
if (existingCompany) {
|
|
224
270
|
throw new BadRequestException('Company creation failed. Please try a different name.');
|
|
225
271
|
}
|
|
226
|
-
// Create company
|
|
227
272
|
company = companyRepository.create({
|
|
228
|
-
name:
|
|
273
|
+
name: data.newCompanyName,
|
|
229
274
|
slug,
|
|
230
|
-
phone:
|
|
231
|
-
address:
|
|
275
|
+
phone: data.newCompanyPhone,
|
|
276
|
+
address: data.newCompanyAddress,
|
|
232
277
|
isActive: true
|
|
233
278
|
});
|
|
234
279
|
await companyRepository.save(company);
|
|
235
|
-
// Create default branch
|
|
236
280
|
branch = branchRepository.create({
|
|
237
281
|
name: 'Main Branch',
|
|
238
282
|
slug: 'main-branch',
|
|
@@ -241,16 +285,13 @@ export class AuthenticationService {
|
|
|
241
285
|
});
|
|
242
286
|
await branchRepository.save(branch);
|
|
243
287
|
}
|
|
244
|
-
// Create permissions if company exists
|
|
245
288
|
if (company) {
|
|
246
|
-
// Create company permission
|
|
247
289
|
const companyPermission = permissionRepository.create({
|
|
248
290
|
userId: user.id,
|
|
249
291
|
permissionType: PermissionType.COMPANY,
|
|
250
292
|
targetId: company.id
|
|
251
293
|
});
|
|
252
294
|
await permissionRepository.save(companyPermission);
|
|
253
|
-
// Create branch permission if branch exists
|
|
254
295
|
if (branch) {
|
|
255
296
|
const branchPermission = permissionRepository.create({
|
|
256
297
|
userId: user.id,
|
|
@@ -259,10 +300,9 @@ export class AuthenticationService {
|
|
|
259
300
|
});
|
|
260
301
|
await permissionRepository.save(branchPermission);
|
|
261
302
|
}
|
|
262
|
-
return this.
|
|
303
|
+
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
|
|
263
304
|
}
|
|
264
|
-
|
|
265
|
-
return this.generateLoginResponse(user, true);
|
|
305
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
266
306
|
}
|
|
267
307
|
/** Generate URL-friendly slug from name */ generateSlug(name) {
|
|
268
308
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
|
|
@@ -400,6 +440,11 @@ export class AuthenticationService {
|
|
|
400
440
|
const payload = this.jwtService.verify(refreshToken, {
|
|
401
441
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
402
442
|
});
|
|
443
|
+
// Check if token has been revoked (user logged out after token was issued)
|
|
444
|
+
const isRevoked = await this.isTokenRevoked(payload.id, payload.iat);
|
|
445
|
+
if (isRevoked) {
|
|
446
|
+
throw new UnauthorizedException('Token has been revoked');
|
|
447
|
+
}
|
|
403
448
|
const userRepository = await this.getUserRepository();
|
|
404
449
|
const user = await userRepository.findOne({
|
|
405
450
|
where: {
|
|
@@ -480,8 +525,9 @@ export class AuthenticationService {
|
|
|
480
525
|
}
|
|
481
526
|
/** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
|
|
482
527
|
if (!this.cacheManager) {
|
|
483
|
-
// Cache
|
|
484
|
-
|
|
528
|
+
// Cache is required for secure logout - tokens cannot be revoked without it
|
|
529
|
+
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.');
|
|
530
|
+
throw new BadRequestException('Logout service temporarily unavailable. Please try again later.');
|
|
485
531
|
}
|
|
486
532
|
const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
|
|
487
533
|
const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
@@ -489,6 +535,7 @@ export class AuthenticationService {
|
|
|
489
535
|
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
490
536
|
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
491
537
|
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
538
|
+
this.logger.log(`User ${userId} logged out - all tokens revoked`);
|
|
492
539
|
}
|
|
493
540
|
/** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
|
|
494
541
|
if (!this.cacheManager || !tokenIssuedAt) {
|
|
@@ -615,6 +662,7 @@ export class AuthenticationService {
|
|
|
615
662
|
id: user.id,
|
|
616
663
|
email: user.email,
|
|
617
664
|
name: user.name ?? undefined,
|
|
665
|
+
phone: user.phone ?? undefined,
|
|
618
666
|
profilePictureId: user.profilePictureId ?? undefined
|
|
619
667
|
}
|
|
620
668
|
};
|
|
@@ -701,6 +749,7 @@ export class AuthenticationService {
|
|
|
701
749
|
id: user.id,
|
|
702
750
|
email: user.email,
|
|
703
751
|
name: user.name ?? undefined,
|
|
752
|
+
phone: user.phone ?? null,
|
|
704
753
|
profilePictureId: user.profilePictureId ?? null
|
|
705
754
|
},
|
|
706
755
|
companyFeatureEnabled
|
|
@@ -736,6 +785,7 @@ export class AuthenticationService {
|
|
|
736
785
|
id: user.id,
|
|
737
786
|
email: user.email,
|
|
738
787
|
name: user.name ?? undefined,
|
|
788
|
+
phone: user.phone ?? null,
|
|
739
789
|
profilePictureId: user.profilePictureId ?? null
|
|
740
790
|
},
|
|
741
791
|
company: {
|
|
@@ -781,32 +831,83 @@ export class AuthenticationService {
|
|
|
781
831
|
id: user.id,
|
|
782
832
|
email: user.email,
|
|
783
833
|
name: user.name ?? undefined,
|
|
834
|
+
phone: user.phone ?? null,
|
|
784
835
|
profilePictureId: user.profilePictureId ?? null
|
|
785
836
|
},
|
|
786
837
|
companies: companiesWithBranches
|
|
787
838
|
};
|
|
788
839
|
}
|
|
840
|
+
/** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
|
|
841
|
+
const userInfo = {
|
|
842
|
+
id: user.id,
|
|
843
|
+
email: user.email,
|
|
844
|
+
name: user.name ?? undefined,
|
|
845
|
+
phone: user.phone ?? null,
|
|
846
|
+
profilePictureId: user.profilePictureId ?? null
|
|
847
|
+
};
|
|
848
|
+
if (requiresEmailVerification) {
|
|
849
|
+
return {
|
|
850
|
+
user: userInfo,
|
|
851
|
+
companyFeatureEnabled,
|
|
852
|
+
requiresEmailVerification: true
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
|
|
856
|
+
return {
|
|
857
|
+
...loginResponse,
|
|
858
|
+
requiresEmailVerification: false
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
/** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
|
|
862
|
+
const userInfo = {
|
|
863
|
+
id: user.id,
|
|
864
|
+
email: user.email,
|
|
865
|
+
name: user.name ?? undefined,
|
|
866
|
+
phone: user.phone ?? null,
|
|
867
|
+
profilePictureId: user.profilePictureId ?? null
|
|
868
|
+
};
|
|
869
|
+
const companyInfo = {
|
|
870
|
+
id: company.id,
|
|
871
|
+
name: company.name,
|
|
872
|
+
slug: company.slug,
|
|
873
|
+
logoId: company.logoId ?? null
|
|
874
|
+
};
|
|
875
|
+
const branchInfo = branch ? {
|
|
876
|
+
id: branch.id,
|
|
877
|
+
name: branch.name,
|
|
878
|
+
slug: branch.slug,
|
|
879
|
+
logoId: branch.logoId ?? null
|
|
880
|
+
} : undefined;
|
|
881
|
+
if (requiresEmailVerification) {
|
|
882
|
+
return {
|
|
883
|
+
user: userInfo,
|
|
884
|
+
company: companyInfo,
|
|
885
|
+
branch: branchInfo,
|
|
886
|
+
companyFeatureEnabled: true,
|
|
887
|
+
requiresEmailVerification: true
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
|
|
891
|
+
return {
|
|
892
|
+
...loginResponse,
|
|
893
|
+
requiresEmailVerification: false
|
|
894
|
+
};
|
|
895
|
+
}
|
|
789
896
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
790
|
-
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService,
|
|
897
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
|
|
791
898
|
_define_property(this, "jwtService", void 0);
|
|
792
899
|
_define_property(this, "authConfig", void 0);
|
|
793
900
|
_define_property(this, "cacheManager", void 0);
|
|
794
901
|
_define_property(this, "dataSourceProvider", void 0);
|
|
795
902
|
_define_property(this, "sessionService", void 0);
|
|
796
|
-
_define_property(this, "
|
|
797
|
-
_define_property(this, "injectedPermissionRepository", void 0);
|
|
798
|
-
_define_property(this, "injectedCompanyRepository", void 0);
|
|
799
|
-
_define_property(this, "injectedBranchRepository", void 0);
|
|
903
|
+
_define_property(this, "authEmailService", void 0);
|
|
800
904
|
_define_property(this, "logger", void 0);
|
|
801
905
|
this.jwtService = jwtService;
|
|
802
906
|
this.authConfig = authConfig;
|
|
803
907
|
this.cacheManager = cacheManager;
|
|
804
908
|
this.dataSourceProvider = dataSourceProvider;
|
|
805
909
|
this.sessionService = sessionService;
|
|
806
|
-
this.
|
|
807
|
-
this.injectedPermissionRepository = injectedPermissionRepository;
|
|
808
|
-
this.injectedCompanyRepository = injectedCompanyRepository;
|
|
809
|
-
this.injectedBranchRepository = injectedBranchRepository;
|
|
910
|
+
this.authEmailService = authEmailService;
|
|
810
911
|
this.logger = new Logger(AuthenticationService.name);
|
|
811
912
|
}
|
|
812
913
|
}
|
|
@@ -823,13 +924,7 @@ AuthenticationService = _ts_decorate([
|
|
|
823
924
|
_ts_param(4, Optional()),
|
|
824
925
|
_ts_param(4, Inject(CompanySelectionSessionService)),
|
|
825
926
|
_ts_param(5, Optional()),
|
|
826
|
-
_ts_param(5,
|
|
827
|
-
_ts_param(6, Optional()),
|
|
828
|
-
_ts_param(6, InjectRepository(UserCompanyPermission)),
|
|
829
|
-
_ts_param(7, Optional()),
|
|
830
|
-
_ts_param(7, InjectRepository(Company)),
|
|
831
|
-
_ts_param(8, Optional()),
|
|
832
|
-
_ts_param(8, InjectRepository(CompanyBranch)),
|
|
927
|
+
_ts_param(5, Inject(AuthEmailService)),
|
|
833
928
|
_ts_metadata("design:type", Function),
|
|
834
929
|
_ts_metadata("design:paramtypes", [
|
|
835
930
|
typeof JwtService === "undefined" ? Object : JwtService,
|
|
@@ -837,9 +932,6 @@ AuthenticationService = _ts_decorate([
|
|
|
837
932
|
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
838
933
|
typeof AuthDataSourceProvider === "undefined" ? Object : AuthDataSourceProvider,
|
|
839
934
|
typeof CompanySelectionSessionService === "undefined" ? Object : CompanySelectionSessionService,
|
|
840
|
-
typeof
|
|
841
|
-
typeof Repository === "undefined" ? Object : Repository,
|
|
842
|
-
typeof Repository === "undefined" ? Object : Repository,
|
|
843
|
-
typeof Repository === "undefined" ? Object : Repository
|
|
935
|
+
typeof AuthEmailService === "undefined" ? Object : AuthEmailService
|
|
844
936
|
])
|
|
845
937
|
], AuthenticationService);
|
|
@@ -27,6 +27,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
|
+
import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
|
|
30
31
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
31
32
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
32
33
|
import { AuthConfigService } from './auth-config.service';
|
|
@@ -66,11 +67,11 @@ export class BranchService extends RequestScopedApiService {
|
|
|
66
67
|
}
|
|
67
68
|
async getExtraManipulateQuery(query, filterDto, user) {
|
|
68
69
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
70
|
+
applyCompanyFilter(query, {
|
|
71
|
+
isCompanyFeatureEnabled: this.authConfig.isCompanyFeatureEnabled(),
|
|
72
|
+
entityAlias: 'company_branch',
|
|
73
|
+
columnName: 'company_id'
|
|
74
|
+
}, user);
|
|
74
75
|
return result;
|
|
75
76
|
}
|
|
76
77
|
constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
|
|
@@ -33,7 +33,7 @@ const SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
33
33
|
export class CompanySelectionSessionService {
|
|
34
34
|
/**
|
|
35
35
|
* Create a new session for company/branch selection
|
|
36
|
-
*/ async createSession(userId
|
|
36
|
+
*/ async createSession(userId) {
|
|
37
37
|
const sessionId = randomUUID();
|
|
38
38
|
const now = new Date();
|
|
39
39
|
const expiresAt = new Date(now.getTime() + SESSION_TTL_MS);
|
package/fesm/services/index.js
CHANGED
|
@@ -25,7 +25,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { BadRequestException, Inject, Injectable,
|
|
28
|
+
import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
|
|
29
29
|
import { PermissionType, UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
30
30
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
31
31
|
export class UserPermissionService {
|
|
@@ -140,10 +140,8 @@ export class UserPermissionService {
|
|
|
140
140
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
141
141
|
constructor(dataSourceProvider){
|
|
142
142
|
_define_property(this, "dataSourceProvider", void 0);
|
|
143
|
-
_define_property(this, "logger", void 0);
|
|
144
143
|
_define_property(this, "_repository", void 0);
|
|
145
144
|
this.dataSourceProvider = dataSourceProvider;
|
|
146
|
-
this.logger = new Logger(UserPermissionService.name);
|
|
147
145
|
this._repository = null;
|
|
148
146
|
}
|
|
149
147
|
}
|