Skip to content

Commit

Permalink
Merge pull request #1 from PatrickOtero/feature/redefinePassUser
Browse files Browse the repository at this point in the history
Feature/redefine pass user
  • Loading branch information
PatrickOtero authored Aug 27, 2023
2 parents 5294ff4 + 4002289 commit 32e583d
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 25 deletions.
44 changes: 41 additions & 3 deletions src/modules/company/company.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ import { EmailDto } from '../user/dtos/email-user.dto';
import { CompanyIdDto } from './dtos/company-id.dto';
import { CreateCompanyDto } from './dtos/create-company.dto';
import { UpdateCompanyDto } from './dtos/update-company.dto';
import { CreatePasswordHashDto } from './dtos/update-my-password.dto';
import {
CreatePasswordHashDto,
UpdateMyPasswordDto,
} from './dtos/update-my-password.dto';
import {
CreateCompanyService,
DeleteCompanyService,
Expand All @@ -45,6 +48,7 @@ import {
import { ActivateCompanyService } from './services/activate-company.service';
import { RecoveryCompanyPasswordByEmail } from './services/recovery-password-by-email.service';
import { UpdatePasswordByEmailService } from './services/update-password-by-email.service';
import { UpdateCompanyPassword } from './services/update-password.service';

@ApiTags('Company')
@Controller('company')
Expand All @@ -56,6 +60,7 @@ export class CompanyController {
private deleteCompanyService: DeleteCompanyService,
private recoveryPasswordByEmail: RecoveryCompanyPasswordByEmail,
private updatePasswordByEmailService: UpdatePasswordByEmailService,
private updateCompanyPassword: UpdateCompanyPassword,
private activateCompanyService: ActivateCompanyService,
) {}

Expand Down Expand Up @@ -213,7 +218,7 @@ export class CompanyController {
return res.status(status).send(data);
}

@Patch('update_password')
@Patch('update_password_email')
@ApiResponse({
status: HttpStatus.OK,
description: 'Exemplo do retorno de sucesso da rota',
Expand All @@ -232,7 +237,7 @@ export class CompanyController {
@ApiOperation({
summary: 'Company update password.',
})
async updatePassword(
async updatePasswordByEmail(
@Body() updatePassword: CreatePasswordHashDto,
@Res() res: Response,
) {
Expand All @@ -242,6 +247,39 @@ export class CompanyController {
return res.status(status).send(data);
}

@Patch('update_password')
@UseGuards(AuthGuard())
@ApiBearerAuth()
@ApiResponse({
status: HttpStatus.OK,
description: 'Exemplo do retorno de sucesso da rota',
type: CreatePasswordHashDto,
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: 'Modelo de erro',
type: UnauthorizedSwagger,
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Modelo de erro',
type: BadRequestSwagger,
})
@ApiOperation({
summary: 'Company update password without recovery e-mail.',
})
async updatePassword(
@LoggedCompany() company: CompaniesEntity,
@Body() updatePassword: UpdateMyPasswordDto,
@Res() res: Response,
) {
const { data, status } = await this.updateCompanyPassword.execute(
company,
updatePassword,
);
return res.status(status).send(data);
}

@Patch(':id')
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
Expand Down
2 changes: 2 additions & 0 deletions src/modules/company/company.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ActivateCompanyService } from './services/activate-company.service';
import { RecoveryCompanyPasswordByEmail } from './services/recovery-password-by-email.service';
import { UpdatePasswordByEmailService } from './services/update-password-by-email.service';
import { PassportModule } from '@nestjs/passport';
import { UpdateCompanyPassword } from './services/update-password.service';

@Module({
imports: [
Expand All @@ -29,6 +30,7 @@ import { PassportModule } from '@nestjs/passport';
DeleteCompanyService,
RecoveryCompanyPasswordByEmail,
UpdatePasswordByEmailService,
UpdateCompanyPassword,
ActivateCompanyService,
],
})
Expand Down
28 changes: 28 additions & 0 deletions src/modules/company/dtos/decorators/match.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';

export function Match(property: string, validationOptions?: ValidationOptions) {
return (object: any, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [property],
validator: MatchConstraint,
});
};
}

@ValidatorConstraint({ name: 'Match' })
export class MatchConstraint implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return value === relatedValue;
}
}
25 changes: 14 additions & 11 deletions src/modules/company/dtos/update-my-password.dto.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator';
import { Match } from '../dtos/decorators/match.decorator';

export class UpdateMyPasswordDto {
@IsString()
@IsNotEmpty()
oldPassword: string;

@IsString()
@IsNotEmpty()
@IsNotEmpty({ message: "O campo 'password' não pode ficar vazio" })
@Length(8, 50)
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
message: 'Inserir senha com os critérios informados',
message: 'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.',
})
@ApiProperty({
description: 'Senha de Login',
Expand All @@ -19,10 +20,10 @@ export class UpdateMyPasswordDto {
password: string;

@IsString()
@IsNotEmpty()
@IsNotEmpty({ message: "O campo 'confirmPassword' não pode ficar vazio" })
@Length(8, 50)
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
message: 'Inserir senha com os critérios informados',
message: 'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.',
})
@ApiProperty({
description: 'Senha de Login',
Expand All @@ -32,25 +33,27 @@ export class UpdateMyPasswordDto {
}

export class CreatePasswordHashDto {
@IsNotEmpty({ message: "O campo 'password' não pode ficar vazio" })
@IsString()
@Length(8, 50)
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
message: 'Inserir senha com os critérios informados',
message:
'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.',
})
@ApiProperty({
description: 'Senha de Login',
example: 'Abcd@1234',
})
password: string;

@IsNotEmpty({ message: "O campo 'confirmPassword' não pode ficar vazio" })
@IsString()
@Length(8, 50)
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
message: 'Inserir senha com os critérios informados',
})
@ApiProperty({
description: 'Confirmação de senha de Login',
example: 'Abcd@1234',
description: 'Confirmação de senha',
example: 'Abcd@123',
})
@Match('password', {
message: 'Os campos de senha precisam ser idênticos.',
})
confirmPassword: string;

Expand Down
50 changes: 50 additions & 0 deletions src/modules/company/services/update-password.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { MailService } from 'src/modules/mails/mail.service';
import { UpdateMyPasswordDto } from '../dtos/update-my-password.dto';
import { CompanyRepository } from '../repository/company-repository';
import { CompaniesEntity } from 'src/database/entities/companies.entity';

@Injectable()
export class UpdateCompanyPassword {
constructor(
private companyRepository: CompanyRepository,
private mailService: MailService,
) {}

async execute(
company: CompaniesEntity,
{ oldPassword, password, confirmNewPassword }: UpdateMyPasswordDto,
) {
const user = await this.companyRepository.findCompanyById(company.id);

const isOldPassCorrect = await bcrypt.compare(oldPassword, user.password);

if (!isOldPassCorrect) {
return {
status: 400,
data: { message: 'A senha atual está incorreta.' },
};
}

if (password != confirmNewPassword) {
return {
status: 400,
data: { message: 'As senhas não conferem!' },
};
}
const passwordHash = await bcrypt.hash(password, 10);

const companyUpdated = await this.companyRepository.updatePassword(
company.id,
passwordHash,
);

await this.mailService.sendCompanyConfirmation(companyUpdated);

return {
status: 200,
data: { message: 'Senha redefinida com sucesso!' },
};
}
}
4 changes: 2 additions & 2 deletions src/modules/mails/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class MailService {

async sendUserConfirmation(user: UsersEntity): Promise<void> {
const { email, name, recoverPasswordToken } = user;
const url = `http://localhost:3333/recovery-password?token=${recoverPasswordToken}&type=USER`;
const url = `${process.env.recoveryPassLink}?token=${recoverPasswordToken}&type=USER`;

if (recoverPasswordToken) {
await this.mailerService.sendMail({
Expand Down Expand Up @@ -54,7 +54,7 @@ export class MailService {

async sendCompanyConfirmation(company: CompaniesEntity) {
const { email, companyName, recoverPasswordToken } = company;
const url = `http://localhost:3333/recovery-password?token=${recoverPasswordToken}&type=COMPANY`;
const url = `${process.env.recoveryPassLink}?token=${recoverPasswordToken}&type=COMPANY`;

if (recoverPasswordToken) {
await this.mailerService.sendMail({
Expand Down
12 changes: 7 additions & 5 deletions src/modules/user/dtos/update-my-password.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator';

export class UpdateMyPasswordDto {
@IsString()
@IsString({ message: "O campo 'oldPassword' não pode ficar vazio" })
@IsNotEmpty()
oldPassword: string;

@IsString()
@IsNotEmpty()
@IsNotEmpty({ message: "O campo 'password' não pode ficar vazio" })
@Length(8, 50)
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
message: 'Inserir senha com os critérios informados',
message:
'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.',
})
@ApiProperty({
description: 'Senha de Login',
Expand All @@ -19,10 +20,11 @@ export class UpdateMyPasswordDto {
password: string;

@IsString()
@IsNotEmpty()
@IsNotEmpty({ message: "O campo 'confirmPassword' não pode ficar vazio" })
@Length(8, 50)
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
message: 'Inserir senha com os critérios informados',
message:
'A senha precisa ter no mínimo 8 caracteres, máximo de 50, uma letra maiúscula, um número e um símbolo.',
})
@ApiProperty({
description: 'Senha de Login',
Expand Down
2 changes: 1 addition & 1 deletion src/modules/user/repository/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class UserRepository extends Repository<UsersEntity> {
}

async findOneById(id: string): Promise<UsersEntity> {
return this.findOne(id).catch(handleError);
return this.findOne({ where: { id } }).catch(handleError);
}

async findOneByEmail(email: string): Promise<UsersEntity> {
Expand Down
60 changes: 60 additions & 0 deletions src/modules/user/services/update-password.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { MailService } from 'src/modules/mails/mail.service';
import { UpdateMyPasswordDto } from '../dtos/update-my-password.dto';
import { UserRepository } from '../repository/user.repository';
import { UsersEntity } from 'src/database/entities/users.entity';

@Injectable()
export class UpdatePasswordService {
constructor(
private userRepository: UserRepository,
private mailService: MailService,
) {}

async execute(
user: UsersEntity,
{ oldPassword, password, confirmNewPassword }: UpdateMyPasswordDto,
) {
const userExists = await this.userRepository.findOneById(user.id);

if (!userExists) {
return {
status: 400,
data: { message: 'usuário não encontrado ou não autenticado.' },
};
}

const isOldPassCorrect = await bcrypt.compare(
oldPassword,
userExists.password,
);

if (!isOldPassCorrect) {
return {
status: 400,
data: { message: 'A senha atual está incorreta.' },
};
}

if (password != confirmNewPassword) {
return {
status: 400,
data: { message: 'As senhas não conferem!' },
};
}
const passwordHash = await bcrypt.hash(password, 10);

const userUpdated = await this.userRepository.updatePassword(
user.id,
passwordHash,
);

await this.mailService.sendUserConfirmation(userUpdated);

return {
status: 200,
data: { message: 'Senha redefinida com sucesso!' },
};
}
}
Loading

0 comments on commit 32e583d

Please sign in to comment.