Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implemented refresh tokens #17

Merged
merged 10 commits into from
Sep 5, 2024
13 changes: 13 additions & 0 deletions brints-estate-api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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,
};
}
}
12 changes: 8 additions & 4 deletions brints-estate-api/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions brints-estate-api/src/auth/config/jwt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
};
});
7 changes: 7 additions & 0 deletions brints-estate-api/src/auth/dto/refresh-token.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IsNotEmpty, IsString } from 'class-validator';

export class RefreshTokenDto {
@IsNotEmpty()
@IsString()
refresh_token: string;
}
6 changes: 6 additions & 0 deletions brints-estate-api/src/auth/http/refresh-tokens.endpoint.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
POST http://localhost:3001/auth/refresh-tokens
Content-Type: application/json

{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlZjI2N2IxMS02NmYzLTQ3MjAtYjM1MS01ZjlkNDc0OGQ3M2YiLCJpYXQiOjE3MjU0NjE2MjIsImV4cCI6MTcyNTU0ODAyMiwiYXVkIjoibG9jYWxob3N0OjMwMDEiLCJpc3MiOiJsb2NhbGhvc3Q6MzAwMSJ9.RFAQdr2rIZlmESj7ayjqRr6g0DPruQaSUBFHi0Z0_gk"
}
7 changes: 7 additions & 0 deletions brints-estate-api/src/auth/providers/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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);
}
}
54 changes: 54 additions & 0 deletions brints-estate-api/src/auth/providers/generate-tokens.provider.ts
Original file line number Diff line number Diff line change
@@ -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<typeof jwtConfig>,
) {}

public async signToken<T>(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<Partial<IActiveUser>>(
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<Partial<IActiveUser>>(
user.id,
this.jwtConfiguration.refresh_token_expires,
),
]);

return { access_token, refresh_token };
}
}
42 changes: 9 additions & 33 deletions brints-estate-api/src/auth/providers/login-user.provider.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<typeof jwtConfig>,
private readonly generateTokensProvider: GenerateTokensProvider,
) {}

public async loginUser(
Expand All @@ -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);
}
}
46 changes: 46 additions & 0 deletions brints-estate-api/src/auth/providers/refresh-tokens.provider.ts
Original file line number Diff line number Diff line change
@@ -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<typeof jwtConfig>,

@InjectRepository(User)
private readonly userRepository: Repository<User>,

private readonly generateTokensProvider: GenerateTokensProvider,
) {}

public async refreshTokens(refreshTokenDto: RefreshTokenDto) {
const { sub } = await this.jwtService.verifyAsync<Pick<IActiveUser, 'sub'>>(
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);
}
}
1 change: 1 addition & 0 deletions brints-estate-api/src/config/environment.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
});
Loading