Skip to content

Commit

Permalink
Merge pull request #10 from olaviolacerda/feature/logging
Browse files Browse the repository at this point in the history
Feature/logging
  • Loading branch information
olaviolacerda authored Mar 3, 2024
2 parents 4bed190 + e5f5a57 commit e4406a7
Show file tree
Hide file tree
Showing 21 changed files with 95 additions and 38 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<li>Error handling</li>
<li>E2E testing</li>
<li>Folder structure (refactor)</li>
<li>Logging</li>
</ul>

<h1 align="center">Welcome to nest-typescript-template 👋</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ export interface PayloadToken {
id: string;
role: Role;
}

export interface Tokens {
accessToken: string;
refreshToken: string;
}
10 changes: 10 additions & 0 deletions src/common/interfaces/users.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Role } from '../enums/role.enum';

export interface User {
id: string;
username: string;
role: Role;
password?: string;
refreshToken?: string;
hashPassword?: () => Promise<void>;
}
3 changes: 2 additions & 1 deletion src/common/responses/auth/tokens.response.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Tokens } from '../../interfaces/auth.interface';

export class TokensResponse {
export class TokensResponse implements Tokens {
@ApiProperty({
example:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
Expand Down
3 changes: 2 additions & 1 deletion src/common/responses/users/user.response.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';

import { Role } from '../../../common/enums/role.enum';
import { User } from './../../interfaces/users.interface';

export class UserResponse {
export class UserResponse implements User {
@ApiProperty({ example: '123e4567-e89b-12d3-a456-426655440000' })
readonly id: string;

Expand Down
7 changes: 6 additions & 1 deletion src/core/auth/guards/jwt-refresh.guard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

import * as jwt from 'jsonwebtoken';
Expand All @@ -7,12 +7,17 @@ const HTTP_STATUS_TOKEN_EXPIRED = 498;

@Injectable()
export class JwtRefreshAuthGuard extends AuthGuard('jwt-refresh') {
private readonly logger = new Logger(JwtRefreshAuthGuard.name);

handleRequest(err, user, info) {
if (info instanceof jwt.TokenExpiredError) {
this.logger.error('Token expired.');
throw new HttpException('Token expired', HTTP_STATUS_TOKEN_EXPIRED);
}

if (err || !user) {
this.logger.error('Unauthorized user.', err?.stack);

throw new HttpException(
{
status: HttpStatus.UNAUTHORIZED,
Expand Down
6 changes: 5 additions & 1 deletion src/core/auth/guards/jwt.guard.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import * as jwt from 'jsonwebtoken';

const HTTP_STATUS_TOKEN_EXPIRED = 498;
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
private readonly logger = new Logger(JwtAuthGuard.name);

handleRequest(err, user, info) {
if (info instanceof jwt.TokenExpiredError) {
this.logger.error('Token expired.');
throw new HttpException('Token expired', HTTP_STATUS_TOKEN_EXPIRED);
}

if (err || !user) {
this.logger.error('Unauthorized user.', err?.stack);
throw new HttpException(
{
status: HttpStatus.UNAUTHORIZED,
Expand Down
2 changes: 1 addition & 1 deletion src/core/auth/strategies/jwt-refresh.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, Request } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';

import { PayloadToken } from '../../../common/types/auth.type';
import { PayloadToken } from '../../../common/interfaces/auth.interface';
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(
Strategy,
Expand Down
2 changes: 1 addition & 1 deletion src/core/auth/strategies/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

import { PayloadToken } from '../../../common/types/auth.type';
import { PayloadToken } from '../../../common/interfaces/auth.interface';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
Expand Down
3 changes: 2 additions & 1 deletion src/core/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { BeforeInsert, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import * as bcrypt from 'bcrypt';

import { Role } from '../../common/enums/role.enum';
import { User } from '../../common/interfaces/users.interface';

@Entity('users')
export class User {
export class UserEntity implements User {
@PrimaryGeneratedColumn('uuid')
id: string;

Expand Down
4 changes: 2 additions & 2 deletions src/core/modules/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { TypeOrmModule } from '@nestjs/typeorm';

import { UsersController } from '../../api/controllers/users.controller';
import { JwtStrategy } from '../auth/strategies/jwt.strategy';
import { User } from '../entities/user.entity';
import { UserEntity } from '../entities/user.entity';
import { UsersService } from '../providers/services/users.service';

@Module({
imports: [TypeOrmModule.forFeature([User])],
imports: [TypeOrmModule.forFeature([UserEntity])],
providers: [UsersService, JwtStrategy],
exports: [UsersService],
controllers: [UsersController],
Expand Down
5 changes: 4 additions & 1 deletion src/core/providers/helpers/auth.helper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { createHash } from 'crypto';

@Injectable()
export class AuthHelper {
private readonly logger = new Logger(AuthHelper.name);

async hashToken(refreshToken: string): Promise<string> {
const hash = createHash('sha256').update(refreshToken).digest('hex');
const hashedToken = await bcrypt.hashSync(hash, 10);
this.logger.log('Hashed completed.');
return hashedToken;
}
}
22 changes: 14 additions & 8 deletions src/core/providers/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';

import * as bcrypt from 'bcrypt';

import { PayloadToken } from '../../../common/types/auth.type';
import { User } from '../../entities/user.entity';
import {
Tokens,
PayloadToken,
} from '../../../common/interfaces/auth.interface';
import { UsersService } from './users.service';
import { AuthHelper } from '../helpers/auth.helper';
import { User } from '../../../common/interfaces/users.interface';

@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);

constructor(
private usersService: UsersService,
private jwtService: JwtService,
Expand All @@ -22,6 +27,7 @@ export class AuthService {
const user = await this.usersService.findByUsername(username);

if (!user) {
this.logger.error(`User not found: ${username}`);
throw new BadRequestException('Something is wrong with your credentials');
}

Expand All @@ -36,33 +42,33 @@ export class AuthService {
return null;
}

async login(user: PayloadToken) {
async login(user: PayloadToken): Promise<Tokens> {
const tokens = await this.getTokens({ id: user.id, role: user.role });
await this.updateRefreshToken(user.id, tokens.refreshToken);
return tokens;
}

async logout(userId: User['id']) {
async logout(userId: User['id']): Promise<Partial<User>> {
return this.usersService.update(userId, { refreshToken: null });
}

async updateRefreshToken(
userId: User['id'],
refreshToken: User['refreshToken'],
) {
): Promise<void> {
const hashedRefreshToken = await this.authHelper.hashToken(refreshToken);
await this.usersService.update(userId, {
refreshToken: hashedRefreshToken,
});
}

async refreshTokens(user: PayloadToken) {
async refreshTokens(user: PayloadToken): Promise<Tokens> {
const tokens = await this.getTokens({ id: user.id, role: user.role });
await this.updateRefreshToken(user.id, tokens.refreshToken);
return tokens;
}

async getTokens(user: PayloadToken) {
async getTokens(user: PayloadToken): Promise<Tokens> {
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(user, {
secret: this.configService.get<string>('JWT_SECRET'),
Expand Down
25 changes: 20 additions & 5 deletions src/core/providers/services/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BadRequestException,
Injectable,
Logger,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
Expand All @@ -10,12 +11,15 @@ import { Repository } from 'typeorm';
import { CreateUserDto } from '../../../common/dtos/users/create-user.dto';
import { UpdateUserDto } from '../../../common/dtos/users/update-user.dto';
import { Role } from '../../../common/enums/role.enum';
import { User } from '../../entities/user.entity';
import { User } from '../../../common/interfaces/users.interface';
import { UserEntity } from '../../entities/user.entity';

@Injectable()
export class UsersService {
private readonly logger = new Logger(UsersService.name);

constructor(
@InjectRepository(User)
@InjectRepository(UserEntity)
private readonly usersRepository: Repository<User>,
) {}

Expand All @@ -29,6 +33,7 @@ export class UsersService {
const user = await this.findByUsername(createUserDto.username);

if (user) {
this.logger.error(`create: User not found: ${createUserDto.username}`);
throw new BadRequestException();
}

Expand All @@ -42,6 +47,10 @@ export class UsersService {
const user = await this.findByUsername(createUserDto.username);

if (user) {
this.logger.error(
`createAdminUser: User not found: ${createUserDto.username}`,
);

throw new BadRequestException();
}

Expand All @@ -64,14 +73,19 @@ export class UsersService {
return this.usersRepository.find();
}

async update(id: User['id'], updateUserDto: Partial<UpdateUserDto>) {
async update(
id: User['id'],
updateUserDto: Partial<UpdateUserDto>,
): Promise<Partial<User>> {
const user = await this.usersRepository.preload({
id,
...updateUserDto,
});

if (!user) {
throw new NotFoundException(`User with id ${id} does not exist`);
this.logger.error(`update: User not found: ${id}`);

throw new NotFoundException(`User not found: ${id}`);
}

const savedUser = await this.usersRepository.save(user);
Expand All @@ -82,7 +96,8 @@ export class UsersService {
const user = await this.usersRepository.findOne({ where: { id } });

if (!user) {
throw new NotFoundException(`User with id ${id} does not exist`);
this.logger.error(`remove: User not found: ${id}`);
throw new NotFoundException(`User not found: ${id}`);
}

return this.usersRepository.remove(user);
Expand Down
4 changes: 3 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import swaggerConfigOptions from './configurations/swagger.config';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
const app = await NestFactory.create(AppModule, {
cors: true,
});

app.setGlobalPrefix('api/v1');
app.useGlobalPipes(new ValidationPipe());
Expand Down
4 changes: 3 additions & 1 deletion test/mocks/auth.mock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { JwtService } from '@nestjs/jwt';

export const mockTokens = {
import { Tokens } from '../../src/common/interfaces/auth.interface';

export const mockTokens: Tokens = {
accessToken: 'fakeJwtToken',
refreshToken: 'fakeJwtToken',
};
Expand Down
2 changes: 1 addition & 1 deletion test/mocks/users.mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Role } from '../../src/common/enums/role.enum';
import { User } from '../../src/core/entities/user.entity';
import { User } from '../../src/common/interfaces/users.interface';

export const mockUser: User = {
id: 'id',
Expand Down
4 changes: 2 additions & 2 deletions test/unit/controllers/auth.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AuthController } from '../../../src/api/controllers/auth.controller';
import { UsersService } from '../../../src/core/providers/services/users.service';
import { AuthService } from './../../../src/core/providers/services/auth.service';
import { UsersRepositoryFake, mockUser } from '../../mocks/users.mock';
import { User } from '../../../src/core/entities/user.entity';
import { UserEntity } from '../../../src/core/entities/user.entity';
import { AuthHelper } from '../../../src/core/providers/helpers/auth.helper';
import { mockTokens } from '../../mocks/auth.mock';

Expand All @@ -23,7 +23,7 @@ describe('AuthController', () => {
providers: [
UsersService,
{
provide: getRepositoryToken(User),
provide: getRepositoryToken(UserEntity),
useClass: UsersRepositoryFake,
},
AuthService,
Expand Down
4 changes: 2 additions & 2 deletions test/unit/controllers/users.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UsersController } from '../../../src/api/controllers/users.controller';
import { UsersService } from '../../../src/core/providers/services/users.service';
import { UsersRepositoryFake, mockUser } from '../../mocks/users.mock';
import { CreateUserDto } from '../../../src/common/dtos/users/create-user.dto';
import { User } from '../../../src/core/entities/user.entity';
import { UserEntity } from '../../../src/core/entities/user.entity';
import { Role } from '../../../src/common/enums/role.enum';
import { UpdateUserDto } from '../../../src/common/dtos/users/update-user.dto';

Expand All @@ -20,7 +20,7 @@ describe('UsersController', () => {
providers: [
UsersService,
{
provide: getRepositoryToken(User),
provide: getRepositoryToken(UserEntity),
useClass: UsersRepositoryFake,
},
],
Expand Down
4 changes: 2 additions & 2 deletions test/unit/providers/services/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ConfigService } from '@nestjs/config';

import { AuthService } from '../../../../src/core/providers/services/auth.service';
import { UsersService } from '../../../../src/core/providers/services/users.service';
import { User } from '../../../../src/core/entities/user.entity';
import { UserEntity } from '../../../../src/core/entities/user.entity';
import { AuthHelper } from '../../../../src/core/providers/helpers/auth.helper';
import { mockJwtService, mockTokens } from '../../../mocks/auth.mock';
import { UsersRepositoryFake, mockUser } from '../../../mocks/users.mock';
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('AuthService', () => {
},
UsersService,
{
provide: getRepositoryToken(User),
provide: getRepositoryToken(UserEntity),
useClass: UsersRepositoryFake,
},
ConfigService,
Expand Down
Loading

0 comments on commit e4406a7

Please sign in to comment.