@flusys/nestjs-auth 3.0.0-rc → 3.0.1
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 +610 -95
- 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/dtos/authentication.dto.js +12 -2
- package/cjs/entities/index.js +16 -17
- package/cjs/entities/pending-registration.entity.js +9 -0
- 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/auth-email.service.js +8 -33
- package/cjs/services/authentication.service.js +155 -110
- package/cjs/services/user.service.js +28 -83
- 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/dtos/authentication.dto.d.ts +1 -0
- package/entities/index.d.ts +5 -10
- package/entities/pending-registration.entity.d.ts +1 -0
- 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/dtos/authentication.dto.js +32 -25
- package/fesm/entities/index.js +17 -9
- package/fesm/entities/pending-registration.entity.js +9 -0
- package/fesm/interfaces/user-enricher.interface.js +2 -24
- 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/auth-email.service.js +8 -33
- package/fesm/services/authentication.service.js +145 -100
- package/fesm/services/user.service.js +28 -83
- package/interfaces/user-enricher.interface.d.ts +3 -2
- 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/auth-email.service.d.ts +1 -0
- package/services/authentication.service.d.ts +7 -1
- package/services/user.service.d.ts +5 -0
|
@@ -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());
|
|
@@ -90,9 +90,6 @@ let AuthEmailService = class AuthEmailService {
|
|
|
90
90
|
isEmailEnabled() {
|
|
91
91
|
return !!this.emailProvider;
|
|
92
92
|
}
|
|
93
|
-
// ============================================
|
|
94
|
-
// Private Helpers
|
|
95
|
-
// ============================================
|
|
96
93
|
async getRepository(entity) {
|
|
97
94
|
if (!this.dataSourceProvider) {
|
|
98
95
|
throw new _common.InternalServerErrorException(`${String(entity)} repository not available`);
|
|
@@ -102,7 +99,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
102
99
|
generateToken() {
|
|
103
100
|
return _crypto.randomBytes(32).toString('hex');
|
|
104
101
|
}
|
|
105
|
-
|
|
102
|
+
hashToken(token) {
|
|
106
103
|
return _crypto.createHash('sha256').update(token).digest('hex');
|
|
107
104
|
}
|
|
108
105
|
getErrorMessage(error) {
|
|
@@ -130,9 +127,6 @@ let AuthEmailService = class AuthEmailService {
|
|
|
130
127
|
throw new _common.BadRequestException(usedMessage);
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
|
-
// ============================================
|
|
134
|
-
// Pending Registration (New Users)
|
|
135
|
-
// ============================================
|
|
136
130
|
async hasPendingRegistration(email) {
|
|
137
131
|
if (!this.isEmailEnabled()) return false;
|
|
138
132
|
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
@@ -172,7 +166,8 @@ let AuthEmailService = class AuthEmailService {
|
|
|
172
166
|
passwordHash,
|
|
173
167
|
token: tokenHash,
|
|
174
168
|
expiresAt,
|
|
175
|
-
companyData: data.companyData ?? null
|
|
169
|
+
companyData: data.companyData ?? null,
|
|
170
|
+
additionalFields: data.additionalFields ?? null
|
|
176
171
|
});
|
|
177
172
|
await pendingRepository.save(pending);
|
|
178
173
|
// Send verification email with raw token (user receives this)
|
|
@@ -183,12 +178,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
183
178
|
message: 'Please check your email to verify your registration.'
|
|
184
179
|
};
|
|
185
180
|
}
|
|
186
|
-
|
|
187
|
-
// Password Reset (Existing Users)
|
|
188
|
-
// ============================================
|
|
189
|
-
/**
|
|
190
|
-
* Request password reset - sends reset email
|
|
191
|
-
*/ async forgotPassword(email, resetUrlBase) {
|
|
181
|
+
async forgotPassword(email, resetUrlBase) {
|
|
192
182
|
this.assertEmailEnabled();
|
|
193
183
|
const userRepository = await this.getRepository(_userentity.User);
|
|
194
184
|
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
@@ -234,9 +224,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
234
224
|
message: 'If the email exists, a password reset link has been sent.'
|
|
235
225
|
};
|
|
236
226
|
}
|
|
237
|
-
|
|
238
|
-
* Reset password using token
|
|
239
|
-
*/ async resetPassword(token, newPassword) {
|
|
227
|
+
async resetPassword(token, newPassword) {
|
|
240
228
|
const userRepository = await this.getRepository(_userentity.User);
|
|
241
229
|
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
242
230
|
// Hash the incoming token to compare with stored hash
|
|
@@ -271,13 +259,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
271
259
|
message: 'Password has been reset successfully.'
|
|
272
260
|
};
|
|
273
261
|
}
|
|
274
|
-
|
|
275
|
-
// Email Verification
|
|
276
|
-
// ============================================
|
|
277
|
-
/**
|
|
278
|
-
* Verify email using token
|
|
279
|
-
* Handles both pending registration and existing user verification
|
|
280
|
-
*/ async verifyEmail(token) {
|
|
262
|
+
async verifyEmail(token) {
|
|
281
263
|
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
282
264
|
const userRepository = await this.getRepository(_userentity.User);
|
|
283
265
|
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
@@ -347,9 +329,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
347
329
|
message: 'Email has been verified successfully.'
|
|
348
330
|
};
|
|
349
331
|
}
|
|
350
|
-
|
|
351
|
-
* Resend verification email - handles both pending registrations and existing users
|
|
352
|
-
*/ async resendVerificationEmail(email, verifyUrlBase) {
|
|
332
|
+
async resendVerificationEmail(email, verifyUrlBase) {
|
|
353
333
|
this.assertEmailEnabled();
|
|
354
334
|
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
355
335
|
const userRepository = await this.getRepository(_userentity.User);
|
|
@@ -404,12 +384,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
404
384
|
message: 'If the email requires verification, a link has been sent.'
|
|
405
385
|
};
|
|
406
386
|
}
|
|
407
|
-
|
|
408
|
-
// Cleanup
|
|
409
|
-
// ============================================
|
|
410
|
-
/**
|
|
411
|
-
* Clean up expired tokens and pending registrations
|
|
412
|
-
*/ async cleanupExpired() {
|
|
387
|
+
async cleanupExpired() {
|
|
413
388
|
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
414
389
|
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
415
390
|
const now = new Date();
|