diff --git a/src/users/change-email.dto.ts b/src/users/change-email.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb783e4490f15c9c32b2f78d4f6dae4f679553fd --- /dev/null +++ b/src/users/change-email.dto.ts @@ -0,0 +1,10 @@ +import { IsEmail, IsNotEmpty } from 'class-validator'; + +export class EmailChangeDto { + @IsNotEmpty() + @IsEmail() + readonly newEmail: string; + @IsNotEmpty() + @IsEmail() + readonly oldEmail: string; +} diff --git a/src/users/user.interface.ts b/src/users/user.interface.ts index 0fe05c6f856caafb64c23ff4dfbaaa778d6b5773..704569733e0657f065a6598cdc4dcfc7bcdf1954 100644 --- a/src/users/user.interface.ts +++ b/src/users/user.interface.ts @@ -7,4 +7,6 @@ export interface IUser extends Document { emailVerified: boolean; validationToken: string; role: number; + changeEmailToken: string; + newEmail: string; } diff --git a/src/users/user.schema.ts b/src/users/user.schema.ts index abb11e91aaac1bac06d07ab6c513567b89a6791c..c41262a381bd19c00b266be3a8345cb91a9c2c51 100644 --- a/src/users/user.schema.ts +++ b/src/users/user.schema.ts @@ -16,6 +16,12 @@ export class User { @Prop({ enum: [UserRole.admin, UserRole.user], default: UserRole.user }) role: number; + + @Prop({ default: null }) + changeEmailToken: string; + + @Prop({ default: null }) + newEmail: string; } export const UserSchema = SchemaFactory.createForClass(User); diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index c7120670e093cfe37ea2cdd5c81586ec2afc6590..b797570a30dd3fd0aa5798698dddf55a4773743a 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, Param, Post, Query, Request, UseGuards } from '@ import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { PasswordChangeDto } from './change-password.dto'; +import { EmailChangeDto } from './change-email.dto'; import { CreateUserDto } from './create-user.dto'; import { UsersService } from './users.service'; @@ -44,4 +45,20 @@ export class UsersController { passwordChangeDto.newPassword ); } + + @UseGuards(JwtAuthGuard) + @Post('change-email') + @ApiResponse({ status: 201, description: 'Email confirmation send' }) + @ApiResponse({ status: 401, description: 'Invalid Email' }) + public async changeEmail(@Request() req, @Body() emailChangeDto: EmailChangeDto) { + return this.usersService.changeUserEmail(emailChangeDto.newEmail, emailChangeDto.oldEmail); + } + + @UseGuards(JwtAuthGuard) + @Post('verify-change-email') + @ApiResponse({ status: 201, description: 'Email changed' }) + @ApiResponse({ status: 401, description: 'Invalid Token' }) + public async verifyAndUpdateEmail(@Request() req, @Query('token') token: string) { + return this.usersService.verifyAndUpdateUserEmail(token); + } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index dbaed19c46e7bf14a8a71eeaf7e5562cf16aaed5..b0770d5554cd047b1a0b7fff291c94466f374101 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -134,6 +134,38 @@ export class UsersService { } } + public async changeUserEmail(newEmail: string, oldEmail: string) { + const user = await this.findOne(oldEmail); + if (user) { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.changeEmail.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.changeEmail.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); + user.changeEmailToken = token; + user.newEmail = newEmail; + user.save(); + } + throw new HttpException('Email sent if account exist', HttpStatus.OK); + } + + public async verifyAndUpdateUserEmail(token: string): Promise<any> { + const user = await this.userModel.findOne({ changeEmailToken: token }).exec(); + if (user) { + user.email = user.newEmail; + user.newEmail = null; + user.changeEmailToken = null; + user.save(); + return user; + } else { + throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + } + } + public async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<any> { const user = await this.findById(userId, true); const arePasswordEqual = await this.comparePassword(oldPassword, user.password);