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

feat(auth): add user verification endpoint

parent ccbfe8ff
No related branches found
No related tags found
3 merge requests!27Recette,!26Dev,!1Feat/auth
...@@ -2,7 +2,7 @@ Bonjour<br /> ...@@ -2,7 +2,7 @@ Bonjour<br />
<br /> <br />
Afin de pouvoir vous connecter sur la plateforme, merci de cliquer sur Afin de pouvoir vous connecter sur la plateforme, merci de cliquer sur
<a <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 >ce lien</a
> >
afin de valider votre inscription<br /> afin de valider votre inscription<br />
......
import { Document } from 'mongoose';
export interface IUser extends Document {
readonly _id: string;
email: string;
password: string;
emailVerified: boolean;
validationToken: string;
role: number;
}
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { UserRole } from './enum/user-role.enum'; import { UserRole } from './enum/user-role.enum';
export type UserDocument = User & Document;
@Schema() @Schema()
export class User { export class User {
@Prop({ required: true }) @Prop({ required: true })
......
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 { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { CreateUserDto } from './create-user.dto'; import { CreateUserDto } from './create-user.dto';
import { UsersService } from './users.service'; import { UsersService } from './users.service';
...@@ -8,13 +9,25 @@ export class UsersController { ...@@ -8,13 +9,25 @@ export class UsersController {
constructor(private usersService: UsersService) {} constructor(private usersService: UsersService) {}
@UseGuards(JwtAuthGuard) @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') @Get('profile')
public getProfile(@Request() req) { public getProfile(@Request() req) {
return req.user; return req.user;
} }
@Post() @Post()
@ApiResponse({ status: 201, description: 'User created' })
public async create(@Body() createUserDto: CreateUserDto) { public async create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(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);
}
} }
...@@ -6,13 +6,18 @@ import * as crypto from 'crypto'; ...@@ -6,13 +6,18 @@ import * as crypto from 'crypto';
import { Model } from 'mongoose'; import { Model } from 'mongoose';
import { LoginDto } from '../auth/login-dto'; import { LoginDto } from '../auth/login-dto';
import { CreateUserDto } from './create-user.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 { MailerService } from '../mailer/mailer.service';
import { IUser } from './user.interface';
@Injectable() @Injectable()
export class UsersService { 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> { public async create(createUserDto: CreateUserDto): Promise<User> {
const userInDb = await this.findOne(createUserDto.email); const userInDb = await this.findOne(createUserDto.email);
if (userInDb) { if (userInDb) {
...@@ -54,13 +59,22 @@ export class UsersService { ...@@ -54,13 +59,22 @@ export class UsersService {
return await bcrypt.hash(password, process.env.SALT); 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) { if (passwordQuery) {
return this.userModel.findOne({ email: mail }).exec(); return this.userModel.findOne({ email: mail }).exec();
} }
return this.userModel.findOne({ email: mail }).select('-password').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> { public async findByLogin({ email, password }: LoginDto): Promise<User> {
const user = await this.findOne(email, true); const user = await this.findOne(email, true);
...@@ -83,16 +97,16 @@ export class UsersService { ...@@ -83,16 +97,16 @@ export class UsersService {
* a new account. * a new account.
* @param user User * @param user User
*/ */
private async verifyUserMail(user: User): Promise<any> { private async verifyUserMail(user: IUser): Promise<any> {
const config = this.mailerService.config; const config = this.mailerService.config;
const ejsPath = this.mailerService.getTemplateLocation(config.templates.verify.ejs); const ejsPath = this.mailerService.getTemplateLocation(config.templates.verify.ejs);
const jsonConfig = this.mailerService.loadJsonConfig(config.templates.verify.json); const jsonConfig = this.mailerService.loadJsonConfig(config.templates.verify.json);
const token = crypto.randomBytes(64).toString('hex'); const token = crypto.randomBytes(64).toString('hex');
const html = await ejs.renderFile(ejsPath, { const html = await ejs.renderFile(ejsPath, {
config, config,
token: token, token: token,
userId: user._id,
}); });
this.mailerService.send(user.email, jsonConfig.subject, html); this.mailerService.send(user.email, jsonConfig.subject, html);
...@@ -100,4 +114,20 @@ export class UsersService { ...@@ -100,4 +114,20 @@ export class UsersService {
user.validationToken = token; user.validationToken = token;
return user; 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);
}
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment