nest-authme 1.2.2 → 1.2.4
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Injectable, UnauthorizedException{{#if features.resetPassword}}, BadRequestException{{/if}}{{#if features.emailVerification}}, NotFoundException{{/if}}{{#if features.accountLockout}}, ForbiddenException{{/if}} } from '@nestjs/common';
|
|
2
|
-
{{#if (or features.emailVerification features.resetPassword)}}
|
|
2
|
+
{{#if (or features.emailVerification features.resetPassword features.refreshTokens)}}
|
|
3
3
|
import * as crypto from 'crypto';
|
|
4
4
|
{{/if}}
|
|
5
5
|
import { JwtService } from '@nestjs/jwt';
|
|
@@ -57,7 +57,7 @@ export class AuthService {
|
|
|
57
57
|
* Validate user credentials
|
|
58
58
|
*/
|
|
59
59
|
async validateUser(email: string, password: string): Promise<any> {
|
|
60
|
-
const user = await this.usersService.findByEmail(email);
|
|
60
|
+
const user = await this.usersService.findByEmail(email.toLowerCase());
|
|
61
61
|
if (!user) {
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
@@ -152,7 +152,7 @@ export class AuthService {
|
|
|
152
152
|
const hashedPassword = await bcrypt.hash(registerDto.password, bcryptRounds);
|
|
153
153
|
|
|
154
154
|
const user = await this.usersService.create({
|
|
155
|
-
email: registerDto.email,
|
|
155
|
+
email: registerDto.email.toLowerCase(),
|
|
156
156
|
{{#if features.useUsername}}
|
|
157
157
|
username: registerDto.username,
|
|
158
158
|
{{/if}}
|
|
@@ -244,6 +244,10 @@ export class AuthService {
|
|
|
244
244
|
* Verify email with token
|
|
245
245
|
*/
|
|
246
246
|
async verifyEmail(token: string): Promise<{ message: string }> {
|
|
247
|
+
if (!token) {
|
|
248
|
+
throw new NotFoundException('Invalid verification token');
|
|
249
|
+
}
|
|
250
|
+
|
|
247
251
|
const user = await this.usersService.findByVerificationToken(token);
|
|
248
252
|
if (!user) {
|
|
249
253
|
throw new NotFoundException('Invalid verification token');
|
|
@@ -261,7 +265,7 @@ export class AuthService {
|
|
|
261
265
|
{{else}}
|
|
262
266
|
async resendVerification(email: string): Promise<{ verificationToken: string; message: string }> {
|
|
263
267
|
{{/if}}
|
|
264
|
-
const user = await this.usersService.findByEmail(email);
|
|
268
|
+
const user = await this.usersService.findByEmail(email.toLowerCase());
|
|
265
269
|
if (!user) {
|
|
266
270
|
throw new NotFoundException('User not found');
|
|
267
271
|
}
|
|
@@ -294,7 +298,7 @@ export class AuthService {
|
|
|
294
298
|
*/
|
|
295
299
|
{{#if features.emailService}}
|
|
296
300
|
async forgotPassword(forgotPasswordDto: ForgotPasswordDto): Promise<{ message: string }> {
|
|
297
|
-
const user = await this.usersService.findByEmail(forgotPasswordDto.email);
|
|
301
|
+
const user = await this.usersService.findByEmail(forgotPasswordDto.email.toLowerCase());
|
|
298
302
|
if (!user) {
|
|
299
303
|
// Return success even if user not found to prevent email enumeration
|
|
300
304
|
return { message: 'If the email exists, a password reset link has been sent' };
|
|
@@ -311,7 +315,7 @@ export class AuthService {
|
|
|
311
315
|
}
|
|
312
316
|
{{else}}
|
|
313
317
|
async forgotPassword(forgotPasswordDto: ForgotPasswordDto): Promise<{ resetToken: string; message: string }> {
|
|
314
|
-
const user = await this.usersService.findByEmail(forgotPasswordDto.email);
|
|
318
|
+
const user = await this.usersService.findByEmail(forgotPasswordDto.email.toLowerCase());
|
|
315
319
|
if (!user) {
|
|
316
320
|
// Return success even if user not found to prevent email enumeration
|
|
317
321
|
return { resetToken: '', message: 'If the email exists, a reset token has been generated' };
|
|
@@ -356,7 +360,7 @@ export class AuthService {
|
|
|
356
360
|
*/
|
|
357
361
|
private async createRefreshToken(userId: string): Promise<any> {
|
|
358
362
|
const token = this.jwtService.sign(
|
|
359
|
-
{ sub: userId },
|
|
363
|
+
{ sub: userId, jti: crypto.randomBytes(16).toString('hex') },
|
|
360
364
|
{ expiresIn: '{{jwt.refreshExpiration}}' }
|
|
361
365
|
);
|
|
362
366
|
|
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import { ConfigService } from '@nestjs/config';
|
|
3
3
|
import * as nodemailer from 'nodemailer';
|
|
4
4
|
|
|
5
5
|
@Injectable()
|
|
6
6
|
export class MailService {
|
|
7
|
+
private readonly logger = new Logger(MailService.name);
|
|
7
8
|
private transporter: nodemailer.Transporter;
|
|
9
|
+
private isConfigured: boolean;
|
|
8
10
|
|
|
9
11
|
constructor(private configService: ConfigService) {
|
|
12
|
+
const smtpUser = this.configService.get('SMTP_USER', '');
|
|
13
|
+
const smtpPass = this.configService.get('SMTP_PASS', '');
|
|
14
|
+
this.isConfigured = !!(smtpUser && smtpPass);
|
|
15
|
+
|
|
10
16
|
this.transporter = nodemailer.createTransport({
|
|
11
17
|
host: this.configService.get('SMTP_HOST', 'smtp.ethereal.email'),
|
|
12
18
|
port: parseInt(this.configService.get('SMTP_PORT', '587'), 10),
|
|
13
19
|
secure: false,
|
|
14
20
|
auth: {
|
|
15
|
-
user:
|
|
16
|
-
pass:
|
|
21
|
+
user: smtpUser,
|
|
22
|
+
pass: smtpPass,
|
|
17
23
|
},
|
|
18
24
|
});
|
|
19
25
|
}
|
|
@@ -33,17 +39,29 @@ export class MailService {
|
|
|
33
39
|
async sendVerificationEmail(to: string, token: string): Promise<void> {
|
|
34
40
|
const verifyUrl = `${this.appUrl}/auth/verify-email?token=${token}`;
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
if (!this.isConfigured) {
|
|
43
|
+
this.logger.warn(
|
|
44
|
+
`SMTP not configured. Verification email for ${to} not sent. Token: ${token}`,
|
|
45
|
+
);
|
|
46
|
+
this.logger.warn(`Verify URL: ${verifyUrl}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await this.transporter.sendMail({
|
|
52
|
+
from: this.mailFrom,
|
|
53
|
+
to,
|
|
54
|
+
subject: 'Verify your email address',
|
|
55
|
+
html: `
|
|
56
|
+
<h2>Email Verification</h2>
|
|
57
|
+
<p>Click the link below to verify your email address:</p>
|
|
58
|
+
<p><a href="${verifyUrl}">${verifyUrl}</a></p>
|
|
59
|
+
<p>If you did not create an account, please ignore this email.</p>
|
|
60
|
+
`,
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
this.logger.error(`Failed to send verification email to ${to}: ${error.message}`);
|
|
64
|
+
}
|
|
47
65
|
}
|
|
48
66
|
{{/if}}
|
|
49
67
|
|
|
@@ -54,18 +72,30 @@ export class MailService {
|
|
|
54
72
|
async sendResetPasswordEmail(to: string, token: string): Promise<void> {
|
|
55
73
|
const resetUrl = `${this.appUrl}/auth/reset-password?token=${token}`;
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
if (!this.isConfigured) {
|
|
76
|
+
this.logger.warn(
|
|
77
|
+
`SMTP not configured. Reset email for ${to} not sent. Token: ${token}`,
|
|
78
|
+
);
|
|
79
|
+
this.logger.warn(`Reset URL: ${resetUrl}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await this.transporter.sendMail({
|
|
85
|
+
from: this.mailFrom,
|
|
86
|
+
to,
|
|
87
|
+
subject: 'Reset your password',
|
|
88
|
+
html: `
|
|
89
|
+
<h2>Password Reset</h2>
|
|
90
|
+
<p>Click the link below to reset your password:</p>
|
|
91
|
+
<p><a href="${resetUrl}">${resetUrl}</a></p>
|
|
92
|
+
<p>This link expires in 1 hour.</p>
|
|
93
|
+
<p>If you did not request a password reset, please ignore this email.</p>
|
|
94
|
+
`,
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this.logger.error(`Failed to send reset email to ${to}: ${error.message}`);
|
|
98
|
+
}
|
|
69
99
|
}
|
|
70
100
|
{{/if}}
|
|
71
101
|
}
|
|
@@ -333,7 +333,11 @@ describe('AuthService', () => {
|
|
|
333
333
|
|
|
334
334
|
const result = await authService.resendVerification('test@example.com');
|
|
335
335
|
|
|
336
|
+
{{#if features.emailService}}
|
|
337
|
+
expect(result.message).toBeDefined();
|
|
338
|
+
{{else}}
|
|
336
339
|
expect(result.verificationToken).toBeDefined();
|
|
340
|
+
{{/if}}
|
|
337
341
|
expect(mockUsersService.setVerificationToken).toHaveBeenCalled();
|
|
338
342
|
});
|
|
339
343
|
});
|
|
@@ -347,7 +351,11 @@ describe('AuthService', () => {
|
|
|
347
351
|
|
|
348
352
|
const result = await authService.forgotPassword({ email: 'test@example.com' });
|
|
349
353
|
|
|
354
|
+
{{#if features.emailService}}
|
|
355
|
+
expect(result.message).toBeDefined();
|
|
356
|
+
{{else}}
|
|
350
357
|
expect(result.resetToken).toBeDefined();
|
|
358
|
+
{{/if}}
|
|
351
359
|
expect(mockUsersService.setResetToken).toHaveBeenCalled();
|
|
352
360
|
});
|
|
353
361
|
|