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

LOR 108 feature: set new password #11

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { ParentModule, MailModule } from './modules';
import { AccountModule, MailModule } from './modules';

@Module({
imports: [PrismaModule, MailModule, ParentModule],
imports: [PrismaModule, MailModule, AccountModule],
})
export class AppModule {}
1 change: 1 addition & 0 deletions src/globals/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const appVersion = process.env.npm_package_version;
export const appLicense = process.env.npm_package_license;

export const fullnameRegExp = /^[a-zÀ-ÿ ]+$/i;
export const recoveryTokenRegExp = /^[a-zA-Z0-9_-]+$/;
export const dataExampleISO8601 = 'YYYY-MM-DDTHH:mm:ss.sssZ';

export const isDevelopmentEnv = () => process.env.NODE_ENV === 'development';
Expand Down
1 change: 1 addition & 0 deletions src/globals/types.d.ts → src/globals/entity.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type ValidationErrorMessagesProps = {
passwordPattern: Message;
birthDatePattern: Message;
genderPattern: Message;
pattern: Message;
};

export type HashDataAsyncProps = {
Expand Down
15 changes: 13 additions & 2 deletions src/globals/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
InternalServerErrorException,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import type { ValidationErrorMessagesProps } from './types';
import type { ValidationErrorMessagesProps } from './entity';
import { dataExampleISO8601, isDevelopmentEnv } from './constants';

export const validationErrorMessages: ValidationErrorMessagesProps = {
Expand Down Expand Up @@ -37,6 +37,9 @@ export const validationErrorMessages: ValidationErrorMessagesProps = {
genderPattern: (args) => {
return `O sexo deve ser [${args.constraints[1]}].`;
},
pattern: (args) => {
return `O ${args.property} está em um formato inválido.`;
},
};

export function prismaKnownRequestErrors(
Expand All @@ -52,10 +55,18 @@ export function prismaKnownRequestErrors(
}
}

export function unknownError(error: Error) {
export function unknownError(error: unknown) {
if (isDevelopmentEnv()) {
console.info('unknownError', error);
}

throw new InternalServerErrorException('Erro ao tentar realizar a ação.');
}

export function hendleErrors(error: unknown) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
prismaKnownRequestErrors(error);
}

unknownError(error);
}
2 changes: 1 addition & 1 deletion src/globals/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createHash } from 'node:crypto';
import type { HashDataAsyncProps, EncryptDataAsyncProps } from './types';
import type { HashDataAsyncProps, EncryptDataAsyncProps } from './entity';
import * as bcrypt from 'bcrypt';

const algorithm = 'sha256';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { CreateAccountDto, ResetPasswordDto } from './parent.dto';
import { ParentService } from './parent.service';
import { MailService } from '../mail/mail.service';
import {
CreateAccountDto,
ResetPasswordDto,
SetPasswordDto,
} from './account.dto';
import { AccountService } from './account.service';

