@flusys/nestjs-auth 1.1.0-beta → 2.0.0-rc.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 +590 -473
- package/cjs/config/auth.constants.js +8 -0
- package/cjs/controllers/authentication.controller.js +218 -39
- package/cjs/controllers/branch.controller.js +44 -1
- package/cjs/controllers/company-selection.controller.js +27 -20
- package/cjs/controllers/company.controller.js +44 -1
- package/cjs/controllers/user-permission.controller.js +82 -154
- package/cjs/controllers/user.controller.js +68 -15
- package/cjs/docs/auth-swagger.config.js +14 -7
- package/cjs/dtos/authentication.dto.js +16 -43
- package/cjs/dtos/index.js +0 -1
- package/cjs/dtos/user-permission.dto.js +28 -0
- package/cjs/dtos/user.dto.js +19 -4
- package/cjs/entities/user-company-permission.entity.js +0 -6
- package/cjs/interceptors/clear-token.interceptor.js +10 -23
- package/cjs/interceptors/set-token.interceptor.js +15 -55
- package/cjs/interfaces/auth-email-provider.interface.js +1 -1
- package/cjs/modules/auth.module.js +63 -147
- package/cjs/services/auth-config.service.js +22 -28
- package/cjs/services/auth-datasource.provider.js +9 -21
- package/cjs/services/auth-email.service.js +89 -85
- package/cjs/services/authentication.service.js +225 -396
- package/cjs/services/branch.service.js +9 -4
- package/cjs/services/company-selection-session.service.js +1 -1
- package/cjs/services/user-permission.service.js +43 -73
- package/cjs/services/user.service.js +78 -64
- package/cjs/strategies/jwt.strategy.js +44 -44
- package/cjs/validators/password.validator.js +45 -0
- package/config/auth.constants.d.ts +2 -0
- package/controllers/authentication.controller.d.ts +5 -3
- package/controllers/company-selection.controller.d.ts +6 -2
- package/controllers/user-permission.controller.d.ts +5 -4
- package/controllers/user.controller.d.ts +6 -0
- package/dtos/authentication.dto.d.ts +0 -4
- package/dtos/company-branch.dto.d.ts +0 -1
- package/dtos/company.dto.d.ts +0 -1
- package/dtos/index.d.ts +0 -1
- package/dtos/user-permission.dto.d.ts +4 -0
- package/dtos/user.dto.d.ts +1 -3
- package/entities/index.d.ts +3 -3
- package/entities/user-company-permission.entity.d.ts +0 -2
- package/fesm/config/auth.constants.js +6 -13
- package/fesm/controllers/authentication.controller.js +221 -42
- package/fesm/controllers/branch.controller.js +45 -2
- package/fesm/controllers/company-selection.controller.js +28 -21
- package/fesm/controllers/company.controller.js +45 -2
- package/fesm/controllers/user-permission.controller.js +84 -156
- package/fesm/controllers/user.controller.js +71 -18
- package/fesm/docs/auth-swagger.config.js +14 -7
- package/fesm/dtos/authentication.dto.js +17 -44
- package/fesm/dtos/index.js +0 -1
- package/fesm/dtos/user-permission.dto.js +28 -0
- package/fesm/dtos/user.dto.js +20 -5
- package/fesm/entities/user-company-permission.entity.js +0 -6
- package/fesm/interceptors/clear-token.interceptor.js +9 -22
- package/fesm/interceptors/set-token.interceptor.js +14 -54
- package/fesm/interfaces/auth-email-provider.interface.js +1 -1
- package/fesm/modules/auth.module.js +64 -148
- package/fesm/services/auth-config.service.js +23 -29
- package/fesm/services/auth-datasource.provider.js +10 -22
- package/fesm/services/auth-email.service.js +90 -86
- package/fesm/services/authentication.service.js +225 -396
- package/fesm/services/branch.service.js +9 -4
- package/fesm/services/company-selection-session.service.js +1 -1
- package/fesm/services/user-permission.service.js +44 -74
- package/fesm/services/user.service.js +79 -65
- package/fesm/strategies/jwt.strategy.js +44 -44
- package/fesm/validators/password.validator.js +48 -0
- package/interceptors/clear-token.interceptor.d.ts +0 -1
- package/interceptors/set-token.interceptor.d.ts +1 -1
- package/interfaces/authentication.interface.d.ts +0 -4
- package/interfaces/user-enricher.interface.d.ts +1 -4
- package/modules/auth.module.d.ts +6 -3
- package/package.json +4 -4
- package/services/auth-config.service.d.ts +6 -8
- package/services/auth-datasource.provider.d.ts +2 -4
- package/services/auth-email.service.d.ts +8 -4
- package/services/authentication.service.d.ts +15 -16
- package/services/company-selection-session.service.d.ts +1 -1
- package/services/user-permission.service.d.ts +6 -9
- package/services/user.service.d.ts +3 -0
- package/strategies/jwt.strategy.d.ts +4 -1
- package/validators/password.validator.d.ts +10 -0
- package/cjs/dtos/user-company-permission.dto.js +0 -110
- package/dtos/user-company-permission.dto.d.ts +0 -10
- package/fesm/dtos/user-company-permission.dto.js +0 -98
|
@@ -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,41 +87,66 @@ function _ts_param(paramIndex, decorator) {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
let AuthEmailService = class AuthEmailService {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return this.dataSourceProvider.getRepository(_userentity.User);
|
|
93
|
-
}
|
|
94
|
-
throw new Error('User repository not available');
|
|
90
|
+
isEmailEnabled() {
|
|
91
|
+
return !!this.emailProvider;
|
|
95
92
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
// ============================================
|
|
94
|
+
// Private Helpers
|
|
95
|
+
// ============================================
|
|
96
|
+
async getRepository(entity) {
|
|
97
|
+
if (!this.dataSourceProvider) {
|
|
98
|
+
throw new _common.InternalServerErrorException(`${String(entity)} repository not available`);
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
return this.dataSourceProvider.getRepository(entity);
|
|
101
|
+
}
|
|
102
|
+
generateToken() {
|
|
103
|
+
return _crypto.randomBytes(32).toString('hex');
|
|
101
104
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
/** Hash token for secure storage - prevents token theft if database is compromised */ hashToken(token) {
|
|
106
|
+
return _crypto.createHash('sha256').update(token).digest('hex');
|
|
107
|
+
}
|
|
108
|
+
getErrorMessage(error) {
|
|
109
|
+
return error instanceof Error ? error.message : 'Unknown error';
|
|
110
|
+
}
|
|
111
|
+
assertEmailEnabled() {
|
|
112
|
+
if (!this.emailProvider) {
|
|
113
|
+
throw new _common.BadRequestException('Email service is not configured');
|
|
105
114
|
}
|
|
106
|
-
throw new Error('PendingRegistration repository not available');
|
|
107
115
|
}
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
}
|
|
110
124
|
}
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
132
|
}
|
|
114
133
|
// ============================================
|
|
115
134
|
// Pending Registration (New Users)
|
|
116
135
|
// ============================================
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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);
|
|
125
150
|
// Check if user already exists
|
|
126
151
|
const existingUser = await userRepository.findOne({
|
|
127
152
|
where: {
|
|
@@ -135,8 +160,9 @@ let AuthEmailService = class AuthEmailService {
|
|
|
135
160
|
await pendingRepository.delete({
|
|
136
161
|
email: data.email
|
|
137
162
|
});
|
|
138
|
-
// Create pending registration
|
|
163
|
+
// Create pending registration with hashed token for secure storage
|
|
139
164
|
const token = this.generateToken();
|
|
165
|
+
const tokenHash = this.hashToken(token);
|
|
140
166
|
const passwordHash = await _bcrypt.hash(data.password, _config.BCRYPT_SALT_ROUNDS);
|
|
141
167
|
const expiresAt = new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY);
|
|
142
168
|
const pending = pendingRepository.create({
|
|
@@ -144,20 +170,14 @@ let AuthEmailService = class AuthEmailService {
|
|
|
144
170
|
name: data.name ?? null,
|
|
145
171
|
phone: data.phone ?? null,
|
|
146
172
|
passwordHash,
|
|
147
|
-
token,
|
|
173
|
+
token: tokenHash,
|
|
148
174
|
expiresAt,
|
|
149
175
|
companyData: data.companyData ?? null
|
|
150
176
|
});
|
|
151
177
|
await pendingRepository.save(pending);
|
|
152
|
-
// Send verification email
|
|
178
|
+
// Send verification email with raw token (user receives this)
|
|
153
179
|
const verifyUrl = `${data.verifyUrlBase}?token=${token}`;
|
|
154
|
-
|
|
155
|
-
await this.emailProvider.sendVerificationEmail(data.email, token, verifyUrl);
|
|
156
|
-
this.logger.log(`Registration verification email sent to: ${data.email}`);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
this.logger.error(`Failed to send verification email: ${error.message}`);
|
|
159
|
-
throw new _common.BadRequestException('Failed to send verification email. Please try again.');
|
|
160
|
-
}
|
|
180
|
+
await this.sendVerificationEmailSafe(data.email, token, verifyUrl, 'Registration');
|
|
161
181
|
return {
|
|
162
182
|
success: true,
|
|
163
183
|
message: 'Please check your email to verify your registration.'
|
|
@@ -169,11 +189,9 @@ let AuthEmailService = class AuthEmailService {
|
|
|
169
189
|
/**
|
|
170
190
|
* Request password reset - sends reset email
|
|
171
191
|
*/ async forgotPassword(email, resetUrlBase) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const userRepository = await this.getUserRepository();
|
|
176
|
-
const tokenRepository = await this.getUserTokenRepository();
|
|
192
|
+
this.assertEmailEnabled();
|
|
193
|
+
const userRepository = await this.getRepository(_userentity.User);
|
|
194
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
177
195
|
const user = await userRepository.findOne({
|
|
178
196
|
where: {
|
|
179
197
|
email
|
|
@@ -192,22 +210,24 @@ let AuthEmailService = class AuthEmailService {
|
|
|
192
210
|
userId: user.id,
|
|
193
211
|
type: _usertokenentity.TokenType.PASSWORD_RESET
|
|
194
212
|
});
|
|
195
|
-
// Create new token
|
|
213
|
+
// Create new token with hashed storage
|
|
196
214
|
const token = this.generateToken();
|
|
215
|
+
const tokenHash = this.hashToken(token);
|
|
197
216
|
const userToken = tokenRepository.create({
|
|
198
217
|
userId: user.id,
|
|
199
218
|
type: _usertokenentity.TokenType.PASSWORD_RESET,
|
|
200
|
-
token,
|
|
219
|
+
token: tokenHash,
|
|
201
220
|
expiresAt: new Date(Date.now() + this.PASSWORD_RESET_EXPIRY)
|
|
202
221
|
});
|
|
203
222
|
await tokenRepository.save(userToken);
|
|
204
|
-
// Send email
|
|
223
|
+
// Send email with raw token
|
|
205
224
|
const resetUrl = `${resetUrlBase}?token=${token}`;
|
|
206
225
|
try {
|
|
207
226
|
await this.emailProvider.sendPasswordResetEmail(email, token, resetUrl);
|
|
208
227
|
this.logger.log(`Password reset email sent to: ${email}`);
|
|
209
228
|
} catch (error) {
|
|
210
|
-
this.logger.error(`Failed to send password reset email: ${error
|
|
229
|
+
this.logger.error(`Failed to send password reset email: ${this.getErrorMessage(error)}`);
|
|
230
|
+
throw new _common.BadRequestException('Failed to process request. Please try again later.');
|
|
211
231
|
}
|
|
212
232
|
return {
|
|
213
233
|
success: true,
|
|
@@ -217,26 +237,20 @@ let AuthEmailService = class AuthEmailService {
|
|
|
217
237
|
/**
|
|
218
238
|
* Reset password using token
|
|
219
239
|
*/ async resetPassword(token, newPassword) {
|
|
220
|
-
const userRepository = await this.
|
|
221
|
-
const tokenRepository = await this.
|
|
240
|
+
const userRepository = await this.getRepository(_userentity.User);
|
|
241
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
242
|
+
// Hash the incoming token to compare with stored hash
|
|
243
|
+
const tokenHash = this.hashToken(token);
|
|
222
244
|
const userToken = await tokenRepository.findOne({
|
|
223
245
|
where: {
|
|
224
|
-
token,
|
|
246
|
+
token: tokenHash,
|
|
225
247
|
type: _usertokenentity.TokenType.PASSWORD_RESET
|
|
226
248
|
}
|
|
227
249
|
});
|
|
228
250
|
if (!userToken) {
|
|
229
251
|
throw new _common.BadRequestException('Invalid or expired reset token');
|
|
230
252
|
}
|
|
231
|
-
|
|
232
|
-
await tokenRepository.delete({
|
|
233
|
-
id: userToken.id
|
|
234
|
-
});
|
|
235
|
-
throw new _common.BadRequestException('Reset token has expired. Please request a new one.');
|
|
236
|
-
}
|
|
237
|
-
if (userToken.usedAt) {
|
|
238
|
-
throw new _common.BadRequestException('This reset token has already been used.');
|
|
239
|
-
}
|
|
253
|
+
this.validateTokenNotExpiredOrUsed(userToken, 'Reset token has expired. Please request a new one.', 'This reset token has already been used.');
|
|
240
254
|
const user = await userRepository.findOne({
|
|
241
255
|
where: {
|
|
242
256
|
id: userToken.userId
|
|
@@ -264,13 +278,15 @@ let AuthEmailService = class AuthEmailService {
|
|
|
264
278
|
* Verify email using token
|
|
265
279
|
* Handles both pending registration and existing user verification
|
|
266
280
|
*/ async verifyEmail(token) {
|
|
267
|
-
const pendingRepository = await this.
|
|
268
|
-
const userRepository = await this.
|
|
269
|
-
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);
|
|
284
|
+
// Hash the incoming token to compare with stored hash
|
|
285
|
+
const tokenHash = this.hashToken(token);
|
|
270
286
|
// Check for pending registration first
|
|
271
287
|
const pending = await pendingRepository.findOne({
|
|
272
288
|
where: {
|
|
273
|
-
token
|
|
289
|
+
token: tokenHash
|
|
274
290
|
}
|
|
275
291
|
});
|
|
276
292
|
if (pending) {
|
|
@@ -292,7 +308,7 @@ let AuthEmailService = class AuthEmailService {
|
|
|
292
308
|
// Check for existing user email verification token
|
|
293
309
|
const userToken = await tokenRepository.findOne({
|
|
294
310
|
where: {
|
|
295
|
-
token,
|
|
311
|
+
token: tokenHash,
|
|
296
312
|
type: _usertokenentity.TokenType.EMAIL_VERIFICATION
|
|
297
313
|
}
|
|
298
314
|
});
|
|
@@ -334,12 +350,10 @@ let AuthEmailService = class AuthEmailService {
|
|
|
334
350
|
/**
|
|
335
351
|
* Resend verification email - handles both pending registrations and existing users
|
|
336
352
|
*/ async resendVerificationEmail(email, verifyUrlBase) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
const userRepository = await this.getUserRepository();
|
|
342
|
-
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);
|
|
343
357
|
// Check pending registration first
|
|
344
358
|
const pending = await pendingRepository.findOne({
|
|
345
359
|
where: {
|
|
@@ -348,16 +362,11 @@ let AuthEmailService = class AuthEmailService {
|
|
|
348
362
|
});
|
|
349
363
|
if (pending) {
|
|
350
364
|
const token = this.generateToken();
|
|
351
|
-
pending.token = token;
|
|
365
|
+
pending.token = this.hashToken(token);
|
|
352
366
|
pending.expiresAt = new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY);
|
|
353
367
|
await pendingRepository.save(pending);
|
|
354
368
|
const verifyUrl = `${verifyUrlBase}?token=${token}`;
|
|
355
|
-
|
|
356
|
-
await this.emailProvider.sendVerificationEmail(email, token, verifyUrl);
|
|
357
|
-
this.logger.log(`Re-sent registration verification email to: ${email}`);
|
|
358
|
-
} catch (error) {
|
|
359
|
-
this.logger.error(`Failed to resend verification email: ${error.message}`);
|
|
360
|
-
}
|
|
369
|
+
await this.sendVerificationEmailSafe(email, token, verifyUrl, 'Re-sent registration');
|
|
361
370
|
return {
|
|
362
371
|
success: true,
|
|
363
372
|
message: 'If the email requires verification, a link has been sent.'
|
|
@@ -384,17 +393,12 @@ let AuthEmailService = class AuthEmailService {
|
|
|
384
393
|
const userToken = tokenRepository.create({
|
|
385
394
|
userId: user.id,
|
|
386
395
|
type: _usertokenentity.TokenType.EMAIL_VERIFICATION,
|
|
387
|
-
token,
|
|
396
|
+
token: this.hashToken(token),
|
|
388
397
|
expiresAt: new Date(Date.now() + this.EMAIL_VERIFICATION_EXPIRY)
|
|
389
398
|
});
|
|
390
399
|
await tokenRepository.save(userToken);
|
|
391
400
|
const verifyUrl = `${verifyUrlBase}?token=${token}`;
|
|
392
|
-
|
|
393
|
-
await this.emailProvider.sendVerificationEmail(user.email, token, verifyUrl);
|
|
394
|
-
this.logger.log(`Verification email sent to: ${user.email}`);
|
|
395
|
-
} catch (error) {
|
|
396
|
-
this.logger.error(`Failed to send verification email: ${error.message}`);
|
|
397
|
-
}
|
|
401
|
+
await this.sendVerificationEmailSafe(user.email, token, verifyUrl, 'Verification');
|
|
398
402
|
return {
|
|
399
403
|
success: true,
|
|
400
404
|
message: 'If the email requires verification, a link has been sent.'
|
|
@@ -406,8 +410,8 @@ let AuthEmailService = class AuthEmailService {
|
|
|
406
410
|
/**
|
|
407
411
|
* Clean up expired tokens and pending registrations
|
|
408
412
|
*/ async cleanupExpired() {
|
|
409
|
-
const tokenRepository = await this.
|
|
410
|
-
const pendingRepository = await this.
|
|
413
|
+
const tokenRepository = await this.getRepository(_usertokenentity.UserToken);
|
|
414
|
+
const pendingRepository = await this.getRepository(_pendingregistrationentity.PendingRegistration);
|
|
411
415
|
const now = new Date();
|
|
412
416
|
const tokenResult = await tokenRepository.delete({
|
|
413
417
|
expiresAt: (0, _typeorm.LessThan)(now)
|