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

feat: forgot password #5

Merged
merged 1 commit into from
Jul 27, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ dist/

# orm
ormconfig.json
sendgrid.env
2 changes: 1 addition & 1 deletion docs/insomnia.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
"dependencies": {
"@prisma/client": "^3.14.0",
"@sendgrid/mail": "^8.1.3",
"aws-sdk": "^2.846.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
Expand All @@ -61,4 +62,4 @@
"typeorm": "^0.2.31",
"uuid": "^8.3.2"
}
}
}
40 changes: 40 additions & 0 deletions src/modules/users/infra/http/controller/UsersController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import ReadAllUsersService from '@modules/users/services/ReadAllUsersService';
import ReadUserByIdService from '@modules/users/services/ReadUserByIdService';
import UpdateUserService from '@modules/users/services/UpdateUserService';
import SendPinToUserEmailService from '@modules/users/services/SendPinToUserEmailService';
import VerifyPinService from '@modules/users/services/VerifyPinService';
import resetPasswordService from '@modules/users/services/resetPasswordService';

export default class UserController {
public async create(req: Request, res: Response): Promise<Response> {
Expand All @@ -32,18 +35,18 @@
return res.status(201).json(user);
}

public async readAll(req: Request, res: Response): Promise<Response> {

Check failure on line 38 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Block must not be padded by blank lines

const readUsers = container.resolve(ReadAllUsersService);

const users = await readUsers.execute();

if(users) {

Check failure on line 44 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Expected space(s) after "if"
users.forEach(user => {

Check failure on line 45 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Expected indentation of 6 spaces but found 8

Check failure on line 45 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Expected parentheses around arrow function argument
user.password = '###';

Check failure on line 46 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Assignment to property of function parameter 'user'
});
}

Check failure on line 49 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Trailing spaces not allowed
return res.status(201).json(users);
}

Expand All @@ -56,7 +59,7 @@
id,
});

if(user){

Check failure on line 62 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Expected space(s) after "if"

Check failure on line 62 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Missing space before opening brace
user.password = '###';
}

Expand Down Expand Up @@ -104,4 +107,41 @@
return res.status(201).json(user);
}

public async sendPin(req: Request, res: Response): Promise<Response> {
const { email } = req.body;

const sendPinToUserEmail = container.resolve(SendPinToUserEmailService);

const user = await sendPinToUserEmail.execute({
email,
});

return res.status(201).json({id: user.id});

Check failure on line 119 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

A space is required after '{'

Check failure on line 119 in src/modules/users/infra/http/controller/UsersController.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

A space is required before '}'
}

public async verifyPin(req: Request, res: Response): Promise<Response> {
const { id } = req.params;
const { pin } = req.body;

const verifyPin = container.resolve(VerifyPinService);

const user = await verifyPin.execute({
id,
pin,
});

return res.status(201).json({id: user.id});
}

public async resetPassword(req: Request, res: Response): Promise<Response> {
const { id } = req.params;
const { password } = req.body;

const resetPassword = container.resolve(resetPasswordService);

const user = await resetPassword.execute({id, password});

return res.status(201).json({id: user.id});
}

}
3 changes: 3 additions & 0 deletions src/modules/users/infra/http/routes/users.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ usersRoutes.patch('/update/:id', usersController.update);
usersRoutes.delete('/delete/:id', usersController.delete);
usersRoutes.get('/read', usersController.readAll);
usersRoutes.get('/read/:id', usersController.readById);
usersRoutes.post('/send-pin', usersController.sendPin);
usersRoutes.post('/verify-pin/:id', usersController.verifyPin);
usersRoutes.post('/reset-password/:id', usersController.resetPassword);


export default usersRoutes;
12 changes: 7 additions & 5 deletions src/modules/users/infra/prisma/entities/users.prisma
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
model Users {
id String @id @default(uuid())
name String @unique
email String
id String @id @default(uuid())
name String
email String @unique
password String
language String
phone String
created_at DateTime @default(now())
updated_at DateTime @default(now())
pin String?
pinExpires DateTime?
created_at DateTime @default(now())
updated_at DateTime @default(now())
}
20 changes: 17 additions & 3 deletions src/modules/users/infra/prisma/repositories/UsersRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,23 @@ export default class UsersRepository implements IUsersRepository {
return user;
}

public async update(id: string, data: IUpdateUserDTO): Promise<Users> {
const user = await this.ormRepository.update({ where: { id }, data });
public update(id: string, data: IUpdateUserDTO): Promise<Users> {
return this.ormRepository.update({ where: { id }, data });
}

public sendPinToUserEmail(email: string, pin: string, pinExpires: Date): Promise<Users> {
return this.ormRepository.update({
where: { email },
data: { pin, pinExpires },
});

return user;
}

public resetPassword(id: string, password: string): Promise<Users> {
return this.ormRepository.update({
where: { id },
data: { password },
});
}

}
2 changes: 2 additions & 0 deletions src/modules/users/repositories/IUsersRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface IUsersRepository {
findById(id: string): Promise<Users | null>;
delete(id: string): Promise<Users>;
update(id: string, data: IUpdateUserDTO): Promise<Users>;
sendPinToUserEmail(email: string, pin: string, pinExpires: Date): Promise<Users>;
resetPassword(id: string, password: string): Promise<Users>;
}