@Controller('/auth')
export class ParentController {
export class AccountController {
constructor(
private parentService: ParentService,
private mailService: MailService,
private accountService: AccountService,
) {}

@ApiTags('Authentication')
@Post('/register')
async register(@Body() createAccountInput: CreateAccountDto) {
await this.parentService.newAccountPropsProcessing(createAccountInput);
async register(@Body() registerInput: CreateAccountDto) {
await this.accountService.newAccountPropsProcessing(registerInput);

return { message: 'Conta criada com sucesso!' };
}
Expand All @@ -23,7 +27,7 @@ export class ParentController {
@Post('/recovery')
@HttpCode(200)
async recovery(@Body() recoveryInput: ResetPasswordDto) {
const created = await this.parentService.createTokenToResetPassword(
const created = await this.accountService.createTokenToResetPassword(
recoveryInput,
);

Expand All @@ -40,4 +44,13 @@ export class ParentController {
'Se o e-mail existir em nossa base de dados você receberá o link para definir uma nova senha. Verifique sua caixa de entrada e spam.',
};
}

@ApiTags('Reset Password')
@Post('/set-password')
@HttpCode(200)
async setPassword(@Body() setPasswordInput: SetPasswordDto) {
await this.accountService.saveNewPassword(setPasswordInput);

return { message: 'Senha redefinida com sucesso!' };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
} from 'class-validator';
import { ApiProperty, PickType } from '@nestjs/swagger';
import { Genders } from '@prisma/client';
import { fullnameRegExp, dataExampleISO8601 } from 'src/globals/constants';
import {
fullnameRegExp,
recoveryTokenRegExp,
dataExampleISO8601,
} from 'src/globals/constants';
import { validationErrorMessages } from 'src/globals/errors';

export class CreateAccountDto {
Expand Down Expand Up @@ -56,3 +60,11 @@ export class CreateAccountDto {
}

export class ResetPasswordDto extends PickType(CreateAccountDto, ['email']) {}

export class SetPasswordDto extends PickType(CreateAccountDto, ['password']) {
@ApiProperty()
@IsNotEmpty()
@IsString({ message: validationErrorMessages.stringField })
@Matches(recoveryTokenRegExp, { message: validationErrorMessages.pattern })
readonly recoveryToken: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ export type GetCredentialIdByEmailOutput = {
fullname: ParentProfile['fullname'];
} | void;

export type getCredentialIdByRecoveryTokenInput = {
hashedToken: string;
now: Date;
};

export type getCredentialIdByRecoveryTokenOutout = {
id: Credential['id'];
} | void;

export type SavePasswordInput = {
credentialId: Credential['id'];
encryptedPassword: Credential['password'];
};

export type PasswordResetInput = Omit<ResetPasswordInfo, 'id'>;

export type PasswordResetOutput = {
Expand Down
13 changes: 13 additions & 0 deletions src/modules/account/account.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from 'src/prisma/prisma.module';
import { MailModule } from '../mail/mail.module';
import { AccountController } from './account.controller';
import { AccountService } from './account.service';
import { AccountRepository } from './account.repository';

@Module({
imports: [PrismaModule, MailModule],
controllers: [AccountController],
providers: [AccountService, AccountRepository],
})
export class AccountModule {}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import {
NewAccountRepositoryInput,
GetCredentialIdByEmailOutput,
PasswordResetInput,
} from './parent.entity';
import { unknownError, prismaKnownRequestErrors } from 'src/globals/errors';
getCredentialIdByRecoveryTokenInput,
getCredentialIdByRecoveryTokenOutout,
SavePasswordInput,
} from './account.entity';
import { hendleErrors } from 'src/globals/errors';

@Injectable()
export class ParentRepository {
export class AccountRepository {
constructor(private prisma: PrismaService) {}

async saveCredentialParentAndChildrenProps(
Expand Down Expand Up @@ -39,13 +41,7 @@ export class ParentRepository {
},
},
})
.catch((error) => {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
prismaKnownRequestErrors(error);
}

unknownError(error);
});
.catch((error) => hendleErrors(error));

return;
}
Expand Down Expand Up @@ -77,17 +73,58 @@ export class ParentRepository {

return null;
})
.catch((error) => {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
prismaKnownRequestErrors(error);
.catch((error) => hendleErrors(error));

return response;
}

async getCredentialIdByRecoveryToken(
input: getCredentialIdByRecoveryTokenInput,
): Promise<getCredentialIdByRecoveryTokenOutout> {
const { hashedToken, now } = input;

const response = await this.prisma.resetPasswordInfo
.findUnique({
where: {
recoveryToken: hashedToken,
expiresIn: {
gte: now,
},
},
select: {
credentialId: true,
},
})
.then((response) => {
if (response) {
return { id: response.credentialId };
}

unknownError(error);
});
return;
})
.catch((error) => hendleErrors(error));

return response;
}

async savePassword(input: SavePasswordInput): Promise<void> {
const { credentialId, encryptedPassword } = input;

await this.prisma.credential
.update({
where: {
id: credentialId,
},
data: {
password: encryptedPassword,
resetPasswordInfo: {
delete: true,
},
},
})
.catch((error) => hendleErrors(error));
}

async savePasswordResetInformation(input: PasswordResetInput): Promise<void> {
const { recoveryToken, expiresIn, credentialId } = input;

Expand All @@ -110,12 +147,6 @@ export class ParentRepository {
},
},
})
.catch((error) => {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
prismaKnownRequestErrors(error);
}

unknownError(error);
});
.catch((error) => hendleErrors(error));
}
}
Loading
Loading