Skip to content

Commit

Permalink
Merge pull request #17 from Brints/refresh-tokens
Browse files Browse the repository at this point in the history
feat: implemented refresh tokens
  • Loading branch information
aniebietafia authored Sep 5, 2024
2 parents b7b22a7 + ff9cc58 commit e297d2e
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 37 deletions.
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(),
});

0 comments on commit e297d2e

Please sign in to comment.