diff --git a/brints-estate-api/src/auth/auth.controller.ts b/brints-estate-api/src/auth/auth.controller.ts index 10a4d09..0318668 100644 --- a/brints-estate-api/src/auth/auth.controller.ts +++ b/brints-estate-api/src/auth/auth.controller.ts @@ -7,6 +7,7 @@ import { CreateUserAuthDto } from 'src/users/dto/create-userauth.dto'; import { LoginUserDto } from './dto/login.dto'; import { Auth } from './decorators/auth.decorator'; import { AuthType } from './enum/auth-type.enum'; +import { RefreshTokenDto } from './dto/refresh-token.dto'; @Controller('auth') @ApiTags('Authentication') @@ -42,4 +43,16 @@ export class AuthController { data: user, }; } + + @Post('refresh-tokens') + @Auth(AuthType.None) + @HttpCode(HttpStatus.OK) + async refreshTokens(@Body() refreshTokenDto: RefreshTokenDto) { + const tokens = await this.authService.refreshTokens(refreshTokenDto); + return { + message: 'Token refresh successful', + status_code: HttpStatus.OK, + data: tokens, + }; + } } diff --git a/brints-estate-api/src/auth/auth.module.ts b/brints-estate-api/src/auth/auth.module.ts index cdda500..c6b6c3a 100644 --- a/brints-estate-api/src/auth/auth.module.ts +++ b/brints-estate-api/src/auth/auth.module.ts @@ -11,21 +11,25 @@ import { CreateUserProvider } from './providers/create-user.provider'; import { UserHelper } from '../utils/userHelper.lib'; import { GenerateTokenHelper } from '../utils/generate-token.lib'; import { LoginUserProvider } from './providers/login-user.provider'; +import { GenerateTokensProvider } from './providers/generate-tokens.provider'; +import { RefreshTokensProvider } from './providers/refresh-tokens.provider'; import jwtConfig from './config/jwt.config'; @Module({ controllers: [AuthController], providers: [ + { + provide: HashingProvider, + useClass: BcryptProvider, + }, ConfigService, AuthService, CreateUserProvider, LoginUserProvider, UserHelper, GenerateTokenHelper, - { - provide: HashingProvider, - useClass: BcryptProvider, - }, + GenerateTokensProvider, + RefreshTokensProvider, ], imports: [ forwardRef(() => UsersModule), diff --git a/brints-estate-api/src/auth/config/jwt.config.ts b/brints-estate-api/src/auth/config/jwt.config.ts index 059b97f..fe2b2ee 100644 --- a/brints-estate-api/src/auth/config/jwt.config.ts +++ b/brints-estate-api/src/auth/config/jwt.config.ts @@ -6,5 +6,6 @@ export default registerAs('jwt', () => { expiresIn: parseInt(process.env.JWT_ACCESS_TOKEN_TTL ?? '3600', 10), audience: process.env.JWT_TOKEN_AUDIENCE, issuer: process.env.JWT_TOKEN_ISSUER, + refresh_token_expires: Number(process.env.JWT_REFRESH_TOKEN_TTL ?? '86400'), }; }); diff --git a/brints-estate-api/src/auth/dto/refresh-token.dto.ts b/brints-estate-api/src/auth/dto/refresh-token.dto.ts new file mode 100644 index 0000000..d5bc3a7 --- /dev/null +++ b/brints-estate-api/src/auth/dto/refresh-token.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class RefreshTokenDto { + @IsNotEmpty() + @IsString() + refresh_token: string; +} diff --git a/brints-estate-api/src/auth/http/refresh-tokens.endpoint.http b/brints-estate-api/src/auth/http/refresh-tokens.endpoint.http new file mode 100644 index 0000000..ddee869 --- /dev/null +++ b/brints-estate-api/src/auth/http/refresh-tokens.endpoint.http @@ -0,0 +1,6 @@ +POST http://localhost:3001/auth/refresh-tokens +Content-Type: application/json + +{ + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlZjI2N2IxMS02NmYzLTQ3MjAtYjM1MS01ZjlkNDc0OGQ3M2YiLCJpYXQiOjE3MjU0NjE2MjIsImV4cCI6MTcyNTU0ODAyMiwiYXVkIjoibG9jYWxob3N0OjMwMDEiLCJpc3MiOiJsb2NhbGhvc3Q6MzAwMSJ9.RFAQdr2rIZlmESj7ayjqRr6g0DPruQaSUBFHi0Z0_gk" +} \ No newline at end of file diff --git a/brints-estate-api/src/auth/providers/auth.service.ts b/brints-estate-api/src/auth/providers/auth.service.ts index 105595b..7b65867 100644 --- a/brints-estate-api/src/auth/providers/auth.service.ts +++ b/brints-estate-api/src/auth/providers/auth.service.ts @@ -5,12 +5,15 @@ import { CreateUserAuthDto } from 'src/users/dto/create-userauth.dto'; import { CreateUserProvider } from './create-user.provider'; import { LoginUserDto } from '../dto/login.dto'; import { LoginUserProvider } from './login-user.provider'; +import { RefreshTokenDto } from '../dto/refresh-token.dto'; +import { RefreshTokensProvider } from './refresh-tokens.provider'; @Injectable() export class AuthService { constructor( private readonly createUserProvider: CreateUserProvider, private readonly loginUserProvider: LoginUserProvider, + private readonly refreshTokensProvider: RefreshTokensProvider, ) {} public async createUser( @@ -23,4 +26,8 @@ export class AuthService { public async loginUser(loginUserDto: LoginUserDto) { return this.loginUserProvider.loginUser(loginUserDto); } + + public async refreshTokens(refreshTokenDto: RefreshTokenDto) { + return this.refreshTokensProvider.refreshTokens(refreshTokenDto); + } } diff --git a/brints-estate-api/src/auth/providers/generate-tokens.provider.ts b/brints-estate-api/src/auth/providers/generate-tokens.provider.ts new file mode 100644 index 0000000..f84f401 --- /dev/null +++ b/brints-estate-api/src/auth/providers/generate-tokens.provider.ts @@ -0,0 +1,54 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import jwtConfig from '../config/jwt.config'; +import { ConfigType } from '@nestjs/config'; +import { User } from 'src/users/entities/user.entity'; +import { IActiveUser } from '../interfaces/active-user.interface'; + +@Injectable() +export class GenerateTokensProvider { + constructor( + private readonly jwtService: JwtService, + + @Inject(jwtConfig.KEY) + private readonly jwtConfiguration: ConfigType, + ) {} + + public async signToken(userId: string, expiresIn: number, payload?: T) { + const data = { + sub: userId, + ...payload, + }; + + return await this.jwtService.signAsync(data, { + secret: this.jwtConfiguration.secret, + expiresIn, + audience: this.jwtConfiguration.audience, + issuer: this.jwtConfiguration.issuer, + }); + } + + public async generateTokens(user: User) { + const [access_token, refresh_token] = await Promise.all([ + this.signToken>( + user.id, + this.jwtConfiguration.expiresIn, + { + first_name: user.first_name, + last_name: user.last_name, + email: user.email, + phone_number: user.phone_number, + role: user.role, + verified: user.isVerified, + }, + ), + + this.signToken>( + user.id, + this.jwtConfiguration.refresh_token_expires, + ), + ]); + + return { access_token, refresh_token }; + } +} diff --git a/brints-estate-api/src/auth/providers/login-user.provider.ts b/brints-estate-api/src/auth/providers/login-user.provider.ts index 4398b14..15e2ca9 100644 --- a/brints-estate-api/src/auth/providers/login-user.provider.ts +++ b/brints-estate-api/src/auth/providers/login-user.provider.ts @@ -1,16 +1,12 @@ import { forwardRef, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; -import { JwtService } from '@nestjs/jwt'; -import { ConfigType } from '@nestjs/config'; import { User } from 'src/users/entities/user.entity'; import { HashingProvider } from './hashing.provider'; import { LoginUserDto } from '../dto/login.dto'; import { CustomException } from 'src/exceptions/custom.exception'; -import jwtConfig from '../config/jwt.config'; -// import { JwtPayload } from 'src/auth/interfaces/jwt.interface'; -import { IActiveUser } from '../interfaces/active-user.interface'; +import { GenerateTokensProvider } from './generate-tokens.provider'; @Injectable() export class LoginUserProvider { @@ -21,10 +17,7 @@ export class LoginUserProvider { @Inject(forwardRef(() => HashingProvider)) private readonly hashingProvider: HashingProvider, - private readonly jwtService: JwtService, - - @Inject(jwtConfig.KEY) - private readonly jwtConfiguration: ConfigType, + private readonly generateTokensProvider: GenerateTokensProvider, ) {} public async loginUser( @@ -50,30 +43,13 @@ export class LoginUserProvider { ); } - // if (!user.isVerified) { - // throw new CustomException( - // HttpStatus.BAD_REQUEST, - // 'User account not verified', - // ); - // } - - const payload: IActiveUser = { - sub: user.id, - first_name: user.first_name, - last_name: user.last_name, - email: user.email, - phone_number: user.phone_number, - role: user.role, - verified: user.isVerified, - }; - - const access_token = await this.jwtService.signAsync(payload, { - secret: this.jwtConfiguration.secret, - expiresIn: this.jwtConfiguration.expiresIn, - audience: this.jwtConfiguration.audience, - issuer: this.jwtConfiguration.issuer, - }); + if (!user.isVerified) { + throw new CustomException( + HttpStatus.BAD_REQUEST, + 'User account not verified', + ); + } - return { access_token }; + return await this.generateTokensProvider.generateTokens(user); } } diff --git a/brints-estate-api/src/auth/providers/refresh-tokens.provider.ts b/brints-estate-api/src/auth/providers/refresh-tokens.provider.ts new file mode 100644 index 0000000..6e8802e --- /dev/null +++ b/brints-estate-api/src/auth/providers/refresh-tokens.provider.ts @@ -0,0 +1,46 @@ +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { RefreshTokenDto } from '../dto/refresh-token.dto'; +import { JwtService } from '@nestjs/jwt'; +import { ConfigType } from '@nestjs/config'; +import jwtConfig from '../config/jwt.config'; +import { Repository } from 'typeorm'; +import { User } from 'src/users/entities/user.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { GenerateTokensProvider } from './generate-tokens.provider'; +import { CustomException } from 'src/exceptions/custom.exception'; +import { IActiveUser } from '../interfaces/active-user.interface'; + +@Injectable() +export class RefreshTokensProvider { + constructor( + private readonly jwtService: JwtService, + + @Inject(jwtConfig.KEY) + private readonly jwtConfiguration: ConfigType, + + @InjectRepository(User) + private readonly userRepository: Repository, + + private readonly generateTokensProvider: GenerateTokensProvider, + ) {} + + public async refreshTokens(refreshTokenDto: RefreshTokenDto) { + const { sub } = await this.jwtService.verifyAsync>( + refreshTokenDto.refresh_token, + { + secret: this.jwtConfiguration.secret, + audience: this.jwtConfiguration.audience, + issuer: this.jwtConfiguration.issuer, + }, + ); + + const user = await this.userRepository.findOneBy({ id: sub }); + // const user = await this.userRepository.findOne(sub); + + if (!user) { + throw new CustomException(HttpStatus.NOT_FOUND, 'User not found'); + } + + return await this.generateTokensProvider.generateTokens(user); + } +} diff --git a/brints-estate-api/src/config/environment.validation.ts b/brints-estate-api/src/config/environment.validation.ts index 5ccc08b..a879fb1 100644 --- a/brints-estate-api/src/config/environment.validation.ts +++ b/brints-estate-api/src/config/environment.validation.ts @@ -15,4 +15,5 @@ export const environmentValidationSchema = Joi.object({ JWT_ACCESS_TOKEN_TTL: Joi.number().required(), JWT_TOKEN_AUDIENCE: Joi.string().required(), JWT_TOKEN_ISSUER: Joi.string().required(), + JWT_REFRESH_TOKEN_TTL: Joi.string().required(), });