Skip to content

Commit

Permalink
feat(password-reset): implementing password reset execute usecase
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardodimarchi committed Dec 24, 2023
1 parent 2f0230a commit 743700d
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@ export class PasswordResetEntity extends BaseEntity<PasswordResetEntityProps> {
public get used(): boolean {
return this.props.used;
}

public setAsUsed(): void {
this.props.used = true;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { PasswordEncryptionService } from '@modules/user/domain/services/password-encryption.service';
import { MockRequestUser } from 'test/factories/mock-request-user';
import { MockPasswordReset } from 'test/factories/password-reset-mock';
import { InMemoryPasswordResetRepository } from 'test/repositories/in-memory-password-reset-repository';
import { InMemoryRepository } from 'test/repositories/in-memory-repository';
import { InMemoryUserRepository } from 'test/repositories/in-memory-user-repository';
import { DeepMocked, createMock } from 'test/utils/create-mock';
import { PasswordResetEntity } from '../../entities/password-reset.entity';
import { IncorrectPasswordResetCodeError } from '../../errors/incorrect-password-reset-code.error';
import { PasswordResetNotFoundError } from '../../errors/password-reset-not-found.error';
import { PasswordResetRepository } from '../../repositories/password-reset.repository';
import { ExecutePasswordResetUseCase } from './execute-password-reset.usecase';
import { MockUser } from 'test/factories/mock-user';
import { IncorrectOldPasswordError } from '../../errors/incorrect-old-password.error';
import { UserNotFoundError } from '@modules/user/domain/errors/user-not-found.error';

describe('ExecutePasswordResetUseCase', () => {
let usecase: ExecutePasswordResetUseCase;
Expand All @@ -35,10 +36,7 @@ describe('ExecutePasswordResetUseCase', () => {
});

it('should return a PasswordResetNotFoundError if a valid reset was not found', async () => {
const requestUser = MockRequestUser.createEntity();

const result = await usecase.exec({
requestUser,
code: 'AbC4dEf1',
oldPassword: '',
newPassword: '',
Expand All @@ -48,24 +46,95 @@ describe('ExecutePasswordResetUseCase', () => {
expect(result.value).toBeInstanceOf(PasswordResetNotFoundError);
});

it('should return an IncorrectPasswordResetCodeError if the provided code does not match', async () => {
const requestUser = MockRequestUser.createEntity();
it('should return an UserNotFoundError if the user does not exists', async () => {
const passwordReset = MockPasswordReset.createEntity();

repository.save(passwordReset);

const result = await usecase.exec({
code: passwordReset.code,
oldPassword: '',
newPassword: '',
});

expect(result.isLeft()).toBeTruthy();
expect(result.value).toBeInstanceOf(UserNotFoundError);
});

it('should return an IncorrectOldPasswordError if the old password is incorrect', async () => {
const user = MockUser.createEntity();

const passwordReset = MockPasswordReset.createEntity({
override: {
userId: requestUser.id,
userId: user.id,
},
});

userRepository.save(user);
repository.save(passwordReset);

passwordEncryptionService.compare.mockResolvedValueOnce(false);

const result = await usecase.exec({
requestUser,
code: passwordReset.code,
oldPassword: '',
newPassword: '',
oldPassword: 'user-wrong-password',
newPassword: 'user-new-password',
});

expect(result.isLeft()).toBeTruthy();
expect(result.value).toBeInstanceOf(IncorrectPasswordResetCodeError);
expect(result.value).toBeInstanceOf(IncorrectOldPasswordError);
});

it('should invalidate password reset', async () => {
const user = MockUser.createEntity();

const passwordReset = MockPasswordReset.createEntity({
override: {
userId: user.id,
},
});

userRepository.save(user);
repository.save(passwordReset);

passwordEncryptionService.compare.mockResolvedValueOnce(true);

const result = await usecase.exec({
code: passwordReset.code,
oldPassword: 'user-password',
newPassword: 'user-new-password',
});

expect(result.isRight()).toBeTruthy();
expect(repository.items[0].used).toBeTruthy();
});

it('should update user password', async () => {
const user = MockUser.createEntity();

const passwordReset = MockPasswordReset.createEntity({
override: {
userId: user.id,
},
});

userRepository.save(user);
repository.save(passwordReset);

passwordEncryptionService.compare.mockResolvedValueOnce(true);
passwordEncryptionService.hash.mockResolvedValue(
'user-hashed-new-password',
);

const result = await usecase.exec({
code: passwordReset.code,
oldPassword: 'user-password',
newPassword: 'user-new-password',
});

expect(result.isRight()).toBeTruthy();
expect(userRepository.items[0].password).toEqual(
'user-hashed-new-password',
);
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { RequestUserEntity } from '@modules/auth/domain/entities/request-user.entity';
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { PasswordEncryptionService } from '@modules/user/domain/services/password-encryption.service';
import { Injectable } from '@nestjs/common';
import { UseCase } from '@shared/domain/usecase';
import { Either, left, right } from '@shared/helpers/either';
import { IncorrectOldPasswordError } from '../../errors/incorrect-old-password.error';
import { IncorrectPasswordResetCodeError } from '../../errors/incorrect-password-reset-code.error';
import { PasswordResetNotFoundError } from '../../errors/password-reset-not-found.error';
import { PasswordResetRepository } from '../../repositories/password-reset.repository';
import { UserNotFoundError } from '@modules/user/domain/errors/user-not-found.error';
import { UseCase } from '@shared/domain/usecases/usecase';

export interface ExecutePasswordResetUseCaseInput {
requestUser: RequestUserEntity;
code: string;
oldPassword: string;
newPassword: string;
Expand All @@ -21,7 +19,7 @@ export interface ExecutePasswordResetUseCaseOutput {}
export type ExecutePasswordResetUseCaseErrors =
| PasswordResetNotFoundError
| IncorrectOldPasswordError
| IncorrectPasswordResetCodeError;
| UserNotFoundError;

@Injectable()
export class ExecutePasswordResetUseCase
Expand All @@ -39,23 +37,44 @@ export class ExecutePasswordResetUseCase
) {}

async exec({
requestUser,
code,
oldPassword,
newPassword,
}: ExecutePasswordResetUseCaseInput): Promise<
Either<ExecutePasswordResetUseCaseErrors, ExecutePasswordResetUseCaseOutput>
> {
const passwordReset = await this.repository.getValidByUserId(
requestUser.id,
);
const passwordReset = await this.repository.getValidByCode(code);

if (!passwordReset) {
return left(new PasswordResetNotFoundError());
}

if (code.toLowerCase() !== passwordReset.code) {
return left(new IncorrectPasswordResetCodeError());
const user = await this.userRepository.getById(passwordReset.userId);

if (!user) {
return left(new UserNotFoundError());
}

const isOldPasswordCorrect = await this.passwordEncryptionService.compare(
oldPassword,
user.password,
);

if (!isOldPasswordCorrect) {
return left(new IncorrectOldPasswordError());
}

passwordReset.setAsUsed();

await this.repository.save(passwordReset);

const userNewPassword =
await this.passwordEncryptionService.hash(newPassword);

user.password = userNewPassword;

await this.userRepository.save(user);

return right({});
}
}

0 comments on commit 743700d

Please sign in to comment.