5
5
UnauthorizedException ,
6
6
} from '@nestjs/common' ;
7
7
import { InjectModel } from '@nestjs/mongoose' ;
8
- import { Model } from 'mongoose' ;
8
+ import { Model , Types } from 'mongoose' ;
9
9
import { User } from 'src/users/entities/user.entity' ;
10
10
import { HashingService } from '../hashing/hashing.service' ;
11
11
import { SignUpDto } from './dto/sign-up.dto' ;
@@ -14,6 +14,9 @@ import { JwtService } from '@nestjs/jwt';
14
14
import { ConfigType } from '@nestjs/config' ;
15
15
import jwtConfig from '../config/jwt.config' ;
16
16
import { ActiveUserData } from '../interfaces/active-user.data.interface' ;
17
+ import { RefreshTokenDto } from './dto/refresh-token.dto' ;
18
+ import { RefreshTokenIdsStorage } from '../storage/refresh-token-ids.storage/refresh-token-ids.storage' ;
19
+ import { randomUUID } from 'crypto' ;
17
20
18
21
@Injectable ( )
19
22
export class AuthService {
@@ -23,6 +26,7 @@ export class AuthService {
23
26
private readonly jwtService : JwtService ,
24
27
@Inject ( jwtConfig . KEY )
25
28
private readonly jwtConfiguration : ConfigType < typeof jwtConfig > ,
29
+ private readonly refreshTokenIdsStorage : RefreshTokenIdsStorage ,
26
30
) { }
27
31
28
32
async signUp ( signUpDto : SignUpDto ) : Promise < User > {
@@ -57,21 +61,72 @@ export class AuthService {
57
61
if ( ! isEqual ) {
58
62
throw new UnauthorizedException ( 'Password does not match' ) ;
59
63
}
60
- const accessToken = await this . jwtService . signAsync (
61
- {
62
- sub : user . id ,
63
- email : user . email ,
64
- } as ActiveUserData ,
65
- {
66
- audience : this . jwtConfiguration . audience ,
67
- issuer : this . jwtConfiguration . issuer ,
68
- secret : this . jwtConfiguration . secret ,
69
- expiresIn : this . jwtConfiguration . accessTokenTtl ,
70
- } ,
71
- ) ;
72
- return { accessToken } ;
64
+ const { accessToken, refreshToken } = await this . generateTokens ( user ) ;
65
+
66
+ return { accessToken, refreshToken } ;
73
67
} catch ( error ) {
74
68
return error ;
75
69
}
76
70
}
71
+
72
+ async refreshTokens ( refreshTokenDto : RefreshTokenDto ) {
73
+ try {
74
+ const { sub, refreshTokenId } = await this . jwtService . verifyAsync <
75
+ Pick < ActiveUserData , 'sub' > & { refreshTokenId : string }
76
+ > ( refreshTokenDto . refreshToken , {
77
+ secret : this . jwtConfiguration . secret ,
78
+ audience : this . jwtConfiguration . audience ,
79
+ issuer : this . jwtConfiguration . issuer ,
80
+ } ) ;
81
+
82
+ const user = await this . userModel
83
+ . findById ( { _id : new Types . ObjectId ( sub ) } )
84
+ . exec ( ) ;
85
+ const isValid = await this . refreshTokenIdsStorage . validate (
86
+ user . id ,
87
+ refreshTokenId ,
88
+ ) ;
89
+ if ( isValid ) {
90
+ await this . refreshTokenIdsStorage . invalidate ( user . id ) ;
91
+ } else {
92
+ throw new Error ( 'Invalid refresh token' ) ;
93
+ }
94
+
95
+ return await this . generateTokens ( user ) ;
96
+ } catch ( error ) {
97
+ console . log ( error ) ;
98
+ throw new UnauthorizedException ( 'Invalid refresh token' ) ;
99
+ }
100
+ }
101
+
102
+ async generateTokens ( user : User ) {
103
+ const refreshTokenId = randomUUID ( ) ;
104
+ const [ accessToken , refreshToken ] = await Promise . all ( [
105
+ this . signToken < Partial < ActiveUserData > > (
106
+ user . id ,
107
+ this . jwtConfiguration . accessTokenTtl ,
108
+ { email : user . email } ,
109
+ ) ,
110
+ this . signToken ( user . id , this . jwtConfiguration . refreshTokenTtl , {
111
+ refreshTokenId,
112
+ } ) ,
113
+ ] ) ;
114
+ this . refreshTokenIdsStorage . insert ( user . id , refreshTokenId ) ;
115
+ return { accessToken, refreshToken } ;
116
+ }
117
+
118
+ private async signToken < T > ( userId : number , expiresIn : number , payload ?: T ) {
119
+ return await this . jwtService . signAsync (
120
+ {
121
+ sub : userId ,
122
+ ...payload ,
123
+ } ,
124
+ {
125
+ audience : this . jwtConfiguration . audience ,
126
+ issuer : this . jwtConfiguration . issuer ,
127
+ secret : this . jwtConfiguration . secret ,
128
+ expiresIn,
129
+ } ,
130
+ ) ;
131
+ }
77
132
}
0 commit comments