@flusys/nestjs-auth 1.0.0-rc → 1.0.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 +530 -396
- package/cjs/controllers/authentication.controller.js +160 -28
- package/cjs/controllers/user-permission.controller.js +49 -136
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +0 -30
- package/cjs/dtos/index.js +0 -1
- package/cjs/entities/user-company-permission.entity.js +3 -3
- package/cjs/interceptors/clear-token.interceptor.js +8 -18
- package/cjs/interceptors/set-token.interceptor.js +8 -55
- package/cjs/interfaces/auth-email-provider.interface.js +1 -1
- package/cjs/modules/auth.module.js +63 -129
- package/cjs/services/auth-config.service.js +6 -29
- package/cjs/services/auth-datasource.provider.js +8 -20
- package/cjs/services/auth-email.service.js +72 -87
- package/cjs/services/authentication.service.js +211 -355
- package/cjs/services/branch.service.js +9 -5
- package/cjs/services/user-permission.service.js +43 -71
- package/cjs/services/user.service.js +18 -47
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/user-permission.controller.d.ts +5 -4
- package/dtos/authentication.dto.d.ts +0 -4
- package/dtos/index.d.ts +0 -1
- package/entities/index.d.ts +3 -3
- package/entities/user-company-permission.entity.d.ts +1 -1
- package/fesm/controllers/authentication.controller.js +162 -30
- package/fesm/controllers/user-permission.controller.js +50 -137
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +0 -30
- package/fesm/dtos/index.js +0 -1
- package/fesm/entities/user-company-permission.entity.js +3 -3
- package/fesm/interceptors/clear-token.interceptor.js +8 -18
- package/fesm/interceptors/set-token.interceptor.js +8 -55
- package/fesm/interfaces/auth-email-provider.interface.js +1 -1
- package/fesm/modules/auth.module.js +63 -129
- package/fesm/services/auth-config.service.js +6 -29
- package/fesm/services/auth-datasource.provider.js +8 -20
- package/fesm/services/auth-email.service.js +72 -87
- package/fesm/services/authentication.service.js +211 -355
- package/fesm/services/branch.service.js +9 -5
- package/fesm/services/user-permission.service.js +44 -72
- package/fesm/services/user.service.js +18 -47
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -2
- package/interfaces/authentication.interface.d.ts +0 -4
- package/modules/auth.module.d.ts +6 -2
- package/package.json +3 -3
- package/services/auth-config.service.d.ts +2 -7
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +7 -4
- package/services/authentication.service.d.ts +14 -8
- package/services/user-permission.service.d.ts +6 -8
- package/services/user.service.d.ts +1 -1
- package/strategies/jwt.strategy.d.ts +4 -1
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/cjs/guards/auth-throttler.guard.js +0 -53
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
- package/fesm/guards/auth-throttler.guard.js +0 -43
- package/guards/auth-throttler.guard.d.ts +0 -7
|
@@ -12,12 +12,12 @@ const _common = require("@nestjs/common");
|
|
|
12
12
|
const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
|
|
13
13
|
const _crypto = /*#__PURE__*/ _interop_require_wildcard(require("crypto"));
|
|
14
14
|
const _typeorm = require("typeorm");
|
|
15
|
-
const
|
|
15
|
+
const _config = require("../config");
|
|
16
16
|
const _pendingregistrationentity = require("../entities/pending-registration.entity");
|
|
17
17
|
const _usertokenentity = require("../entities/user-token.entity");
|
|
18
18
|
const _userentity = require("../entities/user.entity");
|
|
19
19
|
const _interfaces = require("../interfaces");
|
|
20
|
-
const
|
|
20
|
+
const _authdatasourceprovider = require("./auth-datasource.provider");
|
|
21
21
|
function _define_property(obj, key, value) {
|
|
22
22
|
if (key in obj) {
|
|
23
23
|
Object.defineProperty(obj, key, {
|
|
@@ -87,44 +87,66 @@ function _ts_param(paramIndex, decorator) {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
let AuthEmailService = class AuthEmailService {
|
|
90
|
-
async getUserRepository() {
|
|
91
|
-
if (this.dataSourceProvider) {
|
|
92
|
-
return this.dataSourceProvider.getRepository(_userentity.User);
|
|
93
|
-
}
|
|
94
|
-
throw new _common.InternalServerErrorException('User repository not available');
|
|
95
|
-
}
|
|
96
|
-
async getUserTokenRepository() {
|
|
97
|
-
if (this.dataSourceProvider) {
|
|
98
|
-
return this.dataSourceProvider.getRepository(_usertokenentity.UserToken);
|
|
99
|
-
}
|
|
100
|
-
throw new _common.InternalServerErrorException('UserToken repository not available');
|
|
101
|
-
}
|
|
102
|
-
async getPendingRegistrationRepository() {
|
|
103
|
-
if (this.dataSourceProvider) {
|
|
104
|
-
return this.dataSourceProvider.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
105
|
-
}
|
|
106
|
-
throw new _common.InternalServerErrorException('PendingRegistration repository not available');
|
|
107
|
-
}
|
|
108
90
|
isEmailEnabled() {
|
|
109
91
|
return !!this.emailProvider;
|
|
110
92
|
}
|
|
93
|
+
// ============================================
|
|
94
|
+
// Private Helpers
|
|
95
|
+
// ============================================
|
|
96
|
+
async getRepository(entity) {
|
|
97
|
+
if (!this.dataSourceProvider) {
|
|
98
|
+
throw new _common.InternalServerErrorException(`${String(entity)} repository not available`);
|
|
99
|
+
}
|
|
100
|
+
return this.dataSourceProvider.getRepository(entity);
|
|
101
|
+
}
|
|
111
102
|
generateToken() {
|
|
112
103
|
return _crypto.randomBytes(32).toString('hex');
|
|
113
104
|
}
|
|
114
105
|
/** Hash token for secure storage - prevents token theft if database is compromised */ hashToken(token) {
|
|
115
106
|
return _crypto.createHash('sha256').update(token).digest('hex');
|
|
116
107
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* Create pending registration - stores data until email verified
|
|
122
|
-
*/ async createPendingRegistration(data) {
|
|
108
|
+
getErrorMessage(error) {
|
|
109
|
+
return error instanceof Error ? error.message : 'Unknown error';
|
|
110
|
+
}
|
|
111
|
+
assertEmailEnabled() {
|
|
123
112
|
if (!this.emailProvider) {
|
|
124
113
|
throw new _common.BadRequestException('Email service is not configured');
|
|
125
114
|
}
|
|
126
|
-
|
|
127
|
-
|
|
115
|
+
}
|
|
116
|
+
async sendVerificationEmailSafe(email, token, verifyUrl, logContext) {
|
|
117
|
+
try {
|
|
118
|
+
await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
|
|
119
|
+
this.logger.log(`${logContext} verification email sent to: ${email}`);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
this.logger.error(`Failed to send verification email: ${this.getErrorMessage(error)}`);
|
|
122
|
+
throw new _common.BadRequestException('Failed to send verification email. Please try again.');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
validateTokenNotExpiredOrUsed(token, expiredMessage, usedMessage) {
|
|
126
|
+
if (token.expiresAt < new Date()) {
|
|
127
|
+
throw new _common.BadRequestException(expiredMessage);
|
|
128
|
+
}
|
|
129
|
+
if (usedMessage && token.usedAt) {
|
|
130
|
+
throw new _common.BadRequestException(usedMessage);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ============================================
|
|
134
|
+
// Pending Registration (New Users)
|
|
135
|
+
// ============================================
|
|
136
|
+
async hasPendingRegistration(email) {
|
|
137
|
+
if (!this.isEmailEnabled()) return false;
|
|
138
|
+
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
139
|
+
const pending = await pendingRepository.findOne({
|
|
140
|
+
where: {
|
|
141
|
+
email
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return !!pending && pending.expiresAt > new Date();
|
|
145
|
+
}
|
|
146
|
+
async createPendingRegistration(data) {
|
|
147
|
+
this.assertEmailEnabled();
|
|
148
|
+
const userRepository = await this.getRepository(_userentity.User);
|
|
149
|
+
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
128
150
|
// Check if user already exists
|
|
129
151
|
const existingUser = await userRepository.findOne({
|
|
130
152
|
where: {
|
|
@@ -155,14 +177,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
155
177
|
await pendingRepository.save(pending);
|
|
156
178
|
// Send verification email with raw token (user receives this)
|
|
157
179
|
const verifyUrl = `${data.verifyUrlBase}?token=${token}`;
|
|
158
|
-
|
|
159
|
-
await this.emailProvider.sendVerificationEmail(data.email, token, verifyUrl);
|
|
160
|
-
this.logger.log(`Registration verification email sent to: ${data.email}`);
|
|
161
|
-
} catch (error) {
|
|
162
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
163
|
-
this.logger.error(`Failed to send verification email: ${errorMessage}`);
|
|
164
|
-
throw new _common.BadRequestException('Failed to send verification email. Please try again.');
|
|
165
|
-
}
|
|
180
|
+
await this.sendVerificationEmailSafe(data.email, token, verifyUrl, 'Registration');
|
|
166
181
|
return {
|
|
167
182
|
success: true,
|
|
168
183
|
message: 'Please check your email to verify your registration.'
|
|
@@ -174,11 +189,9 @@ let AuthEmailService = class AuthEmailService {
|
|
|
174
189
|
/**
|
|
175
190
|
* Request password reset - sends reset email
|
|
176
191
|
*/ async forgotPassword(email, resetUrlBase) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const userRepository = await this.getUserRepository();
|
|
181
|
-
const tokenRepository = await this.getUserTokenRepository();
|
|
192
|
+
this.assertEmailEnabled();
|
|
193
|
+
const userRepository = await this.getRepository(_userentity.User);
|
|
194
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
182
195
|
const user = await userRepository.findOne({
|
|
183
196
|
where: {
|
|
184
197
|
email
|
|
@@ -213,9 +226,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
213
226
|
await this.emailProvider.sendPasswordResetEmail(email, token, resetUrl);
|
|
214
227
|
this.logger.log(`Password reset email sent to: ${email}`);
|
|
215
228
|
} catch (error) {
|
|
216
|
-
|
|
217
|
-
this.logger.error(`Failed to send password reset email: ${errorMessage}`);
|
|
218
|
-
// Don't silently fail - but still use same message to prevent enumeration
|
|
229
|
+
this.logger.error(`Failed to send password reset email: ${this.getErrorMessage(error)}`);
|
|
219
230
|
throw new _common.BadRequestException('Failed to process request. Please try again later.');
|
|
220
231
|
}
|
|
221
232
|
return {
|
|
@@ -226,8 +237,8 @@ let AuthEmailService = class AuthEmailService {
|
|
|
226
237
|
/**
|
|
227
238
|
* Reset password using token
|
|
228
239
|
*/ async resetPassword(token, newPassword) {
|
|
229
|
-
const userRepository = await this.
|
|
230
|
-
const tokenRepository = await this.
|
|
240
|
+
const userRepository = await this.getRepository(_userentity.User);
|
|
241
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
231
242
|
// Hash the incoming token to compare with stored hash
|
|
232
243
|
const tokenHash = this.hashToken(token);
|
|
233
244
|
const userToken = await tokenRepository.findOne({
|
|
@@ -239,15 +250,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
239
250
|
if (!userToken) {
|
|
240
251
|
throw new _common.BadRequestException('Invalid or expired reset token');
|
|
241
252
|
}
|
|
242
|
-
|
|
243
|
-
await tokenRepository.delete({
|
|
244
|
-
id: userToken.id
|
|
245
|
-
});
|
|
246
|
-
throw new _common.BadRequestException('Reset token has expired. Please request a new one.');
|
|
247
|
-
}
|
|
248
|
-
if (userToken.usedAt) {
|
|
249
|
-
throw new _common.BadRequestException('This reset token has already been used.');
|
|
250
|
-
}
|
|
253
|
+
this.validateTokenNotExpiredOrUsed(userToken, 'Reset token has expired. Please request a new one.', 'This reset token has already been used.');
|
|
251
254
|
const user = await userRepository.findOne({
|
|
252
255
|
where: {
|
|
253
256
|
id: userToken.userId
|
|
@@ -275,9 +278,9 @@ let AuthEmailService = class AuthEmailService {
|
|
|
275
278
|
* Verify email using token
|
|
276
279
|
* Handles both pending registration and existing user verification
|
|
277
280
|
*/ async verifyEmail(token) {
|
|
278
|
-
const pendingRepository = await this.
|
|
279
|
-
const userRepository = await this.
|
|
280
|
-
const tokenRepository = await this.
|
|
281
|
+
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
282
|
+
const userRepository = await this.getRepository(_userentity.User);
|
|
283
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
281
284
|
// Hash the incoming token to compare with stored hash
|
|
282
285
|
const tokenHash = this.hashToken(token);
|
|
283
286
|
// Check for pending registration first
|
|
@@ -302,7 +305,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
302
305
|
pending
|
|
303
306
|
};
|
|
304
307
|
}
|
|
305
|
-
// Check for existing user email verification token
|
|
308
|
+
// Check for existing user email verification token
|
|
306
309
|
const userToken = await tokenRepository.findOne({
|
|
307
310
|
where: {
|
|
308
311
|
token: tokenHash,
|
|
@@ -347,12 +350,10 @@ let AuthEmailService = class AuthEmailService {
|
|
|
347
350
|
/**
|
|
348
351
|
* Resend verification email - handles both pending registrations and existing users
|
|
349
352
|
*/ async resendVerificationEmail(email, verifyUrlBase) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
const userRepository = await this.getUserRepository();
|
|
355
|
-
const tokenRepository = await this.getUserTokenRepository();
|
|
353
|
+
this.assertEmailEnabled();
|
|
354
|
+
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
355
|
+
const userRepository = await this.getRepository(_userentity.User);
|
|
356
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
356
357
|
// Check pending registration first
|
|
357
358
|
const pending = await pendingRepository.findOne({
|
|
358
359
|
where: {
|
|
@@ -361,19 +362,11 @@ let AuthEmailService = class AuthEmailService {
|
|
|
361
362
|
});
|
|
362
363
|
if (pending) {
|
|
363
364
|
const token = this.generateToken();
|
|
364
|
-
|
|
365
|
-
pending.token = tokenHash; // Store hashed token
|
|
365
|
+
pending.token = this.hashToken(token);
|
|
366
366
|
pending.expiresAt = new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY);
|
|
367
367
|
await pendingRepository.save(pending);
|
|
368
368
|
const verifyUrl = `${verifyUrlBase}?token=${token}`;
|
|
369
|
-
|
|
370
|
-
await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
|
|
371
|
-
this.logger.log(`Re-sent registration verification email to: ${email}`);
|
|
372
|
-
} catch (error) {
|
|
373
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
374
|
-
this.logger.error(`Failed to resend verification email: ${errorMessage}`);
|
|
375
|
-
throw new _common.BadRequestException('Failed to send verification email. Please try again.');
|
|
376
|
-
}
|
|
369
|
+
await this.sendVerificationEmailSafe(email, token, verifyUrl, 'Re-sent registration');
|
|
377
370
|
return {
|
|
378
371
|
success: true,
|
|
379
372
|
message: 'If the email requires verification, a link has been sent.'
|
|
@@ -397,23 +390,15 @@ let AuthEmailService = class AuthEmailService {
|
|
|
397
390
|
type: _usertokenentity.TokenType.EMAIL_VERIFICATION
|
|
398
391
|
});
|
|
399
392
|
const token = this.generateToken();
|
|
400
|
-
const tokenHash = this.hashToken(token);
|
|
401
393
|
const userToken = tokenRepository.create({
|
|
402
394
|
userId: user.id,
|
|
403
395
|
type: _usertokenentity.TokenType.EMAIL_VERIFICATION,
|
|
404
|
-
token:
|
|
396
|
+
token: this.hashToken(token),
|
|
405
397
|
expiresAt: new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY)
|
|
406
398
|
});
|
|
407
399
|
await tokenRepository.save(userToken);
|
|
408
400
|
const verifyUrl = `${verifyUrlBase}?token=${token}`;
|
|
409
|
-
|
|
410
|
-
await this.emailProvider.sendVerificationEmail(user.email, token, verifyUrl);
|
|
411
|
-
this.logger.log(`Verification email sent to: ${user.email}`);
|
|
412
|
-
} catch (error) {
|
|
413
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
414
|
-
this.logger.error(`Failed to send verification email: ${errorMessage}`);
|
|
415
|
-
throw new _common.BadRequestException('Failed to send verification email. Please try again.');
|
|
416
|
-
}
|
|
401
|
+
await this.sendVerificationEmailSafe(user.email, token, verifyUrl, 'Verification');
|
|
417
402
|
return {
|
|
418
403
|
success: true,
|
|
419
404
|
message: 'If the email requires verification, a link has been sent.'
|
|
@@ -425,8 +410,8 @@ let AuthEmailService = class AuthEmailService {
|
|
|
425
410
|
/**
|
|
426
411
|
* Clean up expired tokens and pending registrations
|
|
427
412
|
*/ async cleanupExpired() {
|
|
428
|
-
const tokenRepository = await this.
|
|
429
|
-
const pendingRepository = await this.
|
|
413
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
414
|
+
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
430
415
|
const now = new Date();
|
|
431
416
|
const tokenResult = await tokenRepository.delete({
|
|
432
417
|
expiresAt: (0, _typeorm.LessThan)(now)
|