@flusys/nestjs-auth 0.1.0-beta.3 → 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 -301
- package/cjs/config/auth.constants.js +22 -4
- package/cjs/controllers/authentication.controller.js +222 -38
- package/cjs/controllers/branch.controller.js +46 -4
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +46 -4
- package/cjs/controllers/user-permission.controller.js +40 -37
- package/cjs/controllers/user.controller.js +222 -23
- 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/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 -20
- package/cjs/services/auth-config.service.js +42 -47
- package/cjs/services/auth-datasource.provider.js +18 -34
- package/cjs/services/auth-email.service.js +471 -0
- package/cjs/services/authentication.service.js +240 -153
- package/cjs/services/branch.service.js +15 -48
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/company.service.js +7 -36
- package/cjs/services/index.js +1 -0
- package/cjs/services/user-permission.service.js +9 -29
- package/cjs/services/user.service.js +228 -107
- package/cjs/strategies/jwt.strategy.js +14 -1
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +5 -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 +11 -8
- package/fesm/controllers/authentication.controller.js +224 -40
- package/fesm/controllers/branch.controller.js +49 -7
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +49 -7
- package/fesm/controllers/user-permission.controller.js +41 -38
- package/fesm/controllers/user.controller.js +225 -26
- package/fesm/dtos/authentication.dto.js +123 -111
- 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 -7
- 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 +42 -47
- package/fesm/services/auth-datasource.provider.js +19 -35
- package/fesm/services/auth-email.service.js +420 -0
- package/fesm/services/authentication.service.js +238 -151
- package/fesm/services/branch.service.js +16 -49
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/company.service.js +8 -37
- package/fesm/services/index.js +1 -0
- package/fesm/services/user-permission.service.js +10 -30
- package/fesm/services/user.service.js +229 -108
- package/fesm/strategies/jwt.strategy.js +14 -1
- 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 +21 -12
- package/services/branch.service.d.ts +2 -5
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/company.service.d.ts +1 -4
- package/services/index.d.ts +1 -0
- package/services/user-permission.service.d.ts +0 -1
- package/services/user.service.d.ts +20 -6
- package/validators/password.validator.d.ts +10 -0
|
@@ -10,13 +10,14 @@ Object.defineProperty(exports, "AuthenticationService", {
|
|
|
10
10
|
});
|
|
11
11
|
const _common = require("@nestjs/common");
|
|
12
12
|
const _jwt = require("@nestjs/jwt");
|
|
13
|
-
const _typeorm = require("@nestjs/typeorm");
|
|
14
13
|
const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
|
|
15
|
-
const _typeorm1 = require("typeorm");
|
|
16
14
|
const _utils = require("@flusys/nestjs-shared/utils");
|
|
17
15
|
const _authconfigservice = require("./auth-config.service");
|
|
18
16
|
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
19
17
|
const _enums = require("../enums");
|
|
18
|
+
const _config = require("../config");
|
|
19
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
20
|
+
const _authemailservice = require("./auth-email.service");
|
|
20
21
|
const _companybranchentity = require("../entities/company-branch.entity");
|
|
21
22
|
const _companyentity = require("../entities/company.entity");
|
|
22
23
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
@@ -90,62 +91,46 @@ function _ts_param(paramIndex, decorator) {
|
|
|
90
91
|
decorator(target, key, paramIndex);
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
|
-
// Password hashing configuration
|
|
94
|
-
const BCRYPT_SALT_ROUNDS = 12; // Increased from 10 for better security
|
|
95
94
|
let AuthenticationService = class AuthenticationService {
|
|
96
|
-
//
|
|
97
|
-
// REPOSITORY GETTERS
|
|
98
|
-
// ==========================================================================
|
|
95
|
+
// Repository Getters
|
|
99
96
|
async getUserRepository() {
|
|
100
|
-
if (this.dataSourceProvider) {
|
|
101
|
-
|
|
97
|
+
if (!this.dataSourceProvider) {
|
|
98
|
+
throw new Error('User repository not available');
|
|
102
99
|
}
|
|
103
|
-
|
|
104
|
-
return this.injectedUserRepository;
|
|
105
|
-
}
|
|
106
|
-
throw new Error('User repository not available');
|
|
100
|
+
return this.dataSourceProvider.getRepository(_userentity.User);
|
|
107
101
|
}
|
|
108
102
|
async getPermissionRepository() {
|
|
109
|
-
if (!this.authConfig.isCompanyFeatureEnabled()) return null;
|
|
110
|
-
|
|
111
|
-
return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
|
|
112
|
-
}
|
|
113
|
-
return this.injectedPermissionRepository ?? null;
|
|
103
|
+
if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
104
|
+
return this.dataSourceProvider.getRepository(_usercompanypermissionentity.UserCompanyPermission);
|
|
114
105
|
}
|
|
115
106
|
async getCompanyRepository() {
|
|
116
|
-
if (!this.authConfig.isCompanyFeatureEnabled()) return null;
|
|
117
|
-
|
|
118
|
-
return this.dataSourceProvider.getRepository(_companyentity.Company);
|
|
119
|
-
}
|
|
120
|
-
return this.injectedCompanyRepository ?? null;
|
|
107
|
+
if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
108
|
+
return this.dataSourceProvider.getRepository(_companyentity.Company);
|
|
121
109
|
}
|
|
122
110
|
async getBranchRepository() {
|
|
123
|
-
if (!this.authConfig.isCompanyFeatureEnabled()) return null;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
// ==========================================================================
|
|
130
|
-
// LOGIN
|
|
131
|
-
// ==========================================================================
|
|
132
|
-
/**
|
|
133
|
-
* Login with email and password
|
|
134
|
-
* Returns either tokens directly or requires company/branch selection
|
|
135
|
-
*/ async login(email, password) {
|
|
111
|
+
if (!this.authConfig.isCompanyFeatureEnabled() || !this.dataSourceProvider) return null;
|
|
112
|
+
return this.dataSourceProvider.getRepository(_companybranchentity.CompanyBranch);
|
|
113
|
+
}
|
|
114
|
+
// Login
|
|
115
|
+
/** Login with email and password. Returns tokens or requires company/branch selection. */ async login(email, password) {
|
|
136
116
|
const user = await this.validateUser(email, password);
|
|
137
117
|
if (!user.isActive) {
|
|
138
118
|
throw new _common.UnauthorizedException('User account is deactivated');
|
|
139
119
|
}
|
|
140
|
-
//
|
|
120
|
+
// Check email verification if email service is enabled
|
|
121
|
+
if (this.authEmailService?.isEmailEnabled() && !user.emailVerified) {
|
|
122
|
+
return {
|
|
123
|
+
requiresEmailVerification: true,
|
|
124
|
+
email: user.email,
|
|
125
|
+
message: 'Please verify your email address before logging in.'
|
|
126
|
+
};
|
|
127
|
+
}
|
|
141
128
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
142
129
|
return this.generateLoginResponse(user);
|
|
143
130
|
}
|
|
144
131
|
return this.handleCompanyBasedLogin(user);
|
|
145
132
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Handle login when company feature is enabled
|
|
148
|
-
*/ async handleCompanyBasedLogin(user) {
|
|
133
|
+
/** Handle login when company feature is enabled */ async handleCompanyBasedLogin(user) {
|
|
149
134
|
const permissionRepository = await this.getPermissionRepository();
|
|
150
135
|
const branchRepository = await this.getBranchRepository();
|
|
151
136
|
if (!permissionRepository || !branchRepository) {
|
|
@@ -195,16 +180,9 @@ let AuthenticationService = class AuthenticationService {
|
|
|
195
180
|
// Returns all companies with their branches for frontend to display
|
|
196
181
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
197
182
|
}
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
// ==========================================================================
|
|
201
|
-
/**
|
|
202
|
-
* Register a new user
|
|
203
|
-
* If company feature enabled, optionally join/create company
|
|
204
|
-
*/ async register(dto) {
|
|
183
|
+
// Registration
|
|
184
|
+
/** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
|
|
205
185
|
const userRepository = await this.getUserRepository();
|
|
206
|
-
// Check if user exists
|
|
207
|
-
// Note: Generic error message to prevent email enumeration attacks
|
|
208
186
|
const existingUser = await userRepository.findOne({
|
|
209
187
|
where: {
|
|
210
188
|
email: dto.email
|
|
@@ -213,8 +191,34 @@ let AuthenticationService = class AuthenticationService {
|
|
|
213
191
|
if (existingUser) {
|
|
214
192
|
throw new _common.BadRequestException('Registration failed. Please try again or contact support.');
|
|
215
193
|
}
|
|
216
|
-
//
|
|
217
|
-
|
|
194
|
+
// If email verification is enabled, create pending registration instead of user
|
|
195
|
+
if (this.authEmailService?.isEmailEnabled() && verifyUrlBase) {
|
|
196
|
+
const companyData = this.authConfig.isCompanyFeatureEnabled() ? {
|
|
197
|
+
companySlug: dto.companySlug,
|
|
198
|
+
branchSlug: dto.branchSlug,
|
|
199
|
+
newCompanyName: dto.newCompanyName,
|
|
200
|
+
newCompanyPhone: dto.newCompanyPhone,
|
|
201
|
+
newCompanyAddress: dto.newCompanyAddress
|
|
202
|
+
} : undefined;
|
|
203
|
+
const result = await this.authEmailService.createPendingRegistration({
|
|
204
|
+
email: dto.email,
|
|
205
|
+
name: dto.name,
|
|
206
|
+
phone: dto.phone,
|
|
207
|
+
password: dto.password,
|
|
208
|
+
companyData,
|
|
209
|
+
verifyUrlBase
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
requiresEmailVerification: true,
|
|
213
|
+
message: result.message
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// No email verification - create user directly
|
|
217
|
+
return this.createUserDirectly(dto);
|
|
218
|
+
}
|
|
219
|
+
/** Create user directly (no email verification) */ async createUserDirectly(dto) {
|
|
220
|
+
const userRepository = await this.getUserRepository();
|
|
221
|
+
const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
218
222
|
const user = userRepository.create({
|
|
219
223
|
name: dto.name,
|
|
220
224
|
email: dto.email,
|
|
@@ -225,40 +229,68 @@ let AuthenticationService = class AuthenticationService {
|
|
|
225
229
|
phoneVerified: false
|
|
226
230
|
});
|
|
227
231
|
await userRepository.save(user);
|
|
228
|
-
// If company feature not enabled, return simple response
|
|
229
232
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
230
|
-
return this.
|
|
233
|
+
return this.generateRegistrationResponse(user, false, false);
|
|
231
234
|
}
|
|
232
|
-
|
|
233
|
-
|
|
235
|
+
return this.handleCompanyRegistration(user, dto, false);
|
|
236
|
+
}
|
|
237
|
+
/** Create user from pending registration (after email verification) */ async createUserFromPendingRegistration(pending) {
|
|
238
|
+
const userRepository = await this.getUserRepository();
|
|
239
|
+
// Check if user already exists (race condition protection)
|
|
240
|
+
const existingUser = await userRepository.findOne({
|
|
241
|
+
where: {
|
|
242
|
+
email: pending.email
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
if (existingUser) {
|
|
246
|
+
throw new _common.BadRequestException('This email has already been registered.');
|
|
247
|
+
}
|
|
248
|
+
// Create user with verified email
|
|
249
|
+
const user = userRepository.create({
|
|
250
|
+
name: pending.name,
|
|
251
|
+
email: pending.email,
|
|
252
|
+
phone: pending.phone,
|
|
253
|
+
password: pending.passwordHash,
|
|
254
|
+
isActive: true,
|
|
255
|
+
emailVerified: true,
|
|
256
|
+
phoneVerified: false
|
|
257
|
+
});
|
|
258
|
+
await userRepository.save(user);
|
|
259
|
+
// Handle company feature if enabled
|
|
260
|
+
if (this.authConfig.isCompanyFeatureEnabled() && pending.companyData) {
|
|
261
|
+
return this.handleCompanyRegistrationFromPending(user, pending.companyData);
|
|
262
|
+
}
|
|
263
|
+
return this.generateRegistrationResponse(user, this.authConfig.isCompanyFeatureEnabled(), false);
|
|
264
|
+
}
|
|
265
|
+
/** Handle company registration from pending registration data */ async handleCompanyRegistrationFromPending(user, companyData) {
|
|
266
|
+
return this.processCompanyRegistration(user, companyData, false);
|
|
234
267
|
}
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
|
|
268
|
+
/** Handle company-related registration logic */ async handleCompanyRegistration(user, dto, requiresEmailVerification) {
|
|
269
|
+
return this.processCompanyRegistration(user, dto, requiresEmailVerification);
|
|
270
|
+
}
|
|
271
|
+
/** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
|
|
238
272
|
const companyRepository = await this.getCompanyRepository();
|
|
239
273
|
const branchRepository = await this.getBranchRepository();
|
|
240
274
|
const permissionRepository = await this.getPermissionRepository();
|
|
241
275
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
242
|
-
return this.
|
|
276
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
243
277
|
}
|
|
244
278
|
let company = null;
|
|
245
279
|
let branch = null;
|
|
246
|
-
|
|
247
|
-
if (dto.companySlug) {
|
|
280
|
+
if (data.companySlug) {
|
|
248
281
|
company = await companyRepository.findOne({
|
|
249
282
|
where: {
|
|
250
|
-
slug:
|
|
283
|
+
slug: data.companySlug,
|
|
251
284
|
isActive: true
|
|
252
285
|
}
|
|
253
286
|
});
|
|
254
287
|
if (!company) {
|
|
255
288
|
throw new _common.BadRequestException('Company not found or inactive');
|
|
256
289
|
}
|
|
257
|
-
|
|
258
|
-
if (dto.branchSlug) {
|
|
290
|
+
if (data.branchSlug) {
|
|
259
291
|
branch = await branchRepository.findOne({
|
|
260
292
|
where: {
|
|
261
|
-
slug:
|
|
293
|
+
slug: data.branchSlug,
|
|
262
294
|
companyId: company.id,
|
|
263
295
|
isActive: true
|
|
264
296
|
}
|
|
@@ -267,7 +299,6 @@ let AuthenticationService = class AuthenticationService {
|
|
|
267
299
|
throw new _common.BadRequestException('Branch not found or inactive');
|
|
268
300
|
}
|
|
269
301
|
} else {
|
|
270
|
-
// Get first active branch
|
|
271
302
|
branch = await branchRepository.findOne({
|
|
272
303
|
where: {
|
|
273
304
|
companyId: company.id,
|
|
@@ -279,9 +310,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
279
310
|
}
|
|
280
311
|
});
|
|
281
312
|
}
|
|
282
|
-
} else if (
|
|
283
|
-
const slug = this.generateSlug(
|
|
284
|
-
// Check if slug exists
|
|
313
|
+
} else if (data.newCompanyName) {
|
|
314
|
+
const slug = this.generateSlug(data.newCompanyName);
|
|
285
315
|
const existingCompany = await companyRepository.findOne({
|
|
286
316
|
where: {
|
|
287
317
|
slug
|
|
@@ -290,16 +320,14 @@ let AuthenticationService = class AuthenticationService {
|
|
|
290
320
|
if (existingCompany) {
|
|
291
321
|
throw new _common.BadRequestException('Company creation failed. Please try a different name.');
|
|
292
322
|
}
|
|
293
|
-
// Create company
|
|
294
323
|
company = companyRepository.create({
|
|
295
|
-
name:
|
|
324
|
+
name: data.newCompanyName,
|
|
296
325
|
slug,
|
|
297
|
-
phone:
|
|
298
|
-
address:
|
|
326
|
+
phone: data.newCompanyPhone,
|
|
327
|
+
address: data.newCompanyAddress,
|
|
299
328
|
isActive: true
|
|
300
329
|
});
|
|
301
330
|
await companyRepository.save(company);
|
|
302
|
-
// Create default branch
|
|
303
331
|
branch = branchRepository.create({
|
|
304
332
|
name: 'Main Branch',
|
|
305
333
|
slug: 'main-branch',
|
|
@@ -308,16 +336,13 @@ let AuthenticationService = class AuthenticationService {
|
|
|
308
336
|
});
|
|
309
337
|
await branchRepository.save(branch);
|
|
310
338
|
}
|
|
311
|
-
// Create permissions if company exists
|
|
312
339
|
if (company) {
|
|
313
|
-
// Create company permission
|
|
314
340
|
const companyPermission = permissionRepository.create({
|
|
315
341
|
userId: user.id,
|
|
316
342
|
permissionType: _enums.PermissionType.COMPANY,
|
|
317
343
|
targetId: company.id
|
|
318
344
|
});
|
|
319
345
|
await permissionRepository.save(companyPermission);
|
|
320
|
-
// Create branch permission if branch exists
|
|
321
346
|
if (branch) {
|
|
322
347
|
const branchPermission = permissionRepository.create({
|
|
323
348
|
userId: user.id,
|
|
@@ -326,23 +351,15 @@ let AuthenticationService = class AuthenticationService {
|
|
|
326
351
|
});
|
|
327
352
|
await permissionRepository.save(branchPermission);
|
|
328
353
|
}
|
|
329
|
-
return this.
|
|
354
|
+
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
|
|
330
355
|
}
|
|
331
|
-
|
|
332
|
-
return this.generateLoginResponse(user, true);
|
|
356
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
333
357
|
}
|
|
334
|
-
/**
|
|
335
|
-
* Generate URL-friendly slug from name
|
|
336
|
-
*/ generateSlug(name) {
|
|
358
|
+
/** Generate URL-friendly slug from name */ generateSlug(name) {
|
|
337
359
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
|
|
338
360
|
}
|
|
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) {
|
|
361
|
+
// Company/Branch Selection
|
|
362
|
+
/** Select company and branch (step 2 of login). Frontend receives all options and user selects both. */ async select(sessionId, companyId, branchId) {
|
|
346
363
|
this.ensureCompanyFeatureEnabled();
|
|
347
364
|
if (!this.sessionService) {
|
|
348
365
|
throw new _common.BadRequestException('Session service not available');
|
|
@@ -409,9 +426,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
409
426
|
}
|
|
410
427
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
411
428
|
}
|
|
412
|
-
/**
|
|
413
|
-
* Switch company/branch after login
|
|
414
|
-
*/ async switchCompany(userId, companyId, branchId) {
|
|
429
|
+
/** Switch company/branch after login */ async switchCompany(userId, companyId, branchId) {
|
|
415
430
|
this.ensureCompanyFeatureEnabled();
|
|
416
431
|
const userRepository = await this.getUserRepository();
|
|
417
432
|
const user = await userRepository.findOne({
|
|
@@ -470,17 +485,17 @@ let AuthenticationService = class AuthenticationService {
|
|
|
470
485
|
}
|
|
471
486
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
472
487
|
}
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
// ==========================================================================
|
|
476
|
-
/**
|
|
477
|
-
* Refresh access token (with token rotation)
|
|
478
|
-
* Returns new access token and new refresh token
|
|
479
|
-
*/ async refreshToken(refreshToken) {
|
|
488
|
+
// Token Management
|
|
489
|
+
/** Refresh access token (with token rotation). Returns new access token and new refresh token. */ async refreshToken(refreshToken) {
|
|
480
490
|
try {
|
|
481
491
|
const payload = this.jwtService.verify(refreshToken, {
|
|
482
492
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
483
493
|
});
|
|
494
|
+
// Check if token has been revoked (user logged out after token was issued)
|
|
495
|
+
const isRevoked = await this.isTokenRevoked(payload.id, payload.iat);
|
|
496
|
+
if (isRevoked) {
|
|
497
|
+
throw new _common.UnauthorizedException('Token has been revoked');
|
|
498
|
+
}
|
|
484
499
|
const userRepository = await this.getUserRepository();
|
|
485
500
|
const user = await userRepository.findOne({
|
|
486
501
|
where: {
|
|
@@ -542,9 +557,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
542
557
|
throw new _common.UnauthorizedException('Invalid or expired refresh token');
|
|
543
558
|
}
|
|
544
559
|
}
|
|
545
|
-
/**
|
|
546
|
-
* Change password
|
|
547
|
-
*/ async changePassword(userId, currentPassword, newPassword) {
|
|
560
|
+
/** Change password */ async changePassword(userId, currentPassword, newPassword) {
|
|
548
561
|
const userRepository = await this.getUserRepository();
|
|
549
562
|
const user = await userRepository.findOne({
|
|
550
563
|
where: {
|
|
@@ -558,18 +571,55 @@ let AuthenticationService = class AuthenticationService {
|
|
|
558
571
|
if (!isPasswordValid) {
|
|
559
572
|
throw new _common.UnauthorizedException('Current password is incorrect');
|
|
560
573
|
}
|
|
561
|
-
user.password = await _bcrypt.hash(newPassword, BCRYPT_SALT_ROUNDS);
|
|
574
|
+
user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
|
|
562
575
|
await userRepository.save(user);
|
|
563
576
|
}
|
|
564
|
-
/**
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
577
|
+
/** Logout - Revokes all tokens. Stores revocation timestamp in cache. */ async logout(userId) {
|
|
578
|
+
if (!this.cacheManager) {
|
|
579
|
+
// Cache is required for secure logout - tokens cannot be revoked without it
|
|
580
|
+
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.');
|
|
581
|
+
throw new _common.BadRequestException('Logout service temporarily unavailable. Please try again later.');
|
|
582
|
+
}
|
|
583
|
+
const revokedAt = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
|
|
584
|
+
const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
585
|
+
// TTL matches refresh token expiry (tokens older than this are already expired)
|
|
586
|
+
const refreshTokenExpiry = this.authConfig.getRefreshTokenExpiration();
|
|
587
|
+
const ttlMs = this.parseExpiryToMs(refreshTokenExpiry);
|
|
588
|
+
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
589
|
+
this.logger.log(`User ${userId} logged out - all tokens revoked`);
|
|
590
|
+
}
|
|
591
|
+
/** Check if a token is revoked (issued before user's revocation timestamp) */ async isTokenRevoked(userId, tokenIssuedAt) {
|
|
592
|
+
if (!this.cacheManager || !tokenIssuedAt) {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
const cacheKey = `${_config.TOKEN_REVOKED_PREFIX}:${userId}`;
|
|
596
|
+
const revokedAt = await this.cacheManager.get(cacheKey);
|
|
597
|
+
if (!revokedAt) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
// Token is revoked if it was issued before the revocation timestamp
|
|
601
|
+
return tokenIssuedAt < revokedAt;
|
|
602
|
+
}
|
|
603
|
+
/** Parse JWT expiry string to milliseconds */ parseExpiryToMs(expiry) {
|
|
604
|
+
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
605
|
+
if (!match) return 7 * 24 * 60 * 60 * 1000; // Default 7 days
|
|
606
|
+
const value = parseInt(match[1], 10);
|
|
607
|
+
const unit = match[2];
|
|
608
|
+
switch(unit){
|
|
609
|
+
case 's':
|
|
610
|
+
return value * 1000;
|
|
611
|
+
case 'm':
|
|
612
|
+
return value * 60 * 1000;
|
|
613
|
+
case 'h':
|
|
614
|
+
return value * 60 * 60 * 1000;
|
|
615
|
+
case 'd':
|
|
616
|
+
return value * 24 * 60 * 60 * 1000;
|
|
617
|
+
default:
|
|
618
|
+
return 7 * 24 * 60 * 60 * 1000;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// Query Methods
|
|
622
|
+
/** Get user's companies */ async getUserCompanies(userId) {
|
|
573
623
|
this.ensureCompanyFeatureEnabled();
|
|
574
624
|
const permissionRepository = await this.getPermissionRepository();
|
|
575
625
|
const companyRepository = await this.getCompanyRepository();
|
|
@@ -590,9 +640,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
590
640
|
active: true
|
|
591
641
|
}).orderBy('c.name', 'ASC').getMany();
|
|
592
642
|
}
|
|
593
|
-
/**
|
|
594
|
-
* Get branches for a company that user has access to
|
|
595
|
-
*/ async getUserBranches(userId, companyId) {
|
|
643
|
+
/** Get branches for a company that user has access to */ async getUserBranches(userId, companyId) {
|
|
596
644
|
this.ensureCompanyFeatureEnabled();
|
|
597
645
|
const permissionRepository = await this.getPermissionRepository();
|
|
598
646
|
const branchRepository = await this.getBranchRepository();
|
|
@@ -627,9 +675,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
627
675
|
active: true
|
|
628
676
|
}).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
|
|
629
677
|
}
|
|
630
|
-
/**
|
|
631
|
-
* Get company by ID
|
|
632
|
-
*/ async getCompanyById(companyId) {
|
|
678
|
+
/** Get company by ID */ async getCompanyById(companyId) {
|
|
633
679
|
const companyRepository = await this.getCompanyRepository();
|
|
634
680
|
if (!companyRepository) {
|
|
635
681
|
return null;
|
|
@@ -640,9 +686,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
640
686
|
}
|
|
641
687
|
});
|
|
642
688
|
}
|
|
643
|
-
/**
|
|
644
|
-
* Get branch by ID
|
|
645
|
-
*/ async getBranchById(branchId) {
|
|
689
|
+
/** Get branch by ID */ async getBranchById(branchId) {
|
|
646
690
|
const branchRepository = await this.getBranchRepository();
|
|
647
691
|
if (!branchRepository) {
|
|
648
692
|
return null;
|
|
@@ -653,10 +697,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
653
697
|
}
|
|
654
698
|
});
|
|
655
699
|
}
|
|
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) {
|
|
700
|
+
/** Get user info for /auth/me endpoint. Returns user, company, and branch info (no tokens). */ async getUserInfo(userId, companyId, branchId) {
|
|
660
701
|
const userRepository = await this.getUserRepository();
|
|
661
702
|
const user = await userRepository.findOne({
|
|
662
703
|
where: {
|
|
@@ -672,6 +713,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
672
713
|
id: user.id,
|
|
673
714
|
email: user.email,
|
|
674
715
|
name: user.name ?? undefined,
|
|
716
|
+
phone: user.phone ?? undefined,
|
|
675
717
|
profilePictureId: user.profilePictureId ?? undefined
|
|
676
718
|
}
|
|
677
719
|
};
|
|
@@ -707,9 +749,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
707
749
|
}
|
|
708
750
|
return response;
|
|
709
751
|
}
|
|
710
|
-
//
|
|
711
|
-
// HELPER METHODS
|
|
712
|
-
// ==========================================================================
|
|
752
|
+
// Helper Methods
|
|
713
753
|
ensureCompanyFeatureEnabled() {
|
|
714
754
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
715
755
|
throw new _common.BadRequestException('Company feature is not enabled');
|
|
@@ -733,9 +773,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
733
773
|
await userRepository.save(user);
|
|
734
774
|
return user;
|
|
735
775
|
}
|
|
736
|
-
//
|
|
737
|
-
// RESPONSE GENERATORS
|
|
738
|
-
// ==========================================================================
|
|
776
|
+
// Response Generators
|
|
739
777
|
generateLoginResponse(user, companyFeatureEnabled = false) {
|
|
740
778
|
const payload = {
|
|
741
779
|
id: user.id,
|
|
@@ -762,6 +800,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
762
800
|
id: user.id,
|
|
763
801
|
email: user.email,
|
|
764
802
|
name: user.name ?? undefined,
|
|
803
|
+
phone: user.phone ?? null,
|
|
765
804
|
profilePictureId: user.profilePictureId ?? null
|
|
766
805
|
},
|
|
767
806
|
companyFeatureEnabled
|
|
@@ -797,6 +836,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
797
836
|
id: user.id,
|
|
798
837
|
email: user.email,
|
|
799
838
|
name: user.name ?? undefined,
|
|
839
|
+
phone: user.phone ?? null,
|
|
800
840
|
profilePictureId: user.profilePictureId ?? null
|
|
801
841
|
},
|
|
802
842
|
company: {
|
|
@@ -842,30 +882,83 @@ let AuthenticationService = class AuthenticationService {
|
|
|
842
882
|
id: user.id,
|
|
843
883
|
email: user.email,
|
|
844
884
|
name: user.name ?? undefined,
|
|
885
|
+
phone: user.phone ?? null,
|
|
845
886
|
profilePictureId: user.profilePictureId ?? null
|
|
846
887
|
},
|
|
847
888
|
companies: companiesWithBranches
|
|
848
889
|
};
|
|
849
890
|
}
|
|
891
|
+
/** Generate registration response - tokens included only if email verification not required */ generateRegistrationResponse(user, companyFeatureEnabled, requiresEmailVerification) {
|
|
892
|
+
const userInfo = {
|
|
893
|
+
id: user.id,
|
|
894
|
+
email: user.email,
|
|
895
|
+
name: user.name ?? undefined,
|
|
896
|
+
phone: user.phone ?? null,
|
|
897
|
+
profilePictureId: user.profilePictureId ?? null
|
|
898
|
+
};
|
|
899
|
+
if (requiresEmailVerification) {
|
|
900
|
+
return {
|
|
901
|
+
user: userInfo,
|
|
902
|
+
companyFeatureEnabled,
|
|
903
|
+
requiresEmailVerification: true
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
const loginResponse = this.generateLoginResponse(user, companyFeatureEnabled);
|
|
907
|
+
return {
|
|
908
|
+
...loginResponse,
|
|
909
|
+
requiresEmailVerification: false
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
/** Generate registration response with company - tokens included only if email verification not required */ generateRegistrationResponseWithCompany(user, company, branch, requiresEmailVerification = false) {
|
|
913
|
+
const userInfo = {
|
|
914
|
+
id: user.id,
|
|
915
|
+
email: user.email,
|
|
916
|
+
name: user.name ?? undefined,
|
|
917
|
+
phone: user.phone ?? null,
|
|
918
|
+
profilePictureId: user.profilePictureId ?? null
|
|
919
|
+
};
|
|
920
|
+
const companyInfo = {
|
|
921
|
+
id: company.id,
|
|
922
|
+
name: company.name,
|
|
923
|
+
slug: company.slug,
|
|
924
|
+
logoId: company.logoId ?? null
|
|
925
|
+
};
|
|
926
|
+
const branchInfo = branch ? {
|
|
927
|
+
id: branch.id,
|
|
928
|
+
name: branch.name,
|
|
929
|
+
slug: branch.slug,
|
|
930
|
+
logoId: branch.logoId ?? null
|
|
931
|
+
} : undefined;
|
|
932
|
+
if (requiresEmailVerification) {
|
|
933
|
+
return {
|
|
934
|
+
user: userInfo,
|
|
935
|
+
company: companyInfo,
|
|
936
|
+
branch: branchInfo,
|
|
937
|
+
companyFeatureEnabled: true,
|
|
938
|
+
requiresEmailVerification: true
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
const loginResponse = this.generateLoginResponseWithCompany(user, company, branch);
|
|
942
|
+
return {
|
|
943
|
+
...loginResponse,
|
|
944
|
+
requiresEmailVerification: false
|
|
945
|
+
};
|
|
946
|
+
}
|
|
850
947
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
851
|
-
constructor(jwtService, authConfig, dataSourceProvider, sessionService,
|
|
948
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService){
|
|
852
949
|
_define_property(this, "jwtService", void 0);
|
|
853
950
|
_define_property(this, "authConfig", void 0);
|
|
951
|
+
_define_property(this, "cacheManager", void 0);
|
|
854
952
|
_define_property(this, "dataSourceProvider", void 0);
|
|
855
953
|
_define_property(this, "sessionService", void 0);
|
|
856
|
-
_define_property(this, "
|
|
857
|
-
_define_property(this, "injectedPermissionRepository", void 0);
|
|
858
|
-
_define_property(this, "injectedCompanyRepository", void 0);
|
|
859
|
-
_define_property(this, "injectedBranchRepository", void 0);
|
|
954
|
+
_define_property(this, "authEmailService", void 0);
|
|
860
955
|
_define_property(this, "logger", void 0);
|
|
861
956
|
this.jwtService = jwtService;
|
|
862
957
|
this.authConfig = authConfig;
|
|
958
|
+
this.cacheManager = cacheManager;
|
|
863
959
|
this.dataSourceProvider = dataSourceProvider;
|
|
864
960
|
this.sessionService = sessionService;
|
|
865
|
-
this.
|
|
866
|
-
this.injectedPermissionRepository = injectedPermissionRepository;
|
|
867
|
-
this.injectedCompanyRepository = injectedCompanyRepository;
|
|
868
|
-
this.injectedBranchRepository = injectedBranchRepository;
|
|
961
|
+
this.authEmailService = authEmailService;
|
|
869
962
|
this.logger = new _common.Logger(AuthenticationService.name);
|
|
870
963
|
}
|
|
871
964
|
};
|
|
@@ -876,26 +969,20 @@ AuthenticationService = _ts_decorate([
|
|
|
876
969
|
_ts_param(0, (0, _common.Inject)(_jwt.JwtService)),
|
|
877
970
|
_ts_param(1, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
|
|
878
971
|
_ts_param(2, (0, _common.Optional)()),
|
|
879
|
-
_ts_param(2, (0, _common.Inject)(
|
|
972
|
+
_ts_param(2, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
880
973
|
_ts_param(3, (0, _common.Optional)()),
|
|
881
|
-
_ts_param(3, (0, _common.Inject)(
|
|
974
|
+
_ts_param(3, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
|
|
882
975
|
_ts_param(4, (0, _common.Optional)()),
|
|
883
|
-
_ts_param(4, (0,
|
|
976
|
+
_ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
|
|
884
977
|
_ts_param(5, (0, _common.Optional)()),
|
|
885
|
-
_ts_param(5, (0,
|
|
886
|
-
_ts_param(6, (0, _common.Optional)()),
|
|
887
|
-
_ts_param(6, (0, _typeorm.InjectRepository)(_companyentity.Company)),
|
|
888
|
-
_ts_param(7, (0, _common.Optional)()),
|
|
889
|
-
_ts_param(7, (0, _typeorm.InjectRepository)(_companybranchentity.CompanyBranch)),
|
|
978
|
+
_ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
|
|
890
979
|
_ts_metadata("design:type", Function),
|
|
891
980
|
_ts_metadata("design:paramtypes", [
|
|
892
981
|
typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
|
|
893
982
|
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
|
|
983
|
+
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
894
984
|
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
895
985
|
typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
|
|
896
|
-
typeof
|
|
897
|
-
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|
|
898
|
-
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|
|
899
|
-
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository
|
|
986
|
+
typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService
|
|
900
987
|
])
|
|
901
988
|
], AuthenticationService);
|