@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
|
@@ -17,6 +17,9 @@ const _utils = require("@flusys/nestjs-shared/utils");
|
|
|
17
17
|
const _authconfigservice = require("./auth-config.service");
|
|
18
18
|
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
19
19
|
const _enums = require("../enums");
|
|
20
|
+
const _config = require("../config");
|
|
21
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
22
|
+
const _authemailservice = require("./auth-email.service");
|
|
20
23
|
const _companybranchentity = require("../entities/company-branch.entity");
|
|
21
24
|
const _companyentity = require("../entities/company.entity");
|
|
22
25
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
@@ -90,12 +93,8 @@ function _ts_param(paramIndex, decorator) {
|
|
|
90
93
|
decorator(target, key, paramIndex);
|
|
91
94
|
};
|
|
92
95
|
}
|
|
93
|
-
// Password hashing configuration
|
|
94
|
-
const BCRYPT_SALT_ROUNDS = 12; // Increased from 10 for better security
|
|
95
96
|
let AuthenticationService = class AuthenticationService {
|
|
96
|
-
//
|
|
97
|
-
// REPOSITORY GETTERS
|
|
98
|
-
// ==========================================================================
|
|
97
|
+
// Repository Getters
|
|
99
98
|
async getUserRepository() {
|
|
100
99
|
if (this.dataSourceProvider) {
|
|
101
100
|
return this.dataSourceProvider.getRepository(_userentity.User);
|
|
@@ -126,26 +125,26 @@ let AuthenticationService = class AuthenticationService {
|
|
|
126
125
|
}
|
|
127
126
|
return this.injectedBranchRepository ?? null;
|
|
128
127
|
}
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
// ==========================================================================
|
|
132
|
-
/**
|
|
133
|
-
* Login with email and password
|
|
134
|
-
* Returns either tokens directly or requires company/branch selection
|
|
135
|
-
*/ async login(email, password) {
|
|
128
|
+
// Login
|
|
129
|
+
/** Login with email and password. Returns tokens or requires company/branch selection. */ async login(email, password) {
|
|
136
130
|
const user = await this.validateUser(email, password);
|
|
137
131
|
if (!user.isActive) {
|
|
138
132
|
throw new _common.UnauthorizedException('User account is deactivated');
|
|
139
133
|
}
|
|
140
|
-
//
|
|
134
|
+
// Check email verification if email service is enabled
|
|
135
|
+
if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
|
|
136
|
+
return {
|
|
137
|
+
requiresEmailVerification: true,
|
|
138
|
+
email: user.email,
|
|
139
|
+
message: 'Please verify your email address before logging in.'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
141
142
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
142
143
|
return this.generateLoginResponse(user);
|
|
143
144
|
}
|
|
144
145
|
return this.handleCompanyBasedLogin(user);
|
|
145
146
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Handle login when company feature is enabled
|
|
148
|
-
*/ async handleCompanyBasedLogin(user) {
|
|
147
|
+
/** Handle login when company feature is enabled */ async handleCompanyBasedLogin(user) {
|
|
149
148
|
const permissionRepository = await this.getPermissionRepository();
|
|
150
149
|
const branchRepository = await this.getBranchRepository();
|
|
151
150
|
if (!permissionRepository || !branchRepository) {
|
|
@@ -195,16 +194,9 @@ let AuthenticationService = class AuthenticationService {
|
|
|
195
194
|
// Returns all companies with their branches for frontend to display
|
|
196
195
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
197
196
|
}
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
// ==========================================================================
|
|
201
|
-
/**
|
|
202
|
-
* Register a new user
|
|
203
|
-
* If company feature enabled, optionally join/create company
|
|
204
|
-
*/ async register(dto) {
|
|
197
|
+
// Registration
|
|
198
|
+
/** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
|
|
205
199
|
const userRepository = await this.getUserRepository();
|
|
206
|
-
// Check if user exists
|
|
207
|
-
// Note: Generic error message to prevent email enumeration attacks
|
|
208
200
|
const existingUser = await userRepository.findOne({
|
|
209
201
|
where: {
|
|
210
202
|
email: dto.email
|
|
@@ -213,8 +205,34 @@ let AuthenticationService = class AuthenticationService {
|
|
|
213
205
|
if (existingUser) {
|
|
214
206
|
throw new _common.BadRequestException('Registration failed. Please try again or contact support.');
|
|
215
207
|
}
|
|
216
|
-
//
|
|
217
|
-
|
|
208
|
+
// If email verification is enabled, create pending registration instead of user
|
|
209
|
+
if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
|
|
210
|
+
const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
|
|
211
|
+
companySlug: dto.companySlug,
|
|
212
|
+
branchSlug: dto.branchSlug,
|
|
213
|
+
newCompanyName: dto.newCompanyName,
|
|
214
|
+
newCompanyPhone: dto.newCompanyPhone,
|
|
215
|
+
newCompanyAddress: dto.newCompanyAddress
|
|
216
|
+
} : undefined;
|
|
217
|
+
const result = await this.authEmailService.createPendingRegistration({
|
|
218
|
+
email: dto.email,
|
|
219
|
+
name: dto.name,
|
|
220
|
+
phone: dto.phone,
|
|
221
|
+
password: dto.password,
|
|
222
|
+
companyData,
|
|
223
|
+
verifyUrlBase
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
requiresEmailVerification: true,
|
|
227
|
+
message: result.message
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// No email verification - create user directly
|
|
231
|
+
return this.createUserDirectly(dto);
|
|
232
|
+
}
|
|
233
|
+
/** Create user directly (no email verification) */ async createUserDirectly(dto) {
|
|
234
|
+
const userRepository = await this.getUserRepository();
|
|
235
|
+
const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
218
236
|
const user = userRepository.create({
|
|
219
237
|
name: dto.name,
|
|
220
238
|
email: dto.email,
|
|
@@ -225,40 +243,68 @@ let AuthenticationService = class AuthenticationService {
|
|
|
225
243
|
phoneVerified: false
|
|
226
244
|
});
|
|
227
245
|
await userRepository.save(user);
|
|
228
|
-
// If company feature not enabled, return simple response
|
|
229
246
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
230
|
-
return this.
|
|
247
|
+
return this.generateRegistrationResponse(user, false, false);
|
|
248
|
+
}
|
|
249
|
+
return this.handleCompanyRegistration(user, dto, false);
|
|
250
|
+
}
|
|
251
|
+
/** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
|
|
252
|
+
const userRepository = await this.getUserRepository();
|
|
253
|
+
// Check if user already exists (race condition protection)
|
|
254
|
+
const existingUser = await userRepository.findOne({
|
|
255
|
+
where: {
|
|
256
|
+
email: pending.email
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
if (existingUser) {
|
|
260
|
+
throw new _common.BadRequestException('This email has already been registered.');
|
|
231
261
|
}
|
|
232
|
-
//
|
|
233
|
-
|
|
262
|
+
// Create user with verified email
|
|
263
|
+
const user = userRepository.create({
|
|
264
|
+
name: pending.name,
|
|
265
|
+
email: pending.email,
|
|
266
|
+
phone: pending.phone,
|
|
267
|
+
password: pending.passwordHash,
|
|
268
|
+
isActive: true,
|
|
269
|
+
emailVerified: true,
|
|
270
|
+
phoneVerified: false
|
|
271
|
+
});
|
|
272
|
+
await userRepository.save(user);
|
|
273
|
+
// Handle company feature if enabled
|
|
274
|
+
if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
|
|
275
|
+
return this.handleCompanyRegistrationFromPending(user, pending.companyData);
|
|
276
|
+
}
|
|
277
|
+
return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
|
|
278
|
+
}
|
|
279
|
+
/** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
|
|
280
|
+
return this.processCompanyRegistration(user, companyData, false);
|
|
281
|
+
}
|
|
282
|
+
/** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
|
|
283
|
+
return this.processCompanyRegistration(user, dto, requiresEmailVerification);
|
|
234
284
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Handle company-related registration logic
|
|
237
|
-
*/ async handleCompanyRegistration(user, dto) {
|
|
285
|
+
/** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
|
|
238
286
|
const companyRepository = await this.getCompanyRepository();
|
|
239
287
|
const branchRepository = await this.getBranchRepository();
|
|
240
288
|
const permissionRepository = await this.getPermissionRepository();
|
|
241
289
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
242
|
-
return this.
|
|
290
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
243
291
|
}
|
|
244
292
|
let company = null;
|
|
245
293
|
let branch = null;
|
|
246
|
-
|
|
247
|
-
if (dto.companySlug) {
|
|
294
|
+
if (data.companySlug) {
|
|
248
295
|
company = await companyRepository.findOne({
|
|
249
296
|
where: {
|
|
250
|
-
slug:
|
|
297
|
+
slug: data.companySlug,
|
|
251
298
|
isActive: true
|
|
252
299
|
}
|
|
253
300
|
});
|
|
254
301
|
if (!company) {
|
|
255
302
|
throw new _common.BadRequestException('Company not found or inactive');
|
|
256
303
|
}
|
|
257
|
-
|
|
258
|
-
if (dto.branchSlug) {
|
|
304
|
+
if (data.branchSlug) {
|
|
259
305
|
branch = await branchRepository.findOne({
|
|
260
306
|
where: {
|
|
261
|
-
slug:
|
|
307
|
+
slug: data.branchSlug,
|
|
262
308
|
companyId: company.id,
|
|
263
309
|
isActive: true
|
|
264
310
|
}
|
|
@@ -267,7 +313,6 @@ let AuthenticationService = class AuthenticationService {
|
|
|
267
313
|
throw new _common.BadRequestException('Branch not found or inactive');
|
|
268
314
|
}
|
|
269
315
|
} else {
|
|
270
|
-
// Get first active branch
|
|
271
316
|
branch = await branchRepository.findOne({
|
|
272
317
|
where: {
|
|
273
318
|
companyId: company.id,
|
|
@@ -279,9 +324,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
279
324
|
}
|
|
280
325
|
});
|
|
281
326
|
}
|
|
282
|
-
} else if (
|
|
283
|
-
const slug = this.generateSlug(
|
|
284
|
-
// Check if slug exists
|
|
327
|
+
} else if (data.newCompanyName) {
|
|
328
|
+
const slug = this.generateSlug(data.newCompanyName);
|
|
285
329
|
const existingCompany = await companyRepository.findOne({
|
|
286
330
|
where: {
|
|
287
331
|
slug
|
|
@@ -290,16 +334,14 @@ let AuthenticationService = class AuthenticationService {
|
|
|
290
334
|
if (existingCompany) {
|
|
291
335
|
throw new _common.BadRequestException('Company creation failed. Please try a different name.');
|
|
292
336
|
}
|
|
293
|
-
// Create company
|
|
294
337
|
company = companyRepository.create({
|
|
295
|
-
name:
|
|
338
|
+
name: data.newCompanyName,
|
|
296
339
|
slug,
|
|
297
|
-
phone:
|
|
298
|
-
address:
|
|
340
|
+
phone: data.newCompanyPhone,
|
|
341
|
+
address: data.newCompanyAddress,
|
|
299
342
|
isActive: true
|
|
300
343
|
});
|
|
301
344
|
await companyRepository.save(company);
|
|
302
|
-
// Create default branch
|
|
303
345
|
branch = branchRepository.create({
|
|
304
346
|
name: 'Main Branch',
|
|
305
347
|
slug: 'main-branch',
|
|
@@ -308,16 +350,13 @@ let AuthenticationService = class AuthenticationService {
|
|
|
308
350
|
});
|
|
309
351
|
await branchRepository.save(branch);
|
|
310
352
|
}
|
|
311
|
-
// Create permissions if company exists
|
|
312
353
|
if (company) {
|
|
313
|
-
// Create company permission
|
|
314
354
|
const companyPermission = permissionRepository.create({
|
|
315
355
|
userId: user.id,
|
|
316
356
|
permissionType: _enums.PermissionType.COMPANY,
|
|
317
357
|
targetId: company.id
|
|
318
358
|
});
|
|
319
359
|
await permissionRepository.save(companyPermission);
|
|
320
|
-
// Create branch permission if branch exists
|
|
321
360
|
if (branch) {
|
|
322
361
|
const branchPermission = permissionRepository.create({
|
|
323
362
|
userId: user.id,
|
|
@@ -326,23 +365,15 @@ let AuthenticationService = class AuthenticationService {
|
|
|
326
365
|
});
|
|
327
366
|
await permissionRepository.save(branchPermission);
|
|
328
367
|
}
|
|
329
|
-
return this.
|
|
368
|
+
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
|
|
330
369
|
}
|
|
331
|
-
|
|
332
|
-
return this.generateLoginResponse(user, true);
|
|
370
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
333
371
|
}
|
|
334
|
-
/**
|
|
335
|
-
* Generate URL-friendly slug from name
|
|
336
|
-
*/ generateSlug(name) {
|
|
372
|
+
/** Generate URL-friendly slug from name */ generateSlug(name) {
|
|
337
373
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
|
|
338
374
|
}
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
// ==========================================================================
|
|
342
|
-
/**
|
|
343
|
-
* Select company and branch (step 2 of login)
|
|
344
|
-
* Frontend receives all options and user selects both together
|
|
345
|
-
*/ async select(sessionId, companyId, branchId) {
|
|
375
|
+
// Company/Branch Selection
|
|
376
|
+
/** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
|
|
346
377
|
this.ensureCompanyFeatureEnabled();
|
|
347
378
|
if (!this.sessionService) {
|
|
348
379
|
throw new _common.BadRequestException('Session service not available');
|
|
@@ -409,9 +440,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
409
440
|
}
|
|
410
441
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
411
442
|
}
|
|
412
|
-
/**
|
|
413
|
-
* Switch company/branch after login
|
|
414
|
-
*/ async switchCompany(userId, companyId, branchId) {
|
|
443
|
+
/** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
|
|
415
444
|
this.ensureCompanyFeatureEnabled();
|
|
416
445
|
const userRepository = await this.getUserRepository();
|
|
417
446
|
const user = await userRepository.findOne({
|
|
@@ -470,13 +499,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
470
499
|
}
|
|
471
500
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
472
501
|
}
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
// ==========================================================================
|
|
476
|
-
/**
|
|
477
|
-
* Refresh access token (with token rotation)
|
|
478
|
-
* Returns new access token and new refresh token
|
|
479
|
-
*/ async refreshToken(refreshToken) {
|
|
502
|
+
// Token Management
|
|
503
|
+
/** Refresh access token (with token rotation). Returns new access token and new refresh token. */ async refreshToken(refreshToken) {
|
|
480
504
|
try {
|
|
481
505
|
const payload = this.jwtService.verify(refreshToken, {
|
|
482
506
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
@@ -542,9 +566,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
542
566
|
throw new _common.UnauthorizedException('Invalid or expired refresh token');
|
|
543
567
|
}
|
|
544
568
|
}
|
|
545
|
-
/**
|
|
546
|
-
* Change password
|
|
547
|
-
*/ async changePassword(userId, currentPassword, newPassword) {
|
|
569
|
+
/** Change password */ async changePassword(userId, currentPassword, newPassword) {
|
|
548
570
|
const userRepository = await this.getUserRepository();
|
|
549
571
|
const user = await userRepository.findOne({
|
|
550
572
|
where: {
|
|
@@ -558,18 +580,53 @@ let AuthenticationService = class AuthenticationService {
|
|
|
558
580
|
if (!isPasswordValid) {
|
|
559
581
|
throw new _common.UnauthorizedException('Current password is incorrect');
|
|
560
582
|
}
|
|
561
|
-
user.password = await _bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
|
|
583
|
+
user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
|
|
562
584
|
await userRepository.save(user);
|
|
563
585
|
}
|
|
564
|
-
/**
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
586
|
+
/** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
|
|
587
|
+
if (!this.cacheManager) {
|
|
588
|
+
// Cache not available - graceful degradation (client-side logout only)
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
|
|
592
|
+
const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
593
|
+
// TTL matches refresh token expiry (tokens older than this are already expired)
|
|
594
|
+
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
595
|
+
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
596
|
+
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
597
|
+
}
|
|
598
|
+
/** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
|
|
599
|
+
if (!this.cacheManager || !tokenIssuedAt) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
603
|
+
const revokedAt = await this.cacheManager.get(cacheKey);
|
|
604
|
+
if (!revokedAt) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
// Token is revoked if it was issued before the revocation timestamp
|
|
608
|
+
return tokenIssuedAt < revokedAt;
|
|
609
|
+
}
|
|
610
|
+
/** Parse JWT expiry string to milliseconds */ parseExpiryToMs(expiry) {
|
|
611
|
+
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
612
|
+
if (!match) return 7 * 24 * 60 * 60 * 1000; // Default 7 days
|
|
613
|
+
const value = parseInt(match[1], 10);
|
|
614
|
+
const unit = match[2];
|
|
615
|
+
switch(unit){
|
|
616
|
+
case 's':
|
|
617
|
+
return value * 1000;
|
|
618
|
+
case 'm':
|
|
619
|
+
return value * 60 * 1000;
|
|
620
|
+
case 'h':
|
|
621
|
+
return value * 60 * 60 * 1000;
|
|
622
|
+
case 'd':
|
|
623
|
+
return value * 24 * 60 * 60 * 1000;
|
|
624
|
+
default:
|
|
625
|
+
return 7 * 24 * 60 * 60 * 1000;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Query Methods
|
|
629
|
+
/** Get user's companies */ async getUserCompanies(userId) {
|
|
573
630
|
this.ensureCompanyFeatureEnabled();
|
|
574
631
|
const permissionRepository = await this.getPermissionRepository();
|
|
575
632
|
const companyRepository = await this.getCompanyRepository();
|
|
@@ -590,9 +647,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
590
647
|
active: true
|
|
591
648
|
}).orderBy('c.name', 'ASC').getMany();
|
|
592
649
|
}
|
|
593
|
-
/**
|
|
594
|
-
* Get branches for a company that user has access to
|
|
595
|
-
*/ async getUserBranches(userId, companyId) {
|
|
650
|
+
/** Get branches for a company that user has access to */ async getUserBranches(userId, companyId) {
|
|
596
651
|
this.ensureCompanyFeatureEnabled();
|
|
597
652
|
const permissionRepository = await this.getPermissionRepository();
|
|
598
653
|
const branchRepository = await this.getBranchRepository();
|
|
@@ -627,9 +682,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
627
682
|
active: true
|
|
628
683
|
}).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
|
|
629
684
|
}
|
|
630
|
-
/**
|
|
631
|
-
* Get company by ID
|
|
632
|
-
*/ async getCompanyById(companyId) {
|
|
685
|
+
/** Get company by ID */ async getCompanyById(companyId) {
|
|
633
686
|
const companyRepository = await this.getCompanyRepository();
|
|
634
687
|
if (!companyRepository) {
|
|
635
688
|
return null;
|
|
@@ -640,9 +693,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
640
693
|
}
|
|
641
694
|
});
|
|
642
695
|
}
|
|
643
|
-
/**
|
|
644
|
-
* Get branch by ID
|
|
645
|
-
*/ async getBranchById(branchId) {
|
|
696
|
+
/** Get branch by ID */ async getBranchById(branchId) {
|
|
646
697
|
const branchRepository = await this.getBranchRepository();
|
|
647
698
|
if (!branchRepository) {
|
|
648
699
|
return null;
|
|
@@ -653,10 +704,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
653
704
|
}
|
|
654
705
|
});
|
|
655
706
|
}
|
|
656
|
-
/**
|
|
657
|
-
* Get user info for /auth/me endpoint
|
|
658
|
-
* Returns only user, company, and branch information (no tokens)
|
|
659
|
-
*/ async getUserInfo(userId, companyId, branchId) {
|
|
707
|
+
/** Get user info for /auth/me endpoint. Returns user, company, and branch info (no tokens). */ async getUserInfo(userId, companyId, branchId) {
|
|
660
708
|
const userRepository = await this.getUserRepository();
|
|
661
709
|
const user = await userRepository.findOne({
|
|
662
710
|
where: {
|
|
@@ -672,6 +720,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
672
720
|
id: user.id,
|
|
673
721
|
email: user.email,
|
|
674
722
|
name: user.name ?? undefined,
|
|
723
|
+
phone: user.phone ?? undefined,
|
|
675
724
|
profilePictureId: user.profilePictureId ?? undefined
|
|
676
725
|
}
|
|
677
726
|
};
|
|
@@ -707,9 +756,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
707
756
|
}
|
|
708
757
|
return response;
|
|
709
758
|
}
|
|
710
|
-
//
|
|
711
|
-
// HELPER METHODS
|
|
712
|
-
// ==========================================================================
|
|
759
|
+
// Helper Methods
|
|
713
760
|
ensureCompanyFeatureEnabled() {
|
|
714
761
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
715
762
|
throw new _common.BadRequestException('Company feature is not enabled');
|
|
@@ -733,9 +780,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
733
780
|
await userRepository.save(user);
|
|
734
781
|
return user;
|
|
735
782
|
}
|
|
736
|
-
//
|
|
737
|
-
// RESPONSE GENERATORS
|
|
738
|
-
// ==========================================================================
|
|
783
|
+
// Response Generators
|
|
739
784
|
generateLoginResponse(user, companyFeatureEnabled = false) {
|
|
740
785
|
const payload = {
|
|
741
786
|
id: user.id,
|
|
@@ -762,6 +807,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
762
807
|
id: user.id,
|
|
763
808
|
email: user.email,
|
|
764
809
|
name: user.name ?? undefined,
|
|
810
|
+
phone: user.phone ?? null,
|
|
765
811
|
profilePictureId: user.profilePictureId ?? null
|
|
766
812
|
},
|
|
767
813
|
companyFeatureEnabled
|
|
@@ -797,6 +843,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
797
843
|
id: user.id,
|
|
798
844
|
email: user.email,
|
|
799
845
|
name: user.name ?? undefined,
|
|
846
|
+
phone: user.phone ?? null,
|
|
800
847
|
profilePictureId: user.profilePictureId ?? null
|
|
801
848
|
},
|
|
802
849
|
company: {
|
|
@@ -842,17 +889,76 @@ let AuthenticationService = class AuthenticationService {
|
|
|
842
889
|
id: user.id,
|
|
843
890
|
email: user.email,
|
|
844
891
|
name: user.name ?? undefined,
|
|
892
|
+
phone: user.phone ?? null,
|
|
845
893
|
profilePictureId: user.profilePictureId ?? null
|
|
846
894
|
},
|
|
847
895
|
companies: companiesWithBranches
|
|
848
896
|
};
|
|
849
897
|
}
|
|
898
|
+
/** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
|
|
899
|
+
const userInfo = {
|
|
900
|
+
id: user.id,
|
|
901
|
+
email: user.email,
|
|
902
|
+
name: user.name ?? undefined,
|
|
903
|
+
phone: user.phone ?? null,
|
|
904
|
+
profilePictureId: user.profilePictureId ?? null
|
|
905
|
+
};
|
|
906
|
+
if (requiresEmailVerification) {
|
|
907
|
+
return {
|
|
908
|
+
user: userInfo,
|
|
909
|
+
companyFeatureEnabled,
|
|
910
|
+
requiresEmailVerification: true
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
|
|
914
|
+
return {
|
|
915
|
+
...loginResponse,
|
|
916
|
+
requiresEmailVerification: false
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
/** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
|
|
920
|
+
const userInfo = {
|
|
921
|
+
id: user.id,
|
|
922
|
+
email: user.email,
|
|
923
|
+
name: user.name ?? undefined,
|
|
924
|
+
phone: user.phone ?? null,
|
|
925
|
+
profilePictureId: user.profilePictureId ?? null
|
|
926
|
+
};
|
|
927
|
+
const companyInfo = {
|
|
928
|
+
id: company.id,
|
|
929
|
+
name: company.name,
|
|
930
|
+
slug: company.slug,
|
|
931
|
+
logoId: company.logoId ?? null
|
|
932
|
+
};
|
|
933
|
+
const branchInfo = branch ? {
|
|
934
|
+
id: branch.id,
|
|
935
|
+
name: branch.name,
|
|
936
|
+
slug: branch.slug,
|
|
937
|
+
logoId: branch.logoId ?? null
|
|
938
|
+
} : undefined;
|
|
939
|
+
if (requiresEmailVerification) {
|
|
940
|
+
return {
|
|
941
|
+
user: userInfo,
|
|
942
|
+
company: companyInfo,
|
|
943
|
+
branch: branchInfo,
|
|
944
|
+
companyFeatureEnabled: true,
|
|
945
|
+
requiresEmailVerification: true
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
|
|
949
|
+
return {
|
|
950
|
+
...loginResponse,
|
|
951
|
+
requiresEmailVerification: false
|
|
952
|
+
};
|
|
953
|
+
}
|
|
850
954
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
851
|
-
constructor(jwtService, authConfig, dataSourceProvider, sessionService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
|
|
955
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
|
|
852
956
|
_define_property(this, "jwtService", void 0);
|
|
853
957
|
_define_property(this, "authConfig", void 0);
|
|
958
|
+
_define_property(this, "cacheManager", void 0);
|
|
854
959
|
_define_property(this, "dataSourceProvider", void 0);
|
|
855
960
|
_define_property(this, "sessionService", void 0);
|
|
961
|
+
_define_property(this, "authEmailService", void 0);
|
|
856
962
|
_define_property(this, "injectedUserRepository", void 0);
|
|
857
963
|
_define_property(this, "injectedPermissionRepository", void 0);
|
|
858
964
|
_define_property(this, "injectedCompanyRepository", void 0);
|
|
@@ -860,8 +966,10 @@ let AuthenticationService = class AuthenticationService {
|
|
|
860
966
|
_define_property(this, "logger", void 0);
|
|
861
967
|
this.jwtService = jwtService;
|
|
862
968
|
this.authConfig = authConfig;
|
|
969
|
+
this.cacheManager = cacheManager;
|
|
863
970
|
this.dataSourceProvider = dataSourceProvider;
|
|
864
971
|
this.sessionService = sessionService;
|
|
972
|
+
this.authEmailService = authEmailService;
|
|
865
973
|
this.injectedUserRepository = injectedUserRepository;
|
|
866
974
|
this.injectedPermissionRepository = injectedPermissionRepository;
|
|
867
975
|
this.injectedCompanyRepository = injectedCompanyRepository;
|
|
@@ -876,23 +984,29 @@ AuthenticationService = _ts_decorate([
|
|
|
876
984
|
_ts_param(0, (0, _common.Inject)(_jwt.JwtService)),
|
|
877
985
|
_ts_param(1, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
|
|
878
986
|
_ts_param(2, (0, _common.Optional)()),
|
|
879
|
-
_ts_param(2, (0, _common.Inject)(
|
|
987
|
+
_ts_param(2, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
880
988
|
_ts_param(3, (0, _common.Optional)()),
|
|
881
|
-
_ts_param(3, (0, _common.Inject)(
|
|
989
|
+
_ts_param(3, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
|
|
882
990
|
_ts_param(4, (0, _common.Optional)()),
|
|
883
|
-
_ts_param(4, (0,
|
|
991
|
+
_ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
|
|
884
992
|
_ts_param(5, (0, _common.Optional)()),
|
|
885
|
-
_ts_param(5, (0,
|
|
993
|
+
_ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
|
|
886
994
|
_ts_param(6, (0, _common.Optional)()),
|
|
887
|
-
_ts_param(6, (0, _typeorm.InjectRepository)(
|
|
995
|
+
_ts_param(6, (0, _typeorm.InjectRepository)(_userentity.User)),
|
|
888
996
|
_ts_param(7, (0, _common.Optional)()),
|
|
889
|
-
_ts_param(7, (0, _typeorm.InjectRepository)(
|
|
997
|
+
_ts_param(7, (0, _typeorm.InjectRepository)(_usercompanypermissionentity.UserCompanyPermission)),
|
|
998
|
+
_ts_param(8, (0, _common.Optional)()),
|
|
999
|
+
_ts_param(8, (0, _typeorm.InjectRepository)(_companyentity.Company)),
|
|
1000
|
+
_ts_param(9, (0, _common.Optional)()),
|
|
1001
|
+
_ts_param(9, (0, _typeorm.InjectRepository)(_companybranchentity.CompanyBranch)),
|
|
890
1002
|
_ts_metadata("design:type", Function),
|
|
891
1003
|
_ts_metadata("design:paramtypes", [
|
|
892
1004
|
typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
|
|
893
1005
|
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
|
|
1006
|
+
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
894
1007
|
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
895
1008
|
typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
|
|
1009
|
+
typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService,
|
|
896
1010
|
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|
|
897
1011
|
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|
|
898
1012
|
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|