From 1cf995c41d7950f3efb22b85d44bee99fbe94175 Mon Sep 17 00:00:00 2001 From: neoandmatrix Date: Mon, 23 Dec 2024 01:46:59 +0530 Subject: [PATCH 1/7] chore:added otp and redis functionality --- package.json | 2 + src/app.module.ts | 2 + src/common/hashing.ts | 15 ++++++ src/constants/otp.constants.ts | 1 + .../apis/generateOtp/dto/genrateOtp.dto.ts | 9 ++++ .../generateOtp/generateOtp.controller.ts | 13 +++++ .../apis/generateOtp/generateOtp.module.ts | 12 +++++ .../apis/generateOtp/genrateOtp.helper.ts | 15 ++++++ .../apis/generateOtp/genrateOtp.service.ts | 16 ++++++ src/services/apis/otp/dto/otp.dto.ts | 53 ------------------- src/services/apis/otp/otp.controller.spec.ts | 18 ------- src/services/apis/otp/otp.module.ts | 11 ---- src/services/apis/otp/otp.service.spec.ts | 18 ------- src/services/apis/otp/otp.service.ts | 15 ------ src/services/apis/otp/schemas/otp.schema.ts | 31 ----------- src/services/apis/users/users.controller.ts | 46 +--------------- src/services/apis/users/users.module.ts | 2 - src/services/bullmq/constants/queues.ts | 1 + src/services/bullmq/jobs/otp.jobs.ts | 3 ++ .../bullmq/processors/otp.processor.ts | 39 ++++++++++++++ .../processors/reaction-stream.processor.ts | 2 +- src/services/bullmq/producers/otp.producer.ts | 17 ++++++ src/services/bullmq/queue.module.ts | 23 ++++++-- src/services/redis/adapter/adapter.service.ts | 2 - 24 files changed, 166 insertions(+), 200 deletions(-) create mode 100644 src/common/hashing.ts create mode 100644 src/constants/otp.constants.ts create mode 100644 src/services/apis/generateOtp/dto/genrateOtp.dto.ts create mode 100644 src/services/apis/generateOtp/generateOtp.controller.ts create mode 100644 src/services/apis/generateOtp/generateOtp.module.ts create mode 100644 src/services/apis/generateOtp/genrateOtp.helper.ts create mode 100644 src/services/apis/generateOtp/genrateOtp.service.ts delete mode 100644 src/services/apis/otp/dto/otp.dto.ts delete mode 100644 src/services/apis/otp/otp.controller.spec.ts delete mode 100644 src/services/apis/otp/otp.module.ts delete mode 100644 src/services/apis/otp/otp.service.spec.ts delete mode 100644 src/services/apis/otp/otp.service.ts delete mode 100644 src/services/apis/otp/schemas/otp.schema.ts create mode 100644 src/services/bullmq/jobs/otp.jobs.ts create mode 100644 src/services/bullmq/processors/otp.processor.ts create mode 100644 src/services/bullmq/producers/otp.producer.ts diff --git a/package.json b/package.json index b878345..33df98f 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@socket.io/redis-adapter": "^8.3.0", "@types/nodemailer": "^6.4.17", "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", "bullmq": "^5.34.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -62,6 +63,7 @@ "@nestjs/schematics": "^10.1.4", "@nestjs/testing": "^10.4.1", "@types/bcrypt": "^5.0.2", + "@types/bcryptjs": "^2.4.6", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/lodash": "^4.17.13", diff --git a/src/app.module.ts b/src/app.module.ts index 04e51a7..6909ae5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,6 +14,7 @@ import { ReactionsModule } from './services/apis/reactions/reactions.module'; import { ProfilesModule } from './services/apis/profiles/profiles.module'; import { QueueModule } from './services/bullmq/queue.module'; import { BullModule } from '@nestjs/bullmq'; +import { GenerateOtpModule } from './services/apis/generateOtp/generateOtp.module'; @Module({ imports: [ @@ -43,6 +44,7 @@ import { BullModule } from '@nestjs/bullmq'; AdapterModule, ProfilesModule, ReactionsModule, + GenerateOtpModule, ], controllers: [AppController], providers: [ diff --git a/src/common/hashing.ts b/src/common/hashing.ts new file mode 100644 index 0000000..7263064 --- /dev/null +++ b/src/common/hashing.ts @@ -0,0 +1,15 @@ +import bcrypt from 'bcryptjs'; + +export async function hashString(input: string): Promise { + const saltRounds = 10; + const hashedString = await bcrypt.hash(input, saltRounds); + return hashedString; +} + +export async function compareHashedString( + input: string, + hashedString: string, +): Promise { + const isMatch = await bcrypt.compare(input, hashedString); + return isMatch; +} diff --git a/src/constants/otp.constants.ts b/src/constants/otp.constants.ts new file mode 100644 index 0000000..af21ef8 --- /dev/null +++ b/src/constants/otp.constants.ts @@ -0,0 +1 @@ +export const OTP_TTL = 10 * 60; // 10 minutes diff --git a/src/services/apis/generateOtp/dto/genrateOtp.dto.ts b/src/services/apis/generateOtp/dto/genrateOtp.dto.ts new file mode 100644 index 0000000..9b1c045 --- /dev/null +++ b/src/services/apis/generateOtp/dto/genrateOtp.dto.ts @@ -0,0 +1,9 @@ +// DTO for incoming OTP payload. This ensures the payload structure and validates fields. +export class OtpDto { + /** + * The email of the user to send OTP. + * @example "test@gmail.com" + * */ + + email: string; +} diff --git a/src/services/apis/generateOtp/generateOtp.controller.ts b/src/services/apis/generateOtp/generateOtp.controller.ts new file mode 100644 index 0000000..c3c4a4a --- /dev/null +++ b/src/services/apis/generateOtp/generateOtp.controller.ts @@ -0,0 +1,13 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { GenrateOtpService } from './genrateOtp.service'; +import { OtpDto } from './dto/genrateOtp.dto'; + +@Controller('generate-OTP') +export class GenerateOtpController { + constructor(private readonly genrateOtpService: GenrateOtpService) {} + + @Post() + async create(@Body() genrateOtpDto: OtpDto) { + return await this.genrateOtpService.enqueueOtpJob(genrateOtpDto); + } +} diff --git a/src/services/apis/generateOtp/generateOtp.module.ts b/src/services/apis/generateOtp/generateOtp.module.ts new file mode 100644 index 0000000..e35258b --- /dev/null +++ b/src/services/apis/generateOtp/generateOtp.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { GenerateOtpController } from './generateOtp.controller'; +import { QueueModule } from 'src/services/bullmq/queue.module'; +import { GenrateOtpService } from './genrateOtp.service'; + +@Module({ + imports: [QueueModule], + controllers: [GenerateOtpController], + providers: [GenrateOtpService], + exports: [GenrateOtpService], // not exporting services as no need in testing +}) +export class GenerateOtpModule {} diff --git a/src/services/apis/generateOtp/genrateOtp.helper.ts b/src/services/apis/generateOtp/genrateOtp.helper.ts new file mode 100644 index 0000000..4418a6d --- /dev/null +++ b/src/services/apis/generateOtp/genrateOtp.helper.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +const emailSchema = z.string().email(); + +export function processEmail(input: { email: string }): { email: string } { + const parsedEmail = emailSchema.safeParse(input.email); + + if (!parsedEmail.success) { + throw new Error('Invalid email: Must be a valid email format'); + } + + return { + email: input.email.trim(), + }; +} diff --git a/src/services/apis/generateOtp/genrateOtp.service.ts b/src/services/apis/generateOtp/genrateOtp.service.ts new file mode 100644 index 0000000..c91a7f0 --- /dev/null +++ b/src/services/apis/generateOtp/genrateOtp.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { OtpProducer } from 'src/services/bullmq/producers/otp.producer'; +import { OtpDto } from './dto/genrateOtp.dto'; +import { processEmail } from './genrateOtp.helper'; + +@Injectable() +export class GenrateOtpService { + constructor(private readonly otpProducer: OtpProducer) {} + + async enqueueOtpJob(createOtpDto: OtpDto) { + const email = processEmail(createOtpDto); + await this.otpProducer.pushForAsyncStream(`process-otp`, email, { + removeOnComplete: true, + }); + } +} diff --git a/src/services/apis/otp/dto/otp.dto.ts b/src/services/apis/otp/dto/otp.dto.ts deleted file mode 100644 index 5dd4f95..0000000 --- a/src/services/apis/otp/dto/otp.dto.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { z } from 'zod'; -import { Types } from 'mongoose'; - -export const CreateOtpValidation = z.object({ - name: z.string().optional(), - createdBy: z - .string() - .refine((val) => Types.ObjectId.isValid(val), { - message: 'Invalid creator ID', - }) - .optional(), - createdAt: z.date().optional(), - updatedAt: z.date().optional(), - deleted: z.boolean().optional().default(false), - deletedBy: z - .string() - .refine((val) => Types.ObjectId.isValid(val), { - message: 'Invalid deleter ID', - }) - .optional(), - deletedAt: z.date().optional(), -}); - -export const PatchOtpValidation = z.object({ - name: z.string().optional(), - updatedAt: z.date().optional(), - createdAt: z.date().optional(), - deleted: z.boolean().optional(), - deletedBy: z - .string() - .refine((val) => Types.ObjectId.isValid(val), { - message: 'Invalid deleter ID', - }) - .optional(), - deletedAt: z.date().optional(), -}); - -export const RemoveOtpValidation = z.object({ - id: z.string().refine((val) => Types.ObjectId.isValid(val), { - message: 'Invalid user ID', - }), - deletedBy: z - .string() - .refine((val) => Types.ObjectId.isValid(val), { - message: 'Invalid deleter ID', - }) - .optional(), - deletedAt: z.date().optional(), -}); - -export type CreateOtpDTO = z.infer; -export type PatchOtpDTO = z.infer; -export type RemoveOtpDTO = z.infer; diff --git a/src/services/apis/otp/otp.controller.spec.ts b/src/services/apis/otp/otp.controller.spec.ts deleted file mode 100644 index cac5fdb..0000000 --- a/src/services/apis/otp/otp.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { OtpController } from './otp.controller'; - -describe('OtpController', () => { - let controller: OtpController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [OtpController], - }).compile(); - - controller = module.get(OtpController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/services/apis/otp/otp.module.ts b/src/services/apis/otp/otp.module.ts deleted file mode 100644 index a265435..0000000 --- a/src/services/apis/otp/otp.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; -import { OtpService } from './otp.service'; -import { Otp, OtpSchema } from './schemas/otp.schema'; - -@Module({ - imports: [MongooseModule.forFeature([{ name: Otp.name, schema: OtpSchema }])], - providers: [OtpService], - exports: [OtpService], -}) -export class OtpModule {} diff --git a/src/services/apis/otp/otp.service.spec.ts b/src/services/apis/otp/otp.service.spec.ts deleted file mode 100644 index 28e2afc..0000000 --- a/src/services/apis/otp/otp.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { OtpService } from './otp.service'; - -describe('OtpService', () => { - let service: OtpService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [OtpService], - }).compile(); - - service = module.get(OtpService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/services/apis/otp/otp.service.ts b/src/services/apis/otp/otp.service.ts deleted file mode 100644 index a449603..0000000 --- a/src/services/apis/otp/otp.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { GlobalService } from 'src/common/global-service'; -import { Otp, OtpDocument } from './schemas/otp.schema'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; - -@Injectable() -export class OtpService extends GlobalService { - constructor( - @InjectModel(Otp.name) - private readonly otpModel: Model, - ) { - super(otpModel); - } -} diff --git a/src/services/apis/otp/schemas/otp.schema.ts b/src/services/apis/otp/schemas/otp.schema.ts deleted file mode 100644 index 66c4aa0..0000000 --- a/src/services/apis/otp/schemas/otp.schema.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument, Types } from 'mongoose'; -import { Users } from '../../users/schemas/users.schema'; - -export type OtpDocument = HydratedDocument; - -@Schema({ - timestamps: true, -}) -export class Otp { - @Prop({ - type: String, - enum: ['mobile', 'email'], - default: 'email', - }) - type?: string; - - @Prop({ - type: String, - required: true, - }) - dest: string; - - @Prop({ - type: String, - required: true, - }) - otp: string; -} - -export const OtpSchema = SchemaFactory.createForClass(Otp); diff --git a/src/services/apis/users/users.controller.ts b/src/services/apis/users/users.controller.ts index cdbeb45..528c6a3 100644 --- a/src/services/apis/users/users.controller.ts +++ b/src/services/apis/users/users.controller.ts @@ -12,8 +12,6 @@ import { import { UsersService } from './users.service'; import { User } from '../users/decorator/user.decorator'; import { Users } from './schemas/users.schema'; -import { OtpService } from '../otp/otp.service'; -import { Otp } from '../otp/schemas/otp.schema'; import { Public } from '../auth/decorators/public.decorator'; import * as bcrypt from 'bcrypt'; import { JwtService } from '@nestjs/jwt'; @@ -21,7 +19,6 @@ import { JwtService } from '@nestjs/jwt'; export class UsersController { constructor( private readonly usersService: UsersService, - private readonly otpService: OtpService, private jwtService: JwtService, ) {} @@ -48,7 +45,7 @@ export class UsersController { password, })) as Users; - await this.removeOTP(createUsersDto.email); + // await this.removeOTP(createUsersDto.email); const sanitizedUser = this.usersService.sanitizeUser(user); const payload = { sub: { id: user._id }, user }; @@ -77,46 +74,5 @@ export class UsersController { throw new BadRequestException('OTP not provided!'); } console.log({ createUsersDto }); - - const codes = (await this.otpService._find({ - $paginate: false, - dest: createUsersDto.email, - $sort: { - createdAt: -1, - }, - })) as Otp[]; - - const code = codes[codes.length - 1]; - - if (!code) { - throw new BadRequestException('otp not found!'); - } - - console.log(code.otp, createUsersDto['otp']); - if (createUsersDto['otp'] === '000000') return; - if (code.otp !== createUsersDto['otp']) { - throw new BadRequestException('OTP Mismatched!'); - } - return; - } - - async removeOTP(dest: string) { - await this.otpService._remove( - null, - { - dest, - }, - null, - { - handleSoftDelete: false, - deleteKey: '', - deletedByKey: '', - deletedAtKey: '', - defaultPagination: false, - defaultLimit: 0, - defaultSkip: 0, - multi: false, - }, - ); } } diff --git a/src/services/apis/users/users.module.ts b/src/services/apis/users/users.module.ts index 0f6ad32..c37364e 100644 --- a/src/services/apis/users/users.module.ts +++ b/src/services/apis/users/users.module.ts @@ -5,12 +5,10 @@ import { UsersService } from './users.service'; import { Users, UsersSchema } from './schemas/users.schema'; import { APP_GUARD } from '@nestjs/core'; import { RolesGuard } from './roles.guard'; -import { OtpModule } from '../otp/otp.module'; @Module({ imports: [ MongooseModule.forFeature([{ name: Users.name, schema: UsersSchema }]), - OtpModule, ], controllers: [UsersController], providers: [ diff --git a/src/services/bullmq/constants/queues.ts b/src/services/bullmq/constants/queues.ts index 4df4540..6836faa 100644 --- a/src/services/bullmq/constants/queues.ts +++ b/src/services/bullmq/constants/queues.ts @@ -1 +1,2 @@ export const REACTION_STREAM_QUEUE = 'REACTION_STREAM_QUEUE'; +export const OTP_QUEUE = 'OTP_QUEUE'; diff --git a/src/services/bullmq/jobs/otp.jobs.ts b/src/services/bullmq/jobs/otp.jobs.ts new file mode 100644 index 0000000..f91a6c0 --- /dev/null +++ b/src/services/bullmq/jobs/otp.jobs.ts @@ -0,0 +1,3 @@ +export interface OtpJob { + email: string; +} diff --git a/src/services/bullmq/processors/otp.processor.ts b/src/services/bullmq/processors/otp.processor.ts new file mode 100644 index 0000000..19cc68b --- /dev/null +++ b/src/services/bullmq/processors/otp.processor.ts @@ -0,0 +1,39 @@ +import { Processor, WorkerHost } from '@nestjs/bullmq'; +import { OTP_QUEUE } from '../constants/queues'; +import { RedisService } from 'src/services/redis/redis.service'; +import { Job } from 'bullmq'; +import { OTP_TTL } from 'src/constants/otp.constants'; +import { hashString } from 'src/common/hashing'; +import { OtpJob } from '../jobs/otp.jobs'; +import generateRandomNumber from 'src/common/generate-random-number'; + +@Processor(OTP_QUEUE) +export class OtpQueueProcessor extends WorkerHost { + constructor(private readonly redisService: RedisService) { + super(); + } + otp: string = generateRandomNumber(); + async process(job: Job): Promise { + try { + const { + data: { email }, + } = job as { data: OtpJob }; + await this.storeOtpWithTTL(email, parseInt(this.otp)); + console.log(`OTP for ${email} is ${this.otp} and stored in Redis`); + } catch (error) { + console.error('Error in processing OTP job', error); + } + } + + async storeOtpWithTTL(email: string, otp: number): Promise { + const key = await generateKey(email, otp); + const value = new Date().toISOString(); + await this.redisService.set(key, value, OTP_TTL); + } +} + +async function generateKey(email: string, otp: number): Promise { + const hashedEmail = await hashString(email); + const hashedOtp = await hashString(otp.toString()); + return `verification::email:${hashedEmail}:otp:${hashedOtp}`; +} diff --git a/src/services/bullmq/processors/reaction-stream.processor.ts b/src/services/bullmq/processors/reaction-stream.processor.ts index 74af37c..523a72b 100644 --- a/src/services/bullmq/processors/reaction-stream.processor.ts +++ b/src/services/bullmq/processors/reaction-stream.processor.ts @@ -11,7 +11,7 @@ export class ReactionStreamProcessor extends WorkerHost { super(); } - async process(job: Job, token?: string): Promise { + async process(job: Job): Promise { const { data: { emoji, sport }, } = job as { diff --git a/src/services/bullmq/producers/otp.producer.ts b/src/services/bullmq/producers/otp.producer.ts new file mode 100644 index 0000000..1e58b81 --- /dev/null +++ b/src/services/bullmq/producers/otp.producer.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { OTP_QUEUE } from '../constants/queues'; +import { JobsOptions, Queue } from 'bullmq'; +import { InjectQueue } from '@nestjs/bullmq'; + +@Injectable() +export class OtpProducer { + constructor(@InjectQueue(OTP_QUEUE) private otpQueue: Queue) {} + async pushForAsyncStream( + jobName: string, + data: Record, + options?: JobsOptions, + ) { + await this.otpQueue.add(jobName, data, options); + console.log(`Job "${jobName}" added to the queue successfully.`); + } +} diff --git a/src/services/bullmq/queue.module.ts b/src/services/bullmq/queue.module.ts index 0bb314c..e8bb428 100644 --- a/src/services/bullmq/queue.module.ts +++ b/src/services/bullmq/queue.module.ts @@ -1,17 +1,32 @@ import { Global, Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; -import { REACTION_STREAM_QUEUE } from './constants/queues'; +import { REACTION_STREAM_QUEUE, OTP_QUEUE } from './constants/queues'; import { ReactionStreamProducer } from './producers/reaction-stream.producer'; import { ReactionStreamProcessor } from './processors/reaction-stream.processor'; +import { OtpProducer } from './producers/otp.producer'; import { PresenceModule } from '../gateways/presence/presence.module'; +import { OtpQueueProcessor } from './processors/otp.processor'; @Global() @Module({ imports: [ - BullModule.registerQueue({ name: REACTION_STREAM_QUEUE }), + BullModule.registerQueue( + { name: REACTION_STREAM_QUEUE }, + { name: OTP_QUEUE }, + ), PresenceModule, ], - providers: [ReactionStreamProcessor, ReactionStreamProducer], - exports: [ReactionStreamProcessor, ReactionStreamProducer], + providers: [ + ReactionStreamProcessor, + ReactionStreamProducer, + OtpProducer, + OtpQueueProcessor, + ], + exports: [ + ReactionStreamProcessor, + ReactionStreamProducer, + OtpProducer, + OtpQueueProcessor, + ], }) export class QueueModule {} diff --git a/src/services/redis/adapter/adapter.service.ts b/src/services/redis/adapter/adapter.service.ts index 06cf38e..075c6b7 100644 --- a/src/services/redis/adapter/adapter.service.ts +++ b/src/services/redis/adapter/adapter.service.ts @@ -2,8 +2,6 @@ import { IoAdapter } from '@nestjs/platform-socket.io'; import { ServerOptions } from 'socket.io'; import { createAdapter } from '@socket.io/redis-adapter'; import { createClient } from 'redis'; -import { INestApplication } from '@nestjs/common'; -import fs from 'node:fs'; export class RedisIoAdapter extends IoAdapter { private adapterConstructor: ReturnType; From 43d77d395c3842f6ec280886e35b391af5e32224 Mon Sep 17 00:00:00 2001 From: neoandmatrix Date: Mon, 23 Dec 2024 10:46:33 +0530 Subject: [PATCH 2/7] fix:otp queue fixed --- src/common/hashing.ts | 2 +- src/services/apis/generateOtp/generateOtp.controller.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/hashing.ts b/src/common/hashing.ts index 7263064..c124bab 100644 --- a/src/common/hashing.ts +++ b/src/common/hashing.ts @@ -1,4 +1,4 @@ -import bcrypt from 'bcryptjs'; +import * as bcrypt from 'bcryptjs'; export async function hashString(input: string): Promise { const saltRounds = 10; diff --git a/src/services/apis/generateOtp/generateOtp.controller.ts b/src/services/apis/generateOtp/generateOtp.controller.ts index c3c4a4a..1ecd4eb 100644 --- a/src/services/apis/generateOtp/generateOtp.controller.ts +++ b/src/services/apis/generateOtp/generateOtp.controller.ts @@ -1,11 +1,13 @@ import { Body, Controller, Post } from '@nestjs/common'; import { GenrateOtpService } from './genrateOtp.service'; import { OtpDto } from './dto/genrateOtp.dto'; +import { Public } from '../auth/decorators/public.decorator'; @Controller('generate-OTP') export class GenerateOtpController { constructor(private readonly genrateOtpService: GenrateOtpService) {} + @Public() @Post() async create(@Body() genrateOtpDto: OtpDto) { return await this.genrateOtpService.enqueueOtpJob(genrateOtpDto); From 3cff95912a10354d2f024cc3e3e34fc7446dc80a Mon Sep 17 00:00:00 2001 From: neoandmatrix Date: Mon, 23 Dec 2024 18:55:31 +0530 Subject: [PATCH 3/7] chore : added verify otp route --- .../apis/generateOtp/dto/genrateOtp.dto.ts | 10 ++++++++++ .../generateOtp/generateOtp.controller.ts | 14 ++++++++++--- .../apis/generateOtp/genrateOtp.service.ts | 15 ++++++++++++-- .../bullmq/processors/otp.processor.ts | 20 +++++++++++++------ 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/services/apis/generateOtp/dto/genrateOtp.dto.ts b/src/services/apis/generateOtp/dto/genrateOtp.dto.ts index 9b1c045..013ccfe 100644 --- a/src/services/apis/generateOtp/dto/genrateOtp.dto.ts +++ b/src/services/apis/generateOtp/dto/genrateOtp.dto.ts @@ -7,3 +7,13 @@ export class OtpDto { email: string; } + +export class VerifyOtpDto { + /** + * The email of the user to send OTP. + * @example " + * */ + + email: string; + otp: string; +} diff --git a/src/services/apis/generateOtp/generateOtp.controller.ts b/src/services/apis/generateOtp/generateOtp.controller.ts index 1ecd4eb..e0c879c 100644 --- a/src/services/apis/generateOtp/generateOtp.controller.ts +++ b/src/services/apis/generateOtp/generateOtp.controller.ts @@ -1,15 +1,23 @@ import { Body, Controller, Post } from '@nestjs/common'; import { GenrateOtpService } from './genrateOtp.service'; -import { OtpDto } from './dto/genrateOtp.dto'; +import { OtpDto, VerifyOtpDto } from './dto/genrateOtp.dto'; import { Public } from '../auth/decorators/public.decorator'; -@Controller('generate-OTP') +@Controller('otp') export class GenerateOtpController { constructor(private readonly genrateOtpService: GenrateOtpService) {} @Public() - @Post() + @Post('generate') async create(@Body() genrateOtpDto: OtpDto) { return await this.genrateOtpService.enqueueOtpJob(genrateOtpDto); } + + @Public() + @Post('verify') + async verify(@Body() verifyOtpDto: VerifyOtpDto) { + return (await this.genrateOtpService.compareOtp(verifyOtpDto)) + ? 'OTP is correct' + : 'OTP is incorrect'; + } } diff --git a/src/services/apis/generateOtp/genrateOtp.service.ts b/src/services/apis/generateOtp/genrateOtp.service.ts index c91a7f0..2f5f553 100644 --- a/src/services/apis/generateOtp/genrateOtp.service.ts +++ b/src/services/apis/generateOtp/genrateOtp.service.ts @@ -1,11 +1,15 @@ import { Injectable } from '@nestjs/common'; import { OtpProducer } from 'src/services/bullmq/producers/otp.producer'; -import { OtpDto } from './dto/genrateOtp.dto'; +import { OtpDto, VerifyOtpDto } from './dto/genrateOtp.dto'; import { processEmail } from './genrateOtp.helper'; +import { OtpQueueProcessor } from 'src/services/bullmq/processors/otp.processor'; @Injectable() export class GenrateOtpService { - constructor(private readonly otpProducer: OtpProducer) {} + constructor( + private readonly otpProducer: OtpProducer, + private readonly processor: OtpQueueProcessor, + ) {} async enqueueOtpJob(createOtpDto: OtpDto) { const email = processEmail(createOtpDto); @@ -13,4 +17,11 @@ export class GenrateOtpService { removeOnComplete: true, }); } + + async compareOtp(verifyOtpDto: VerifyOtpDto): Promise { + return await this.processor.comapreOtp( + verifyOtpDto.email, + verifyOtpDto.otp, + ); + } } diff --git a/src/services/bullmq/processors/otp.processor.ts b/src/services/bullmq/processors/otp.processor.ts index 19cc68b..e7dd771 100644 --- a/src/services/bullmq/processors/otp.processor.ts +++ b/src/services/bullmq/processors/otp.processor.ts @@ -6,7 +6,9 @@ import { OTP_TTL } from 'src/constants/otp.constants'; import { hashString } from 'src/common/hashing'; import { OtpJob } from '../jobs/otp.jobs'; import generateRandomNumber from 'src/common/generate-random-number'; +import { Injectable } from '@nestjs/common'; +@Injectable() @Processor(OTP_QUEUE) export class OtpQueueProcessor extends WorkerHost { constructor(private readonly redisService: RedisService) { @@ -26,14 +28,20 @@ export class OtpQueueProcessor extends WorkerHost { } async storeOtpWithTTL(email: string, otp: number): Promise { - const key = await generateKey(email, otp); - const value = new Date().toISOString(); + const key = await generateKey(email); + const hashedOtp = await hashString(otp.toString()); + const value = hashedOtp; await this.redisService.set(key, value, OTP_TTL); } + + async comapreOtp(email: string, otp: string): Promise { + const key = await generateKey(email); + const hashedOtp = await hashString(otp); + const storedOtp = await this.redisService.get(key); + return hashedOtp === storedOtp; + } } -async function generateKey(email: string, otp: number): Promise { - const hashedEmail = await hashString(email); - const hashedOtp = await hashString(otp.toString()); - return `verification::email:${hashedEmail}:otp:${hashedOtp}`; +async function generateKey(email: string): Promise { + return `verification::email:${email}`; } From 4284ed386f9b0b3620b887696642a3c7f5a1ac2f Mon Sep 17 00:00:00 2001 From: neoandmatrix Date: Mon, 23 Dec 2024 19:30:56 +0530 Subject: [PATCH 4/7] chore : added otp mail functionality and files renamed --- src/app.module.ts | 4 +- src/services/apis/mailer/mailer.controller.ts | 40 +++++----- src/services/apis/mailer/mailer.module.ts | 4 +- src/services/apis/mailer/mailer.service.ts | 15 ++-- .../apis/mailer/templates/partials/otp.hbs | 74 +++++++++++++++++-- .../dto/genrateOtp.dto.ts | 0 .../generateOtp.controller.ts | 0 .../generateOtp.module.ts | 0 .../{generateOtp => otp}/genrateOtp.helper.ts | 0 .../genrateOtp.service.ts | 0 .../bullmq/processors/otp.processor.ts | 12 ++- src/services/bullmq/queue.module.ts | 2 + 12 files changed, 115 insertions(+), 36 deletions(-) rename src/services/apis/{generateOtp => otp}/dto/genrateOtp.dto.ts (100%) rename src/services/apis/{generateOtp => otp}/generateOtp.controller.ts (100%) rename src/services/apis/{generateOtp => otp}/generateOtp.module.ts (100%) rename src/services/apis/{generateOtp => otp}/genrateOtp.helper.ts (100%) rename src/services/apis/{generateOtp => otp}/genrateOtp.service.ts (100%) diff --git a/src/app.module.ts b/src/app.module.ts index 6909ae5..c3636ca 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,7 +14,8 @@ import { ReactionsModule } from './services/apis/reactions/reactions.module'; import { ProfilesModule } from './services/apis/profiles/profiles.module'; import { QueueModule } from './services/bullmq/queue.module'; import { BullModule } from '@nestjs/bullmq'; -import { GenerateOtpModule } from './services/apis/generateOtp/generateOtp.module'; +import { GenerateOtpModule } from './services/apis/otp/generateOtp.module'; +import { MailerModule } from './services/apis/mailer/mailer.module'; @Module({ imports: [ @@ -45,6 +46,7 @@ import { GenerateOtpModule } from './services/apis/generateOtp/generateOtp.modul ProfilesModule, ReactionsModule, GenerateOtpModule, + MailerModule, ], controllers: [AppController], providers: [ diff --git a/src/services/apis/mailer/mailer.controller.ts b/src/services/apis/mailer/mailer.controller.ts index 23c0f06..3173021 100644 --- a/src/services/apis/mailer/mailer.controller.ts +++ b/src/services/apis/mailer/mailer.controller.ts @@ -1,22 +1,22 @@ -import { Controller, Post } from '@nestjs/common'; -import { MailerService } from './mailer.service'; +// import { Controller, Post } from '@nestjs/common'; +// import { MailerService } from './mailer.service'; -@Controller('mail') -export class MailerController { - constructor(private mailerService: MailerService) {} +// @Controller('mail') +// export class MailerController { +// constructor(private mailerService: MailerService) {} - // @Public() - // @Post() - // async sendMail() { - // await this.mailerService.sendMPassGenerationEmail( - // 'saswat.pb03@gmail.com', - // 'Saswat', - // 'XYZ' - // ) - // await this.mailerService.sendSupportTicketResolvedEmail( - // 'saswat.pb03@gmail.com', - // 'Saswat', - // '123456' - // ) - // } -} +// // @Public() +// // @Post() +// // async sendMail() { +// // await this.mailerService.sendMPassGenerationEmail( +// // 'saswat.pb03@gmail.com', +// // 'Saswat', +// // 'XYZ' +// // ) +// // await this.mailerService.sendSupportTicketResolvedEmail( +// // 'saswat.pb03@gmail.com', +// // 'Saswat', +// // '123456' +// // ) +// // } +// } diff --git a/src/services/apis/mailer/mailer.module.ts b/src/services/apis/mailer/mailer.module.ts index 12a61e5..88db40b 100644 --- a/src/services/apis/mailer/mailer.module.ts +++ b/src/services/apis/mailer/mailer.module.ts @@ -4,7 +4,7 @@ import { MailerService } from './mailer.service'; import { MailerModule as NestMailerModule } from '@nestjs-modules/mailer'; import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; import * as path from 'path'; -import { MailerController } from './mailer.controller'; +// import { MailerController } from './mailer.controller'; @Global() @Module({ @@ -41,7 +41,7 @@ import { MailerController } from './mailer.controller'; }), }), ], - controllers: [MailerController], + controllers: [], providers: [MailerService], exports: [MailerService], }) diff --git a/src/services/apis/mailer/mailer.service.ts b/src/services/apis/mailer/mailer.service.ts index ccb7ff3..795c2ef 100644 --- a/src/services/apis/mailer/mailer.service.ts +++ b/src/services/apis/mailer/mailer.service.ts @@ -9,17 +9,20 @@ export class MailerService { async sendMail( to: string, - subject: string, - content: string, + // subject: string, + // content: string, + otp: string, template?: string, - context?: Record, + //context?: Record, ) { await this.mailerService.sendMail({ to, - subject, - text: content, + subject: 'OTP for your account', template: template ? `./${template}` : undefined, - context: context || {}, + context: { + otp, + supportEmail: 'techsociety@iiit-bh.ac.in', + }, }); } } diff --git a/src/services/apis/mailer/templates/partials/otp.hbs b/src/services/apis/mailer/templates/partials/otp.hbs index 2c73732..a4c8109 100644 --- a/src/services/apis/mailer/templates/partials/otp.hbs +++ b/src/services/apis/mailer/templates/partials/otp.hbs @@ -1,6 +1,68 @@ -

Use - the OTP below to complete the process:

-
{{otp}}
-

This OTP is valid for 15 minutes. If you didn’t request this, please ignore - this email or contact support at - {{supportEmail}}

\ No newline at end of file + + + + + + + + +
+
+

Programming Society

+
+
+

Dear Member,

+

Use the OTP below to complete the process:

+
{{otp}}
+

This OTP is valid for 10 minutes. If you didn’t request this, please ignore this email or contact support at {{supportEmail}}.

+
+ +
+ + \ No newline at end of file diff --git a/src/services/apis/generateOtp/dto/genrateOtp.dto.ts b/src/services/apis/otp/dto/genrateOtp.dto.ts similarity index 100% rename from src/services/apis/generateOtp/dto/genrateOtp.dto.ts rename to src/services/apis/otp/dto/genrateOtp.dto.ts diff --git a/src/services/apis/generateOtp/generateOtp.controller.ts b/src/services/apis/otp/generateOtp.controller.ts similarity index 100% rename from src/services/apis/generateOtp/generateOtp.controller.ts rename to src/services/apis/otp/generateOtp.controller.ts diff --git a/src/services/apis/generateOtp/generateOtp.module.ts b/src/services/apis/otp/generateOtp.module.ts similarity index 100% rename from src/services/apis/generateOtp/generateOtp.module.ts rename to src/services/apis/otp/generateOtp.module.ts diff --git a/src/services/apis/generateOtp/genrateOtp.helper.ts b/src/services/apis/otp/genrateOtp.helper.ts similarity index 100% rename from src/services/apis/generateOtp/genrateOtp.helper.ts rename to src/services/apis/otp/genrateOtp.helper.ts diff --git a/src/services/apis/generateOtp/genrateOtp.service.ts b/src/services/apis/otp/genrateOtp.service.ts similarity index 100% rename from src/services/apis/generateOtp/genrateOtp.service.ts rename to src/services/apis/otp/genrateOtp.service.ts diff --git a/src/services/bullmq/processors/otp.processor.ts b/src/services/bullmq/processors/otp.processor.ts index e7dd771..47f7cb2 100644 --- a/src/services/bullmq/processors/otp.processor.ts +++ b/src/services/bullmq/processors/otp.processor.ts @@ -7,11 +7,15 @@ import { hashString } from 'src/common/hashing'; import { OtpJob } from '../jobs/otp.jobs'; import generateRandomNumber from 'src/common/generate-random-number'; import { Injectable } from '@nestjs/common'; +import { MailerService } from 'src/services/apis/mailer/mailer.service'; @Injectable() @Processor(OTP_QUEUE) export class OtpQueueProcessor extends WorkerHost { - constructor(private readonly redisService: RedisService) { + constructor( + private readonly redisService: RedisService, + private readonly mailerService: MailerService, + ) { super(); } otp: string = generateRandomNumber(); @@ -22,6 +26,12 @@ export class OtpQueueProcessor extends WorkerHost { } = job as { data: OtpJob }; await this.storeOtpWithTTL(email, parseInt(this.otp)); console.log(`OTP for ${email} is ${this.otp} and stored in Redis`); + await this.mailerService.sendMail( + email, + this.otp, + 'templates/partials/otp.hbs', + ); + console.log(`OTP email sent to ${email}`); } catch (error) { console.error('Error in processing OTP job', error); } diff --git a/src/services/bullmq/queue.module.ts b/src/services/bullmq/queue.module.ts index e8bb428..7ee2b9d 100644 --- a/src/services/bullmq/queue.module.ts +++ b/src/services/bullmq/queue.module.ts @@ -6,6 +6,7 @@ import { ReactionStreamProcessor } from './processors/reaction-stream.processor' import { OtpProducer } from './producers/otp.producer'; import { PresenceModule } from '../gateways/presence/presence.module'; import { OtpQueueProcessor } from './processors/otp.processor'; +import { MailerModule } from '../apis/mailer/mailer.module'; @Global() @Module({ @@ -15,6 +16,7 @@ import { OtpQueueProcessor } from './processors/otp.processor'; { name: OTP_QUEUE }, ), PresenceModule, + MailerModule, ], providers: [ ReactionStreamProcessor, From 2d710083fa2aad84f8debf271b5948320005b8a7 Mon Sep 17 00:00:00 2001 From: Soubhik Kumar Gon Date: Mon, 23 Dec 2024 22:05:33 +0530 Subject: [PATCH 5/7] feat: changes in auth system --- src/constants/kafkajs-consumer-options.ts | 7 --- src/constants/sports-enum.ts | 10 ++++ src/services/apis/mailer/mailer.module.ts | 4 +- src/services/apis/otp/dto/generateOtp.dto.ts | 23 ++++++++++ src/services/apis/otp/dto/genrateOtp.dto.ts | 19 -------- .../apis/otp/generateOtp.controller.ts | 19 +++++--- src/services/apis/otp/generateOtp.module.ts | 9 ++-- src/services/apis/otp/generateOtp.service.ts | 46 +++++++++++++++++++ src/services/apis/otp/genrateOtp.helper.ts | 15 ------ src/services/apis/otp/genrateOtp.service.ts | 27 ----------- .../apis/otp/helpers/generateOtp.helper.ts | 15 ++++++ .../apis/profiles/schemas/profiles.schema.ts | 4 +- src/services/apis/users/users.controller.ts | 28 +++++++---- src/services/apis/users/users.module.ts | 2 + .../bullmq/constants/generate-keys.ts | 3 ++ .../bullmq/processors/otp.processor.ts | 17 +++++-- src/services/bullmq/producers/otp.producer.ts | 7 ++- yarn.lock | 10 ++++ 18 files changed, 166 insertions(+), 99 deletions(-) delete mode 100644 src/constants/kafkajs-consumer-options.ts create mode 100644 src/services/apis/otp/dto/generateOtp.dto.ts delete mode 100644 src/services/apis/otp/dto/genrateOtp.dto.ts create mode 100644 src/services/apis/otp/generateOtp.service.ts delete mode 100644 src/services/apis/otp/genrateOtp.helper.ts delete mode 100644 src/services/apis/otp/genrateOtp.service.ts create mode 100644 src/services/apis/otp/helpers/generateOtp.helper.ts create mode 100644 src/services/bullmq/constants/generate-keys.ts diff --git a/src/constants/kafkajs-consumer-options.ts b/src/constants/kafkajs-consumer-options.ts deleted file mode 100644 index eaef49c..0000000 --- a/src/constants/kafkajs-consumer-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ConsumerConfig, ConsumerSubscribeTopics, KafkaMessage } from 'kafkajs'; - -export interface kafkaConsumerOptions { - topic: ConsumerSubscribeTopics; - config: ConsumerConfig; - onMessage: (message: KafkaMessage) => Promise; -} diff --git a/src/constants/sports-enum.ts b/src/constants/sports-enum.ts index 650b89d..2dce22d 100644 --- a/src/constants/sports-enum.ts +++ b/src/constants/sports-enum.ts @@ -5,4 +5,14 @@ enum SportsEnum { Basketball = 'Basketball', Volleyball = 'Volleyball', } + +export const SportsEnumList = [ + SportsEnum.Football, + SportsEnum.Badminton, + SportsEnum.Basketball, + SportsEnum.Cricket, + SportsEnum.Football, + SportsEnum.Volleyball, +]; + export default SportsEnum; diff --git a/src/services/apis/mailer/mailer.module.ts b/src/services/apis/mailer/mailer.module.ts index 88db40b..beb01e6 100644 --- a/src/services/apis/mailer/mailer.module.ts +++ b/src/services/apis/mailer/mailer.module.ts @@ -24,7 +24,7 @@ import * as path from 'path'; from: configService.get('MAIL_FROM'), }, template: { - dir: path.join(__dirname, 'templates'), + dir: path.join(__dirname, ''), adapter: new HandlebarsAdapter(), options: { strict: true, @@ -32,7 +32,7 @@ import * as path from 'path'; }, options: { partials: { - dir: path.join(__dirname, 'templates/partials'), + dir: path.join(__dirname, '/partials'), options: { strict: true, }, diff --git a/src/services/apis/otp/dto/generateOtp.dto.ts b/src/services/apis/otp/dto/generateOtp.dto.ts new file mode 100644 index 0000000..d6e7625 --- /dev/null +++ b/src/services/apis/otp/dto/generateOtp.dto.ts @@ -0,0 +1,23 @@ +import { date, z } from 'zod'; + +// DTO for incoming OTP payload. This ensures the payload structure and validates fields. +/** + * The email of the user to send OTP. + * @example "test@gmail.com" + * */ + +export const OtpValidation = z.object({ + email: z.string().email(), +}); + +/** + * The email of the user to send OTP. + * @example " + * */ +export const VerifyOtpValidation = z.object({ + email: z.string().email(), + otp: z.string().trim(), +}); + +export type OtpDto = z.infer; +export type VerifyOtpDto = z.infer; diff --git a/src/services/apis/otp/dto/genrateOtp.dto.ts b/src/services/apis/otp/dto/genrateOtp.dto.ts deleted file mode 100644 index 013ccfe..0000000 --- a/src/services/apis/otp/dto/genrateOtp.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -// DTO for incoming OTP payload. This ensures the payload structure and validates fields. -export class OtpDto { - /** - * The email of the user to send OTP. - * @example "test@gmail.com" - * */ - - email: string; -} - -export class VerifyOtpDto { - /** - * The email of the user to send OTP. - * @example " - * */ - - email: string; - otp: string; -} diff --git a/src/services/apis/otp/generateOtp.controller.ts b/src/services/apis/otp/generateOtp.controller.ts index e0c879c..54b86cd 100644 --- a/src/services/apis/otp/generateOtp.controller.ts +++ b/src/services/apis/otp/generateOtp.controller.ts @@ -1,22 +1,29 @@ import { Body, Controller, Post } from '@nestjs/common'; -import { GenrateOtpService } from './genrateOtp.service'; -import { OtpDto, VerifyOtpDto } from './dto/genrateOtp.dto'; +import { GenerateOtpService } from './generateOtp.service'; +import { + OtpDto, + OtpValidation, + VerifyOtpDto, + VerifyOtpValidation, +} from './dto/generateOtp.dto'; import { Public } from '../auth/decorators/public.decorator'; @Controller('otp') export class GenerateOtpController { - constructor(private readonly genrateOtpService: GenrateOtpService) {} + constructor(private readonly generateOtpService: GenerateOtpService) {} @Public() @Post('generate') - async create(@Body() genrateOtpDto: OtpDto) { - return await this.genrateOtpService.enqueueOtpJob(genrateOtpDto); + async create(@Body() generateOtpDto: OtpDto) { + generateOtpDto = OtpValidation.parse(generateOtpDto); + return await this.generateOtpService.enqueueOtpJob(generateOtpDto); } @Public() @Post('verify') async verify(@Body() verifyOtpDto: VerifyOtpDto) { - return (await this.genrateOtpService.compareOtp(verifyOtpDto)) + verifyOtpDto = VerifyOtpValidation.parse(verifyOtpDto); + return (await this.generateOtpService.compareOtp(verifyOtpDto)) ? 'OTP is correct' : 'OTP is incorrect'; } diff --git a/src/services/apis/otp/generateOtp.module.ts b/src/services/apis/otp/generateOtp.module.ts index e35258b..6a94cb1 100644 --- a/src/services/apis/otp/generateOtp.module.ts +++ b/src/services/apis/otp/generateOtp.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; import { GenerateOtpController } from './generateOtp.controller'; -import { QueueModule } from 'src/services/bullmq/queue.module'; -import { GenrateOtpService } from './genrateOtp.service'; +import { GenerateOtpService } from './generateOtp.service'; @Module({ - imports: [QueueModule], + imports: [], controllers: [GenerateOtpController], - providers: [GenrateOtpService], - exports: [GenrateOtpService], // not exporting services as no need in testing + providers: [GenerateOtpService], + exports: [GenerateOtpService], // not exporting services as no need in testing (??) }) export class GenerateOtpModule {} diff --git a/src/services/apis/otp/generateOtp.service.ts b/src/services/apis/otp/generateOtp.service.ts new file mode 100644 index 0000000..e5f51ed --- /dev/null +++ b/src/services/apis/otp/generateOtp.service.ts @@ -0,0 +1,46 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { OtpProducer } from 'src/services/bullmq/producers/otp.producer'; +import { OtpDto, VerifyOtpDto } from './dto/generateOtp.dto'; +// import { processEmail } from './helpers/generateOtp.helper'; +import { OtpQueueProcessor } from 'src/services/bullmq/processors/otp.processor'; +import { compareHashedString } from 'src/common/hashing'; +import { RedisService } from 'src/services/redis/redis.service'; +import { generateKey } from 'src/services/bullmq/constants/generate-keys'; + +@Injectable() +export class GenerateOtpService { + constructor( + private readonly otpProducer: OtpProducer, + private readonly redisService: RedisService, + ) {} + + async enqueueOtpJob(createOtpDto: OtpDto) { + const email = createOtpDto['email'].trim(); + const resp = await this.otpProducer.pushForAsyncMailing( + `process-otp`, + { email }, + { + removeOnComplete: true, + }, + ); + return resp; + } + + async compareOtp( + verifyOtpDto: VerifyOtpDto, + removeEntryAfterCheck = false, + ): Promise { + const key = generateKey(verifyOtpDto.email); + const val = await this.redisService.get(key); + + if (!val) { + throw new BadRequestException( + 'The OTP has expired or no request for an OTP was found. Please try requesting a new OTP.', + ); + } + + const resp = await compareHashedString(verifyOtpDto.otp, val); + if (removeEntryAfterCheck) await this.redisService.del(key); + return resp; + } +} diff --git a/src/services/apis/otp/genrateOtp.helper.ts b/src/services/apis/otp/genrateOtp.helper.ts deleted file mode 100644 index 4418a6d..0000000 --- a/src/services/apis/otp/genrateOtp.helper.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { z } from 'zod'; - -const emailSchema = z.string().email(); - -export function processEmail(input: { email: string }): { email: string } { - const parsedEmail = emailSchema.safeParse(input.email); - - if (!parsedEmail.success) { - throw new Error('Invalid email: Must be a valid email format'); - } - - return { - email: input.email.trim(), - }; -} diff --git a/src/services/apis/otp/genrateOtp.service.ts b/src/services/apis/otp/genrateOtp.service.ts deleted file mode 100644 index 2f5f553..0000000 --- a/src/services/apis/otp/genrateOtp.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { OtpProducer } from 'src/services/bullmq/producers/otp.producer'; -import { OtpDto, VerifyOtpDto } from './dto/genrateOtp.dto'; -import { processEmail } from './genrateOtp.helper'; -import { OtpQueueProcessor } from 'src/services/bullmq/processors/otp.processor'; - -@Injectable() -export class GenrateOtpService { - constructor( - private readonly otpProducer: OtpProducer, - private readonly processor: OtpQueueProcessor, - ) {} - - async enqueueOtpJob(createOtpDto: OtpDto) { - const email = processEmail(createOtpDto); - await this.otpProducer.pushForAsyncStream(`process-otp`, email, { - removeOnComplete: true, - }); - } - - async compareOtp(verifyOtpDto: VerifyOtpDto): Promise { - return await this.processor.comapreOtp( - verifyOtpDto.email, - verifyOtpDto.otp, - ); - } -} diff --git a/src/services/apis/otp/helpers/generateOtp.helper.ts b/src/services/apis/otp/helpers/generateOtp.helper.ts new file mode 100644 index 0000000..942da3b --- /dev/null +++ b/src/services/apis/otp/helpers/generateOtp.helper.ts @@ -0,0 +1,15 @@ +// import { z } from 'zod'; + +// const emailSchema = z.string().email(); + +// export function processEmail(input: { email: string }): { email: string } { +// const parsedEmail = emailSchema.safeParse(input.email); + +// if (!parsedEmail.success) { +// throw new Error('Invalid email: Must be a valid email format'); +// } + +// return { +// email: input.email.trim(), +// }; +// } diff --git a/src/services/apis/profiles/schemas/profiles.schema.ts b/src/services/apis/profiles/schemas/profiles.schema.ts index a56353d..8cdc922 100644 --- a/src/services/apis/profiles/schemas/profiles.schema.ts +++ b/src/services/apis/profiles/schemas/profiles.schema.ts @@ -3,7 +3,7 @@ import { HydratedDocument, Types } from 'mongoose'; import { SoftDeleteSchema } from 'src/common/soft-delete-schema'; import EnsureObjectId from 'src/common/EnsureObjectId'; import { Users } from '../../users/schemas/users.schema'; -import SportsEnum from 'src/constants/sports-enum'; +import SportsEnum, { SportsEnumList } from 'src/constants/sports-enum'; export type ProfilesDocument = HydratedDocument; @@ -13,7 +13,7 @@ export type ProfilesDocument = HydratedDocument; export class Profiles extends SoftDeleteSchema { @Prop({ type: String, - enum: Object.values(SportsEnum), + enum: SportsEnumList, required: true, }) sport: SportsEnum; diff --git a/src/services/apis/users/users.controller.ts b/src/services/apis/users/users.controller.ts index 528c6a3..3b695aa 100644 --- a/src/services/apis/users/users.controller.ts +++ b/src/services/apis/users/users.controller.ts @@ -15,11 +15,14 @@ import { Users } from './schemas/users.schema'; import { Public } from '../auth/decorators/public.decorator'; import * as bcrypt from 'bcrypt'; import { JwtService } from '@nestjs/jwt'; +import { CreateUsersDTO } from './dto/users.dto'; +import { GenerateOtpService } from '../otp/generateOtp.service'; @Controller('users') export class UsersController { constructor( private readonly usersService: UsersService, - private jwtService: JwtService, + private readonly jwtService: JwtService, + private readonly generateOtpService: GenerateOtpService, ) {} @Get() @@ -35,7 +38,20 @@ export class UsersController { @Public() @Post() async create(@Body() createUsersDto: Users) { - await this.verifyOTP(createUsersDto); + /** + * @todo use zod for validations... + */ + if (!createUsersDto.email || !createUsersDto['otp']) { + throw new BadRequestException('Email or OTP not provided!'); + } + + await this.generateOtpService.compareOtp( + { + email: createUsersDto.email.trim(), + otp: String(createUsersDto['otp']).trim(), + }, + true, // removeEntryAfterCheck + ); const saltOrRounds = 10; const password = await bcrypt.hash(createUsersDto.password, saltOrRounds); @@ -45,7 +61,6 @@ export class UsersController { password, })) as Users; - // await this.removeOTP(createUsersDto.email); const sanitizedUser = this.usersService.sanitizeUser(user); const payload = { sub: { id: user._id }, user }; @@ -68,11 +83,4 @@ export class UsersController { async delete(@Param('id') id, @Query() query, @User() user) { return await this.usersService._remove(id, query, user); } - - async verifyOTP(createUsersDto: Users) { - if (!Object.keys(createUsersDto).includes('otp')) { - throw new BadRequestException('OTP not provided!'); - } - console.log({ createUsersDto }); - } } diff --git a/src/services/apis/users/users.module.ts b/src/services/apis/users/users.module.ts index c37364e..f2d8875 100644 --- a/src/services/apis/users/users.module.ts +++ b/src/services/apis/users/users.module.ts @@ -5,10 +5,12 @@ import { UsersService } from './users.service'; import { Users, UsersSchema } from './schemas/users.schema'; import { APP_GUARD } from '@nestjs/core'; import { RolesGuard } from './roles.guard'; +import { GenerateOtpModule } from '../otp/generateOtp.module'; @Module({ imports: [ MongooseModule.forFeature([{ name: Users.name, schema: UsersSchema }]), + GenerateOtpModule, ], controllers: [UsersController], providers: [ diff --git a/src/services/bullmq/constants/generate-keys.ts b/src/services/bullmq/constants/generate-keys.ts new file mode 100644 index 0000000..127d342 --- /dev/null +++ b/src/services/bullmq/constants/generate-keys.ts @@ -0,0 +1,3 @@ +export const generateKey = (email: string): string => + `verification::email:${email}`; +// more keys diff --git a/src/services/bullmq/processors/otp.processor.ts b/src/services/bullmq/processors/otp.processor.ts index 47f7cb2..ef0d3df 100644 --- a/src/services/bullmq/processors/otp.processor.ts +++ b/src/services/bullmq/processors/otp.processor.ts @@ -3,7 +3,7 @@ import { OTP_QUEUE } from '../constants/queues'; import { RedisService } from 'src/services/redis/redis.service'; import { Job } from 'bullmq'; import { OTP_TTL } from 'src/constants/otp.constants'; -import { hashString } from 'src/common/hashing'; +import { compareHashedString, hashString } from 'src/common/hashing'; import { OtpJob } from '../jobs/otp.jobs'; import generateRandomNumber from 'src/common/generate-random-number'; import { Injectable } from '@nestjs/common'; @@ -19,7 +19,7 @@ export class OtpQueueProcessor extends WorkerHost { super(); } otp: string = generateRandomNumber(); - async process(job: Job): Promise { + async process(job: Job) { try { const { data: { email }, @@ -32,8 +32,15 @@ export class OtpQueueProcessor extends WorkerHost { 'templates/partials/otp.hbs', ); console.log(`OTP email sent to ${email}`); + return { + success: true, + }; } catch (error) { console.error('Error in processing OTP job', error); + return { + success: false, + message: error, + }; } } @@ -44,14 +51,14 @@ export class OtpQueueProcessor extends WorkerHost { await this.redisService.set(key, value, OTP_TTL); } - async comapreOtp(email: string, otp: string): Promise { - const key = await generateKey(email); + async compareOtp(email: string, otp: string): Promise { + const key = generateKey(email); const hashedOtp = await hashString(otp); const storedOtp = await this.redisService.get(key); return hashedOtp === storedOtp; } } -async function generateKey(email: string): Promise { +function generateKey(email: string): string { return `verification::email:${email}`; } diff --git a/src/services/bullmq/producers/otp.producer.ts b/src/services/bullmq/producers/otp.producer.ts index 1e58b81..d02bea6 100644 --- a/src/services/bullmq/producers/otp.producer.ts +++ b/src/services/bullmq/producers/otp.producer.ts @@ -6,12 +6,17 @@ import { InjectQueue } from '@nestjs/bullmq'; @Injectable() export class OtpProducer { constructor(@InjectQueue(OTP_QUEUE) private otpQueue: Queue) {} - async pushForAsyncStream( + async pushForAsyncMailing( jobName: string, data: Record, options?: JobsOptions, ) { await this.otpQueue.add(jobName, data, options); console.log(`Job "${jobName}" added to the queue successfully.`); + return { + success: true, + message: `Your OTP request is being processed. We'll send the OTP to your email shortly.`, + jobName, + }; } } diff --git a/yarn.lock b/yarn.lock index 1600bd5..3665012 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1281,6 +1281,11 @@ dependencies: "@types/node" "*" +"@types/bcryptjs@^2.4.6": + version "2.4.6" + resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz#2b92e3c2121c66eba3901e64faf8bb922ec291fa" + integrity sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ== + "@types/body-parser@*": version "1.19.5" resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz" @@ -2092,6 +2097,11 @@ bcrypt@^5.1.1: "@mapbox/node-pre-gyp" "^1.0.11" node-addon-api "^5.0.0" +bcryptjs@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" + integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ== + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" From cafb16dec931ef9b184d148760d368b8ddebd1ac Mon Sep 17 00:00:00 2001 From: neoandmatrix Date: Mon, 23 Dec 2024 22:27:01 +0530 Subject: [PATCH 6/7] fix: file path changes fixed --- src/services/apis/mailer/mailer.module.ts | 10 ++++++++-- src/services/bullmq/processors/otp.processor.ts | 6 +----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/services/apis/mailer/mailer.module.ts b/src/services/apis/mailer/mailer.module.ts index 88db40b..1bf403c 100644 --- a/src/services/apis/mailer/mailer.module.ts +++ b/src/services/apis/mailer/mailer.module.ts @@ -24,7 +24,10 @@ import * as path from 'path'; from: configService.get('MAIL_FROM'), }, template: { - dir: path.join(__dirname, 'templates'), + dir: path.join( + __dirname, + '../../../../src/services/apis/mailer/templates', + ), adapter: new HandlebarsAdapter(), options: { strict: true, @@ -32,7 +35,10 @@ import * as path from 'path'; }, options: { partials: { - dir: path.join(__dirname, 'templates/partials'), + dir: path.join( + __dirname, + '../../../../src/services/apis/mailer/templates/partials', + ), options: { strict: true, }, diff --git a/src/services/bullmq/processors/otp.processor.ts b/src/services/bullmq/processors/otp.processor.ts index 47f7cb2..4ae9a67 100644 --- a/src/services/bullmq/processors/otp.processor.ts +++ b/src/services/bullmq/processors/otp.processor.ts @@ -26,11 +26,7 @@ export class OtpQueueProcessor extends WorkerHost { } = job as { data: OtpJob }; await this.storeOtpWithTTL(email, parseInt(this.otp)); console.log(`OTP for ${email} is ${this.otp} and stored in Redis`); - await this.mailerService.sendMail( - email, - this.otp, - 'templates/partials/otp.hbs', - ); + await this.mailerService.sendMail(email, this.otp, 'partials/otp.hbs'); console.log(`OTP email sent to ${email}`); } catch (error) { console.error('Error in processing OTP job', error); From 67c9d1b81c875eba6ee5bf8f65693fa41760ea4a Mon Sep 17 00:00:00 2001 From: neoandmatrix Date: Mon, 23 Dec 2024 22:38:04 +0530 Subject: [PATCH 7/7] fix:otp compare logic fixed --- src/services/bullmq/processors/otp.processor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/bullmq/processors/otp.processor.ts b/src/services/bullmq/processors/otp.processor.ts index ab43a91..2e67f6a 100644 --- a/src/services/bullmq/processors/otp.processor.ts +++ b/src/services/bullmq/processors/otp.processor.ts @@ -51,7 +51,7 @@ export class OtpQueueProcessor extends WorkerHost { const key = generateKey(email); const hashedOtp = await hashString(otp); const storedOtp = await this.redisService.get(key); - return hashedOtp === storedOtp; + return await compareHashedString(hashedOtp, storedOtp); } }