From 0fba212e8631183c7507a19cfb992ffa306ec5fa Mon Sep 17 00:00:00 2001 From: thisme8 Date: Thu, 16 Oct 2025 01:55:45 +0545 Subject: [PATCH 1/4] Verify the email address with token --- .../dto/verify-email.dto.ts | 8 ++++++ .../email-verification.controller.ts | 14 +++++++--- .../email-verification.module.ts | 3 ++- .../email-verification.service.ts | 27 +++++++++++++++++-- src/user/user.service.ts | 26 ++++++++++++++++-- 5 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 src/email-verification/dto/verify-email.dto.ts diff --git a/src/email-verification/dto/verify-email.dto.ts b/src/email-verification/dto/verify-email.dto.ts new file mode 100644 index 0000000..1edf763 --- /dev/null +++ b/src/email-verification/dto/verify-email.dto.ts @@ -0,0 +1,8 @@ +import {IsNotEmpty, IsString } from 'class-validator'; + +export class VerifyEmailDto { + @IsString() + @IsNotEmpty() + token: string; + +} \ No newline at end of file diff --git a/src/email-verification/email-verification.controller.ts b/src/email-verification/email-verification.controller.ts index 899f615..685b530 100644 --- a/src/email-verification/email-verification.controller.ts +++ b/src/email-verification/email-verification.controller.ts @@ -1,4 +1,5 @@ import { RequestEmailDto } from './dto/request-email.dto'; +import { VerifyEmailDto } from './dto/verify-email.dto'; import { EmailVerificationService } from './email-verification.service'; import { Body, Controller, Post } from '@nestjs/common'; @@ -6,9 +7,16 @@ import { Body, Controller, Post } from '@nestjs/common'; export class EmailVerificationController { constructor(private readonly emailVerificationService: EmailVerificationService) {} - @Post('send') - async SendEmail(@Body() dto: RequestEmailDto) { - await this.emailVerificationService.SendEmail(dto.email); + @Post('send-email') + async sendEmail(@Body() dto: RequestEmailDto) { + await this.emailVerificationService.sendEmail(dto.email); return { message: 'Verification link sent to your email' }; } + + @Post('verify-email') + async verifyEmail(@Body() dto: VerifyEmailDto) { + const email = await this.emailVerificationService.decodeVerificationToken(dto.token); + await this.emailVerificationService.verifyEmail(email); + return { message: 'The email is verified' }; + } } diff --git a/src/email-verification/email-verification.module.ts b/src/email-verification/email-verification.module.ts index 7a4381b..5a56ad6 100644 --- a/src/email-verification/email-verification.module.ts +++ b/src/email-verification/email-verification.module.ts @@ -5,10 +5,11 @@ import { EmailVerificationController } from './email-verification.controller'; import { EmailVerificationService } from './email-verification.service'; import { User } from '../user/entities/user.entity'; import { JwtService } from '@nestjs/jwt'; +import { UserService } from 'user/user.service'; @Module({ imports: [TypeOrmModule.forFeature([EmailVerification, User])], controllers: [EmailVerificationController], - providers: [EmailVerificationService, JwtService], + providers: [EmailVerificationService, JwtService, UserService], }) export class EmailVerificationModule {} diff --git a/src/email-verification/email-verification.service.ts b/src/email-verification/email-verification.service.ts index 43f103c..b3baaad 100644 --- a/src/email-verification/email-verification.service.ts +++ b/src/email-verification/email-verification.service.ts @@ -1,6 +1,7 @@ +import { UserService } from './../user/user.service'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { EmailVerification } from './entities/email-verification.entity'; @@ -16,9 +17,10 @@ export class EmailVerificationService { private readonly userRepo: Repository, private readonly jwtService: JwtService, private readonly configService: ConfigService, + private readonly userService: UserService, ) {} - public async SendEmail(email: string) { + public async sendEmail(email: string) { const user = await this.userRepo.findOneBy({ email }); if (!user) throw new Error('User not found'); @@ -55,4 +57,25 @@ export class EmailVerificationService { await this.emailVerificationRepo.save({ user, token, expires_at }); } + + public async verifyEmail(email: string) { + await this.userService.confirmEmail(email); + } + + public async decodeVerificationToken(token: string) { + try { + const payload = await this.jwtService.verify(token, { + secret: this.configService.get('JWT_SECRET'), + }); + + if (typeof payload === 'object' && 'email' in payload) { + return payload.email; + } + throw new BadRequestException(); + } catch (error) { + if (error?.name === 'TokenExpiredError') { + throw new BadRequestException('Your email confirmation token has expired'); + } + } + } } diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 668a7d6..61d5553 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,4 +1,26 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { User } from './entities/user.entity'; +import { Repository } from 'typeorm'; @Injectable() -export class UserService {} +export class UserService { + constructor( + @InjectRepository(User) + private readonly userRepo: Repository, + ) {} + + async confirmEmail(email: string) { + const user = await this.userRepo.findOne({ where: { email } }); + if (!user) throw new BadRequestException('User not found'); + + if (user.verified_at) { + return 'This user has already been verified'; + } + + user.verified_at = new Date(); + await this.userRepo.save(user); + + return 'User email successfully verified'; + } +} From 2bad9c5e830a904bb76557c4be40fa90662036f3 Mon Sep 17 00:00:00 2001 From: thisme8 Date: Thu, 16 Oct 2025 06:19:11 +0545 Subject: [PATCH 2/4] Add if not exists to user migration --- src/migrations/1760485409896-users.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/migrations/1760485409896-users.ts b/src/migrations/1760485409896-users.ts index 7c4112c..4e0386d 100644 --- a/src/migrations/1760485409896-users.ts +++ b/src/migrations/1760485409896-users.ts @@ -3,7 +3,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; export class Users1760485409896 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE TABLE "users" ( + `CREATE TABLE IF NOT EXISTS "users" ( "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(), "name" VARCHAR(255) NOT NULL, "username" VARCHAR(255) UNIQUE NOT NULL, From 3946f6dd9f6873ace5aae6ee4f70ded3e15aee6c Mon Sep 17 00:00:00 2001 From: thisme8 Date: Fri, 17 Oct 2025 07:51:43 +0545 Subject: [PATCH 3/4] Resolve pr comments in US-05-01 --- src/email-verification/email-verification.service.ts | 3 ++- src/user/user.service.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/email-verification/email-verification.service.ts b/src/email-verification/email-verification.service.ts index b3baaad..5d7f18b 100644 --- a/src/email-verification/email-verification.service.ts +++ b/src/email-verification/email-verification.service.ts @@ -71,11 +71,12 @@ export class EmailVerificationService { if (typeof payload === 'object' && 'email' in payload) { return payload.email; } - throw new BadRequestException(); + throw new BadRequestException('Invalid token payload structure'); } catch (error) { if (error?.name === 'TokenExpiredError') { throw new BadRequestException('Your email confirmation token has expired'); } } + throw new BadRequestException('Failed to verify email token'); } } diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 61d5553..8c20419 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -20,7 +20,5 @@ export class UserService { user.verified_at = new Date(); await this.userRepo.save(user); - - return 'User email successfully verified'; } } From d6503d4c70fd195656d52c4b88db2a07712405e0 Mon Sep 17 00:00:00 2001 From: thisme8 Date: Fri, 17 Oct 2025 10:55:11 +0545 Subject: [PATCH 4/4] throw error for the verified user --- src/user/user.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 8c20419..81f0ff3 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -15,7 +15,7 @@ export class UserService { if (!user) throw new BadRequestException('User not found'); if (user.verified_at) { - return 'This user has already been verified'; + throw new BadRequestException('Email already verified'); } user.verified_at = new Date();