Skip to content
Snippets Groups Projects
Commit ffea481f authored by Hugo SUBTIL's avatar Hugo SUBTIL
Browse files

feat: add password reset

parent d7a30ed7
No related branches found
No related tags found
3 merge requests!27Recette,!26Dev,!8Feat/reset password
......@@ -17,5 +17,9 @@ export const config = {
ejs: 'changeEmail.ejs',
json: 'changeEmail.json',
},
resetPassword: {
ejs: 'resetPassword.ejs',
json: 'resetPassword.json',
},
},
};
Bonjour<br />
<br />
Vous avez demandé une réinitialisation de votre mot de passe pour le
<em>Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon</em>. Pour changer de mot de passe, merci de
cliquer sur le lien suivant :
<a
href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/users/reset-password/apply?token=<%= token %>"
>ce lien</a
><br />
Si vous n'avez pas demander de réinitiallisation de votre mot de passe, merci d'ignorer cet email.
<br />
Ce mail est un mail automatique. Merci de ne pas y répondre.
{
"subject": "Réinitialisation de mot de passe"
}
import { IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class PasswordResetApplyDto {
@ApiProperty({ type: String })
@IsNotEmpty()
@IsString()
readonly password: string;
@ApiProperty({ type: String })
@IsNotEmpty()
@IsString()
readonly token: string;
}
......@@ -6,5 +6,6 @@ export interface IUser extends Document {
password: string;
emailVerified: boolean;
validationToken: string;
resetPasswordToken: string;
role: number;
}
......@@ -14,6 +14,9 @@ export class User {
@Prop({ default: null })
validationToken: string;
@Prop({ default: null })
resetPasswordToken: string;
@Prop({ enum: [UserRole.admin, UserRole.user], default: UserRole.user })
role: number;
}
......
import { Body, Controller, Get, Param, Post, Query, Req, Request, UseGuards } from '@nestjs/common';
import { Body, Controller, Get, Param, Post, Query, Request, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { PasswordChangeDto } from './change-password.dto';
import { CreateUserDto } from './create-user.dto';
import { PasswordResetApplyDto } from './reset-password-apply.dto';
import { PasswordResetDto } from './reset-password.dto';
import { UsersService } from './users.service';
@Controller('users')
......@@ -44,4 +46,16 @@ export class UsersController {
passwordChangeDto.newPassword
);
}
@Post('reset-password')
@ApiResponse({ status: 200, description: 'Email sent if account exist' })
public async resetPassword(@Body() passwordReset: PasswordResetDto) {
return this.usersService.sendResetPasswordEmail(passwordReset.email);
}
@Post('reset-password/apply')
@ApiResponse({ status: 200, description: 'Email sent if account exist' })
public async resetPasswordApply(@Body() passwordResetApplyDto: PasswordResetApplyDto) {
return this.usersService.validatePasswordResetToken(passwordResetApplyDto.password, passwordResetApplyDto.token);
}
}
......@@ -38,6 +38,7 @@ describe('UsersService', () => {
emailVerified: false,
email: 'jacques.dupont@mii.com',
password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.yazDArYIpf2WnQF1jRHOjBxADEjUEA3',
resetPasswordToken: null,
};
const userDto: CreateUserDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' };
jest.spyOn(service, 'create').mockImplementation(async (): Promise<User> => result);
......@@ -71,6 +72,7 @@ describe('UsersService', () => {
email: 'jacques.dupont@mii.com',
password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.yazDArYIpf2WnQF1jRHOjBxADEjUEA3',
role: 0,
resetPasswordToken: null,
};
const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' };
jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<User> => result);
......
......@@ -149,4 +149,42 @@ export class UsersService {
user.password = await this.hashPassword(newPassword);
user.save();
}
public async sendResetPasswordEmail(email: string): Promise<HttpException> {
const user = await this.findOne(email);
if (user) {
const config = this.mailerService.config;
const ejsPath = this.mailerService.getTemplateLocation(config.templates.resetPassword.ejs);
const jsonConfig = this.mailerService.loadJsonConfig(config.templates.resetPassword.json);
const token = crypto.randomBytes(64).toString('hex');
const html = await ejs.renderFile(ejsPath, {
config,
token: token,
});
this.mailerService.send(user.email, jsonConfig.subject, html);
// Save token
user.resetPasswordToken = token;
user.save();
}
throw new HttpException('Email sent if account exist', HttpStatus.OK);
}
public async validatePasswordResetToken(password: string, token: string): Promise<any> {
const user = await this.userModel.findOne({ resetPasswordToken: token }).exec();
if (user) {
if (!this.isStrongPassword(password)) {
throw new HttpException(
'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer',
HttpStatus.UNPROCESSABLE_ENTITY
);
}
user.password = await this.hashPassword(password);
user.resetPasswordToken = null;
user.save();
throw new HttpException('Password Reset', HttpStatus.OK);
}
throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment