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,8 @@ export class AppService {
49
56
const tokens = await this . generateTokens ( {
50
57
id : userId ,
51
58
email : newUser . email ,
59
+ isOnboarded : newUser . isOnboarded ,
60
+ roles : newUser . roles ,
52
61
} ) ;
53
62
await this . updateRefreshToken ( {
54
63
id : userId ,
@@ -85,6 +94,8 @@ export class AppService {
85
94
const tokens = await this . generateTokens ( {
86
95
id : userId ,
87
96
email : user . email ,
97
+ isOnboarded : user . isOnboarded ,
98
+ roles : user . roles ,
88
99
} ) ;
89
100
await this . updateRefreshToken ( {
90
101
id : userId ,
@@ -132,6 +143,8 @@ export class AppService {
132
143
const tokens = await this . generateTokens ( {
133
144
id : id ,
134
145
email : user . email ,
146
+ isOnboarded : user . isOnboarded ,
147
+ roles : user . roles ,
135
148
} ) ;
136
149
await this . updateRefreshToken ( { id, refreshToken : tokens . refresh_token } ) ;
137
150
@@ -141,6 +154,102 @@ export class AppService {
141
154
}
142
155
}
143
156
157
+ public async generateResetPasswordRequest ( dto : ResetPasswordRequestDto ) : Promise < boolean > {
158
+ const user = await firstValueFrom (
159
+ this . userClient . send (
160
+ {
161
+ cmd : 'get-user-by-email' ,
162
+ } ,
163
+ dto . email ,
164
+ ) ,
165
+ ) ;
166
+
167
+ if ( ! user ) {
168
+ throw new RpcException ( 'User not found' ) ;
169
+ }
170
+
171
+ const resetToken = this . jwtService . sign (
172
+ { userId : user . _id . toString ( ) , email : dto . email , type : 'reset-password' } ,
173
+ {
174
+ secret : process . env . JWT_SECRET ,
175
+ expiresIn : '1hr' ,
176
+ } ,
177
+ ) ;
178
+
179
+ // Send reset password email
180
+ await this . sendResetEmail ( user . email , resetToken ) ;
181
+
182
+ return true ;
183
+ }
184
+
185
+ public async resetPassword ( dto : ResetPasswordDto ) : Promise < boolean > {
186
+ const { userId, email } = await this . validatePasswordResetToken ( dto . token ) ;
187
+
188
+ const hashedPassword = await bcrypt . hash ( dto . password , SALT_ROUNDS ) ;
189
+
190
+ const response = await firstValueFrom (
191
+ this . userClient . send (
192
+ { cmd : 'update-user-password' } ,
193
+ { id : userId , password : hashedPassword } ,
194
+ ) ,
195
+ ) ;
196
+
197
+ if ( ! response ) {
198
+ throw new RpcException ( 'Error resetting password' ) ;
199
+ }
200
+
201
+ return true ;
202
+ }
203
+
204
+ public async validatePasswordResetToken ( token : string ) : Promise < any > {
205
+ try {
206
+ const decoded = this . jwtService . verify ( token , {
207
+ secret : process . env . JWT_SECRET ,
208
+ } ) ;
209
+ const { userId, email, type } = decoded ;
210
+ if ( type !== 'reset-password' ) {
211
+ throw new RpcException ( {
212
+ statusCode : HttpStatus . UNAUTHORIZED ,
213
+ message : 'Unauthorized: Invalid or expired password reset token' ,
214
+ } ) ;
215
+ }
216
+ return { userId, email } ;
217
+ } catch ( err ) {
218
+ throw new RpcException ( {
219
+ statusCode : HttpStatus . UNAUTHORIZED ,
220
+ message : 'Unauthorized: Invalid or expired password reset token' ,
221
+ } ) ;
222
+ }
223
+ }
224
+
225
+ private async sendResetEmail ( email : string , token : string ) {
226
+ const resetUrl = `${ process . env . FRONTEND_URL } /reset-password?token=${ token } ` ; // To change next time
227
+
228
+ const transporter = nodemailer . createTransport ( {
229
+ service : 'gmail' ,
230
+ host : 'smtp.gmail.com' ,
231
+ port : 465 ,
232
+ secure : true ,
233
+ auth : {
234
+ user : process . env . NODEMAILER_GMAIL_USER ,
235
+ pass : process . env . NODEMAILER_GMAIL_PASSWORD ,
236
+ } ,
237
+ } ) ;
238
+
239
+ const mailOptions = {
240
+
241
+ to : email ,
242
+ subject : 'Password Reset for PeerPrep' ,
243
+ text : `Click here to reset your password: ${ resetUrl } ` ,
244
+ } ;
245
+
246
+ transporter . sendMail ( mailOptions , ( error , info ) => {
247
+ if ( error ) {
248
+ throw new RpcException ( `Error sending reset email: ${ error . message } ` ) ;
249
+ }
250
+ } ) ;
251
+ }
252
+
144
253
public async validateAccessToken ( accessToken : string ) : Promise < any > {
145
254
try {
146
255
const decoded = this . jwtService . verify ( accessToken , {
@@ -180,23 +289,23 @@ export class AppService {
180
289
181
290
// Could include other fields like roles in the future
182
291
private async generateTokens ( payload : TokenPayload ) : Promise < Token > {
183
- const { id, email } = payload ;
292
+ const { id, ... rest } = payload ;
184
293
185
294
const [ accessToken , refreshToken ] = await Promise . all ( [
186
295
this . jwtService . signAsync (
187
- {
188
- sub : id ,
189
- email ,
190
- } ,
191
- {
192
- secret : process . env . JWT_SECRET ,
193
- expiresIn : '15m ' , // 15 minute
296
+ {
297
+ sub : id ,
298
+ ... rest ,
299
+ } ,
300
+ {
301
+ secret : process . env . JWT_SECRET ,
302
+ expiresIn : '1h ' , // 1 hour
194
303
} ,
195
304
) ,
196
305
this . jwtService . signAsync (
197
306
{
198
307
sub : id ,
199
- email ,
308
+ ... rest ,
200
309
} ,
201
310
{
202
311
secret : process . env . JWT_REFRESH_SECRET ,
@@ -257,6 +366,8 @@ export class AppService {
257
366
const jwtTokens = await this . generateTokens ( {
258
367
id : user . _id . toString ( ) ,
259
368
email : user . email ,
369
+ isOnboarded : user . isOnboarded ,
370
+ roles : user . roles ,
260
371
} ) ;
261
372
262
373
await this . updateRefreshToken ( {
@@ -358,6 +469,8 @@ export class AppService {
358
469
const jwtTokens = await this . generateTokens ( {
359
470
id : user . _id . toString ( ) ,
360
471
email : user . email ,
472
+ isOnboarded : user . isOnboarded ,
473
+ roles : user . roles ,
361
474
} ) ;
362
475
363
476
await this . updateRefreshToken ( {
@@ -412,7 +525,7 @@ export class AppService {
412
525
return {
413
526
...response . data ,
414
527
email : emailResponse . data . find ( ( email ) => email . primary ) ?. email ,
415
- }
528
+ } ;
416
529
} catch ( error ) {
417
530
throw new RpcException (
418
531
`Unable to retrieve Github user profile: ${ error . message } ` ,
0 commit comments