@flusys/nestjs-auth 1.0.1 → 1.1.0
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 +61 -13
- package/cjs/controllers/authentication.controller.js +2 -173
- package/cjs/controllers/email-verification.controller.js +220 -0
- package/cjs/controllers/index.js +1 -0
- package/cjs/entities/index.js +16 -17
- package/cjs/modules/auth.module.js +33 -24
- package/cjs/services/auth-config.service.js +11 -7
- package/cjs/services/auth-datasource.provider.js +15 -27
- package/cjs/services/authentication.service.js +34 -29
- package/controllers/authentication.controller.d.ts +2 -10
- package/controllers/email-verification.controller.d.ts +20 -0
- package/controllers/index.d.ts +1 -0
- package/entities/index.d.ts +5 -10
- package/fesm/controllers/authentication.controller.js +3 -174
- package/fesm/controllers/email-verification.controller.js +210 -0
- package/fesm/controllers/index.js +1 -0
- package/fesm/entities/index.js +17 -9
- package/fesm/modules/auth.module.js +35 -26
- package/fesm/services/auth-config.service.js +11 -7
- package/fesm/services/auth-datasource.provider.js +15 -27
- package/fesm/services/authentication.service.js +34 -29
- package/modules/auth.module.d.ts +3 -1
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +1 -0
- package/services/auth-datasource.provider.d.ts +1 -0
- package/services/authentication.service.d.ts +1 -0
|
@@ -46,9 +46,9 @@ let AuthModule = class AuthModule {
|
|
|
46
46
|
...options.providers ?? []
|
|
47
47
|
], options.imports);
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
static buildModule(options, providers, externalImports = []) {
|
|
50
50
|
const { global = false, includeController = true, bootstrapAppConfig } = options;
|
|
51
|
-
const
|
|
51
|
+
const featureFlags = this.getFeatureFlags(bootstrapAppConfig);
|
|
52
52
|
return {
|
|
53
53
|
module: AuthModule,
|
|
54
54
|
global,
|
|
@@ -56,15 +56,21 @@ let AuthModule = class AuthModule {
|
|
|
56
56
|
...externalImports,
|
|
57
57
|
...this.buildImports()
|
|
58
58
|
],
|
|
59
|
-
controllers: this.buildControllers(includeController,
|
|
59
|
+
controllers: this.buildControllers(includeController, featureFlags),
|
|
60
60
|
providers: [
|
|
61
61
|
...providers,
|
|
62
|
-
...this.buildProviders(
|
|
62
|
+
...this.buildProviders(featureFlags)
|
|
63
63
|
],
|
|
64
|
-
exports: this.buildExports(
|
|
64
|
+
exports: this.buildExports(featureFlags)
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
static getFeatureFlags(bootstrapAppConfig) {
|
|
68
|
+
return {
|
|
69
|
+
enableCompanyFeature: bootstrapAppConfig?.enableCompanyFeature ?? false,
|
|
70
|
+
enableEmailVerification: bootstrapAppConfig?.enableEmailVerification ?? true
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
static buildImports() {
|
|
68
74
|
return [
|
|
69
75
|
_passport.PassportModule.register({
|
|
70
76
|
defaultStrategy: 'jwt'
|
|
@@ -74,35 +80,40 @@ let AuthModule = class AuthModule {
|
|
|
74
80
|
_modules.UtilsModule
|
|
75
81
|
];
|
|
76
82
|
}
|
|
77
|
-
|
|
83
|
+
static buildControllers(includeController, flags) {
|
|
78
84
|
if (!includeController) return [];
|
|
79
85
|
const controllers = [
|
|
80
86
|
_controllers.AuthenticationController,
|
|
81
87
|
_controllers.UserController
|
|
82
88
|
];
|
|
83
|
-
if (
|
|
89
|
+
if (flags.enableEmailVerification) {
|
|
90
|
+
controllers.push(_controllers.EmailVerificationController);
|
|
91
|
+
}
|
|
92
|
+
if (flags.enableCompanyFeature) {
|
|
84
93
|
controllers.push(_controllers.CompanySelectionController, _controllers.CompanyController, _controllers.BranchController, _controllers.UserPermissionController);
|
|
85
94
|
}
|
|
86
95
|
return controllers;
|
|
87
96
|
}
|
|
88
|
-
|
|
97
|
+
static buildProviders(flags) {
|
|
89
98
|
const providers = [
|
|
90
99
|
_authconfigservice.AuthConfigService,
|
|
91
100
|
_authdatasourceprovider.AuthDataSourceProvider,
|
|
92
101
|
_jwtstrategy.JwtStrategy,
|
|
93
102
|
_guards.JwtAuthGuard,
|
|
94
103
|
_services.AuthenticationService,
|
|
95
|
-
_services.AuthEmailService,
|
|
96
104
|
_settokeninterceptor.SetToken,
|
|
97
105
|
_cleartokeninterceptor.ClearToken,
|
|
98
106
|
_services.UserService
|
|
99
107
|
];
|
|
100
|
-
if (
|
|
108
|
+
if (flags.enableEmailVerification) {
|
|
109
|
+
providers.push(_services.AuthEmailService);
|
|
110
|
+
}
|
|
111
|
+
if (flags.enableCompanyFeature) {
|
|
101
112
|
providers.push(_services.CompanySelectionSessionService, _services.CompanyService, _services.BranchService, _services.UserPermissionService);
|
|
102
113
|
}
|
|
103
114
|
return providers;
|
|
104
115
|
}
|
|
105
|
-
|
|
116
|
+
static buildExports(flags) {
|
|
106
117
|
const exports1 = [
|
|
107
118
|
_passport.PassportModule,
|
|
108
119
|
_authconstants.AUTH_MODULE_OPTIONS,
|
|
@@ -111,15 +122,17 @@ let AuthModule = class AuthModule {
|
|
|
111
122
|
_jwtstrategy.JwtStrategy,
|
|
112
123
|
_guards.JwtAuthGuard,
|
|
113
124
|
_services.AuthenticationService,
|
|
114
|
-
_services.AuthEmailService,
|
|
115
125
|
_services.UserService
|
|
116
126
|
];
|
|
117
|
-
if (
|
|
127
|
+
if (flags.enableEmailVerification) {
|
|
128
|
+
exports1.push(_services.AuthEmailService);
|
|
129
|
+
}
|
|
130
|
+
if (flags.enableCompanyFeature) {
|
|
118
131
|
exports1.push(_services.CompanySelectionSessionService, _services.CompanyService, _services.BranchService, _services.UserPermissionService);
|
|
119
132
|
}
|
|
120
133
|
return exports1;
|
|
121
134
|
}
|
|
122
|
-
|
|
135
|
+
static createAsyncProviders(options) {
|
|
123
136
|
const staticOptions = {
|
|
124
137
|
global: options.global,
|
|
125
138
|
includeController: options.includeController,
|
|
@@ -176,15 +189,11 @@ let AuthModule = class AuthModule {
|
|
|
176
189
|
}
|
|
177
190
|
];
|
|
178
191
|
}
|
|
179
|
-
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
_entities.UserCompanyPermission
|
|
185
|
-
] : [
|
|
186
|
-
_entities.User
|
|
187
|
-
];
|
|
192
|
+
static getEntities(config) {
|
|
193
|
+
return (0, _entities.getEntitiesByConfig)({
|
|
194
|
+
enableCompanyFeature: config.enableCompanyFeature ?? false,
|
|
195
|
+
enableEmailVerification: config.enableEmailVerification ?? true
|
|
196
|
+
});
|
|
188
197
|
}
|
|
189
198
|
};
|
|
190
199
|
AuthModule = _ts_decorate([
|
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "AuthConfigService", {
|
|
|
8
8
|
return AuthConfigService;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
const _config = require("@flusys/nestjs-core/config");
|
|
11
12
|
const _common = require("@nestjs/common");
|
|
12
13
|
const _authconstants = require("../config/auth.constants");
|
|
13
14
|
const _authmoduleoptionsinterface = require("../interfaces/auth-module-options.interface");
|
|
@@ -50,21 +51,24 @@ let AuthConfigService = class AuthConfigService {
|
|
|
50
51
|
isCompanyFeatureEnabled() {
|
|
51
52
|
return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
52
53
|
}
|
|
54
|
+
isEmailVerificationEnabled() {
|
|
55
|
+
return this.options.bootstrapAppConfig?.enableEmailVerification ?? true;
|
|
56
|
+
}
|
|
53
57
|
// JWT Configuration
|
|
54
|
-
|
|
58
|
+
getJwtSecret() {
|
|
55
59
|
if (!this.config.jwtSecret) {
|
|
56
60
|
throw new _common.InternalServerErrorException('[AuthModule] JWT_SECRET is required - set via environment or module config');
|
|
57
61
|
}
|
|
58
62
|
return this.config.jwtSecret;
|
|
59
63
|
}
|
|
60
|
-
|
|
64
|
+
getJwtExpiration() {
|
|
61
65
|
return this.config.jwtExpiration ?? '1h';
|
|
62
66
|
}
|
|
63
|
-
|
|
67
|
+
getRefreshTokenSecret() {
|
|
64
68
|
if (this.config.refreshTokenSecret) {
|
|
65
69
|
return this.config.refreshTokenSecret;
|
|
66
70
|
}
|
|
67
|
-
if (
|
|
71
|
+
if (_config.envConfig.isProduction()) {
|
|
68
72
|
throw new _common.InternalServerErrorException('[AuthModule] REFRESH_TOKEN_SECRET is required in production - ' + 'using the same secret for access and refresh tokens is a security risk');
|
|
69
73
|
}
|
|
70
74
|
if (!this.refreshTokenSecretWarned) {
|
|
@@ -73,14 +77,14 @@ let AuthConfigService = class AuthConfigService {
|
|
|
73
77
|
}
|
|
74
78
|
return this.getJwtSecret();
|
|
75
79
|
}
|
|
76
|
-
|
|
80
|
+
getRefreshTokenExpiration() {
|
|
77
81
|
return this.config.refreshTokenExpiration ?? '7d';
|
|
78
82
|
}
|
|
79
|
-
|
|
83
|
+
getRefreshTokenCookieName() {
|
|
80
84
|
return this.config.refreshTokenCookieName ?? 'fsn_refresh_token';
|
|
81
85
|
}
|
|
82
86
|
// Frontend Configuration
|
|
83
|
-
|
|
87
|
+
getFrontendUrl() {
|
|
84
88
|
return this.config.frontendUrl;
|
|
85
89
|
}
|
|
86
90
|
constructor(injectedOptions){
|
|
@@ -83,8 +83,7 @@ function _ts_param(paramIndex, decorator) {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.MultiTenantDataSourceService {
|
|
86
|
-
|
|
87
|
-
/** Override to access Auth-specific static properties */ async getSingleDataSource() {
|
|
86
|
+
async getSingleDataSource() {
|
|
88
87
|
if (!AuthDataSourceProvider.singleDataSource) {
|
|
89
88
|
if (AuthDataSourceProvider.singleConnectionLock) {
|
|
90
89
|
return AuthDataSourceProvider.singleConnectionLock;
|
|
@@ -108,18 +107,15 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
108
107
|
}
|
|
109
108
|
return AuthDataSourceProvider.singleDataSource;
|
|
110
109
|
}
|
|
111
|
-
|
|
112
|
-
// Return existing initialized connection from Auth-specific cache
|
|
110
|
+
async getOrCreateTenantConnection(tenant) {
|
|
113
111
|
const existing = AuthDataSourceProvider.tenantConnections.get(tenant.id);
|
|
114
112
|
if (existing?.isInitialized) {
|
|
115
113
|
return existing;
|
|
116
114
|
}
|
|
117
|
-
// If another request is creating this tenant's connection, wait for it
|
|
118
115
|
const pendingConnection = AuthDataSourceProvider.connectionLocks.get(tenant.id);
|
|
119
116
|
if (pendingConnection) {
|
|
120
117
|
return pendingConnection;
|
|
121
118
|
}
|
|
122
|
-
// Create connection with lock to prevent race conditions
|
|
123
119
|
const config = this.buildTenantDatabaseConfig(tenant);
|
|
124
120
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
125
121
|
AuthDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
|
|
@@ -131,8 +127,7 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
131
127
|
AuthDataSourceProvider.connectionLocks.delete(tenant.id);
|
|
132
128
|
}
|
|
133
129
|
}
|
|
134
|
-
|
|
135
|
-
/** Build parent options from AuthModuleOptions */ static buildParentOptions(options) {
|
|
130
|
+
static buildParentOptions(options) {
|
|
136
131
|
return {
|
|
137
132
|
bootstrapAppConfig: options.bootstrapAppConfig,
|
|
138
133
|
defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
|
|
@@ -140,35 +135,28 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _modules.Multi
|
|
|
140
135
|
tenants: options.config?.tenants
|
|
141
136
|
};
|
|
142
137
|
}
|
|
143
|
-
|
|
144
|
-
/** Check if company feature is enabled (tenant-specific or global fallback) */ isCompanyFeatureEnabled(tenant) {
|
|
138
|
+
isCompanyFeatureEnabled(tenant) {
|
|
145
139
|
const effectiveTenant = tenant ?? this.getCurrentTenant();
|
|
146
140
|
return effectiveTenant?.enableCompanyFeature ?? this.authOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
147
141
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Company,
|
|
159
|
-
CompanyBranch,
|
|
160
|
-
UserCompanyPermission
|
|
161
|
-
] : coreEntities;
|
|
142
|
+
isEmailVerificationEnabled(tenant) {
|
|
143
|
+
const effectiveTenant = tenant ?? this.getCurrentTenant();
|
|
144
|
+
return effectiveTenant?.enableEmailVerification ?? this.authOptions.bootstrapAppConfig?.enableEmailVerification ?? true;
|
|
145
|
+
}
|
|
146
|
+
async getAuthEntities() {
|
|
147
|
+
const { getEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
|
|
148
|
+
return getEntitiesByConfig({
|
|
149
|
+
enableCompanyFeature: this.isCompanyFeatureEnabled(),
|
|
150
|
+
enableEmailVerification: this.isEmailVerificationEnabled()
|
|
151
|
+
});
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
/** Override to dynamically set entities based on tenant config */ async createDataSourceFromConfig(config) {
|
|
153
|
+
async createDataSourceFromConfig(config) {
|
|
165
154
|
return super.createDataSourceFromConfig(config, await this.getAuthEntities());
|
|
166
155
|
}
|
|
167
156
|
constructor(authOptions, request){
|
|
168
157
|
super(AuthDataSourceProvider.buildParentOptions(authOptions), request), _define_property(this, "authOptions", void 0), _define_property(this, "logger", void 0), this.authOptions = authOptions, this.logger = new _common.Logger(AuthDataSourceProvider.name);
|
|
169
158
|
}
|
|
170
159
|
};
|
|
171
|
-
// Override parent's static properties to have Auth-specific cache (isolated from IAM/Storage)
|
|
172
160
|
_define_property(AuthDataSourceProvider, "tenantConnections", new Map());
|
|
173
161
|
_define_property(AuthDataSourceProvider, "singleDataSource", null);
|
|
174
162
|
_define_property(AuthDataSourceProvider, "tenantsRegistry", new Map());
|
|
@@ -93,9 +93,14 @@ function _ts_param(paramIndex, decorator) {
|
|
|
93
93
|
}
|
|
94
94
|
let AuthenticationService = class AuthenticationService {
|
|
95
95
|
// Feature Flags
|
|
96
|
-
|
|
96
|
+
isCompanyFeatureEnabled() {
|
|
97
97
|
return this.dataSourceProvider?.isCompanyFeatureEnabled() ?? this.authConfig.isCompanyFeatureEnabled();
|
|
98
98
|
}
|
|
99
|
+
isEmailVerificationEnabled() {
|
|
100
|
+
const featureEnabled = this.dataSourceProvider?.isEmailVerificationEnabled() ?? this.authConfig.isEmailVerificationEnabled();
|
|
101
|
+
const providerAvailable = this.authEmailService?.isEmailEnabled() ?? false;
|
|
102
|
+
return featureEnabled && providerAvailable;
|
|
103
|
+
}
|
|
99
104
|
// Repository Getters
|
|
100
105
|
async getUserRepository() {
|
|
101
106
|
if (!this.dataSourceProvider) {
|
|
@@ -118,7 +123,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
118
123
|
// Login
|
|
119
124
|
async login(email, password) {
|
|
120
125
|
// Check for pending registration first (user registered but hasn't verified email yet)
|
|
121
|
-
if (await this.authEmailService
|
|
126
|
+
if (this.isEmailVerificationEnabled() && await this.authEmailService.hasPendingRegistration(email)) {
|
|
122
127
|
return {
|
|
123
128
|
requiresEmailVerification: true,
|
|
124
129
|
email,
|
|
@@ -129,7 +134,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
129
134
|
if (!user.isActive) {
|
|
130
135
|
throw new _common.UnauthorizedException('User account is deactivated');
|
|
131
136
|
}
|
|
132
|
-
if (this.
|
|
137
|
+
if (this.isEmailVerificationEnabled() && !user.emailVerified) {
|
|
133
138
|
return {
|
|
134
139
|
requiresEmailVerification: true,
|
|
135
140
|
email: user.email,
|
|
@@ -141,7 +146,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
141
146
|
}
|
|
142
147
|
return this.handleCompanyBasedLogin(user);
|
|
143
148
|
}
|
|
144
|
-
|
|
149
|
+
async handleCompanyBasedLogin(user) {
|
|
145
150
|
const permissionRepository = await this.getPermissionRepository();
|
|
146
151
|
const branchRepository = await this.getBranchRepository();
|
|
147
152
|
if (!permissionRepository || !branchRepository) {
|
|
@@ -192,7 +197,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
192
197
|
return await this.generateCompanySelectionResponse(user, companies, branches);
|
|
193
198
|
}
|
|
194
199
|
// Registration
|
|
195
|
-
|
|
200
|
+
async register(dto, verifyUrlBase) {
|
|
196
201
|
const userRepository = await this.getUserRepository();
|
|
197
202
|
const existingUser = await userRepository.findOne({
|
|
198
203
|
where: {
|
|
@@ -208,7 +213,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
208
213
|
throw new _common.BadRequestException('Registration failed due to email exists. Please try again or contact support.');
|
|
209
214
|
}
|
|
210
215
|
// If email verification is enabled, create pending registration instead of user
|
|
211
|
-
if (this.
|
|
216
|
+
if (this.isEmailVerificationEnabled() && verifyUrlBase) {
|
|
212
217
|
const companyData = this.isCompanyFeatureEnabled() ? {
|
|
213
218
|
companySlug: dto.companySlug,
|
|
214
219
|
branchSlug: dto.branchSlug,
|
|
@@ -232,7 +237,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
232
237
|
// No email verification - create user directly
|
|
233
238
|
return this.createUserDirectly(dto);
|
|
234
239
|
}
|
|
235
|
-
|
|
240
|
+
async handleExistingUserCompanyRegistration(existingUser, dto) {
|
|
236
241
|
if (!dto.companySlug && !dto.newCompanyName) {
|
|
237
242
|
throw new _common.BadRequestException('Email already registered. Please login or provide a company to join/create.');
|
|
238
243
|
}
|
|
@@ -264,7 +269,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
264
269
|
}
|
|
265
270
|
return this.processCompanyRegistration(existingUser, dto, true);
|
|
266
271
|
}
|
|
267
|
-
|
|
272
|
+
async createUserDirectly(dto) {
|
|
268
273
|
const userRepository = await this.getUserRepository();
|
|
269
274
|
const hashedPassword = await _bcrypt.hash(dto.password, _config.BCRYPT_SALT_ROUNDS);
|
|
270
275
|
const user = userRepository.create({
|
|
@@ -282,7 +287,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
282
287
|
}
|
|
283
288
|
return this.processCompanyRegistration(user, dto);
|
|
284
289
|
}
|
|
285
|
-
|
|
290
|
+
async createUserFromPendingRegistration(pending) {
|
|
286
291
|
const userRepository = await this.getUserRepository();
|
|
287
292
|
const existingUser = await userRepository.findOne({
|
|
288
293
|
where: {
|
|
@@ -307,7 +312,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
307
312
|
}
|
|
308
313
|
return this.generateRegistrationResponse(user);
|
|
309
314
|
}
|
|
310
|
-
|
|
315
|
+
async processCompanyRegistration(user, data, throwOnUnavailable = false) {
|
|
311
316
|
const companyRepository = await this.getCompanyRepository();
|
|
312
317
|
const branchRepository = await this.getBranchRepository();
|
|
313
318
|
const permissionRepository = await this.getPermissionRepository();
|
|
@@ -322,7 +327,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
322
327
|
}
|
|
323
328
|
return this.generateRegistrationResponse(user);
|
|
324
329
|
}
|
|
325
|
-
|
|
330
|
+
async findOrCreateCompanyAndBranch(data, companyRepo, branchRepo) {
|
|
326
331
|
if (data.companySlug) {
|
|
327
332
|
const company = await companyRepo.findOne({
|
|
328
333
|
where: {
|
|
@@ -386,7 +391,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
386
391
|
branch: null
|
|
387
392
|
};
|
|
388
393
|
}
|
|
389
|
-
|
|
394
|
+
async assignUserPermissions(userId, companyId, branchId, permissionRepo) {
|
|
390
395
|
await permissionRepo.save(permissionRepo.create({
|
|
391
396
|
userId,
|
|
392
397
|
permissionType: _enums.PermissionType.COMPANY,
|
|
@@ -401,7 +406,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
401
406
|
}
|
|
402
407
|
}
|
|
403
408
|
// Company/Branch Selection
|
|
404
|
-
|
|
409
|
+
async select(sessionId, companyId, branchId) {
|
|
405
410
|
this.ensureCompanyFeatureEnabled();
|
|
406
411
|
if (!this.sessionService) {
|
|
407
412
|
throw new _common.BadRequestException('Session service not available');
|
|
@@ -412,11 +417,11 @@ let AuthenticationService = class AuthenticationService {
|
|
|
412
417
|
}
|
|
413
418
|
return this.validateAndLoginWithCompany(session.userId, companyId, branchId);
|
|
414
419
|
}
|
|
415
|
-
|
|
420
|
+
async switchCompany(userId, companyId, branchId) {
|
|
416
421
|
this.ensureCompanyFeatureEnabled();
|
|
417
422
|
return this.validateAndLoginWithCompany(userId, companyId, branchId);
|
|
418
423
|
}
|
|
419
|
-
|
|
424
|
+
async validateAndLoginWithCompany(userId, companyId, branchId) {
|
|
420
425
|
const userRepository = await this.getUserRepository();
|
|
421
426
|
const user = await userRepository.findOne({
|
|
422
427
|
where: {
|
|
@@ -476,7 +481,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
476
481
|
return this.generateLoginResponseWithCompany(user, company, branch);
|
|
477
482
|
}
|
|
478
483
|
// Token Management
|
|
479
|
-
|
|
484
|
+
async refreshToken(refreshToken) {
|
|
480
485
|
try {
|
|
481
486
|
const payload = this.jwtService.verify(refreshToken, {
|
|
482
487
|
secret: this.authConfig.getRefreshTokenSecret()
|
|
@@ -510,7 +515,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
510
515
|
throw new _common.UnauthorizedException('Invalid or expired refresh token');
|
|
511
516
|
}
|
|
512
517
|
}
|
|
513
|
-
|
|
518
|
+
async changePassword(userId, currentPassword, newPassword) {
|
|
514
519
|
const userRepository = await this.getUserRepository();
|
|
515
520
|
const user = await userRepository.findOne({
|
|
516
521
|
where: {
|
|
@@ -524,7 +529,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
524
529
|
user.password = await _bcrypt.hash(newPassword, _config.BCRYPT_SALT_ROUNDS);
|
|
525
530
|
await userRepository.save(user);
|
|
526
531
|
}
|
|
527
|
-
|
|
532
|
+
async logout(userId) {
|
|
528
533
|
if (!this.cacheManager) {
|
|
529
534
|
// Cache is required for secure logout - tokens cannot be revoked without it
|
|
530
535
|
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.');
|
|
@@ -538,7 +543,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
538
543
|
await this.cacheManager.set(cacheKey, revokedAt, ttlMs);
|
|
539
544
|
this.logger.log(`User ${userId} logged out - all tokens revoked`);
|
|
540
545
|
}
|
|
541
|
-
|
|
546
|
+
async isTokenRevoked(userId, tokenIssuedAt) {
|
|
542
547
|
if (!this.cacheManager || !tokenIssuedAt) {
|
|
543
548
|
return false;
|
|
544
549
|
}
|
|
@@ -550,7 +555,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
550
555
|
// Token is revoked if it was issued before the revocation timestamp
|
|
551
556
|
return tokenIssuedAt < revokedAt;
|
|
552
557
|
}
|
|
553
|
-
|
|
558
|
+
parseExpiryToMs(expiry) {
|
|
554
559
|
const match = expiry.match(/^(\d+)([smhd])$/);
|
|
555
560
|
if (!match) return 7 * 24 * 60 * 60 * 1000; // Default 7 days
|
|
556
561
|
const value = parseInt(match[1], 10);
|
|
@@ -569,7 +574,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
569
574
|
}
|
|
570
575
|
}
|
|
571
576
|
// Query Methods
|
|
572
|
-
|
|
577
|
+
async getUserCompanies(userId) {
|
|
573
578
|
this.ensureCompanyFeatureEnabled();
|
|
574
579
|
const permissionRepository = await this.getPermissionRepository();
|
|
575
580
|
const companyRepository = await this.getCompanyRepository();
|
|
@@ -590,7 +595,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
590
595
|
active: true
|
|
591
596
|
}).orderBy('c.name', 'ASC').getMany();
|
|
592
597
|
}
|
|
593
|
-
|
|
598
|
+
async getUserBranches(userId, companyId) {
|
|
594
599
|
this.ensureCompanyFeatureEnabled();
|
|
595
600
|
const permissionRepository = await this.getPermissionRepository();
|
|
596
601
|
const branchRepository = await this.getBranchRepository();
|
|
@@ -625,7 +630,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
625
630
|
active: true
|
|
626
631
|
}).orderBy('b.serial', 'ASC').addOrderBy('b.name', 'ASC').getMany();
|
|
627
632
|
}
|
|
628
|
-
|
|
633
|
+
async getCompanyById(companyId) {
|
|
629
634
|
const companyRepository = await this.getCompanyRepository();
|
|
630
635
|
if (!companyRepository) {
|
|
631
636
|
return null;
|
|
@@ -636,7 +641,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
636
641
|
}
|
|
637
642
|
});
|
|
638
643
|
}
|
|
639
|
-
|
|
644
|
+
async getBranchById(branchId) {
|
|
640
645
|
const branchRepository = await this.getBranchRepository();
|
|
641
646
|
if (!branchRepository) {
|
|
642
647
|
return null;
|
|
@@ -647,7 +652,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
647
652
|
}
|
|
648
653
|
});
|
|
649
654
|
}
|
|
650
|
-
|
|
655
|
+
async getUserInfo(userId, companyId, branchId) {
|
|
651
656
|
const userRepository = await this.getUserRepository();
|
|
652
657
|
const user = await userRepository.findOne({
|
|
653
658
|
where: {
|
|
@@ -694,7 +699,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
694
699
|
return user;
|
|
695
700
|
}
|
|
696
701
|
// Response Generators
|
|
697
|
-
|
|
702
|
+
toUserInfo(user) {
|
|
698
703
|
return {
|
|
699
704
|
id: user.id,
|
|
700
705
|
email: user.email,
|
|
@@ -703,7 +708,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
703
708
|
profilePictureId: user.profilePictureId ?? null
|
|
704
709
|
};
|
|
705
710
|
}
|
|
706
|
-
|
|
711
|
+
toCompanyInfo(company) {
|
|
707
712
|
return {
|
|
708
713
|
id: company.id,
|
|
709
714
|
name: company.name,
|
|
@@ -711,7 +716,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
711
716
|
logoId: company.logoId ?? null
|
|
712
717
|
};
|
|
713
718
|
}
|
|
714
|
-
|
|
719
|
+
toBranchInfo(branch) {
|
|
715
720
|
return {
|
|
716
721
|
id: branch.id,
|
|
717
722
|
name: branch.name,
|
|
@@ -719,7 +724,7 @@ let AuthenticationService = class AuthenticationService {
|
|
|
719
724
|
logoId: branch.logoId ?? null
|
|
720
725
|
};
|
|
721
726
|
}
|
|
722
|
-
|
|
727
|
+
generateTokens(payload) {
|
|
723
728
|
const accessToken = this.jwtService.sign({
|
|
724
729
|
...payload,
|
|
725
730
|
type: 'access'
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
2
2
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
3
3
|
import { Request } from 'express';
|
|
4
|
-
import { ChangePasswordDto, CompanySelectionResponseDto,
|
|
4
|
+
import { ChangePasswordDto, CompanySelectionResponseDto, LoginDto, LoginResponseDto, LoginResponseRequiresEmailVerificationDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto } from '../dtos/authentication.dto';
|
|
5
5
|
import { AuthConfigService } from '../services/auth-config.service';
|
|
6
|
-
import { AuthEmailService } from '../services/auth-email.service';
|
|
7
6
|
import { AuthenticationService } from '../services/authentication.service';
|
|
8
7
|
export declare class AuthenticationController {
|
|
9
8
|
private readonly authService;
|
|
10
9
|
private readonly authConfig?;
|
|
11
|
-
|
|
12
|
-
constructor(authService: AuthenticationService, authConfig?: AuthConfigService, authEmailService?: AuthEmailService);
|
|
10
|
+
constructor(authService: AuthenticationService, authConfig?: AuthConfigService);
|
|
13
11
|
private buildAuthUrl;
|
|
14
|
-
private get emailService();
|
|
15
|
-
private get isEmailEnabled();
|
|
16
12
|
login(loginDto: LoginDto): Promise<LoginResponseDto | CompanySelectionResponseDto | LoginResponseRequiresEmailVerificationDto>;
|
|
17
13
|
register(req: Request, registrationDto: RegistrationDto): Promise<RegistrationResponseDto>;
|
|
18
14
|
refreshToken(req: Request, refreshTokenDto: RefreshTokenDto): Promise<{
|
|
@@ -21,8 +17,4 @@ export declare class AuthenticationController {
|
|
|
21
17
|
getCurrentUser(user: ILoggedUserInfo): Promise<MeResponseDto>;
|
|
22
18
|
logout(userId: string): Promise<MessageResponseDto>;
|
|
23
19
|
changePassword(userId: string, dto: ChangePasswordDto): Promise<MessageResponseDto>;
|
|
24
|
-
forgotPassword(req: Request, dto: ForgotPasswordDto): Promise<MessageResponseDto>;
|
|
25
|
-
resetPassword(dto: ResetPasswordDto): Promise<MessageResponseDto>;
|
|
26
|
-
resendVerification(req: Request, dto: ResendVerificationDto): Promise<MessageResponseDto>;
|
|
27
|
-
verifyEmail(dto: VerifyEmailDto): Promise<MessageResponseDto | RegistrationResponseDto>;
|
|
28
20
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
import { ForgotPasswordDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
|
|
4
|
+
import { AuthConfigService } from '../services/auth-config.service';
|
|
5
|
+
import { AuthDataSourceProvider } from '../services/auth-datasource.provider';
|
|
6
|
+
import { AuthEmailService } from '../services/auth-email.service';
|
|
7
|
+
import { AuthenticationService } from '../services/authentication.service';
|
|
8
|
+
export declare class EmailVerificationController {
|
|
9
|
+
private readonly authService;
|
|
10
|
+
private readonly authEmailService;
|
|
11
|
+
private readonly authConfig;
|
|
12
|
+
private readonly dataSourceProvider;
|
|
13
|
+
constructor(authService: AuthenticationService, authEmailService: AuthEmailService, authConfig: AuthConfigService, dataSourceProvider: AuthDataSourceProvider);
|
|
14
|
+
private ensureEmailVerificationEnabled;
|
|
15
|
+
private buildAuthUrl;
|
|
16
|
+
forgotPassword(req: Request, dto: ForgotPasswordDto): Promise<MessageResponseDto>;
|
|
17
|
+
resetPassword(dto: ResetPasswordDto): Promise<MessageResponseDto>;
|
|
18
|
+
resendVerification(req: Request, dto: ResendVerificationDto): Promise<MessageResponseDto>;
|
|
19
|
+
verifyEmail(dto: VerifyEmailDto): Promise<MessageResponseDto | RegistrationResponseDto>;
|
|
20
|
+
}
|
package/controllers/index.d.ts
CHANGED
package/entities/index.d.ts
CHANGED
|
@@ -4,13 +4,8 @@ export * from './pending-registration.entity';
|
|
|
4
4
|
export { PermissionType, UserCompanyPermission } from './user-company-permission.entity';
|
|
5
5
|
export * from './user.entity';
|
|
6
6
|
export * from './user-token.entity';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import { UserToken } from './user-token.entity';
|
|
13
|
-
export declare const CoreEntities: (typeof PendingRegistration | typeof UserToken | typeof User)[];
|
|
14
|
-
export declare const CompanyEntities: (typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
15
|
-
export declare const AllEntities: (typeof PendingRegistration | typeof UserToken | typeof User | typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
16
|
-
export declare function getEntitiesByConfig(enableCompanyFeature: boolean): (typeof PendingRegistration | typeof UserToken | typeof User | typeof Company | typeof CompanyBranch | typeof UserCompanyPermission)[];
|
|
7
|
+
export interface IAuthEntitiesConfig {
|
|
8
|
+
enableCompanyFeature?: boolean;
|
|
9
|
+
enableEmailVerification?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function getEntitiesByConfig(config: IAuthEntitiesConfig): any[];
|