export default IUsersRepository;
38 changes: 38 additions & 0 deletions src/modules/users/services/ResetPasswordService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { inject, injectable } from 'tsyringe';

import { Users } from '@prisma/client';

import AppError from '@shared/errors/AppError';

import IHashProvider from '@shared/container/providers/HashProvider/models/IHashProvider';
import IUsersRepository from '../repositories/IUsersRepository';

interface IRequest {
id: string;
password: string;
}

@injectable()
export default class UpdateUserService {
constructor(
@inject('UsersRepository')
private usersRepository: IUsersRepository,

@inject('HashProvider')
private hashProvider: IHashProvider,
) { }

public async execute({
id,
password,
}: IRequest): Promise<Users> {
const userAlreadyExists = await this.usersRepository.findById(id);

if (!userAlreadyExists) throw new AppError('User with this id does not exist');

const hashedPassword = await this.hashProvider.generateHash(password);
const updatedUser = await this.usersRepository.resetPassword(id, hashedPassword);

return updatedUser;
}
}
55 changes: 55 additions & 0 deletions src/modules/users/services/SendPinToUserEmailService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { inject, injectable } from 'tsyringe';
import sgMail from '@sendgrid/mail';

import { Users } from '@prisma/client';

import AppError from '@shared/errors/AppError';

import IUsersRepository from '../repositories/IUsersRepository';

interface IRequest {
email: string;
}

@injectable()
export default class SendPinToUserEmailService {
constructor(
@inject('UsersRepository')
private usersRepository: IUsersRepository,
) { }

public async execute({
email,
}: IRequest): Promise<Users> {
const userAlreadyExists = await this.usersRepository.findByEmailWithRelations(email);
if (!userAlreadyExists) throw new AppError('User with this email does not exist');

const expiration = 5;
const pin = Math.floor(Math.random() * 9999).toString();
const data = new Date();
data.setMinutes(data.getMinutes() + expiration);
const user = await this.usersRepository.sendPinToUserEmail(
email.toLowerCase(),
pin,
data,
);

sgMail.setApiKey(process.env.SENDGRID_API_KEY as string)
const msg = {
to: email,
from: '[email protected]',
subject: 'Apogeo | Recuperação de senha',
text: user.name + ', seu pin para recuperação de senha é: ' + pin + '. Ele expira em ' + expiration + ' minutos.',
html: user.name + ', seu pin para recuperação de senha é: <strong>' + pin + '</strong>. Ele expira em ' + expiration + ' minutos.',
};

try {
await sgMail.send(msg);
console.log('Email sent');

Check warning on line 48 in src/modules/users/services/SendPinToUserEmailService.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Unexpected console statement
} catch (error: any) {

Check warning on line 49 in src/modules/users/services/SendPinToUserEmailService.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Unexpected any. Specify a different type
console.error(error);

Check warning on line 50 in src/modules/users/services/SendPinToUserEmailService.ts

View workflow job for this annotation

GitHub Actions / Check linting (14.x)

Unexpected console statement
}

return user;
}
}
28 changes: 28 additions & 0 deletions src/modules/users/services/VerifyPinService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { inject, injectable } from 'tsyringe';
import { Users } from '@prisma/client';
import AppError from '@shared/errors/AppError';
import IUsersRepository from '../repositories/IUsersRepository';

interface IRequest {
id: string;
pin: string;
}

@injectable()
export default class VerifyPinService {
constructor(
@inject('UsersRepository')
private usersRepository: IUsersRepository,
) { }

public async execute({
id, pin,
}: IRequest): Promise<Users> {
const user = await this.usersRepository.findById(id);
if (!user) throw new AppError('User with this id does not exist');
if (user?.pinExpires && user.pin != pin) throw new AppError('Pin is incorrect');
if (user?.pinExpires && user.pinExpires < new Date()) throw new AppError('Pin is expired');

return user;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Warnings:

- A unique constraint covering the columns `[email]` on the table `Users` will be added. If there are existing duplicate values, this will fail.

*/
-- DropIndex
DROP INDEX "Users_name_key";

-- AlterTable
ALTER TABLE "Users" ADD COLUMN "pin" TEXT,
ADD COLUMN "pinExpires" TIMESTAMP(3);

-- CreateIndex
CREATE UNIQUE INDEX "Users_email_key" ON "Users"("email");
12 changes: 7 additions & 5 deletions src/shared/infra/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ generator client {
}

model Users {
id String @id @default(uuid())
name String @unique
email String
id String @id @default(uuid())
name String
email String @unique
password String
language String
phone String
created_at DateTime @default(now())
updated_at DateTime @default(now())
pin String?
pinExpires DateTime?
created_at DateTime @default(now())
updated_at DateTime @default(now())
}
Loading
Loading