Skip to content

Commit

Permalink
Refresh token
Browse files Browse the repository at this point in the history
  • Loading branch information
PhamAnhHoang committed Dec 16, 2023
1 parent e2bb037 commit d7a051d
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 66 deletions.
8 changes: 4 additions & 4 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ export class AuthController {
async refreshToken(
@AuthUser() user: Pure<UserEntity>
): Promise<RefreshTokenResponseInterface> {
const accessToken = this.authService.generateAccessToken(user.email);
return {
access_token: accessToken,
const accessToken = this.authService.generateAccessToken(user.email)
return {
access_token: accessToken,
status: StatusType.OK
};
}
}
}
8 changes: 7 additions & 1 deletion apps/api/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ import { JwtRefreshTokenStrategy } from './strategies/jwt-refresh-token.strategy
OrganizationModule
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy, SessionSerializer, JwtRefreshTokenStrategy]
providers: [
AuthService,
LocalStrategy,
JwtStrategy,
SessionSerializer,
JwtRefreshTokenStrategy
]
})
export class AuthModule {}
63 changes: 33 additions & 30 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Pure } from '@isomera/interfaces'
import { generateRandomStringUtil } from '@isomera/utils'
import { OrganizationService } from '../organization/organization.service'
import { ConfigService } from '@nestjs/config'
import * as bcrypt from 'bcrypt'
import * as bcrypt from 'bcrypt'

@Injectable()
export class AuthService {
Expand Down Expand Up @@ -64,7 +64,12 @@ export class AuthService {
}
}

async login(email: string, password: string): Promise<Partial<UserEntity> & { refresh_token: string, access_token: string}> {
async login(
email: string,
password: string
): Promise<
Partial<UserEntity> & { refresh_token: string; access_token: string }
> {
let user: UserEntity

try {
Expand All @@ -82,11 +87,11 @@ export class AuthService {
}
delete user.password

const {refresh_token, access_token} = this.signToken(user);
const { refresh_token, access_token } = this.signToken(user)

await this.storeRefreshToken(user, refresh_token);
return {...user, refresh_token, access_token}
await this.storeRefreshToken(user, refresh_token)

return { ...user, refresh_token, access_token }
}

async verifyPayload(payload: JwtPayload): Promise<UserEntity> {
Expand Down Expand Up @@ -200,35 +205,33 @@ export class AuthService {
}

async getUserIfRefreshTokenMatched(
email: string,
refreshToken: string,
): Promise<UserEntity> {
const user = await this.userService.findOne({ where: { email } })
if (!user) {
throw new UnauthorizedException();
}
await this.verifyPlainContentWithHashedContent(
refreshToken,
user.refreshToken,
);
return user;
}
email: string,
refreshToken: string
): Promise<UserEntity> {
const user = await this.userService.findOne({ where: { email } })
if (!user) {
throw new UnauthorizedException()
}
await this.verifyPlainContentWithHashedContent(
refreshToken,
user.refreshToken
)
return user
}

private async verifyPlainContentWithHashedContent(
plainText: string,
hashedText: string,
) {
const is_matching = await bcrypt.compare(plainText, hashedText);
hashedText: string
) {
const is_matching = await bcrypt.compare(plainText, hashedText)
if (!is_matching) {
throw new BadRequestException();
throw new BadRequestException()
}
}
}

async storeRefreshToken(user: UserEntity, token: string): Promise<void> {
async storeRefreshToken(user: UserEntity, token: string): Promise<void> {
const salt = await bcrypt.genSalt()
const hashed_token = await bcrypt.hash(token, salt);
await this.userService.storeRefreshToken(user, hashed_token);
}


const hashed_token = await bcrypt.hash(token, salt)
await this.userService.storeRefreshToken(user, hashed_token)
}
}
4 changes: 3 additions & 1 deletion apps/api/src/auth/interceptors/token.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export class TokenInterceptor implements NestInterceptor {

intercept(
context: ExecutionContext,
next: CallHandler<Partial<UserEntity> & { access_token: string; refresh_token: string }>
next: CallHandler<
Partial<UserEntity> & { access_token: string; refresh_token: string }
>
): Observable<
Partial<UserEntity> & { access_token: string; refresh_token: string }
> {
Expand Down
48 changes: 23 additions & 25 deletions apps/api/src/auth/strategies/jwt-refresh-token.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import { Request } from 'express';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from '../auth.service';
import { JwtPayload } from '@isomera/interfaces';
import { Request } from 'express'
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'
import { AuthService } from '../auth.service'
import { JwtPayload } from '@isomera/interfaces'

@Injectable()
export class JwtRefreshTokenStrategy extends PassportStrategy(
Strategy,
'jwt-refresh-token',
Strategy,
'jwt-refresh-token'
) {
constructor(
private readonly authService: AuthService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.APP_SECRET,
passReqToCallback: true,
});
}
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.APP_SECRET,
passReqToCallback: true
})
}

async validate(request: Request, payload: JwtPayload) {
return await this.authService.getUserIfRefreshTokenMatched(
payload.sub,
request.headers.authorization.split('Bearer ')[1],
);
}
}
async validate(request: Request, payload: JwtPayload) {
return await this.authService.getUserIfRefreshTokenMatched(
payload.sub,
request.headers.authorization.split('Bearer ')[1]
)
}
}
7 changes: 6 additions & 1 deletion apps/api/src/auth/strategies/local.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
})
}

validate(email: string, password: string): Promise<Partial<UserEntity> & { refresh_token: string, access_token: string}> {
validate(
email: string,
password: string
): Promise<
Partial<UserEntity> & { refresh_token: string; access_token: string }
> {
return this.authService.login(email, password)
}
}
12 changes: 9 additions & 3 deletions apps/api/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,13 @@ export class UserService {
return await this.userRepository.update({ id }, { password })
}

async storeRefreshToken(user: UserEntity, token: string): Promise<UpdateResult> {
return await this.userRepository.update({ id: user.id }, { refreshToken: token })
}
async storeRefreshToken(
user: UserEntity,
token: string
): Promise<UpdateResult> {
return await this.userRepository.update(
{ id: user.id },
{ refreshToken: token }
)
}
}
2 changes: 1 addition & 1 deletion libs/interfaces/src/auth/refreshTokenResponse.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StatusType } from '../generic/Status.type'

export interface RefreshTokenResponseInterface {
access_token: string,
access_token: string
status: StatusType
}

0 comments on commit d7a051d

Please sign in to comment.