From 7b1fe8a6db800f0182c372e09c720d17771382a1 Mon Sep 17 00:00:00 2001 From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com> Date: Fri, 4 Dec 2020 14:35:39 +0100 Subject: [PATCH] feat(auth): add user verification endpoint --- src/mailer/mail-templates/verify.ejs | 2 +- src/users/user.interface.ts | 10 +++++++ src/users/user.schema.ts | 4 --- src/users/users.controller.ts | 15 ++++++++++- src/users/users.service.ts | 40 ++++++++++++++++++++++++---- 5 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 src/users/user.interface.ts diff --git a/src/mailer/mail-templates/verify.ejs b/src/mailer/mail-templates/verify.ejs index e384dae43..2782ec137 100644 --- a/src/mailer/mail-templates/verify.ejs +++ b/src/mailer/mail-templates/verify.ejs @@ -2,7 +2,7 @@ Bonjour<br /> <br /> Afin de pouvoir vous connecter sur la plateforme, merci de cliquer sur <a - href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/users/verify/<%= token %>" + href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/users/verify/<%= userId %>?token=<%= token %>" >ce lien</a > afin de valider votre inscription<br /> diff --git a/src/users/user.interface.ts b/src/users/user.interface.ts new file mode 100644 index 000000000..0fe05c6f8 --- /dev/null +++ b/src/users/user.interface.ts @@ -0,0 +1,10 @@ +import { Document } from 'mongoose'; + +export interface IUser extends Document { + readonly _id: string; + email: string; + password: string; + emailVerified: boolean; + validationToken: string; + role: number; +} diff --git a/src/users/user.schema.ts b/src/users/user.schema.ts index daa58ed0c..abb11e91a 100644 --- a/src/users/user.schema.ts +++ b/src/users/user.schema.ts @@ -1,9 +1,5 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { Document } from 'mongoose'; import { UserRole } from './enum/user-role.enum'; - -export type UserDocument = User & Document; - @Schema() export class User { @Prop({ required: true }) diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index d7ebf5198..8d80697be 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,4 +1,5 @@ -import { Body, Controller, Get, Post, Request, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Query, Req, Request, UseGuards } from '@nestjs/common'; +import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { CreateUserDto } from './create-user.dto'; import { UsersService } from './users.service'; @@ -8,13 +9,25 @@ export class UsersController { constructor(private usersService: UsersService) {} @UseGuards(JwtAuthGuard) + @ApiOperation({ description: 'Get user profile' }) + @ApiResponse({ status: 200, description: 'Return user profil' }) + @ApiResponse({ status: 401, description: 'User does not have sufficient rights' }) @Get('profile') public getProfile(@Request() req) { return req.user; } @Post() + @ApiResponse({ status: 201, description: 'User created' }) public async create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); } + + @Post('verify/:id') + @ApiParam({ name: 'id', type: String, required: true }) + @ApiResponse({ status: 201, description: 'User verified' }) + @ApiResponse({ status: 401, description: "This token does'nt exist or is not associate to this user." }) + public async validateUser(@Param() params, @Query('token') token: string) { + return this.usersService.validateUser(params.id, token); + } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index c2f9e59ae..4ea11e51f 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -6,13 +6,18 @@ import * as crypto from 'crypto'; import { Model } from 'mongoose'; import { LoginDto } from '../auth/login-dto'; import { CreateUserDto } from './create-user.dto'; -import { User, UserDocument } from './user.schema'; +import { User } from './user.schema'; import { MailerService } from '../mailer/mailer.service'; +import { IUser } from './user.interface'; @Injectable() export class UsersService { - constructor(@InjectModel(User.name) private userModel: Model<UserDocument>, private mailerService: MailerService) {} + constructor(@InjectModel(User.name) private userModel: Model<IUser>, private mailerService: MailerService) {} + /** + * Create a user account + * @param createUserDto CreateUserDto + */ public async create(createUserDto: CreateUserDto): Promise<User> { const userInDb = await this.findOne(createUserDto.email); if (userInDb) { @@ -54,13 +59,22 @@ export class UsersService { return await bcrypt.hash(password, process.env.SALT); } - public async findOne(mail: string, passwordQuery?: boolean): Promise<User | undefined> { + public async findOne(mail: string, passwordQuery?: boolean): Promise<IUser | undefined> { if (passwordQuery) { return this.userModel.findOne({ email: mail }).exec(); } return this.userModel.findOne({ email: mail }).select('-password').exec(); } + public async findById(id: string): Promise<IUser | undefined> { + return this.userModel.findById(id).select('-password').exec(); + } + + /** + * Return a user after credential checking. + * Use for login action + * @param param LoginDto + */ public async findByLogin({ email, password }: LoginDto): Promise<User> { const user = await this.findOne(email, true); @@ -83,16 +97,16 @@ export class UsersService { * a new account. * @param user User */ - private async verifyUserMail(user: User): Promise<any> { + private async verifyUserMail(user: IUser): Promise<any> { const config = this.mailerService.config; const ejsPath = this.mailerService.getTemplateLocation(config.templates.verify.ejs); const jsonConfig = this.mailerService.loadJsonConfig(config.templates.verify.json); const token = crypto.randomBytes(64).toString('hex'); - const html = await ejs.renderFile(ejsPath, { config, token: token, + userId: user._id, }); this.mailerService.send(user.email, jsonConfig.subject, html); @@ -100,4 +114,20 @@ export class UsersService { user.validationToken = token; return user; } + + /** + * Check that the given token is associated to userId. If it's true, validate user account. + * @param userId string + * @param token string + */ + public async validateUser(userId: string, token: string): Promise<any> { + const user = await this.findById(userId); + if (user && user.validationToken === token) { + user.validationToken = null; + user.emailVerified = true; + user.save(); + } else { + throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + } + } } -- GitLab