Commit 52b3d84b authored by Guilherme Moraes's avatar Guilherme Moraes
Browse files

feat: restructure auth module and migrate DTOs to modules, add JWT authentication

parent b98f24d0
Pipeline #22332 failed with stages
Showing with 884 additions and 171 deletions
+884 -171
This diff is collapsed.
...@@ -3,15 +3,17 @@ import { ConfigModule } from '@nestjs/config' ...@@ -3,15 +3,17 @@ import { ConfigModule } from '@nestjs/config'
import * as Joi from 'joi' import * as Joi from 'joi'
import { WinstonModule } from 'nest-winston' import { WinstonModule } from 'nest-winston'
import { winstonLoggerConfig } from './config/winston-logger/winston-config' import { winstonLoggerConfig } from './config/winston-logger/winston-config'
import { AuthModule } from './modules/auth/auth.module'
import { ExampleModuleModule } from './modules/example-module/example-module.module' import { ExampleModuleModule } from './modules/example-module/example-module.module'
import { PrismaModule } from './modules/prisma/prisma.module' import { PrismaModule } from './modules/prisma/prisma.module'
import { AuthModule } from './auth/auth.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
validationSchema: Joi.object({ validationSchema: Joi.object({
PORT: Joi.number().required(), PORT: Joi.number().required(),
JWT_SECRET: Joi.string().required(),
JWT_EXPIRES_IN: Joi.string().default('1d'),
}), }),
}), }),
WinstonModule.forRoot(winstonLoggerConfig), WinstonModule.forRoot(winstonLoggerConfig),
......
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
providers: [AuthService],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PrismaService } from '../modules/prisma/prisma.service';
@Module({
controllers: [AuthController],
providers: [AuthService, PrismaService],
})
export class AuthModule {}
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable, BadRequestException, UnauthorizedException } from '@nestjs/common';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { PrismaService } from '../modules/prisma/prisma.service';
import * as bcrypt from 'bcrypt';
import * as jwt from 'jsonwebtoken';
const JWT_SECRET = "123456"
//env(SECRET)
@Injectable()
export class AuthService {
constructor(private prisma: PrismaService) {}
async register (registerDto: RegisterDto) {
const {email, password, name}= registerDto;
const userExists = await this.prisma.user.findUnique({where: {email: registerDto.email}})
if (userExists) {
throw new BadRequestException('Usuario ja existe')
}
const passwordHash= await bcrypt.hash(password, 10)
const user = await this.prisma.user.create({
data:{
email:email,
senha: passwordHash,
nome:name,
},
});
return {messege: "usuário cadastrado com sucesso", id:user.id_user}
}
async login (loginDto : LoginDto){
const{email, password}= loginDto;
const user = await this.prisma.user.findUnique({
where:{email}
});
if(!user){
throw new UnauthorizedException("email incorreto")
}
const match = await bcrypt.compare(password, user.senha);
if(!match){
throw new UnauthorizedException("senha incorreta")
}
const token = jwt.sign({id:user.id_user}, JWT_SECRET, {expiresIn:'2h'});
return {messege: "login com sucesso", token};
}
}
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
export const UserTokenInfo = createParamDecorator(
(_data: unknown, ctx: ExecutionContext) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const request = ctx.switchToHttp().getRequest()
const userInfo = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
id: request.user.id,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
email: request.user.email,
}
return userInfo
},
)
export interface JwtPayload {
email: string
id: number
}
import { ValidationPipe } from '@nestjs/common'
import { NestFactory } from '@nestjs/core' import { NestFactory } from '@nestjs/core'
import * as dotenv from 'dotenv' import * as dotenv from 'dotenv'
import { WinstonModule } from 'nest-winston' import { WinstonModule } from 'nest-winston'
...@@ -23,6 +24,14 @@ async function bootstrap() { ...@@ -23,6 +24,14 @@ async function bootstrap() {
logger: nestLogger, logger: nestLogger,
}) })
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
)
const { PORT } = process.env const { PORT } = process.env
await app.listen(PORT) await app.listen(PORT)
} }
......
import { Controller, Post, Body } from '@nestjs/common'; import { Body, Controller, Post } from '@nestjs/common'
import { AuthService } from './auth.service'; import { AuthService } from './auth.service'
import {LoginDto} from './dto/login.dto' import { LoginDto } from './dto/login.dto'
import {RegisterDto} from './dto/register.dto' import { RegisterDto } from './dto/register.dto'
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
constructor(private readonly authservice: AuthService){} constructor(private readonly authservice: AuthService) {}
@Post('cadastro') @Post('register')
register(@Body() dto:RegisterDto){ register(@Body() dto: RegisterDto) {
return this.authservice.register(dto) return this.authservice.register(dto)
} }
@Post('login') @Post('login')
login(@Body() dto:LoginDto){ login(@Body() dto: LoginDto) {
return this.authservice.login(dto) return this.authservice.login(dto)
} }
} }
// src/auth/auth.module.ts
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { JwtModule } from '@nestjs/jwt'
import { AuthController } from './auth.controller'
import { AuthService } from './auth.service'
import { JwtAuthMiddleware } from './jwt-auth.middleware'
@Module({
imports: [
ConfigModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: configService.get<string>('JWT_EXPIRES_IN'),
},
}),
}),
],
controllers: [AuthController],
providers: [AuthService, JwtAuthMiddleware],
exports: [AuthService],
})
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(JwtAuthMiddleware)
.exclude(
{ path: 'auth/login', method: RequestMethod.ALL },
{ path: 'auth/register', method: RequestMethod.ALL },
)
.forRoutes('*')
}
}
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import * as bcrypt from 'bcrypt'
import { PrismaService } from '../prisma/prisma.service'
import { LoginDto } from './dto/login.dto'
import { RegisterDto } from './dto/register.dto'
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
) {}
async register(registerDto: RegisterDto) {
const { email, password, name } = registerDto
const userExists = await this.prisma.user.findUnique({
where: { email: registerDto.email },
})
if (userExists) {
throw new BadRequestException('Usuário já existe com este e-mail')
}
const passwordHash = await bcrypt.hash(password, 10)
const user = await this.prisma.user.create({
data: {
email: email,
senha: passwordHash,
nome: name,
},
})
return { message: 'Usuário cadastrado com sucesso', id: user.id_user }
}
async login(loginDto: LoginDto) {
const { email, password } = loginDto
const user = await this.prisma.user.findUnique({
where: { email },
})
if (!user) {
throw new UnauthorizedException('Usuário não encontrado com este e-mail')
}
const match = await bcrypt.compare(password, user.senha)
if (!match) {
throw new UnauthorizedException('Email ou senha inválido')
}
const payload = { sub: user.id_user, email: user.email, id: user.id_user }
const token = await this.jwtService.signAsync(payload)
return { message: 'Login realizado com sucesso', accessToken: token }
}
}
import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; import { IsEmail, IsNotEmpty, IsString } from 'class-validator'
export class LoginDto { export class LoginDto {
@IsEmail() @IsEmail()
email: string; email: string
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
password: string; password: string
} }
import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; import { IsEmail, IsNotEmpty, IsString } from 'class-validator'
export class RegisterDto { export class RegisterDto {
@IsEmail() @IsEmail()
email: string; email: string
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
password: string; password: string
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
name: string; name: string
} }
// src/auth/jwt-auth.middleware.ts
import {
Injectable,
NestMiddleware,
UnauthorizedException,
} from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { NextFunction, Request, Response } from 'express'
@Injectable()
export class JwtAuthMiddleware implements NestMiddleware {
constructor(private readonly jwtService: JwtService) {}
use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers['authorization']
if (!authHeader) {
throw new UnauthorizedException('Authorization header missing')
}
const token = authHeader.split(' ')[1]
if (!token) {
throw new UnauthorizedException('Bearer Token not found')
}
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload = this.jwtService.verify(token)
// Optionally attach user info to request (req.user = payload)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
req['user'] = payload
console.log('Payload:', payload)
} catch (error) {
throw new UnauthorizedException('Invalid token: ' + error)
}
return next()
}
}
export class CreateExampleModuleDto {} export class CreateExampleModuleDto {}
import { PartialType } from '@nestjs/mapped-types'; import { PartialType } from '@nestjs/mapped-types'
import { CreateExampleModuleDto } from './create-example-module.dto'; import { CreateExampleModuleDto } from './create-example-module.dto'
export class UpdateExampleModuleDto extends PartialType(CreateExampleModuleDto) {} export class UpdateExampleModuleDto extends PartialType(
CreateExampleModuleDto,
) {}
import { import { Body, Controller, Get, Post } from '@nestjs/common'
Body, import { UserTokenInfo } from 'src/decorators/user-info.decorator'
Controller, import { JwtPayload } from 'src/interfaces/user-info.interface'
Delete,
Get,
Param,
Patch,
Post,
} from '@nestjs/common'
import { PaggesLogger } from 'src/config/winston-logger/pagges-logger.utils'
import { CreateExampleModuleDto } from './dto/create-example-module.dto' import { CreateExampleModuleDto } from './dto/create-example-module.dto'
import { UpdateExampleModuleDto } from './dto/update-example-module.dto'
import { ExampleModuleService } from './example-module.service' import { ExampleModuleService } from './example-module.service'
@Controller('example-module') @Controller('example-module')
...@@ -28,35 +20,8 @@ export class ExampleModuleController { ...@@ -28,35 +20,8 @@ export class ExampleModuleController {
} }
@Get('/hello') @Get('/hello')
hello() { hello(@UserTokenInfo() userInfo: JwtPayload, @Body() body: { abc: string }) {
PaggesLogger.log('Testing info log') console.log('userInfo', userInfo, 'user', body)
PaggesLogger.error('Testing error log') return userInfo
PaggesLogger.warn('Testing warn log')
PaggesLogger.debug('Testing debug log')
PaggesLogger.http('Testing http log')
return 'Hello from example'
}
@Get()
findAll() {
return this.exampleModuleService.findAll()
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.exampleModuleService.findOne(+id)
}
@Patch(':id')
update(
@Param('id') id: string,
@Body() updateExampleModuleDto: UpdateExampleModuleDto,
) {
return this.exampleModuleService.update(+id, updateExampleModuleDto)
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.exampleModuleService.remove(+id)
} }
} }
...@@ -12,7 +12,8 @@ export class ExampleModuleService { ...@@ -12,7 +12,8 @@ export class ExampleModuleService {
const userCreated = await this.prismaService.user.create({ const userCreated = await this.prismaService.user.create({
data: { data: {
email: 'abc', email: 'abc',
password: 'abc', senha: '123',
nome: 'abc',
}, },
}) })
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment