@flusys/nestjs-auth 1.0.0-beta → 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/cjs/config/auth.constants.js +4 -0
- package/cjs/controllers/authentication.controller.js +167 -5
- package/cjs/controllers/user.controller.js +151 -0
- 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/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 +4 -0
- package/cjs/services/auth-datasource.provider.js +4 -2
- package/cjs/services/auth-email.service.js +452 -0
- package/cjs/services/authentication.service.js +156 -37
- package/cjs/services/index.js +1 -0
- package/cjs/services/user.service.js +147 -11
- package/config/auth.constants.d.ts +1 -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 +11 -0
- package/fesm/controllers/authentication.controller.js +168 -6
- package/fesm/controllers/user.controller.js +151 -0
- package/fesm/dtos/authentication.dto.js +92 -3
- 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 -1
- 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 +4 -0
- package/fesm/services/auth-datasource.provider.js +4 -2
- package/fesm/services/auth-email.service.js +401 -0
- package/fesm/services/authentication.service.js +156 -37
- package/fesm/services/index.js +1 -0
- package/fesm/services/user.service.js +149 -13
- 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 +16 -4
- package/services/index.d.ts +1 -0
- package/services/user.service.d.ts +16 -3
|
@@ -19,6 +19,7 @@ const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
|
19
19
|
const _enums = require("../enums");
|
|
20
20
|
const _config = require("../config");
|
|
21
21
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
22
|
+
const _authemailservice = require("./auth-email.service");
|
|
22
23
|
const _companybranchentity = require("../entities/company-branch.entity");
|
|
23
24
|
const _companyentity = require("../entities/company.entity");
|
|
24
25
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
@@ -130,7 +131,14 @@ let AuthenticationService = class AuthenticationService {
|
|
|
130
131
|
if (!user.isActive) {
|
|
131
132
|
throw new _common.UnauthorizedException('User account is deactivated');
|
|
132
133
|
}
|
|
133
|
-
//
|
|
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
|
+
}
|
|
134
142
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
135
143
|
return this.generateLoginResponse(user);
|
|
136
144
|
}
|
|
@@ -187,10 +195,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
187
195
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
188
196
|
}
|
|
189
197
|
// Registration
|
|
190
|
-
/** Register a new user. If
|
|
198
|
+
/** Register a new user. If email enabled, creates pending registration. Otherwise creates user directly. */ async register(dto, verifyUrlBase) {
|
|
191
199
|
const userRepository = await this.getUserRepository();
|
|
192
|
-
// Check if user exists
|
|
193
|
-
// Note: Generic error message to prevent email enumeration attacks
|
|
194
200
|
const existingUser = await userRepository.findOne({
|
|
195
201
|
where: {
|
|
196
202
|
email: dto.email
|
|
@@ -199,7 +205,33 @@ let AuthenticationService = class AuthenticationService {
|
|
|
199
205
|
if (existingUser) {
|
|
200
206
|
throw new _common.BadRequestException('Registration failed. Please try again or contact support.');
|
|
201
207
|
}
|
|
202
|
-
//
|
|
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();
|
|
203
235
|
const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
204
236
|
const user = userRepository.create({
|
|
205
237
|
name: dto.name,
|
|
@@ -211,38 +243,68 @@ let AuthenticationService = class AuthenticationService {
|
|
|
211
243
|
phoneVerified: false
|
|
212
244
|
});
|
|
213
245
|
await userRepository.save(user);
|
|
214
|
-
// If company feature not enabled, return simple response
|
|
215
246
|
if (!this.authConfig.isCompanyFeatureEnabled()) {
|
|
216
|
-
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.');
|
|
261
|
+
}
|
|
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);
|
|
217
276
|
}
|
|
218
|
-
|
|
219
|
-
|
|
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);
|
|
220
284
|
}
|
|
221
|
-
/**
|
|
285
|
+
/** Shared logic for company registration (join existing or create new) */ async processCompanyRegistration(user, data, requiresEmailVerification) {
|
|
222
286
|
const companyRepository = await this.getCompanyRepository();
|
|
223
287
|
const branchRepository = await this.getBranchRepository();
|
|
224
288
|
const permissionRepository = await this.getPermissionRepository();
|
|
225
289
|
if (!companyRepository || !branchRepository || !permissionRepository) {
|
|
226
|
-
return this.
|
|
290
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
227
291
|
}
|
|
228
292
|
let company = null;
|
|
229
293
|
let branch = null;
|
|
230
|
-
|
|
231
|
-
if (dto.companySlug) {
|
|
294
|
+
if (data.companySlug) {
|
|
232
295
|
company = await companyRepository.findOne({
|
|
233
296
|
where: {
|
|
234
|
-
slug:
|
|
297
|
+
slug: data.companySlug,
|
|
235
298
|
isActive: true
|
|
236
299
|
}
|
|
237
300
|
});
|
|
238
301
|
if (!company) {
|
|
239
302
|
throw new _common.BadRequestException('Company not found or inactive');
|
|
240
303
|
}
|
|
241
|
-
|
|
242
|
-
if (dto.branchSlug) {
|
|
304
|
+
if (data.branchSlug) {
|
|
243
305
|
branch = await branchRepository.findOne({
|
|
244
306
|
where: {
|
|
245
|
-
slug:
|
|
307
|
+
slug: data.branchSlug,
|
|
246
308
|
companyId: company.id,
|
|
247
309
|
isActive: true
|
|
248
310
|
}
|
|
@@ -251,7 +313,6 @@ let AuthenticationService = class AuthenticationService {
|
|
|
251
313
|
throw new _common.BadRequestException('Branch not found or inactive');
|
|
252
314
|
}
|
|
253
315
|
} else {
|
|
254
|
-
// Get first active branch
|
|
255
316
|
branch = await branchRepository.findOne({
|
|
256
317
|
where: {
|
|
257
318
|
companyId: company.id,
|
|
@@ -263,9 +324,8 @@ let AuthenticationService = class AuthenticationService {
|
|
|
263
324
|
}
|
|
264
325
|
});
|
|
265
326
|
}
|
|
266
|
-
} else if (
|
|
267
|
-
const slug = this.generateSlug(
|
|
268
|
-
// Check if slug exists
|
|
327
|
+
} else if (data.newCompanyName) {
|
|
328
|
+
const slug = this.generateSlug(data.newCompanyName);
|
|
269
329
|
const existingCompany = await companyRepository.findOne({
|
|
270
330
|
where: {
|
|
271
331
|
slug
|
|
@@ -274,16 +334,14 @@ let AuthenticationService = class AuthenticationService {
|
|
|
274
334
|
if (existingCompany) {
|
|
275
335
|
throw new _common.BadRequestException('Company creation failed. Please try a different name.');
|
|
276
336
|
}
|
|
277
|
-
// Create company
|
|
278
337
|
company = companyRepository.create({
|
|
279
|
-
name:
|
|
338
|
+
name: data.newCompanyName,
|
|
280
339
|
slug,
|
|
281
|
-
phone:
|
|
282
|
-
address:
|
|
340
|
+
phone: data.newCompanyPhone,
|
|
341
|
+
address: data.newCompanyAddress,
|
|
283
342
|
isActive: true
|
|
284
343
|
});
|
|
285
344
|
await companyRepository.save(company);
|
|
286
|
-
// Create default branch
|
|
287
345
|
branch = branchRepository.create({
|
|
288
346
|
name: 'Main Branch',
|
|
289
347
|
slug: 'main-branch',
|
|
@@ -292,16 +350,13 @@ let AuthenticationService = class AuthenticationService {
|
|
|
292
350
|
});
|
|
293
351
|
await branchRepository.save(branch);
|
|
294
352
|
}
|
|
295
|
-
// Create permissions if company exists
|
|
296
353
|
if (company) {
|
|
297
|
-
// Create company permission
|
|
298
354
|
const companyPermission = permissionRepository.create({
|
|
299
355
|
userId: user.id,
|
|
300
356
|
permissionType: _enums.PermissionType.COMPANY,
|
|
301
357
|
targetId: company.id
|
|
302
358
|
});
|
|
303
359
|
await permissionRepository.save(companyPermission);
|
|
304
|
-
// Create branch permission if branch exists
|
|
305
360
|
if (branch) {
|
|
306
361
|
const branchPermission = permissionRepository.create({
|
|
307
362
|
userId: user.id,
|
|
@@ -310,10 +365,9 @@ let AuthenticationService = class AuthenticationService {
|
|
|
310
365
|
});
|
|
311
366
|
await permissionRepository.save(branchPermission);
|
|
312
367
|
}
|
|
313
|
-
return this.
|
|
368
|
+
return this.generateRegistrationResponseWithCompany(user, company, branch ?? undefined, requiresEmailVerification);
|
|
314
369
|
}
|
|
315
|
-
|
|
316
|
-
return this.generateLoginResponse(user, true);
|
|
370
|
+
return this.generateRegistrationResponse(user, true, requiresEmailVerification);
|
|
317
371
|
}
|
|
318
372
|
/** Generate URL-friendly slug from name */ generateSlug(name) {
|
|
319
373
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100);
|
|
@@ -666,6 +720,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
666
720
|
id: user.id,
|
|
667
721
|
email: user.email,
|
|
668
722
|
name: user.name ?? undefined,
|
|
723
|
+
phone: user.phone ?? undefined,
|
|
669
724
|
profilePictureId: user.profilePictureId ?? undefined
|
|
670
725
|
}
|
|
671
726
|
};
|
|
@@ -752,6 +807,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
752
807
|
id: user.id,
|
|
753
808
|
email: user.email,
|
|
754
809
|
name: user.name ?? undefined,
|
|
810
|
+
phone: user.phone ?? null,
|
|
755
811
|
profilePictureId: user.profilePictureId ?? null
|
|
756
812
|
},
|
|
757
813
|
companyFeatureEnabled
|
|
@@ -787,6 +843,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
787
843
|
id: user.id,
|
|
788
844
|
email: user.email,
|
|
789
845
|
name: user.name ?? undefined,
|
|
846
|
+
phone: user.phone ?? null,
|
|
790
847
|
profilePictureId: user.profilePictureId ?? null
|
|
791
848
|
},
|
|
792
849
|
company: {
|
|
@@ -832,18 +889,76 @@ let AuthenticationService = class AuthenticationService {
|
|
|
832
889
|
id: user.id,
|
|
833
890
|
email: user.email,
|
|
834
891
|
name: user.name ?? undefined,
|
|
892
|
+
phone: user.phone ?? null,
|
|
835
893
|
profilePictureId: user.profilePictureId ?? null
|
|
836
894
|
},
|
|
837
895
|
companies: companiesWithBranches
|
|
838
896
|
};
|
|
839
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
|
+
}
|
|
840
954
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
841
|
-
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
|
|
955
|
+
constructor(jwtService, authConfig, cacheManager, dataSourceProvider, sessionService, authEmailService, injectedUserRepository, injectedPermissionRepository, injectedCompanyRepository, injectedBranchRepository){
|
|
842
956
|
_define_property(this, "jwtService", void 0);
|
|
843
957
|
_define_property(this, "authConfig", void 0);
|
|
844
958
|
_define_property(this, "cacheManager", void 0);
|
|
845
959
|
_define_property(this, "dataSourceProvider", void 0);
|
|
846
960
|
_define_property(this, "sessionService", void 0);
|
|
961
|
+
_define_property(this, "authEmailService", void 0);
|
|
847
962
|
_define_property(this, "injectedUserRepository", void 0);
|
|
848
963
|
_define_property(this, "injectedPermissionRepository", void 0);
|
|
849
964
|
_define_property(this, "injectedCompanyRepository", void 0);
|
|
@@ -854,6 +969,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
854
969
|
this.cacheManager = cacheManager;
|
|
855
970
|
this.dataSourceProvider = dataSourceProvider;
|
|
856
971
|
this.sessionService = sessionService;
|
|
972
|
+
this.authEmailService = authEmailService;
|
|
857
973
|
this.injectedUserRepository = injectedUserRepository;
|
|
858
974
|
this.injectedPermissionRepository = injectedPermissionRepository;
|
|
859
975
|
this.injectedCompanyRepository = injectedCompanyRepository;
|
|
@@ -874,13 +990,15 @@ AuthenticationService = _ts_decorate([
|
|
|
874
990
|
_ts_param(4, (0, _common.Optional)()),
|
|
875
991
|
_ts_param(4, (0, _common.Inject)(_companyselectionsessionservice.CompanySelectionSessionService)),
|
|
876
992
|
_ts_param(5, (0, _common.Optional)()),
|
|
877
|
-
_ts_param(5, (0,
|
|
993
|
+
_ts_param(5, (0, _common.Inject)(_authemailservice.AuthEmailService)),
|
|
878
994
|
_ts_param(6, (0, _common.Optional)()),
|
|
879
|
-
_ts_param(6, (0, _typeorm.InjectRepository)(
|
|
995
|
+
_ts_param(6, (0, _typeorm.InjectRepository)(_userentity.User)),
|
|
880
996
|
_ts_param(7, (0, _common.Optional)()),
|
|
881
|
-
_ts_param(7, (0, _typeorm.InjectRepository)(
|
|
997
|
+
_ts_param(7, (0, _typeorm.InjectRepository)(_usercompanypermissionentity.UserCompanyPermission)),
|
|
882
998
|
_ts_param(8, (0, _common.Optional)()),
|
|
883
|
-
_ts_param(8, (0, _typeorm.InjectRepository)(
|
|
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)),
|
|
884
1002
|
_ts_metadata("design:type", Function),
|
|
885
1003
|
_ts_metadata("design:paramtypes", [
|
|
886
1004
|
typeof _jwt.JwtService === "undefined" ? Object : _jwt.JwtService,
|
|
@@ -888,6 +1006,7 @@ AuthenticationService = _ts_decorate([
|
|
|
888
1006
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
889
1007
|
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
890
1008
|
typeof _companyselectionsessionservice.CompanySelectionSessionService === "undefined" ? Object : _companyselectionsessionservice.CompanySelectionSessionService,
|
|
1009
|
+
typeof _authemailservice.AuthEmailService === "undefined" ? Object : _authemailservice.AuthEmailService,
|
|
891
1010
|
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|
|
892
1011
|
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|
|
893
1012
|
typeof _typeorm1.Repository === "undefined" ? Object : _typeorm1.Repository,
|
package/cjs/services/index.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./authentication.service"), exports);
|
|
6
|
+
_export_star(require("./auth-email.service"), exports);
|
|
6
7
|
_export_star(require("./company-selection-session.service"), exports);
|
|
7
8
|
_export_star(require("./user.service"), exports);
|
|
8
9
|
_export_star(require("./company.service"), exports);
|
|
@@ -17,6 +17,7 @@ const _authconfigservice = require("./auth-config.service");
|
|
|
17
17
|
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
18
18
|
const _config = require("../config");
|
|
19
19
|
const _enums = require("../enums");
|
|
20
|
+
const _userenricherinterface = require("../interfaces/user-enricher.interface");
|
|
20
21
|
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
21
22
|
const _userentity = require("../entities/user.entity");
|
|
22
23
|
function _define_property(obj, key, value) {
|
|
@@ -170,8 +171,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
170
171
|
/** Convert DTO to entity (hash password before saving) */ async convertSingleDtoToEntity(dto, user) {
|
|
171
172
|
// Repository initialization is handled automatically by RequestScopedApiService
|
|
172
173
|
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
173
|
-
// Hash password if provided
|
|
174
|
-
if (dto.password) {
|
|
174
|
+
// Hash password if provided (CreateUserDto has password field)
|
|
175
|
+
if ('password' in dto && dto.password) {
|
|
175
176
|
entity.password = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
176
177
|
}
|
|
177
178
|
return entity;
|
|
@@ -213,7 +214,11 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
213
214
|
}
|
|
214
215
|
/** Add company permission-based isActive check when company feature enabled. Returns isRaw: true for computed fields. */ async getExtraManipulateQuery(query, filterAndPaginationDto, user) {
|
|
215
216
|
// Repository initialization is handled automatically by RequestScopedApiService
|
|
216
|
-
|
|
217
|
+
let result = await super.getExtraManipulateQuery(query, filterAndPaginationDto, user);
|
|
218
|
+
// Apply user enricher query modifications if available
|
|
219
|
+
if (this.userEnricher?.enrichListQuery) {
|
|
220
|
+
await this.userEnricher.enrichListQuery(query, user);
|
|
221
|
+
}
|
|
217
222
|
// Only apply company filtering if:
|
|
218
223
|
// 1. Company feature is enabled at bootstrap
|
|
219
224
|
// 2. User has company context
|
|
@@ -333,16 +338,140 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
333
338
|
await queryRunner.release();
|
|
334
339
|
}
|
|
335
340
|
}
|
|
336
|
-
/**
|
|
337
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Get user profile with extras from enricher
|
|
343
|
+
* Returns base user data plus any extra data provided by enricher (roles, permissions, etc.)
|
|
344
|
+
*/ async getProfileWithExtras(userId, user) {
|
|
345
|
+
await this.ensureRepositoryInitialized();
|
|
346
|
+
const userEntity = await this.repository.findOne({
|
|
347
|
+
where: {
|
|
348
|
+
id: userId
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
if (!userEntity) {
|
|
352
|
+
throw new _common.NotFoundException('User not found');
|
|
353
|
+
}
|
|
354
|
+
const userData = this.convertEntityToResponseDto(userEntity, false);
|
|
355
|
+
// Get extras from enricher if available
|
|
356
|
+
let extras;
|
|
357
|
+
if (this.userEnricher?.getProfileExtras) {
|
|
358
|
+
extras = await this.userEnricher.getProfileExtras(userId, user);
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
user: userData,
|
|
362
|
+
extras
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get enriched user list
|
|
367
|
+
* Applies enricher transformations after query execution
|
|
368
|
+
*/ async getEnrichedList(users, user) {
|
|
369
|
+
if (this.userEnricher?.enrichListItems) {
|
|
370
|
+
return this.userEnricher.enrichListItems(users, user);
|
|
371
|
+
}
|
|
372
|
+
return users;
|
|
373
|
+
}
|
|
374
|
+
// ============================================
|
|
375
|
+
// Multi-Step Profile Methods (via Enricher)
|
|
376
|
+
// ============================================
|
|
377
|
+
/**
|
|
378
|
+
* Get profile section definitions from enricher
|
|
379
|
+
*/ async getProfileSections() {
|
|
380
|
+
if (this.userEnricher?.getProfileSections) {
|
|
381
|
+
return this.userEnricher.getProfileSections();
|
|
382
|
+
}
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get specific profile section data
|
|
387
|
+
*/ async getProfileSectionData(userId, sectionId, user) {
|
|
388
|
+
await this.ensureRepositoryInitialized();
|
|
389
|
+
// Verify user exists
|
|
390
|
+
const userEntity = await this.repository.findOne({
|
|
391
|
+
where: {
|
|
392
|
+
id: userId
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
if (!userEntity) {
|
|
396
|
+
throw new _common.NotFoundException('User not found');
|
|
397
|
+
}
|
|
398
|
+
if (this.userEnricher?.getProfileSectionData) {
|
|
399
|
+
return this.userEnricher.getProfileSectionData(userId, sectionId, user);
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Update specific profile section
|
|
405
|
+
*/ async updateProfileSection(userId, sectionId, data, user) {
|
|
406
|
+
await this.ensureRepositoryInitialized();
|
|
407
|
+
if (!this.dataSource) {
|
|
408
|
+
throw new _common.BadRequestException('DataSource not available');
|
|
409
|
+
}
|
|
410
|
+
// Verify user exists
|
|
411
|
+
const userEntity = await this.repository.findOne({
|
|
412
|
+
where: {
|
|
413
|
+
id: userId
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
if (!userEntity) {
|
|
417
|
+
throw new _common.NotFoundException('User not found');
|
|
418
|
+
}
|
|
419
|
+
if (!this.userEnricher?.updateProfileSection) {
|
|
420
|
+
throw new _common.BadRequestException('Profile sections not supported');
|
|
421
|
+
}
|
|
422
|
+
const queryRunner = this.dataSource.createQueryRunner();
|
|
423
|
+
await queryRunner.connect();
|
|
424
|
+
await queryRunner.startTransaction();
|
|
425
|
+
try {
|
|
426
|
+
await this.userEnricher.updateProfileSection(userId, sectionId, data, queryRunner);
|
|
427
|
+
await queryRunner.commitTransaction();
|
|
428
|
+
await this.clearCacheForId([
|
|
429
|
+
userEntity
|
|
430
|
+
]);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
await queryRunner.rollbackTransaction();
|
|
433
|
+
_utils.ErrorHandler.logError(this.logger, error, 'updateProfileSection', {
|
|
434
|
+
entity: this.entityName,
|
|
435
|
+
sectionId
|
|
436
|
+
});
|
|
437
|
+
_utils.ErrorHandler.rethrowError(error);
|
|
438
|
+
} finally{
|
|
439
|
+
await queryRunner.release();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Get profile completion percentage
|
|
444
|
+
*/ async getProfileCompletion(userId, user) {
|
|
445
|
+
await this.ensureRepositoryInitialized();
|
|
446
|
+
// Verify user exists
|
|
447
|
+
const userEntity = await this.repository.findOne({
|
|
448
|
+
where: {
|
|
449
|
+
id: userId
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
if (!userEntity) {
|
|
453
|
+
throw new _common.NotFoundException('User not found');
|
|
454
|
+
}
|
|
455
|
+
if (this.userEnricher?.calculateProfileCompletion) {
|
|
456
|
+
return this.userEnricher.calculateProfileCompletion(userId, user);
|
|
457
|
+
}
|
|
458
|
+
return 100; // Default to 100% if no enricher
|
|
459
|
+
}
|
|
460
|
+
/** Update user profile with password handling and extras */ async profile(dto, user) {
|
|
461
|
+
// Ensure repository and dataSource are initialized
|
|
462
|
+
await this.ensureRepositoryInitialized();
|
|
338
463
|
if (!this.dataSource) {
|
|
339
464
|
throw new _common.BadRequestException('DataSource not available');
|
|
340
465
|
}
|
|
466
|
+
const userId = dto.id ?? user.id;
|
|
467
|
+
// Validate extras if enricher provides validation
|
|
468
|
+
if (dto.extras && this.userEnricher?.validateProfileExtras) {
|
|
469
|
+
await this.userEnricher.validateProfileExtras(userId, dto.extras, user);
|
|
470
|
+
}
|
|
341
471
|
const queryRunner = this.dataSource.createQueryRunner();
|
|
342
472
|
await queryRunner.connect();
|
|
343
473
|
await queryRunner.startTransaction();
|
|
344
474
|
try {
|
|
345
|
-
const userId = dto.id ?? user.id;
|
|
346
475
|
const findUser = await queryRunner.manager.findOne(this.repository.target, {
|
|
347
476
|
where: {
|
|
348
477
|
id: userId
|
|
@@ -361,10 +490,14 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
361
490
|
const salt = await _bcrypt.genSalt();
|
|
362
491
|
findUser.password = await _bcrypt.hash(dto.password, salt);
|
|
363
492
|
}
|
|
364
|
-
// Merge other fields (excluding password)
|
|
365
|
-
const { password, ...restDto } = dto;
|
|
493
|
+
// Merge other fields (excluding password and extras)
|
|
494
|
+
const { password, extras, ...restDto } = dto;
|
|
366
495
|
Object.assign(findUser, restDto);
|
|
367
496
|
await queryRunner.manager.save(this.repository.target, findUser);
|
|
497
|
+
// Handle extras update via enricher
|
|
498
|
+
if (extras && this.userEnricher?.updateProfileExtras) {
|
|
499
|
+
await this.userEnricher.updateProfileExtras(userId, extras, queryRunner);
|
|
500
|
+
}
|
|
368
501
|
await queryRunner.commitTransaction();
|
|
369
502
|
await this.clearCacheForId([
|
|
370
503
|
findUser
|
|
@@ -380,8 +513,8 @@ let UserService = class UserService extends _classes.RequestScopedApiService {
|
|
|
380
513
|
await queryRunner.release();
|
|
381
514
|
}
|
|
382
515
|
}
|
|
383
|
-
constructor(cacheManager, utilsService, dataSourceProvider, authConfig){
|
|
384
|
-
super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig;
|
|
516
|
+
constructor(cacheManager, utilsService, dataSourceProvider, authConfig, userEnricher){
|
|
517
|
+
super('user', null, cacheManager, utilsService, UserService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "authConfig", void 0), _define_property(this, "userEnricher", void 0), _define_property(this, "permissionRepository", void 0), _define_property(this, "dataSource", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.dataSourceProvider = dataSourceProvider, this.authConfig = authConfig, this.userEnricher = userEnricher;
|
|
385
518
|
}
|
|
386
519
|
};
|
|
387
520
|
UserService = _ts_decorate([
|
|
@@ -392,11 +525,14 @@ UserService = _ts_decorate([
|
|
|
392
525
|
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
393
526
|
_ts_param(2, (0, _common.Inject)(_authdatasourceprovider.AuthDataSourceProvider)),
|
|
394
527
|
_ts_param(3, (0, _common.Inject)(_authconfigservice.AuthConfigService)),
|
|
528
|
+
_ts_param(4, (0, _common.Optional)()),
|
|
529
|
+
_ts_param(4, (0, _common.Inject)(_config.USER_ENRICHER)),
|
|
395
530
|
_ts_metadata("design:type", Function),
|
|
396
531
|
_ts_metadata("design:paramtypes", [
|
|
397
532
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
398
533
|
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
399
534
|
typeof _authdatasourceprovider.AuthDataSourceProvider === "undefined" ? Object : _authdatasourceprovider.AuthDataSourceProvider,
|
|
400
|
-
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService
|
|
535
|
+
typeof _authconfigservice.AuthConfigService === "undefined" ? Object : _authconfigservice.AuthConfigService,
|
|
536
|
+
typeof _userenricherinterface.IUserEnricher === "undefined" ? Object : _userenricherinterface.IUserEnricher
|
|
401
537
|
])
|
|
402
538
|
], UserService);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const AUTH_MODULE_OPTIONS = "AUTH_MODULE_OPTIONS";
|
|
2
|
+
export declare const USER_ENRICHER = "USER_ENRICHER";
|
|
2
3
|
export declare const BCRYPT_SALT_ROUNDS = 12;
|
|
3
4
|
export declare const SESSION_CACHE_PREFIX = "auth:session";
|
|
4
5
|
export declare const TOKEN_REVOKED_PREFIX = "auth:revoked";
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
2
2
|
import { Request } from 'express';
|
|
3
|
-
import { ChangePasswordDto, CompanySelectionResponseDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto } from '../dtos/authentication.dto';
|
|
3
|
+
import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
|
|
4
|
+
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
4
5
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
6
|
+
import { AuthEmailService } from '../services/auth-email.service';
|
|
5
7
|
import { AuthenticationService } from '../services/authentication.service';
|
|
6
8
|
export declare class AuthenticationController {
|
|
7
9
|
private readonly authService;
|
|
8
10
|
private readonly authConfig?;
|
|
9
|
-
|
|
11
|
+
private readonly authEmailService?;
|
|
12
|
+
constructor(authService: AuthenticationService, authConfig?: AuthConfigService, authEmailService?: AuthEmailService);
|
|
13
|
+
private buildAuthUrl;
|
|
10
14
|
login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto>;
|
|
11
|
-
register(registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
|
|
15
|
+
register(req: Request, registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
|
|
12
16
|
refreshToken(req: Request, refreshTokenDto: RefreshTokenDto): Promise<{
|
|
13
17
|
accessToken: string;
|
|
14
18
|
}>;
|
|
15
19
|
getCurrentUser(user: ILoggedUserInfo): Promise<MeResponseDto>;
|
|
16
|
-
logout(userId: string): Promise<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
logout(userId: string): Promise<MessageResponseDto>;
|
|
21
|
+
changePassword(userId: string, dto: ChangePasswordDto): Promise<MessageResponseDto>;
|
|
22
|
+
forgotPassword(req: Request, dto: ForgotPasswordDto): Promise<MessageResponseDto>;
|
|
23
|
+
resetPassword(dto: ResetPasswordDto): Promise<MessageResponseDto>;
|
|
24
|
+
resendVerification(req: Request, dto: ResendVerificationDto): Promise<MessageResponseDto>;
|
|
25
|
+
verifyEmail(dto: VerifyEmailDto): Promise<MessageResponseDto | RegistrationResponseDto>;
|
|
22
26
|
}
|