@flusys/nestjs-auth 0.1.0-beta.3 → 1.1.0-beta
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 +1 -11
- package/cjs/config/auth.constants.js +14 -4
- package/cjs/controllers/authentication.controller.js +173 -36
- package/cjs/controllers/branch.controller.js +3 -4
- package/cjs/controllers/company.controller.js +3 -4
- package/cjs/controllers/user-permission.controller.js +6 -18
- package/cjs/controllers/user.controller.js +168 -22
- package/cjs/dtos/authentication.dto.js +106 -3
- package/cjs/entities/index.js +5 -1
- package/cjs/entities/pending-registration.entity.js +125 -0
- package/cjs/entities/user-token.entity.js +129 -0
- package/cjs/interfaces/auth-email-provider.interface.js +45 -0
- package/cjs/interfaces/authentication.interface.js +0 -3
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/user-enricher.interface.js +4 -0
- package/cjs/modules/auth.module.js +8 -2
- package/cjs/services/auth-config.service.js +22 -44
- package/cjs/services/auth-datasource.provider.js +17 -33
- package/cjs/services/auth-email.service.js +452 -0
- package/cjs/services/authentication.service.js +229 -115
- package/cjs/services/branch.service.js +10 -44
- package/cjs/services/company.service.js +7 -36
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +9 -27
- package/cjs/services/user.service.js +177 -99
- package/cjs/strategies/jwt.strategy.js +14 -1
- package/config/auth.constants.d.ts +3 -0
- package/controllers/authentication.controller.d.ts +13 -9
- package/controllers/user.controller.d.ts +27 -0
- package/dtos/authentication.dto.d.ts +21 -3
- package/dtos/user.dto.d.ts +1 -0
- package/entities/index.d.ts +7 -3
- package/entities/pending-registration.entity.d.ts +17 -0
- package/entities/user-token.entity.d.ts +15 -0
- package/fesm/config/auth.constants.js +18 -8
- package/fesm/controllers/authentication.controller.js +174 -37
- package/fesm/controllers/branch.controller.js +5 -6
- package/fesm/controllers/company.controller.js +5 -6
- package/fesm/controllers/user-permission.controller.js +6 -18
- package/fesm/controllers/user.controller.js +168 -22
- package/fesm/dtos/authentication.dto.js +109 -100
- package/fesm/entities/index.js +7 -1
- package/fesm/entities/pending-registration.entity.js +115 -0
- package/fesm/entities/user-token.entity.js +113 -0
- package/fesm/interfaces/auth-email-provider.interface.js +38 -0
- package/fesm/interfaces/authentication.interface.js +1 -7
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/user-enricher.interface.js +26 -0
- package/fesm/modules/auth.module.js +9 -3
- package/fesm/services/auth-config.service.js +22 -44
- package/fesm/services/auth-datasource.provider.js +17 -33
- package/fesm/services/auth-email.service.js +401 -0
- package/fesm/services/authentication.service.js +227 -113
- package/fesm/services/branch.service.js +11 -45
- package/fesm/services/company.service.js +8 -37
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +9 -27
- package/fesm/services/user.service.js +178 -100
- package/fesm/strategies/jwt.strategy.js +14 -1
- 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 +67 -0
- package/interfaces/user.interface.d.ts +1 -0
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +1 -0
- package/services/auth-email.service.d.ts +50 -0
- package/services/authentication.service.d.ts +21 -5
- package/services/branch.service.d.ts +2 -5
- package/services/company.service.d.ts +1 -4
- package/services/index.d.ts +1 -0
- package/services/user.service.d.ts +17 -6
|
@@ -34,17 +34,16 @@ import { ErrorHandler } from '@flusys/nestjs-shared/utils';
|
|
|
34
34
|
import { AuthConfigService } from './auth-config.service';
|
|
35
35
|
import { AuthDataSourceProvider } from './auth-datasource.provider';
|
|
36
36
|
import { PermissionType } from '../enums';
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
import { BCRYPT_SALT_ROUNDS, TOKEN_REVOKED_PREFIX } from '../config';
|
|
38
|
+
import { HybridCache } from '@flusys/nestjs-shared/classes';
|
|
39
|
+
import { AuthEmailService } from './auth-email.service';
|
|
39
40
|
import { CompanyBranch } from '../entities/company-branch.entity';
|
|
40
41
|
import { Company } from '../entities/company.entity';
|
|
41
42
|
import { UserCompanyPermission } from '../entities/user-company-permission.entity';
|
|
42
43
|
import { User } from '../entities/user.entity';
|
|
43
44
|
import { CompanySelectionSessionService } from './company-selection-session.service';
|
|
44
45
|
export class AuthenticationService {
|
|
45
|
-
//
|
|
46
|
-
// REPOSITORY GETTERS
|
|
47
|
-
// ==========================================================================
|
|
46
|
+
// Repository Getters
|
|
48
47
|
async getUserRepository() {
|
|
49
48
|
if (this.dataSourceProvider) {
|
|
50
49
|
return this.dataSourceProvider.getRepository(User);
|
|
@@ -75,26 +74,26 @@ export class AuthenticationService {
|
|
|
75
74
|
}
|
|
76
75
|
return this.injectedBranchRepository ?? null;
|
|
77
76
|
}
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
// ==========================================================================
|
|
81
|
-
/**
|
|
82
|
-
* Login with email and password
|
|
83
|
-
* Returns either tokens directly or requires company/branch selection
|
|
84
|
-
*/ async login(email, password) {
|
|
77
|
+
// Login
|
|
78
|
+
/** Login with email and password. Returns tokens or requires company/branch selection. */ async login(email, password) {
|
|
85
79
|
const user = await this.validateUser(email, password);
|
|
86
80
|
if (!user.isActive) {
|
|
87
81
|
throw new UnauthorizedException('User account is deactivated');
|
|
88
82
|
}
|
|
89
|
-
//
|
|
83
|
+
// Check email verification if email service is enabled
|
|
84
|
+
if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
|
|
85
|
+
return {
|
|
86
|
+
requiresEmailVerification: true,
|
|
87
|
+
email: user.email,
|
|
88
|
+
message: 'Please verify your email address before logging in.'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
90
91
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
91
92
|
return this.generateLoginResponse(user);
|
|
92
93
|
}
|
|
93
94
|
return this.handleCompanyBasedLogin(user);
|
|
94
95
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Handle login when company feature is enabled
|
|
97
|
-
*/ async handleCompanyBasedLogin(user) {
|
|
96
|
+
/** Handle login when company feature is enabled */ async handleCompanyBasedLogin(user) {
|
|
98
97
|
const permissionRepository = await this.getPermissionRepository();
|
|
99
98
|
const branchRepository = await this.getBranchRepository();
|
|
100
99
|
if (!permissionRepository || !branchRepository) {
|
|
@@ -144,16 +143,9 @@ export class AuthenticationService {
|
|
|
144
143
|
// Returns all companies with their branches for frontend to display
|
|
145
144
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
146
145
|
}
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
// ==========================================================================
|
|
150
|
-
/**
|
|
151
|
-
* Register a new user
|
|
152
|
-
* If company feature enabled, optionally join/create company
|
|
153
|
-
*/ async register(dto) {
|
|
146
|
+
// Registration
|
|
147
|
+
/** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
|
|
154
148
|
const userRepository = await this.getUserRepository();
|
|
155
|
-
// Check if user exists
|
|
156
|
-
// Note: Generic error message to prevent email enumeration attacks
|
|
157
149
|
const existingUser = await userRepository.findOne({
|
|
158
150
|
where: {
|
|
159
151
|
email: dto.email
|
|
@@ -162,7 +154,33 @@ export class AuthenticationService {
|
|
|
162
154
|
if (existingUser) {
|
|
163
155
|
throw new BadRequestException('Registration failed. Please try again or contact support.');
|
|
164
156
|
}
|
|
165
|
-
//
|
|
157
|
+
// If email verification is enabled, create pending registration instead of user
|
|
158
|
+
if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
|
|
159
|
+
const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
|
|
160
|
+
companySlug: dto.companySlug,
|
|
161
|
+
branchSlug: dto.branchSlug,
|
|
162
|
+
newCompanyName: dto.newCompanyName,
|
|
163
|
+
newCompanyPhone: dto.newCompanyPhone,
|
|
164
|
+
newCompanyAddress: dto.newCompanyAddress
|
|
165
|
+
} : undefined;
|
|
166
|
+
const result = await this.authEmailService.createPendingRegistration({
|
|
167
|
+
email: dto.email,
|
|
168
|
+
name: dto.name,
|
|
169
|
+
phone: dto.phone,
|
|
170
|
+
password: dto.password,
|
|
171
|
+
companyData,
|
|
172
|
+
verifyUrlBase
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
requiresEmailVerification: true,
|
|
176
|
+
message: result.message
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// No email verification - create user directly
|
|
180
|
+
return this.createUserDirectly(dto);
|
|
181
|
+
}
|
|
182
|
+
/** Create user directly (no email verification) */ async createUserDirectly(dto) {
|
|
183
|
+
const userRepository = await this.getUserRepository();
|
|
166
184
|
const hashedPassword = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS);
|
|
167
185
|
const user = userRepository.create({
|
|
168
186
|
name: dto.name,
|
|
@@ -174,40 +192,68 @@ export class AuthenticationService {
|
|
|
174
192
|
phoneVerified: false
|
|
175
193
|
});
|
|
176
194
|
await userRepository.save(user);
|
|
177
|
-
// If company feature not enabled, return simple response
|
|
178
195
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
179
|
-
return this.
|
|
196
|
+
return this.generateRegistrationResponse(user, false, false);
|
|
197
|
+
}
|
|
198
|
+
return this.handleCompanyRegistration(user, dto, false);
|
|
199
|
+
}
|
|
200
|
+
/** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
|
|
201
|
+
const userRepository = await this.getUserRepository();
|
|
202
|
+
// Check if user already exists (race condition protection)
|
|
203
|
+
const existingUser = await userRepository.findOne({
|
|
204
|
+
where: {
|
|
205
|
+
email: pending.email
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
if (existingUser) {
|
|
209
|
+
throw new BadRequestException('This email has already been registered.');
|
|
180
210
|
}
|
|
181
|
-
//
|
|
182
|
-
|
|
211
|
+
// Create user with verified email
|
|
212
|
+
const user = userRepository.create({
|
|
213
|
+
name: pending.name,
|
|
214
|
+
email: pending.email,
|
|
215
|
+
phone: pending.phone,
|
|
216
|
+
password: pending.passwordHash,
|
|
217
|
+
isActive: true,
|
|
218
|
+
emailVerified: true,
|
|
219
|
+
phoneVerified: false
|
|
220
|
+
});
|
|
221
|
+
await userRepository.save(user);
|
|
222
|
+
// Handle company feature if enabled
|
|
223
|
+
if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
|
|
224
|
+
return this.handleCompanyRegistrationFromPending(user, pending.companyData);
|
|
225
|
+
}
|
|
226
|
+
return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
|
|
227
|
+
}
|
|
228
|
+
/** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
|
|
229
|
+
return this.processCompanyRegistration(user, companyData, false);
|
|
230
|
+
}
|
|
231
|
+
/** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
|
|
232
|
+
return this.processCompanyRegistration(user, dto, requiresEmailVerification);
|
|
183
233
|
}
|
|
184
|
-
/**
|
|
185
|
-
* Handle company-related registration logic
|
|
186
|
-
*/ async handleCompanyRegistration(user, dto) {
|
|
234
|
+
/** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
|
|
187
235
|
const companyRepository = await this.getCompanyRepository();
|
|
188
236
|
const branchRepository = await this.getBranchRepository();
|
|
189
237
|
const permissionRepository = await this.getPermissionRepository();
|
|
190
238
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
191
|
-
return this.
|
|
239
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
192
240
|
}
|
|
193
241
|
let company = null;
|
|
194
242
|
let branch = null;
|
|
195
|
-
|
|
196
|
-
if (dto.companySlug) {
|
|
243
|
+
if (data.companySlug) {
|
|
197
244
|
company = await companyRepository.findOne({
|
|
198
245
|
where: {
|
|
199
|
-
slug:
|
|
246
|
+
slug: data.companySlug,
|
|
200
247
|
isActive: true
|
|
201
248
|
}
|
|
202
249
|
});
|
|
203
250
|
if (!company) {
|
|
204
251
|
throw new BadRequestException('Company not found or inactive');
|
|
205
252
|
}
|
|
206
|
-
|
|
207
|
-
if (dto.branchSlug) {
|
|
253
|
+
if (data.branchSlug) {
|
|
208
254
|
branch = await branchRepository.findOne({
|
|
209
255
|
where: {
|
|
210
|
-
slug:
|
|
256
|
+
slug: data.branchSlug,
|
|
211
257
|
companyId: company.id,
|
|
212
258
|
isActive: true
|
|
213
259
|
}
|
|
@@ -216,7 +262,6 @@ export class AuthenticationService {
|
|
|
216
262
|
throw new BadRequestException('Branch not found or inactive');
|
|
217
263
|
}
|
|
218
264
|
} else {
|
|
219
|
-
// Get first active branch
|
|
220
265
|
branch = await branchRepository.findOne({
|
|
221
266
|
where: {
|
|
222
267
|
companyId: company.id,
|
|
@@ -228,9 +273,8 @@ export class AuthenticationService {
|
|
|
228
273
|
}
|
|
229
274
|
});
|
|
230
275
|
}
|
|
231
|
-
} else if (
|
|
232
|
-
const slug = this.generateSlug(
|
|
233
|
-
// Check if slug exists
|
|
276
|
+
} else if (data.newCompanyName) {
|
|
277
|
+
const slug = this.generateSlug(data.newCompanyName);
|
|
234
278
|
const existingCompany = await companyRepository.findOne({
|
|
235
279
|
where: {
|
|
236
280
|
slug
|
|
@@ -239,16 +283,14 @@ export class AuthenticationService {
|
|
|
239
283
|
if (existingCompany) {
|
|
240
284
|
throw new BadRequestException('Company creation failed. Please try a different name.');
|
|
241
285
|
}
|
|
242
|
-
// Create company
|
|
243
286
|
company = companyRepository.create({
|
|
244
|
-
name:
|
|
287
|
+
name: data.newCompanyName,
|
|
245
288
|
slug,
|
|
246
|
-
phone:
|
|
247
|
-
address:
|
|
289
|
+
phone: data.newCompanyPhone,
|
|
290
|
+
address: data.newCompanyAddress,
|
|
248
291
|
isActive: true
|
|
249
292
|
});
|
|
250
293
|
await companyRepository.save(company);
|
|
251
|
-
// Create default branch
|
|
252
294
|
branch = branchRepository.create({
|
|
253
295
|
name: 'Main Branch',
|
|
254
296
|
slug: 'main-branch',
|
|
@@ -257,16 +299,13 @@ export class AuthenticationService {
|
|
|
257
299
|
});
|
|
258
300
|
await branchRepository.save(branch);
|
|
259
301
|
}
|
|
260
|
-
// Create permissions if company exists
|
|
261
302
|
if (company) {
|
|
262
|
-
// Create company permission
|
|
263
303
|
const companyPermission = permissionRepository.create({
|
|
264
304
|
userId: user.id,
|
|
265
305
|
permissionType: PermissionType.COMPANY,
|
|
266
306
|
targetId: company.id
|
|
267
307
|
});
|
|
268
308
|
await permissionRepository.save(companyPermission);
|
|
269
|
-
// Create branch permission if branch exists
|
|
270
309
|
if (branch) {
|
|
271
310
|
const branchPermission = permissionRepository.create({
|
|
272
311
|
userId: user.id,
|
|
@@ -275,23 +314,15 @@ export class AuthenticationService {
|
|
|
275
314
|
});
|
|
276
315
|
await permissionRepository.save(branchPermission);
|
|
277
316
|
}
|
|
278
|
-
return this.
|
|
317
|
+
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
|
|
279
318
|
}
|
|
280
|
-
|
|
281
|
-
return this.generateLoginResponse(user, true);
|
|
319
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
282
320
|
}
|
|
283
|
-
/**
|
|
284
|
-
* Generate URL-friendly slug from name
|
|
285
|
-
*/ generateSlug(name) {
|
|
321
|
+
/** Generate URL-friendly slug from name */ generateSlug(name) {
|
|
286
322
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
|
|
287
323
|
}
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
// ==========================================================================
|
|
291
|
-
/**
|
|
292
|
-
* Select company and branch (step 2 of login)
|
|
293
|
-
* Frontend receives all options and user selects both together
|
|
294
|
-
*/ async select(sessionId, companyId, branchId) {
|
|
324
|
+
// Company/Branch Selection
|
|
325
|
+
/** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
|
|
295
326
|
this.ensureCompanyFeatureEnabled();
|
|
296
327
|
if (!this.sessionService) {
|
|
297
328
|
throw new BadRequestException('Session service not available');
|
|
@@ -358,9 +389,7 @@ export class AuthenticationService {
|
|
|
358
389
|
}
|
|
359
390
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
360
391
|
}
|
|
361
|
-
/**
|
|
362
|
-
* Switch company/branch after login
|
|
363
|
-
*/ async switchCompany(userId, companyId, branchId) {
|
|
392
|
+
/** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
|
|
364
393
|
this.ensureCompanyFeatureEnabled();
|
|
365
394
|
const userRepository = await this.getUserRepository();
|
|
366
395
|
const user = await userRepository.findOne({
|
|
@@ -419,13 +448,8 @@ export class AuthenticationService {
|
|
|
419
448
|
}
|
|
420
449
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
421
450
|
}
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
// ==========================================================================
|
|
425
|
-
/**
|
|
426
|
-
* Refresh access token (with token rotation)
|
|
427
|
-
* Returns new access token and new refresh token
|
|
428
|
-
*/ async refreshToken(refreshToken) {
|
|
451
|
+
// Token Management
|
|
452
|
+
/** Refresh access token (with token rotation). Returns new access token and new refresh token. */ async refreshToken(refreshToken) {
|
|
429
453
|
try {
|
|
430
454
|
const payload = this.jwtService.verify(refreshToken, {
|
|
431
455
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
@@ -491,9 +515,7 @@ export class AuthenticationService {
|
|
|
491
515
|
throw new UnauthorizedException('Invalid or expired refresh token');
|
|
492
516
|
}
|
|
493
517
|
}
|
|
494
|
-
/**
|
|
495
|
-
* Change password
|
|
496
|
-
*/ async changePassword(userId, currentPassword, newPassword) {
|
|
518
|
+
/** Change password */ async changePassword(userId, currentPassword, newPassword) {
|
|
497
519
|
const userRepository = await this.getUserRepository();
|
|
498
520
|
const user = await userRepository.findOne({
|
|
499
521
|
where: {
|
|
@@ -510,15 +532,50 @@ export class AuthenticationService {
|
|
|
510
532
|
user.password = await bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
|
|
511
533
|
await userRepository.save(user);
|
|
512
534
|
}
|
|
513
|
-
/**
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
535
|
+
/** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
|
|
536
|
+
if (!this.cacheManager) {
|
|
537
|
+
// Cache not available - graceful degradation (client-side logout only)
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
|
|
541
|
+
const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
542
|
+
// TTL matches refresh token expiry (tokens older than this are already expired)
|
|
543
|
+
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
544
|
+
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
545
|
+
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
546
|
+
}
|
|
547
|
+
/** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
|
|
548
|
+
if (!this.cacheManager || !tokenIssuedAt) {
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
const cacheKey = `${TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
552
|
+
const revokedAt = await this.cacheManager.get(cacheKey);
|
|
553
|
+
if (!revokedAt) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
// Token is revoked if it was issued before the revocation timestamp
|
|
557
|
+
return tokenIssuedAt < revokedAt;
|
|
558
|
+
}
|
|
559
|
+
/** Parse JWT expiry string to milliseconds */ parseExpiryToMs(expiry) {
|
|
560
|
+
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
561
|
+
if (!match) return 7 * 24 * 60 * 60 * 1000; // Default 7 days
|
|
562
|
+
const value = parseInt(match[1], 10);
|
|
563
|
+
const unit = match[2];
|
|
564
|
+
switch(unit){
|
|
565
|
+
case 's':
|
|
566
|
+
return value * 1000;
|
|
567
|
+
case 'm':
|
|
568
|
+
return value * 60 * 1000;
|
|
569
|
+
case 'h':
|
|
570
|
+
return value * 60 * 60 * 1000;
|
|
571
|
+
case 'd':
|
|
572
|
+
return value * 24 * 60 * 60 * 1000;
|
|
573
|
+
default:
|
|
574
|
+
return 7 * 24 * 60 * 60 * 1000;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// Query Methods
|
|
578
|
+
/** Get user's companies */ async getUserCompanies(userId) {
|
|
522
579
|
this.ensureCompanyFeatureEnabled();
|
|
523
580
|
const permissionRepository = await this.getPermissionRepository();
|
|
524
581
|
const companyRepository = await this.getCompanyRepository();
|
|
@@ -539,9 +596,7 @@ export class AuthenticationService {
|
|
|
539
596
|
active: true
|
|
540
597
|
}).orderBy('c.name', 'ASC').getMany();
|
|
541
598
|
}
|
|
542
|
-
/**
|
|
543
|
-
* Get branches for a company that user has access to
|
|
544
|
-
*/ async getUserBranches(userId, companyId) {
|
|
599
|
+
/** Get branches for a company that user has access to */ async getUserBranches(userId, companyId) {
|
|
545
600
|
this.ensureCompanyFeatureEnabled();
|
|
546
601
|
const permissionRepository = await this.getPermissionRepository();
|
|
547
602
|
const branchRepository = await this.getBranchRepository();
|
|
@@ -576,9 +631,7 @@ export class AuthenticationService {
|
|
|
576
631
|
active: true
|
|
577
632
|
}).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
|
|
578
633
|
}
|
|
579
|
-
/**
|
|
580
|
-
* Get company by ID
|
|
581
|
-
*/ async getCompanyById(companyId) {
|
|
634
|
+
/** Get company by ID */ async getCompanyById(companyId) {
|
|
582
635
|
const companyRepository = await this.getCompanyRepository();
|
|
583
636
|
if (!companyRepository) {
|
|
584
637
|
return null;
|
|
@@ -589,9 +642,7 @@ export class AuthenticationService {
|
|
|
589
642
|
}
|
|
590
643
|
});
|
|
591
644
|
}
|
|
592
|
-
/**
|
|
593
|
-
* Get branch by ID
|
|
594
|
-
*/ async getBranchById(branchId) {
|
|
645
|
+
/** Get branch by ID */ async getBranchById(branchId) {
|
|
595
646
|
const branchRepository = await this.getBranchRepository();
|
|
596
647
|
if (!branchRepository) {
|
|
597
648
|
return null;
|
|
@@ -602,10 +653,7 @@ export class AuthenticationService {
|
|
|
602
653
|
}
|
|
603
654
|
});
|
|
604
655
|
}
|
|
605
|
-
/**
|
|
606
|
-
* Get user info for /auth/me endpoint
|
|
607
|
-
* Returns only user, company, and branch information (no tokens)
|
|
608
|
-
*/ async getUserInfo(userId, companyId, branchId) {
|
|
656
|
+
/** Get user info for /auth/me endpoint. Returns user, company, and branch info (no tokens). */ async getUserInfo(userId, companyId, branchId) {
|
|
609
657
|
const userRepository = await this.getUserRepository();
|
|
610
658
|
const user = await userRepository.findOne({
|
|
611
659
|
where: {
|
|
@@ -621,6 +669,7 @@ export class AuthenticationService {
|
|
|
621
669
|
id: user.id,
|
|
622
670
|
email: user.email,
|
|
623
671
|
name: user.name ?? undefined,
|
|
672
|
+
phone: user.phone ?? undefined,
|
|
624
673
|
profilePictureId: user.profilePictureId ?? undefined
|
|
625
674
|
}
|
|
626
675
|
};
|
|
@@ -656,9 +705,7 @@ export class AuthenticationService {
|
|
|
656
705
|
}
|
|
657
706
|
return response;
|
|
658
707
|
}
|
|
659
|
-
//
|
|
660
|
-
// HELPER METHODS
|
|
661
|
-
// ==========================================================================
|
|
708
|
+
// Helper Methods
|
|
662
709
|
ensureCompanyFeatureEnabled() {
|
|
663
710
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
664
711
|
throw new BadRequestException('Company feature is not enabled');
|
|
@@ -682,9 +729,7 @@ export class AuthenticationService {
|
|
|
682
729
|
await userRepository.save(user);
|
|
683
730
|
return user;
|
|
684
731
|
}
|
|
685
|
-
//
|
|
686
|
-
// RESPONSE GENERATORS
|
|
687
|
-
// ==========================================================================
|
|
732
|
+
// Response Generators
|
|
688
733
|
generateLoginResponse(user, companyFeatureEnabled = false) {
|
|
689
734
|
const payload = {
|
|
690
735
|
id: user.id,
|
|
@@ -711,6 +756,7 @@ export class AuthenticationService {
|
|
|
711
756
|
id: user.id,
|
|
712
757
|
email: user.email,
|
|
713
758
|
name: user.name ?? undefined,
|
|
759
|
+
phone: user.phone ?? null,
|
|
714
760
|
profilePictureId: user.profilePictureId ?? null
|
|
715
761
|
},
|
|
716
762
|
companyFeatureEnabled
|
|
@@ -746,6 +792,7 @@ export class AuthenticationService {
|
|
|
746
792
|
id: user.id,
|
|
747
793
|
email: user.email,
|
|
748
794
|
name: user.name ?? undefined,
|
|
795
|
+
phone: user.phone ?? null,
|
|
749
796
|
profilePictureId: user.profilePictureId ?? null
|
|
750
797
|
},
|
|
751
798
|
company: {
|
|
@@ -791,17 +838,76 @@ export class AuthenticationService {
|
|
|
791
838
|
id: user.id,
|
|
792
839
|
email: user.email,
|
|
793
840
|
name: user.name ?? undefined,
|
|
841
|
+
phone: user.phone ?? null,
|
|
794
842
|
profilePictureId: user.profilePictureId ?? null
|
|
795
843
|
},
|
|
796
844
|
companies: companiesWithBranches
|
|
797
845
|
};
|
|
798
846
|
}
|
|
847
|
+
/** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
|
|
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);
|
|
863
|
+
return {
|
|
864
|
+
...loginResponse,
|
|
865
|
+
requiresEmailVerification: false
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
/** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
|
|
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);
|
|
898
|
+
return {
|
|
899
|
+
...loginResponse,
|
|
900
|
+
requiresEmailVerification: false
|
|
901
|
+
};
|
|
902
|
+
}
|
|
799
903
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
800
|
-
constructor(jwtService, authConfig, dataSourceProvider, sessionService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
|
|
904
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
|
|
801
905
|
_define_property(this, "jwtService", void 0);
|
|
802
906
|
_define_property(this, "authConfig", void 0);
|
|
907
|
+
_define_property(this, "cacheManager", void 0);
|
|
803
908
|
_define_property(this, "dataSourceProvider", void 0);
|
|
804
909
|
_define_property(this, "sessionService", void 0);
|
|
910
|
+
_define_property(this, "authEmailService", void 0);
|
|
805
911
|
_define_property(this, "injectedUserRepository", void 0);
|
|
806
912
|
_define_property(this, "injectedPermissionRepository", void 0);
|
|
807
913
|
_define_property(this, "injectedCompanyRepository", void 0);
|
|
@@ -809,8 +915,10 @@ export class AuthenticationService {
|
|
|
809
915
|
_define_property(this, "logger", void 0);
|
|
810
916
|
this.jwtService = jwtService;
|
|
811
917
|
this.authConfig = authConfig;
|
|
918
|
+
this.cacheManager = cacheManager;
|
|
812
919
|
this.dataSourceProvider = dataSourceProvider;
|
|
813
920
|
this.sessionService = sessionService;
|
|
921
|
+
this.authEmailService = authEmailService;
|
|
814
922
|
this.injectedUserRepository = injectedUserRepository;
|
|
815
923
|
this.injectedPermissionRepository = injectedPermissionRepository;
|
|
816
924
|
this.injectedCompanyRepository = injectedCompanyRepository;
|
|
@@ -825,23 +933,29 @@ AuthenticationService = _ts_decorate([
|
|
|
825
933
|
_ts_param(0, Inject(JwtService)),
|
|
826
934
|
_ts_param(1, Inject(AuthConfigService)),
|
|
827
935
|
_ts_param(2, Optional()),
|
|
828
|
-
_ts_param(2, Inject(
|
|
936
|
+
_ts_param(2, Inject('CACHE_INSTANCE')),
|
|
829
937
|
_ts_param(3, Optional()),
|
|
830
|
-
_ts_param(3, Inject(
|
|
938
|
+
_ts_param(3, Inject(AuthDataSourceProvider)),
|
|
831
939
|
_ts_param(4, Optional()),
|
|
832
|
-
_ts_param(4,
|
|
940
|
+
_ts_param(4, Inject(CompanySelectionSessionService)),
|
|
833
941
|
_ts_param(5, Optional()),
|
|
834
|
-
_ts_param(5,
|
|
942
|
+
_ts_param(5, Inject(AuthEmailService)),
|
|
835
943
|
_ts_param(6, Optional()),
|
|
836
|
-
_ts_param(6, InjectRepository(
|
|
944
|
+
_ts_param(6, InjectRepository(User)),
|
|
837
945
|
_ts_param(7, Optional()),
|
|
838
|
-
_ts_param(7, InjectRepository(
|
|
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)),
|
|
839
951
|
_ts_metadata("design:type", Function),
|
|
840
952
|
_ts_metadata("design:paramtypes", [
|
|
841
953
|
typeof JwtService === "undefined" ? Object : JwtService,
|
|
842
954
|
typeof AuthConfigService === "undefined" ? Object : AuthConfigService,
|
|
955
|
+
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
843
956
|
typeof AuthDataSourceProvider === "undefined" ? Object : AuthDataSourceProvider,
|
|
844
957
|
typeof CompanySelectionSessionService === "undefined" ? Object : CompanySelectionSessionService,
|
|
958
|
+
typeof AuthEmailService === "undefined" ? Object : AuthEmailService,
|
|
845
959
|
typeof Repository === "undefined" ? Object : Repository,
|
|
846
960
|
typeof Repository === "undefined" ? Object : Repository,
|
|
847
961
|
typeof Repository === "undefined" ? Object : Repository,
|