From 7431dad85f33764ab1ac412c51d3770dfc832910 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Mon, 20 Jan 2025 12:50:04 +0330 Subject: [PATCH] chore: add register of user --- package.json | 7 ++- pnpm-lock.yaml | 11 ---- src/app.module.ts | 2 + src/common/enums/message.enum.ts | 31 ++++++++--- src/configs/sms.config.ts | 14 ++--- src/modules/auth/DTO/complete-register.dto.ts | 51 +++++++++++++++++++ .../auth/{DTOs => DTO}/loginPassword.dto.ts | 12 +++-- src/modules/auth/DTO/request-otp.dto.ts | 11 ++++ src/modules/auth/auth.controller.ts | 49 +++++++++++++++++- src/modules/auth/auth.module.ts | 3 +- src/modules/auth/auth.service.ts | 47 ++++++++++++++--- src/modules/users/DTO/user-register.dto.ts | 1 - src/modules/users/entities/user.entity.ts | 2 +- src/modules/users/providers/users.service.ts | 15 ++++-- src/modules/utils/interfaces/IOtpKey.ts | 1 + src/modules/utils/providers/otp.service.ts | 18 ++++--- 16 files changed, 224 insertions(+), 51 deletions(-) create mode 100644 src/modules/auth/DTO/complete-register.dto.ts rename src/modules/auth/{DTOs => DTO}/loginPassword.dto.ts (50%) create mode 100644 src/modules/auth/DTO/request-otp.dto.ts delete mode 100644 src/modules/users/DTO/user-register.dto.ts create mode 100644 src/modules/utils/interfaces/IOtpKey.ts diff --git a/package.json b/package.json index 5478613..1f5d8c9 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,12 @@ "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", + "typeorm": "node -r ts-node/register ./node_modules/typeorm/cli", + "migration:run": "npx typeorm-ts-node-commonjs migration:run -d ./database/connection.ts", + "migration:generate": "npx typeorm-ts-node-commonjs migration:generate -d ./database/connection.ts ./database/migrations/$npm_config_name", + "migration:create": "npx typeorm-ts-node-commonjs migration:create ./database/migrations/$npm_config_name", + "migration:revert": "npx typeorm-ts-node-commonjs migration:revert -d ./database/connection.ts", + "migration:show": "npx typeorm-ts-node-commonjs migration:show -d ./database/connection.ts ", "prepare": "husky || true" }, "dependencies": { @@ -25,7 +31,6 @@ "@keyv/redis": "^4.2.0", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/cache-manager": "^3.0.0", - "@nestjs/class-validator": "^0.13.4", "@nestjs/common": "^10.4.15", "@nestjs/config": "^4.0.0", "@nestjs/core": "^10.4.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fb7b67..18bf7a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,6 @@ importers: '@nestjs/cache-manager': specifier: ^3.0.0 version: 3.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(cache-manager@6.3.2)(rxjs@7.8.1) - '@nestjs/class-validator': - specifier: ^0.13.4 - version: 0.13.4 '@nestjs/common': specifier: ^10.4.15 version: 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -713,9 +710,6 @@ packages: cache-manager: '>=6' rxjs: ^7.8.1 - '@nestjs/class-validator@0.13.4': - resolution: {integrity: sha512-/mqZL36LJ5uV5WDhi87Cd52IssuO+SStaOr2+6sBsvCCGUWkoJes4Wwzmm3m/gdHH+tsNxX60sVSzYcU6hAy9Q==} - '@nestjs/cli@10.4.9': resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} engines: {node: '>= 16.14'} @@ -5459,11 +5453,6 @@ snapshots: cache-manager: 6.3.2 rxjs: 7.8.1 - '@nestjs/class-validator@0.13.4': - dependencies: - libphonenumber-js: 1.11.18 - validator: 13.12.0 - '@nestjs/cli@10.4.9': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) diff --git a/src/app.module.ts b/src/app.module.ts index 9410167..d09f81a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,6 +6,7 @@ import { TypeOrmModule } from "@nestjs/typeorm"; import { cacheConfig } from "./configs/cache.config"; import { DatabaseConfigs } from "./configs/typeorm.config"; import { HTTPLogger } from "./core/middlewares/logger.middleware"; +import { AuthModule } from "./modules/auth/auth.module"; import { TicketsModule } from "./modules/tickets/tickets.module"; import { UsersModule } from "./modules/users/users.module"; @@ -16,6 +17,7 @@ import { UsersModule } from "./modules/users/users.module"; TypeOrmModule.forRootAsync(DatabaseConfigs()), UsersModule, TicketsModule, + AuthModule, ], controllers: [], providers: [], diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 354333d..527cbe0 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -1,6 +1,6 @@ export const enum AuthMessage { PHONE_REGISTERED = "شماره ثبت شد", - INVALID_NEZAM_PHONE = "شماره موبایل وارد نامعتبر می باشد", + INVALID_PHONE_FORMAT = "فرمت شماره تلفن صحیح نیست", OTP_SENT = "کد یکبار مصرف به شماره شما ارسال شد.", INVALID_PHONE = "شماره موبایل اشتباه می باشد", INVALID_CREDENTIAL = "شماره موبایل یا رمز عبور اشتباه می باشد", @@ -10,11 +10,11 @@ export const enum AuthMessage { FORGOT_PASSWORD_SUCCESS = "درخواست فراموشی رمز عبور با موفقیت ثبت شد", PASSWORD_SET_SUCCESS = "پسورد با موفقیت تنظیم شد", PASSWORD_UPDATE_SUCCESS = "رمز عبور شما تغییر کرد", - INVALID_OTP = "کد صحیح نمی‌باشد", + INVALID_OTP = "کد صحیح نمی‌باشد یا منقضی شده است", PASSWORD_MISMATCH = "رمز عبور یکسان نیست", INVALID_PASSWORD = "ایمیل یا رمز عبور اشتباه است", - EmailNotEmpty = "ایمیل نمیتواند خالی باشد.", - IncorrectEmail = "ایمیل صحیح نیست", + EMAIL_NOT_EMPTY = "ایمیل نمیتواند خالی باشد.", + INVALID_EMAIL_FORMAT = "فرمت ایمیل صحیح نیست", PasswordNotEmpty = "پسورد نمیتواند خالی باشد.", USER_NOT_FOUND = "کاربری با این شماره وجود ندارد", USER_EXISTS = "با این شماره قبلا ثبت نام شده است", @@ -23,13 +23,32 @@ export const enum AuthMessage { PHONE_NOT_FOUND = "کاربری با این آیدی یافت نشد", TOKEN_EXPIRED = "توکن منقضی شده است", TOKEN_INVALID = "توکن نامعتبر است", - Banned = "در حال حاضر شما امکان دسترسی به این سرویس را ندارید", + BANNED = "در حال حاضر شما امکان دسترسی به این سرویس را ندارید", PHONE_EXISTS = "شماره تلفن قبلا ثبت شده است", ADMIN_CREATED = "ادمین با موفقیت ایجاد شد", - Permission_NOT_FOUND = "دسترسی یافت نشد", + PERM_NOT_FOUND = "دسترسی یافت نشد", ADMIN_NOT_FOUND = "ادمین یافت نشد", + INVALID_PASS_FORMAT = "فرمت رمز عبور صحیح نیست", + PHONE_NOT_EMPTY = "شماره تلفن نمیتواند خالی باشد", + PASSWORD_FORMAT_INVALID = "رمز عبور باید به صورت رشته باشد", + OTP_FORMAT_INVALID = "کد یکبار مصرف باید عددی و ۵ رقمی باشد", + PHONE_FORMAT_INVALID = "شماره تلفن باید معتبر و به فرمت ایران باشد", + TOKEN_NOT_EMPTY = "توکن نمی‌تواند خالی باشد", + PASSWORD_NOT_EMPTY = "رمز عبور نمی‌تواند خالی باشد", + PASSWORD_CONFIRMATION_NOT_EMPTY = "تایید رمز عبور نمی‌تواند خالی باشد", + PASSWORD_LENGTH = "رمز عبور باید حداقل ۸ کاراکتر باشد", + OTP_NOT_EMPTY = "کد یکبار مصرف نمی‌تواند خالی باشد", + LAST_PASSWORD_NOT_EMPTY = "رمز عبور قبلی نمی‌تواند خالی باشد", + FIRST_NAME_NOT_EMPTY = "نام نمی‌تواند خالی باشد", + LAST_NAME_NOT_EMPTY = "نام خانوادگی نمی‌تواند خالی باشد", + FIRST_NAME_SHOULD_BE_BETWEEN_2_AND_50 = "نام باید بین ۲ تا ۵۰ کاراکتر باشد", + LAST_NAME_SHOULD_BE_BETWEEN_2_AND_50 = "نام خانوادگی باید بین ۲ تا ۵۰ کاراکتر باشد", + BIRTH_DATE_NOT_EMPTY = "تاریخ تولد نمی‌تواند خالی باشد", + NATIONAL_CODE_INCORRECT = "کد ملی باید ۱۰ رقمی باشد", + NATIONAL_NOT_EMPTY = "کد ملی نمی‌تواند خالی باشد", } export const enum UserMessage { USER_NOT_FOUND = "کاربری با این مشخصات یافت نشد", + EMAIL_EXIST = "ایمیل قبلا ثبت شده است", } diff --git a/src/configs/sms.config.ts b/src/configs/sms.config.ts index 7973321..b68e510 100644 --- a/src/configs/sms.config.ts +++ b/src/configs/sms.config.ts @@ -5,14 +5,14 @@ export function SmsConfigs() { inject: [ConfigService], useFactory(configService: ConfigService) { return { - URL: configService.getOrThrow("URL"), - API_KEY: configService.getOrThrow("API_KEY"), - SECRET_KEY: configService.getOrThrow("SECRET_KEY"), + API_URL: configService.getOrThrow("SMS_API_URL"), + API_KEY: configService.getOrThrow("SMS_API_KEY"), + SECRET_KEY: configService.getOrThrow("SMS_SECRET"), SMS_PATTERN_OTP: configService.getOrThrow("SMS_PATTERN_OTP"), - SMS_SECRET_BODY: { - UserApiKey: configService.getOrThrow("API_KEY"), - SecretKey: configService.getOrThrow("SECRET_KEY"), - }, + // SMS_SECRET_BODY: { + // UserApiKey: configService.getOrThrow("SMS_API_KEY"), + // SecretKey: configService.getOrThrow("SECRET_KEY"), + // }, }; }, }; diff --git a/src/modules/auth/DTO/complete-register.dto.ts b/src/modules/auth/DTO/complete-register.dto.ts new file mode 100644 index 0000000..e4a0f53 --- /dev/null +++ b/src/modules/auth/DTO/complete-register.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsEmail, IsMobilePhone, IsNotEmpty, IsNumberString, IsString, Length, MinLength } from "class-validator"; + +import { AuthMessage } from "../../../common/enums/message.enum"; + +export class CompleteRegistrationDto { + @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) + @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) + @ApiProperty({ description: "phone number", default: "09123456789" }) + phone: string; + + @ApiProperty({ description: "OTP code received via SMS", example: "56893" }) + @IsNotEmpty({ message: AuthMessage.OTP_NOT_EMPTY }) + @IsNumberString(undefined, { message: AuthMessage.OTP_FORMAT_INVALID }) + @Length(5, 5, { message: AuthMessage.OTP_FORMAT_INVALID }) + code: string; + + @IsNotEmpty({ message: AuthMessage.FIRST_NAME_NOT_EMPTY }) + @IsString({ message: AuthMessage.FIRST_NAME_NOT_EMPTY }) + @Length(2, 50, { message: AuthMessage.FIRST_NAME_SHOULD_BE_BETWEEN_2_AND_50 }) + @ApiProperty({ description: "First name", example: "mahyar" }) + firstName: string; + + @IsNotEmpty({ message: AuthMessage.LAST_NAME_NOT_EMPTY }) + @IsString({ message: AuthMessage.LAST_NAME_NOT_EMPTY }) + @Length(2, 50, { message: AuthMessage.LAST_NAME_SHOULD_BE_BETWEEN_2_AND_50 }) + @ApiProperty({ description: "Last name", example: "jamshidi" }) + lastName: string; + + @IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY }) + @IsEmail({}, { message: AuthMessage.INVALID_EMAIL_FORMAT }) + @ApiProperty({ description: "Email", example: "ma@gmail.com" }) + email: string; + + @IsNotEmpty({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) + @IsString({ message: AuthMessage.BIRTH_DATE_NOT_EMPTY }) + @ApiProperty({ description: "Birth date", example: "1403/01/01" }) + birthDate: string; + + @IsNotEmpty({ message: AuthMessage.NATIONAL_NOT_EMPTY }) + @IsNumberString({ no_symbols: true }, { message: AuthMessage.NATIONAL_CODE_INCORRECT }) + @Length(10, 10, { message: AuthMessage.NATIONAL_CODE_INCORRECT }) + @ApiProperty({ description: "iranian format (10 char)", example: "4569852169" }) + nationalCode: string; + + @IsNotEmpty({ message: AuthMessage.PASSWORD_NOT_EMPTY }) + @IsString({ message: AuthMessage.PASSWORD_FORMAT_INVALID }) + @ApiProperty({ description: "password", example: "12S345SS678" }) + @MinLength(8, { message: AuthMessage.PASSWORD_LENGTH }) + password: string; +} diff --git a/src/modules/auth/DTOs/loginPassword.dto.ts b/src/modules/auth/DTO/loginPassword.dto.ts similarity index 50% rename from src/modules/auth/DTOs/loginPassword.dto.ts rename to src/modules/auth/DTO/loginPassword.dto.ts index 4de197b..e3fd475 100644 --- a/src/modules/auth/DTOs/loginPassword.dto.ts +++ b/src/modules/auth/DTO/loginPassword.dto.ts @@ -1,14 +1,16 @@ -import { IsEmail, IsNotEmpty } from "@nestjs/class-validator"; -import { AuthMessage } from "../../../common/enums/message.enum"; import { ApiProperty } from "@nestjs/swagger"; +import { IsEmail, IsNotEmpty, IsString } from "class-validator"; + +import { AuthMessage } from "../../../common/enums/message.enum"; export class LoginPasswordDTO { - @IsNotEmpty({ message: AuthMessage.EmailNotEmpty }) - @IsEmail(undefined, { message: AuthMessage.IncorrectEmail }) - @ApiProperty({ description: "email of user", default: "user@abrclick.ir" }) + @IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY }) + @IsEmail(undefined, { message: AuthMessage.INVALID_EMAIL_FORMAT }) + @ApiProperty({ description: "email of user", default: "user@test.ir" }) email: string; @IsNotEmpty({ message: AuthMessage.PasswordNotEmpty }) + @IsString({ message: AuthMessage.INVALID_PASS_FORMAT }) @ApiProperty({ type: "string", description: "password user", example: "@1234" }) password: string; } diff --git a/src/modules/auth/DTO/request-otp.dto.ts b/src/modules/auth/DTO/request-otp.dto.ts new file mode 100644 index 0000000..a41358b --- /dev/null +++ b/src/modules/auth/DTO/request-otp.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsMobilePhone, IsNotEmpty } from "class-validator"; + +import { AuthMessage } from "../../../common/enums/message.enum"; + +export class RequestOtpDto { + @IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY }) + @IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT }) + @ApiProperty({ description: "phone number", default: "09123456789" }) + phone: string; +} diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 438edde..b49ae30 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -1,4 +1,49 @@ -import { Controller } from "@nestjs/common"; +import { Body, Controller, Post } from "@nestjs/common"; +import { ApiOperation, ApiTags } from "@nestjs/swagger"; +import { AuthService } from "./auth.service"; +import { CompleteRegistrationDto } from "./DTO/complete-register.dto"; +import { RequestOtpDto } from "./DTO/request-otp.dto"; + +@ApiTags("Auth") @Controller("auth") -export class AuthController {} +export class AuthController { + constructor(private authService: AuthService) {} + + @ApiOperation({ summary: "Initiate registration" }) + @Post("register/initiate") + async register(@Body() registerDto: RequestOtpDto) { + return this.authService.initiateRegistration(registerDto); + } + + @ApiOperation({ summary: "complete registration ==> step 2" }) + @Post("register/complete") + async completeRegistration(@Body() completeRegistrationDto: CompleteRegistrationDto) { + return this.authService.completeRegistration(completeRegistrationDto); + } + + // @Post("login") + // async login(@Body() loginDto: LoginDto) { + // return this.authService.login(loginDto); + // } + + // @Post("otp/register/send") + // async sendOtp(@Body() sendOtpDto: RequestOtpDto) { + // return this.authService.requestRegisterOTP(sendOtpDto); + // } + + // @Post("otp/verify") + // async verifyOtp(@Body() verifyOtpDto: VerifyOtpDto) { + // return this.authService.verifyOtp(verifyOtpDto); + // } + + // @Post("forgot-password") + // async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto) { + // return this.authService.forgotPassword(forgotPasswordDto); + // } + + // @Post("reset-password") + // async resetPassword(@Body() resetPasswordDto: ResetPasswordDto) { + // return this.authService.resetPassword(resetPasswordDto); + // } +} diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 3730dd1..cc39e8f 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -2,10 +2,11 @@ import { Module } from "@nestjs/common"; import { AuthController } from "./auth.controller"; import { AuthService } from "./auth.service"; +import { UsersModule } from "../users/users.module"; import { UtilsModule } from "../utils/utils.module"; @Module({ - imports: [UtilsModule], + imports: [UtilsModule, UsersModule], controllers: [AuthController], providers: [AuthService], }) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index b901453..c234e8d 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,20 +1,55 @@ -import { BadRequestException, Injectable } from "@nestjs/common"; -import { UsersService } from "../users/providers/users.service"; -import { LoginPasswordDTO } from "./DTOs/loginPassword.dto"; -import { PasswordService } from "../utils/providers/password.service"; +import { BadRequestException, Injectable, Logger } from "@nestjs/common"; + +import { CompleteRegistrationDto } from "./DTO/complete-register.dto"; +import { LoginPasswordDTO } from "./DTO/loginPassword.dto"; +import { RequestOtpDto } from "./DTO/request-otp.dto"; import { AuthMessage, UserMessage } from "../../common/enums/message.enum"; +import { UsersService } from "../users/providers/users.service"; +import { OTPService } from "../utils/providers/otp.service"; +import { PasswordService } from "../utils/providers/password.service"; @Injectable() export class AuthService { + private readonly logger = new Logger(AuthService.name); constructor( - private readonly userSerivce: UsersService, + private readonly userService: UsersService, private readonly passwordService: PasswordService, + private readonly otpService: OTPService, ) {} + //****************** */ + async initiateRegistration(requestOtpDto: RequestOtpDto) { + const { phone } = requestOtpDto; + const otpCode = await this.otpService.generateAndSetInCache(phone, "REGISTER"); + // await SmsService.sendOtp(phone, otpCode); + this.logger.log(`OTP sent to ${phone}: ${otpCode}`); + + return { + message: AuthMessage.OTP_SENT, + otpCode, + }; + } + //****************** */ + async completeRegistration(completeRegistrationDto: CompleteRegistrationDto) { + const { phone, code } = completeRegistrationDto; + const isValid = await this.otpService.verifyOtp(phone, code, "REGISTER"); + + if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); + + await this.otpService.delOtpFormCache(phone, "REGISTER"); + const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); + const user = await this.userService.createUser(completeRegistrationDto, hashedPassword); + + return { + message: AuthMessage.USER_REGISTER_SUCCESS, + user, + }; + } + //****************** */ async loginWithPassword(loginDto: LoginPasswordDTO) { const { email, password } = loginDto; - const user = await this.userSerivce.findOneWithEmail(email); + const user = await this.userService.findOneWithEmail(email); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); const passCompare = await this.passwordService.comparePassword(password, user.password); diff --git a/src/modules/users/DTO/user-register.dto.ts b/src/modules/users/DTO/user-register.dto.ts deleted file mode 100644 index ea97012..0000000 --- a/src/modules/users/DTO/user-register.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class UserRegisterDto {} diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index 1341d21..b3f2a39 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -15,7 +15,7 @@ export class User extends BaseEntity { password: string; @Column({ type: "varchar", length: 150 }) - fistName: string; + firstName: string; @Column({ type: "varchar", length: 200 }) lastName: string; diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 55ec65e..ba6d207 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -1,10 +1,11 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common"; -import { UserRepository } from "./users.repository"; -import { User } from "../entities/user.entity"; -import { UserMessage } from "../../../common/enums/message.enum"; import { RoleRepository } from "./roles.repository"; +import { UserRepository } from "./users.repository"; import { roles } from "../../../../db/seeders/role.seeder"; +import { UserMessage } from "../../../common/enums/message.enum"; +import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; +import { User } from "../entities/user.entity"; @Injectable() export class UsersService { @@ -25,4 +26,12 @@ export class UsersService { this.logger.log({ createdRoles }); return createdRoles; } + + async createUser(registerDto: CompleteRegistrationDto, hashedPassword: string): Promise { + const existEmail = await this.userRepository.findOneWithEmail(registerDto.email); + if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST); + + const user = this.userRepository.create({ ...registerDto, password: hashedPassword }); + return await this.userRepository.save(user); + } } diff --git a/src/modules/utils/interfaces/IOtpKey.ts b/src/modules/utils/interfaces/IOtpKey.ts new file mode 100644 index 0000000..565fad8 --- /dev/null +++ b/src/modules/utils/interfaces/IOtpKey.ts @@ -0,0 +1 @@ +export type OtpCacheKeyType = "LOGIN" | "REGISTER" | "FORGOT_PASSWORD"; diff --git a/src/modules/utils/providers/otp.service.ts b/src/modules/utils/providers/otp.service.ts index 80aff3f..f965209 100644 --- a/src/modules/utils/providers/otp.service.ts +++ b/src/modules/utils/providers/otp.service.ts @@ -1,9 +1,9 @@ import { randomInt } from "node:crypto"; -import { BadRequestException, Injectable } from "@nestjs/common"; +import { Injectable } from "@nestjs/common"; import { CacheService } from "./cache.service"; -import { AuthMessage } from "../../../common/enums/message.enum"; +import { OtpCacheKeyType } from "../interfaces/IOtpKey"; @Injectable() export class OTPService { @@ -13,17 +13,21 @@ export class OTPService { return randomInt(10000, 99999).toString(); } - async generateAndStore(identifier: string, cacheKey: string = "Login") { + async generateAndSetInCache(identifier: string, cacheKey: OtpCacheKeyType) { const otp = this.generateOTP(); const key = `${identifier}:${cacheKey}:OTP`; await this.cacheService.set(key, otp); + return otp; } - async verifyOtp(identifier: string, otpCode: string, cacheKey: string = "Login") { - const key = `${identifier}:${cacheKey}:Otp`; - const codeInCache = await this.cacheService.get(key); + async verifyOtp(identifier: string, otpCode: string, cacheKey: OtpCacheKeyType) { + const key = `${identifier}:${cacheKey}:OTP`; + const codeInCache = await this.cacheService.get(key); + return codeInCache && otpCode === codeInCache; + } - if (!codeInCache || otpCode !== codeInCache) throw new BadRequestException(AuthMessage.INVALID_OTP); + async delOtpFormCache(identifier: string, cacheKey: OtpCacheKeyType) { + const key = `${identifier}:${cacheKey}:OTP`; await this.cacheService.del(key); }