Skip to content
This repository has been archived by the owner on Feb 26, 2022. It is now read-only.

Commit

Permalink
feat: confirm maill auth
Browse files Browse the repository at this point in the history
  • Loading branch information
ludovicGendre committed Jan 27, 2022
1 parent 3f66a00 commit b37adf3
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { AuthModule } from './auth/auth.module';
import { PrismaService } from './prisma.service';
import { UserModule } from './user/user.module';
import { EmailModule } from './email/email.module';
import { EmailConfirmationModule } from './email/confirmation/emailConfirmation.module';

@Module({
imports: [
PrismaService,
AuthModule,
ConfigModule,
ScheduleModule.forRoot(),
EmailConfirmationModule,
EmailModule,
CityModule,
UserModule,
Expand Down
3 changes: 3 additions & 0 deletions src/dtos/auth/auth-token.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';

export class AuthTokenDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
public token!: string;
}
33 changes: 33 additions & 0 deletions src/email/confirmation/emailConfirmation.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
Controller,
ClassSerializerInterceptor,
UseInterceptors,
Post,
Body,
} from '@nestjs/common';
import { AuthTokenDto } from 'src/dtos';
import { EmailConfirmationService } from './emailConfirmation.service';
// import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard';
// import RequestWithUser from '../emailSchedule.controller';

@Controller('email-confirmation')
@UseInterceptors(ClassSerializerInterceptor)
export class EmailConfirmationController {
constructor(
private readonly emailConfirmationService: EmailConfirmationService,
) {}

@Post('confirm')
async confirm(@Body() confirmationData: AuthTokenDto) {
const email = await this.emailConfirmationService.decodeConfirmationToken(
confirmationData.token,
);
await this.emailConfirmationService.confirmEmail(email);
}

// @Post('resend-confirmation-link')
// @UseGuards(JwtAuthenticationGuard)
// async resendConfirmationLink(@Req() request: RequestWithUser) {
// await this.emailConfirmationService.resendConfirmationLink(request.user.id);
// }
}
25 changes: 25 additions & 0 deletions src/email/confirmation/emailConfirmation.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
import { UserDto } from 'src/dtos';

export interface RequestWithUser extends Request {
user: UserDto;
}

@Injectable()
export class EmailConfirmationGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const request: RequestWithUser = context.switchToHttp().getRequest();

if (!request.user?.isEmailConfirmed) {
throw new UnauthorizedException('Confirm your email first');
}

return true;
}
}
34 changes: 34 additions & 0 deletions src/email/confirmation/emailConfirmation.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Module } from '@nestjs/common';
import { PrismaService } from 'src/prisma.service';
import { UserService } from 'src/user/user.service';
import EmailService from '../email.service';
import { EmailConfirmationController } from './emailConfirmation.controller';
import { EmailConfirmationService } from 'src/email/confirmation/emailConfirmation.service';
import { JwtModule, JwtService } from '@nestjs/jwt';
import { AuthService } from 'src/auth/auth.service';
import { AuthController } from 'src/auth/auth.controller';
import { AuthModule } from 'src/auth/auth.module';
import { JwtStrategy } from 'src/auth/jwt-strategy';
import { ConfigService } from 'config/config.service';

@Module({
imports: [
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.jwtSecret,
signOptions: { expiresIn: '8h' },
}),
}),
],
controllers: [EmailConfirmationController, AuthController],
providers: [
AuthService,
EmailService,
JwtStrategy,
EmailConfirmationService,
PrismaService,
UserService,
],
})
export class EmailConfirmationModule {}
39 changes: 38 additions & 1 deletion src/email/confirmation/emailConfirmation.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Id } from 'src/dtos';
import { UserService } from 'src/user/user.service';
import EmailService from '../email.service';

interface VerificationTokenPayload {
Expand All @@ -11,6 +13,7 @@ export class EmailConfirmationService {
constructor(
private readonly jwtService: JwtService,
private readonly emailService: EmailService,
private readonly usersService: UserService,
) {}

public sendVerificationLink(email: string) {
Expand All @@ -30,4 +33,38 @@ export class EmailConfirmationService {
text,
});
}

public async resendConfirmationLink(userId: Id) {
const user = await this.usersService.get(userId);
if (user.isEmailConfirmed) {
throw new BadRequestException('Email already confirmed');
}
await this.sendVerificationLink(user.email);
}

public async confirmEmail(email: string) {
const user = await this.usersService.getByEmail(email);
if (user.isEmailConfirmed) {
throw new BadRequestException('Email already confirmed');
}
await this.usersService.markEmailAsConfirmed(email);
}

public async decodeConfirmationToken(token: string) {
try {
const payload = await this.jwtService.verify(token, {
secret: process.env.JWT_VERIFICATION_TOKEN_SECRET,
});

if (typeof payload === 'object' && 'email' in payload) {
return payload.email;
}
throw new BadRequestException();
} catch (error) {
if (error?.name === 'TokenExpiredError') {
throw new BadRequestException('Email confirmation token expired');
}
throw new BadRequestException('Bad confirmation token');
}
}
}
1 change: 0 additions & 1 deletion src/email/email.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@ import { PrismaService } from '../prisma.service';
@Module({
controllers: [EmailSchedulingController],
providers: [EmailService, EmailSchedulingService, PrismaService],
exports: [EmailService],
})
export class EmailModule {}
20 changes: 19 additions & 1 deletion src/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';
import { User, Prisma, File } from '@prisma/client';
@Injectable()
Expand Down Expand Up @@ -101,6 +101,24 @@ export class UserService {
where: { id: fileId },
});
}

async getByEmail(email: string) {
const user = await this.prisma.user.findFirst({ where: { email: email } });
if (user) {
return user;
}
throw new HttpException(
'User with this email does not exist',
HttpStatus.NOT_FOUND,
);
}

async markEmailAsConfirmed(email: string) {
return this.prisma.user.update({
where: { email: email },
data: { email: email, isEmailConfirmed: true },
});
}
}

//This function takes in latitude and longitude of two location and returns the distance between them as the crow flies (in km)
Expand Down

0 comments on commit b37adf3

Please sign in to comment.