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

Validação de senha BugFix-US04 #11

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
174 changes: 148 additions & 26 deletions src/auth/auth.service.spec.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talvez por mudança de atributos os testes deste arquivo apresetaram erro, vejam se consegue resolver sozinho e me contatem por favor

Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { JwtService } from '@nestjs/jwt';
import { SignUpDto } from '../auth/dtos/signUp.dto';
import * as bcrypt from 'bcryptjs';
import { repositoryMockFactory } from '../../test/database/utils';
import { UnauthorizedException } from '@nestjs/common';
import { UnauthorizedException, BadRequestException } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
import { SignInDto } from './dtos/signIn.dto';


describe('AuthService', () => {
let service: AuthService;
Expand Down Expand Up @@ -43,23 +45,22 @@ describe('AuthService', () => {
userRepository = module.get<Repository<User>>(getRepositoryToken(User));
jwtService = module.get<JwtService>(JwtService);

jest.spyOn(bcrypt, 'hash').mockResolvedValueOnce('hashed-password');
jest.spyOn(bcrypt, 'genSalt').mockResolvedValueOnce(10);
// Removido o mock de bcrypt do beforeEach

sendMailMock = jest.fn();
jest.spyOn(nodemailer, 'createTransport').mockReturnValue({
sendMail: sendMailMock,
} as any);
});

//signUp
describe('signUp', () => {
it('should create a new user and return a signed token', async () => {
const signUpDto: SignUpDto = {
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
phone: '123456789',
password: 'password',
password: 'Password123!', // Senha correta
};

const user = new User();
Expand All @@ -71,14 +72,17 @@ describe('AuthService', () => {
user.role = UserRoles.User;

jest.spyOn(userRepository, 'findOneBy').mockResolvedValueOnce(null);

jest.spyOn(userRepository, 'create').mockReturnValue(user);
jest.spyOn(userRepository, 'save').mockResolvedValue(user);
jest.spyOn(service, 'signIn').mockResolvedValue({
accessToken: 'access-token',
refreshToken: 'refresh-token',
});

// Mock de bcrypt DENTRO do teste, depois de definir a senha correta
jest.spyOn(bcrypt, 'hash').mockImplementation(async () => 'hashed-password');
jest.spyOn(bcrypt, 'genSalt').mockResolvedValue(10);

const response = await service.signUp(signUpDto);

expect(userRepository.findOneBy).toHaveBeenCalledWith({
Expand All @@ -88,9 +92,9 @@ describe('AuthService', () => {
expect(userRepository.create).toHaveBeenCalledWith({
...signUpDto,
role: UserRoles.User,
password: expect.any(String),
password: 'hashed-password', // Agora o password está sendo hasheado corretamente
});
expect(bcrypt.hash).toHaveBeenCalledWith('password', 10);
expect(bcrypt.hash).toHaveBeenCalledWith('Password123!', 10);
expect(userRepository.save).toHaveBeenCalled();
expect(response).toEqual({
accessToken: 'access-token',
Expand All @@ -99,32 +103,80 @@ describe('AuthService', () => {
});

it('should throw an error if user already exists', async () => {
const signUpDto: SignUpDto = {
const signUpDto: SignUpDto = {
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
phone: '123456789',
password: 'Password123!',
};

const existingUser = new User();
existingUser.email = '[email protected]';

jest.spyOn(userRepository, 'findOneBy').mockResolvedValueOnce(existingUser);

await expect(service.signUp(signUpDto)).rejects.toThrow(BadRequestException);
await expect(service.signUp(signUpDto)).rejects.toThrowError('Usuário já cadastrado.');

expect(userRepository.create).not.toHaveBeenCalled();
expect(userRepository.save).not.toHaveBeenCalled();
});

it('should reject passwords shorter than 8 characters', async () => {
const invalidDto: SignUpDto = {
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
phone: '123456789',
password: 'password',
password: 'Short1!',
};
await expect(service.signUp(invalidDto)).rejects.toThrow(BadRequestException);
await expect(service.signUp(invalidDto)).rejects.toThrowError('A senha deve ter pelo menos 8 caracteres.');
expect(userRepository.findOneBy).not.toHaveBeenCalled();
});

const existingUser = new User();
existingUser.email = '[email protected]';

jest
.spyOn(userRepository, 'findOneBy')
.mockResolvedValueOnce(existingUser);
it('should reject passwords without uppercase letters', async () => {
const invalidDto: SignUpDto = {
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
phone: '123456789',
password: 'nopassword123!',
};
await expect(service.signUp(invalidDto)).rejects.toThrow(BadRequestException);
await expect(service.signUp(invalidDto)).rejects.toThrowError('A senha deve conter pelo menos uma letra maiúscula.');
expect(userRepository.findOneBy).not.toHaveBeenCalled();
});

try {
await service.signUp(signUpDto);
fail('An error should be thrown');
} catch (error) {
expect(error).toBeInstanceOf(UnauthorizedException);
expect((error as Error).message).toBe('Usuário já cadastrado.');
expect(userRepository.create).not.toHaveBeenCalled();
expect(userRepository.save).not.toHaveBeenCalled();
}

it('should reject passwords without numbers', async () => {
const invalidDto: SignUpDto = {
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
phone: '123456789',
password: 'NoNumberPassword!',
};
await expect(service.signUp(invalidDto)).rejects.toThrow(BadRequestException);
await expect(service.signUp(invalidDto)).rejects.toThrowError('A senha deve conter pelo menos um número.');
expect(userRepository.findOneBy).not.toHaveBeenCalled();
});

it('should reject passwords without special characters', async () => {
const invalidDto: SignUpDto = {
firstName: 'Test',
lastName: 'User',
email: '[email protected]',
phone: '123456789',
password: 'NoSpecialChar123',
};
await expect(service.signUp(invalidDto)).rejects.toThrow(BadRequestException);
await expect(service.signUp(invalidDto)).rejects.toThrowError('A senha deve conter pelo menos um caractere especial.');
expect(userRepository.findOneBy).not.toHaveBeenCalled();
});
});
});

describe('signIn', () => {
it('should throw an UnauthorizedException for invalid credentials', async () => {
Expand Down Expand Up @@ -245,4 +297,74 @@ describe('AuthService', () => {
expect(sendMailMock).toHaveBeenCalled();
});
});
});

describe('signIn with keepLoggedIn', () => {
it('should return a token with 30m expiration when keepLoggedIn is false', async () => {
const signInDto: SignInDto = {
email: '[email protected]',
password: 'password',
role: UserRoles.User,
keepLoggedIn: false,
};
const user = new User();
user.id = 'user-id';
user.email = signInDto.email;
user.password = 'hashed-password';
user.role = UserRoles.User;

jest.spyOn(userRepository, 'findOneBy').mockResolvedValue(user);
jest.spyOn(bcrypt, 'compare').mockResolvedValue(true);
// Mock das novas funções
const generateAccessTokenSpy = jest.spyOn(service, 'generateAccessToken').mockResolvedValue('access-token');
const generateRefreshTokenSpy = jest.spyOn(service, 'generateRefreshToken').mockResolvedValue('refresh-token'); // Corrigido o retorno para 'refresh-token'

const result = await service.signIn(signInDto);

expect(result.accessToken).toBe('access-token');
expect(result.refreshToken).toBe('refresh-token');
expect(generateAccessTokenSpy).toHaveBeenCalledWith(
{ sub: user.id, email: user.email, role: user.role },
'30m',
);
expect(generateRefreshTokenSpy).toHaveBeenCalledWith({
sub: user.id,
email: user.email,
role: user.role,
});
});

it('should return a token with 7d expiration when keepLoggedIn is true', async () => {
const signInDto: SignInDto = {
email: '[email protected]',
password: 'password',
role: UserRoles.User,
keepLoggedIn: true,
};
const user = new User();
user.id = 'user-id';
user.email = signInDto.email;
user.password = 'hashed-password';
user.role = UserRoles.User;

jest.spyOn(userRepository, 'findOneBy').mockResolvedValue(user);
jest.spyOn(bcrypt, 'compare').mockResolvedValue(true);
// Mock das novas funções
const generateAccessTokenSpy = jest.spyOn(service, 'generateAccessToken').mockResolvedValue('access-token');
const generateRefreshTokenSpy = jest.spyOn(service, 'generateRefreshToken').mockResolvedValue('refresh-token'); // Corrigido o retorno para 'refresh-token'

const result = await service.signIn(signInDto);

expect(result.accessToken).toBe('access-token');
expect(result.refreshToken).toBe('refresh-token');
expect(generateAccessTokenSpy).toHaveBeenCalledWith(
{ sub: user.id, email: user.email, role: user.role },
'7d',
);
expect(generateRefreshTokenSpy).toHaveBeenCalledWith({
sub: user.id,
email: user.email,
role: user.role,
});
});
});
});
44 changes: 43 additions & 1 deletion src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import {
Injectable,
UnauthorizedException,
BadRequestException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { User, UserRoles } from '../database/entities/user.entity';
Expand All @@ -17,6 +21,34 @@ export class AuthService {
private jwtService: JwtService,
) {}

private validatePassword(password: string): void {
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasNumber = /[0-9]/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);

if (password.length < minLength) {
throw new BadRequestException(
'A senha deve ter pelo menos 8 caracteres.',
);
}
if (!hasUpperCase) {
throw new BadRequestException(
'A senha deve conter pelo menos uma letra maiúscula.',
);
}
if (!hasNumber) {
throw new BadRequestException(
'A senha deve conter pelo menos um número.',
);
}
if (!hasSpecialChar) {
throw new BadRequestException(
'A senha deve conter pelo menos um caractere especial.',
);
}
}

async signIn({
email,
password,
Expand All @@ -35,10 +67,13 @@ export class AuthService {
}

async signUp(dto: SignUpDto): Promise<SignInResponseDto> {
this.validatePassword(dto.password); // Validate password before proceeding

const userExists = await this.usersRepository.findOneBy({
email: dto.email,
});
if (userExists) throw new UnauthorizedException('Usuário já cadastrado.');

const user = this.usersRepository.create({
...dto,
role: UserRoles.User,
Expand All @@ -51,6 +86,13 @@ export class AuthService {
role: user.role,
});
}
async generateAccessToken(payload: any, expiresIn: string): Promise<string> {
return this.jwtService.signAsync(payload, { expiresIn });
}

async generateRefreshToken(payload: any): Promise<string> {
return this.jwtService.signAsync(payload);
}

async getProfile(data: { sub: string; email: string }): Promise<User> {
const user = await this.usersRepository.findOneBy({ id: data.sub });
Expand Down
2 changes: 2 additions & 0 deletions src/auth/dtos/signIn.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ export class SignInDto {

@IsNotEmpty()
role: UserRoles;

keepLoggedIn?: boolean;
}
12 changes: 6 additions & 6 deletions src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { UpdateUserDto } from './dtos/updateUser.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { UpdateUserDto } from './dtos/updateUser.dto';
import { Repository } from 'typeorm';
import { User } from '../database/entities/user.entity';
import * as bcrypt from 'bcrypt';
import { ListUsersQueryDto } from './dtos/listUsersQuery.dto';
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';

@Injectable()
export class UsersService {
Expand Down
Loading