1
1
import { BadRequestException , Injectable , Logger , NotFoundException } from '@nestjs/common' ;
2
2
import * as bcrypt from 'bcrypt' ;
3
+ import { differenceInMinutes } from 'date-fns' ;
3
4
import { Types } from 'mongoose' ;
4
5
import { EmailsService } from '../emails/emails.service' ;
5
6
import { UserDocument } from '../models/users/schemas/user.schema' ;
7
+ import { UsersTokenService } from '../models/users/users-token.service' ;
6
8
import { UsersService } from '../models/users/users.service' ;
7
9
import { UserRegisterDto } from './dto/user-register.dto' ;
8
10
import { EmailConfirmTemplate } from './templates/email-confirm.template' ;
9
- import { UsersTokenService } from '../models/users/users-token.service' ;
10
- import { EMAIL_CONFIRM_TOKEN } from './types/emailTokenTypes' ;
11
- import { log } from 'console' ;
12
- import { differenceInDays , differenceInMinutes } from 'date-fns' ;
11
+ import { PasswordRestTemplate } from './templates/password-reset.template' ;
12
+ import { EMAIL_CONFIRM_TOKEN , PASSWORD_RESET_TOKEN } from './types/emailTokenTypes' ;
13
13
14
14
@Injectable ( )
15
15
export class AuthService {
@@ -22,15 +22,15 @@ export class AuthService {
22
22
private readonly logger = new Logger ( AuthService . name ) ;
23
23
24
24
async registerUser ( data : UserRegisterDto ) : Promise < UserDocument > {
25
- const hash = await bcrypt . hash ( data . password , 12 ) ;
25
+ const hash = await this . hashPassword ( data . password ) ;
26
26
27
27
const user = await this . usersService . create ( {
28
28
username : data . username ,
29
29
email : data . email ,
30
30
passwordHash : hash ,
31
31
} ) ;
32
32
33
- await this . sendEmailConfirmation ( user ) . catch ( ( error ) => {
33
+ await this . sendEmailConfirmation ( user . _id ) . catch ( ( error ) => {
34
34
this . logger . error ( `Failed to send initial confirmation E-mail ${ error } ` ) ;
35
35
} ) ;
36
36
@@ -53,7 +53,7 @@ export class AuthService {
53
53
}
54
54
}
55
55
56
- async confirmUserEmail ( userId : Types . ObjectId , token : string ) : Promise < any > {
56
+ async confirmUserEmail ( userId : Types . ObjectId , token : string ) : Promise < UserDocument > {
57
57
const tokenValid = await this . usersTokenService . validateToken ( {
58
58
userId,
59
59
token,
@@ -68,26 +68,40 @@ export class AuthService {
68
68
return this . usersService . setConfirmed ( userId , true ) ;
69
69
}
70
70
71
- async retryConfirmationEmail ( userId : Types . ObjectId ) {
71
+ async resetUserPassword (
72
+ userId : Types . ObjectId ,
73
+ token : string ,
74
+ password : string ,
75
+ ) : Promise < UserDocument > {
76
+ const tokenValid = await this . usersTokenService . validateToken ( {
77
+ userId,
78
+ token,
79
+ type : PASSWORD_RESET_TOKEN ,
80
+ } ) ;
81
+
82
+ if ( ! tokenValid ) {
83
+ throw new BadRequestException ( 'This password reset token is invalid or expired' ) ;
84
+ }
85
+
86
+ await this . usersTokenService . invalidateToken ( userId , token ) ;
87
+
88
+ // Confirm email on password reset
89
+ await this . usersService . setConfirmed ( userId , true ) ;
90
+
91
+ const hash = await this . hashPassword ( password ) ;
92
+ return this . usersService . findOneByIdAndUpdate ( userId , { 'auth.password' : hash } ) ;
93
+ }
94
+
95
+ async sendEmailConfirmation ( userId : Types . ObjectId ) {
72
96
const user = await this . usersService . findById ( userId ) ;
73
97
if ( ! user ) throw new NotFoundException ( 'User not found' ) ;
74
98
75
99
if ( user . profile . isConfirmed ) {
76
100
throw new BadRequestException ( 'This user E-mail address is already confirmed' ) ;
77
101
}
78
102
79
- const lastRetry = await this . usersTokenService . getLastRetry ( userId , EMAIL_CONFIRM_TOKEN ) ;
80
- if ( lastRetry ) {
81
- const minutesDiff = differenceInMinutes ( new Date ( ) , lastRetry ) ;
82
- if ( minutesDiff < 5 ) {
83
- throw new BadRequestException ( 'Please wait before sending next confirmation email' ) ;
84
- }
85
- }
86
-
87
- return this . sendEmailConfirmation ( user ) ;
88
- }
103
+ await this . validateIfCanSendEmail ( userId , EMAIL_CONFIRM_TOKEN ) ;
89
104
90
- async sendEmailConfirmation ( user : UserDocument ) {
91
105
const token = await this . usersTokenService . generateAndSaveToken ( {
92
106
userId : user . _id ,
93
107
type : EMAIL_CONFIRM_TOKEN ,
@@ -100,4 +114,40 @@ export class AuthService {
100
114
text : content . toString ( ) ,
101
115
} ) ;
102
116
}
117
+
118
+ async sendPasswordResetEmail ( email : string ) {
119
+ const user = await this . usersService . findByEmail ( email ) ;
120
+ if ( ! user ) throw new NotFoundException ( 'This E-mail is not associated with any user account!' ) ;
121
+
122
+ await this . validateIfCanSendEmail ( user . _id , PASSWORD_RESET_TOKEN ) ;
123
+
124
+ const token = await this . usersTokenService . generateAndSaveToken ( {
125
+ userId : user . _id ,
126
+ type : PASSWORD_RESET_TOKEN ,
127
+ } ) ;
128
+ const content = new PasswordRestTemplate ( user . _id , token ) ;
129
+
130
+ return this . emailService . sendEmail ( {
131
+ to : user . profile . email ,
132
+ subject : '[StockedUp] Password reset request' ,
133
+ text : content . toString ( ) ,
134
+ } ) ;
135
+ }
136
+
137
+ private async validateIfCanSendEmail ( userId : Types . ObjectId , token : string ) : Promise < boolean > {
138
+ const lastRetry = await this . usersTokenService . getLastRetry ( userId , token ) ;
139
+ if ( lastRetry ) {
140
+ const minutesDiff = differenceInMinutes ( new Date ( ) , lastRetry ) ;
141
+ if ( minutesDiff < 10 ) {
142
+ throw new BadRequestException (
143
+ 'The confirmation email was sent recently, please check your inbox' ,
144
+ ) ;
145
+ }
146
+ }
147
+ return true ;
148
+ }
149
+
150
+ private hashPassword ( input : string ) : Promise < string > {
151
+ return bcrypt . hash ( input , 12 ) ;
152
+ }
103
153
}
0 commit comments