1
1
import * as bcrypt from 'bcryptjs' ;
2
- import { Inject , Injectable } from '@nestjs/common' ;
2
+ import { HttpStatus , Inject , Injectable } from '@nestjs/common' ;
3
3
import { JwtService } from '@nestjs/jwt' ;
4
4
import { Credentials , OAuth2Client } from 'google-auth-library' ;
5
5
import { ClientProxy } from '@nestjs/microservices' ;
6
- import { AuthDto , AuthIdDto , RefreshTokenDto } from './dto' ;
6
+ import {
7
+ AuthDto ,
8
+ AuthIdDto ,
9
+ RefreshTokenDto ,
10
+ ResetPasswordDto ,
11
+ ResetPasswordRequestDto ,
12
+ } from './dto' ;
7
13
import { HttpService } from '@nestjs/axios' ;
8
14
import { RpcException } from '@nestjs/microservices' ;
9
15
import { firstValueFrom } from 'rxjs' ;
10
16
import axios , { AxiosResponse } from 'axios' ;
11
17
import { Token , TokenPayload } from './interfaces' ;
12
18
import { AccountProvider } from './constants/account-provider.enum' ;
19
+ import * as nodemailer from 'nodemailer' ;
13
20
14
21
const SALT_ROUNDS = 10 ;
15
22
@@ -49,6 +56,7 @@ export class AppService {
49
56
const tokens = await this . generateTokens ( {
50
57
id : userId ,
51
58
email : newUser . email ,
59
+ roles : newUser . roles ,
52
60
} ) ;
53
61
await this . updateRefreshToken ( {
54
62
id : userId ,
@@ -85,6 +93,7 @@ export class AppService {
85
93
const tokens = await this . generateTokens ( {
86
94
id : userId ,
87
95
email : user . email ,
96
+ roles : user . roles ,
88
97
} ) ;
89
98
await this . updateRefreshToken ( {
90
99
id : userId ,
@@ -132,6 +141,7 @@ export class AppService {
132
141
const tokens = await this . generateTokens ( {
133
142
id : id ,
134
143
email : user . email ,
144
+ roles : user . roles ,
135
145
} ) ;
136
146
await this . updateRefreshToken ( { id, refreshToken : tokens . refresh_token } ) ;
137
147
@@ -141,6 +151,102 @@ export class AppService {
141
151
}
142
152
}
143
153
154
+ public async generateResetPasswordRequest ( dto : ResetPasswordRequestDto ) : Promise < boolean > {
155
+ const user = await firstValueFrom (
156
+ this . userClient . send (
157
+ {
158
+ cmd : 'get-user-by-email' ,
159
+ } ,
160
+ dto . email ,
161
+ ) ,
162
+ ) ;
163
+
164
+ if ( ! user ) {
165
+ throw new RpcException ( 'User not found' ) ;
166
+ }
167
+
168
+ const resetToken = this . jwtService . sign (
169
+ { userId : user . _id . toString ( ) , email : dto . email , type : 'reset-password' } ,
170
+ {
171
+ secret : process . env . JWT_SECRET ,
172
+ expiresIn : '1hr' ,
173
+ } ,
174
+ ) ;
175
+
176
+ // Send reset password email
177
+ await this . sendResetEmail ( user . email , resetToken ) ;
178
+
179
+ return true ;
180
+ }
181
+
182
+ public async resetPassword ( dto : ResetPasswordDto ) : Promise < boolean > {
183
+ const { userId, email } = await this . validatePasswordResetToken ( dto . token ) ;
184
+
185
+ const hashedPassword = await bcrypt . hash ( dto . password , SALT_ROUNDS ) ;
186
+
187
+ const response = await firstValueFrom (
188
+ this . userClient . send (
189
+ { cmd : 'update-user-password' } ,
190
+ { id : userId , password : hashedPassword } ,
191
+ ) ,
192
+ ) ;
193
+
194
+ if ( ! response ) {
195
+ throw new RpcException ( 'Error resetting password' ) ;
196
+ }
197
+
198
+ return true ;
199
+ }
200
+
201
+ public async validatePasswordResetToken ( token : string ) : Promise < any > {
202
+ try {
203
+ const decoded = this . jwtService . verify ( token , {
204
+ secret : process . env . JWT_SECRET ,
205
+ } ) ;
206
+ const { userId, email, type } = decoded ;
207
+ if ( type !== 'reset-password' ) {
208
+ throw new RpcException ( {
209
+ statusCode : HttpStatus . UNAUTHORIZED ,
210
+ message : 'Unauthorized: Invalid or expired password reset token' ,
211
+ } ) ;
212
+ }
213
+ return { userId, email } ;
214
+ } catch ( err ) {
215
+ throw new RpcException ( {
216
+ statusCode : HttpStatus . UNAUTHORIZED ,
217
+ message : 'Unauthorized: Invalid or expired password reset token' ,
218
+ } ) ;
219
+ }
220
+ }
221
+
222
+ private async sendResetEmail ( email : string , token : string ) {
223
+ const resetUrl = `${ process . env . FRONTEND_URL } /reset-password?token=${ token } ` ; // To change next time
224
+
225
+ const transporter = nodemailer . createTransport ( {
226
+ service : 'gmail' ,
227
+ host : 'smtp.gmail.com' ,
228
+ port : 465 ,
229
+ secure : true ,
230
+ auth : {
231
+ user : process . env . NODEMAILER_GMAIL_USER ,
232
+ pass : process . env . NODEMAILER_GMAIL_PASSWORD ,
233
+ } ,
234
+ } ) ;
235
+
236
+ const mailOptions = {
237
+
238
+ to : email ,
239
+ subject : 'Password Reset for PeerPrep' ,
240
+ text : `Click here to reset your password: ${ resetUrl } ` ,
241
+ } ;
242
+
243
+ transporter . sendMail ( mailOptions , ( error , info ) => {
244
+ if ( error ) {
245
+ throw new RpcException ( `Error sending reset email: ${ error . message } ` ) ;
246
+ }
247
+ } ) ;
248
+ }
249
+
144
250
public async validateAccessToken ( accessToken : string ) : Promise < any > {
145
251
try {
146
252
const decoded = this . jwtService . verify ( accessToken , {
@@ -180,13 +286,14 @@ export class AppService {
180
286
181
287
// Could include other fields like roles in the future
182
288
private async generateTokens ( payload : TokenPayload ) : Promise < Token > {
183
- const { id, email } = payload ;
289
+ const { id, email, roles } = payload ;
184
290
185
291
const [ accessToken , refreshToken ] = await Promise . all ( [
186
292
this . jwtService . signAsync (
187
293
{
188
294
sub : id ,
189
295
email,
296
+ roles,
190
297
} ,
191
298
{
192
299
secret : process . env . JWT_SECRET ,
@@ -197,6 +304,7 @@ export class AppService {
197
304
{
198
305
sub : id ,
199
306
email,
307
+ roles,
200
308
} ,
201
309
{
202
310
secret : process . env . JWT_REFRESH_SECRET ,
@@ -257,6 +365,7 @@ export class AppService {
257
365
const jwtTokens = await this . generateTokens ( {
258
366
id : user . _id . toString ( ) ,
259
367
email : user . email ,
368
+ roles : user . roles ,
260
369
} ) ;
261
370
262
371
await this . updateRefreshToken ( {
@@ -358,6 +467,7 @@ export class AppService {
358
467
const jwtTokens = await this . generateTokens ( {
359
468
id : user . _id . toString ( ) ,
360
469
email : user . email ,
470
+ roles : user . roles ,
361
471
} ) ;
362
472
363
473
await this . updateRefreshToken ( {
@@ -412,7 +522,7 @@ export class AppService {
412
522
return {
413
523
...response . data ,
414
524
email : emailResponse . data . find ( ( email ) => email . primary ) ?. email ,
415
- }
525
+ } ;
416
526
} catch ( error ) {
417
527
throw new RpcException (
418
528
`Unable to retrieve Github user profile: ${ error . message } ` ,
0 commit comments