Skip to content
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
5 changes: 4 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ MONGO_URI=mongodb://localhost:27017/prodesk

# APP
PORT=3000
NODE_ENV=development
NODE_ENV=development

EMAIL_USER=seuemail@gmail.com
EMAIL_PASS=sua_senha_de_app
10 changes: 10 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"morgan": "^1.10.1",
"multer": "^2.1.1",
"node-nlp": "^5.0.0-alpha.5",
"nodemailer": "^8.0.5",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
Expand Down
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ChatModule } from './modules/chat/chat.module';
import { TriageModule } from './modules/triage/triage.module';
import { CategoryModule } from './modules/category/category.module';
import { TicketModule } from './modules/ticket/ticket.module';
import { EmailModule } from './modules/email/email.module';
import { FileModule } from './modules/file/file.module';

@Module({
Expand All @@ -27,6 +28,7 @@ import { FileModule } from './modules/file/file.module';
TriageModule,
CategoryModule,
TicketModule,
EmailModule,
FileModule,
],
controllers: [],
Expand Down
47 changes: 47 additions & 0 deletions backend/src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,51 @@ export class AuthController {
login(@Body() user: ExistingUserDTO): Promise<{ token: string } | null> {
return this.authService.login(user);
}

@Post('forgot-password')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Solicitar recuperação de senha' })
@ApiBody({
schema: {
example: {
email: 'usuario@email.com',
},
},
})
@ApiResponse({
status: 200,
description:
'Se o email existir, um link de recuperação será enviado',
})
forgotPassword(@Body() body: { email: string }): Promise<void> {
return this.authService.forgotPassword(body.email);
}

@Post('reset-password')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Redefinir senha do usuário' })
@ApiBody({
schema: {
example: {
token: 'jwt.token.aqui',
newPassword: 'NovaSenha@123',
},
},
})
@ApiResponse({
status: 200,
description: 'Senha redefinida com sucesso',
})
@ApiResponse({
status: 400,
description: 'Token inválido ou expirado',
})
resetPassword(
@Body() body: { token: string; newPassword: string },
): Promise<void> {
return this.authService.resetPassword(
body.token,
body.newPassword,
);
}
}
2 changes: 2 additions & 0 deletions backend/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { UserModule } from '../user/user.module';
import { JwtModule } from '@nestjs/jwt';
import { JwtGuard } from './guards/jwt.guard';
import { JwtStrategy } from './guards/jwt.strategy';
import { EmailModule } from '../email/email.module';

@Module({
imports: [
UserModule,
EmailModule,
JwtModule.registerAsync({
useFactory: () => ({
secret: 'secret',
Expand Down
48 changes: 48 additions & 0 deletions backend/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import { UserDetails } from '../user/user.interface';
import { ExistingUserDTO } from '../user/dtos/existingUserDTO';
import { JwtService } from '@nestjs/jwt';
import { UserRole } from '../user/user.schema';
import { EmailService } from '../email/email.service';

@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
private emailService: EmailService,
) {}

async hashPassword(password: string): Promise<string> {
Expand Down Expand Up @@ -101,4 +103,50 @@ export class AuthService {
await this.register(admin);
}
}

async forgotPassword(email: string): Promise<void> {
const user = await this.userService.findByEmail(email);

if (!user) return;

const payload = {
sub: user._id,
email: user.email,
type: 'reset-password',
};

const token = await this.jwtService.signAsync(payload, {
expiresIn: '15m',
});

await this.emailService.sendResetPasswordEmail(
user.email,
token,
);
}

async resetPassword(token: string, newPassword: string): Promise<void> {
try {
const payload = await this.jwtService.verifyAsync(token);

if (payload.type !== 'reset-password') {
throw new BadRequestException('Token inválido');
}

const user = await this.userService.findById(payload.sub);

if (!user) {
throw new BadRequestException('Usuário não encontrado');
}

const hashedPassword = await this.hashPassword(newPassword);

await this.userService.updateUser(payload.sub, {
password: hashedPassword,
});

} catch (error) {
throw new BadRequestException('Token inválido ou expirado');
}
}
}
6 changes: 6 additions & 0 deletions backend/src/modules/category/dtos/forgotPassword.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IsEmail } from 'class-validator';

export class ForgotPasswordDTO {
@IsEmail()
email: string;
}
21 changes: 21 additions & 0 deletions backend/src/modules/category/dtos/resetPassword.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IsString, IsStrongPassword } from 'class-validator';

export class ResetPasswordDTO {
@IsString()
token: string;

@IsStrongPassword(
{
minLength: 8,
minLowercase: 1,
minUppercase: 1,
minNumbers: 1,
minSymbols: 1,
},
{
message:
'Password must be at least 8 characters long and include uppercase, lowercase, number and symbol.',
},
)
newPassword: string;
}
7 changes: 7 additions & 0 deletions backend/src/modules/email/email.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Controller } from '@nestjs/common';
import { EmailService } from './email.service';

@Controller('email')
export class EmailController {
constructor(private readonly emailService: EmailService) {}
}
10 changes: 10 additions & 0 deletions backend/src/modules/email/email.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { EmailService } from './email.service';
import { EmailController } from './email.controller';

@Module({
controllers: [EmailController],
providers: [EmailService],
exports: [EmailService],
})
export class EmailModule {}
29 changes: 29 additions & 0 deletions backend/src/modules/email/email.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';

@Injectable()
export class EmailService {
private transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});

async sendResetPasswordEmail(email: string, token: string) {
const resetLink = `myapp://reset-password?token=${token}`;

await this.transporter.sendMail({
to: email,
subject: 'Recuperação de senha',
html: `
<h2>Recuperação de senha</h2>
<p>Clique no link abaixo:</p>
<a href="${resetLink}">Redefinir senha</a>
<p>Se não abrir, copie o link:</p>
<p>${resetLink}</p>
`,
});
}
}
8 changes: 0 additions & 8 deletions backend/src/modules/user/dtos/createUserDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,4 @@ export class CreateClientDTO {
@ApiProperty({ example: '65f1a2b3c9d123456789abcd' })
@IsString()
companyId: string;

@ApiPropertyOptional({
example: ['65f1a2b3c9d123456789abcd'],
})
@IsOptional()
@IsArray()
@IsMongoId({ each: true })
categories?: string[];
}
1 change: 1 addition & 0 deletions backend/src/modules/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export class UserService {
data: Partial<{
name: string;
email: string;
password: string;
role: UserRole;
companyId: string;
categories: string[];
Expand Down
Loading