feat: added some new functions to the auth service

This commit is contained in:
2026-07-23 20:48:49 +03:30
parent f380b1267d
commit 71529f7d24
12 changed files with 343 additions and 17 deletions
+13
View File
@@ -0,0 +1,13 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsString, Matches } from "class-validator";
export class LoginCredentialDto {
@ApiProperty({example: '09123456789'})
@IsString()
@Matches(/^09\d{9}$/)
phoneNumber: string;
@ApiProperty({example: 'Mystrongpassword@47'})
@IsString()
password: string;
}
+17
View File
@@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, Matches, Length } from 'class-validator';
export class VerifyOtpDto {
@ApiProperty({
example: '09123456789',
})
@Matches(/^09\d{9}$/)
phoneNumber: string;
@ApiProperty({
example: '123456',
})
@IsString()
@Length(6, 6)
code: string;
}
+28 -2
View File
@@ -1,7 +1,9 @@
import { Body, Controller, Post } from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { AuthService } from "./auth.service";
import { SendOtpDto } from "./DTO/send-otp.dto";
import { LoginCredentialDto } from "./DTO/login-credential.dto";
import { VerifyOtpDto } from "./DTO/verify-otp.dto";
@ApiTags('Authentication')
@Controller('auth')
@@ -14,9 +16,33 @@ export class AuthController {
@ApiOperation({
summary: 'Send OTP to a phone number',
})
@ApiResponse({status: 200, description: 'Sent Otp Successfully!'})
@ApiResponse({status: 400, description: 'Bad Request from User!'})
async sendOtp(
@Body() dto: SendOtpDto,
@Body() dto: SendOtpDto
) {
return this.authService.sendOTP(dto.phoneNumber);
}
@Post('verify-otp')
@ApiOperation({
summary: 'Verify OTP.'
})
@ApiResponse({status: 200, description: 'Verified Otp Successfully!'})
@ApiResponse({status: 400, description: 'Bad Request from the user'})
async verifyOtp(
@Body() dto: VerifyOtpDto
) {
return this.authService.verifyOTP(dto);
}
@Post('check-credential')
@ApiOperation({summary: 'Check Login Credentials. If Valid Sends OTP'})
@ApiResponse({status: 200, description: 'Valid Credentials!'})
@ApiResponse({status: 400, description: 'Credentials Invalid!'})
async checkLoginCredential(
@Body() dto: LoginCredentialDto
) {
return this.authService.checkLoginCredentials(dto);
}
}
+9 -1
View File
@@ -1,4 +1,5 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UtilsModule } from 'src/utils/utils.module';
@@ -6,9 +7,16 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/users/entities/user.entity';
import { OTP } from './entities/otp.entity';
import { OtpRepository } from './repositories/otp.repository';
import { UsersModule } from 'src/users/users.module';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [TypeOrmModule.forFeature([User, OTP]), UtilsModule],
imports: [
TypeOrmModule.forFeature([User, OTP]),
JwtModule.register({}),
UtilsModule,
UsersModule,
],
controllers: [AuthController],
providers: [AuthService, OtpRepository],
})
+78 -9
View File
@@ -1,10 +1,16 @@
import { Inject, Injectable } from '@nestjs/common';
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import type { ISmsConfigs } from 'src/config/sms.config';
import { SMS_CONFIG } from 'src/utils/constants';
import { SmsService } from 'src/utils/providers/sms.service';
import { OtpRepository } from './repositories/otp.repository';
import { OtpStatus } from './enums/otp-status.enum';
import { VerifyOtpDto } from './DTO/verify-otp.dto';
import { OTPMessages, UserMessages } from 'src/common/enums/messages.enum';
import { LoginCredentialDto } from './DTO/login-credential.dto';
import { UsersService } from 'src/users/users.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
@@ -13,6 +19,8 @@ export class AuthService {
private readonly smsService: SmsService,
private readonly configService: ConfigService,
private readonly OtpRepository: OtpRepository,
private readonly userService: UsersService,
private readonly jwtService: JwtService
) {}
async sendOTP(phone: string) {
@@ -35,18 +43,18 @@ export class AuthService {
{ status: OtpStatus.EXPIRED },
);
await this.OtpRepository.save(
this.OtpRepository.create({
phoneNumber: phone,
code: otp,
expirationDate: Date.now() + 2 * 60 * 1000,
status: OtpStatus.PENDING,
numberOfTries: 0
})
phoneNumber: phone,
code: otp,
expirationDate: new Date(Date.now() + 2 * 60 * 1000),
status: OtpStatus.PENDING,
numberOfTries: 0,
}),
);
return {otp};
return { otp };
} catch (error) {
if (this.configService.get('NODE_ENV') !== 'production') {
return { otp };
@@ -56,7 +64,68 @@ export class AuthService {
}
}
async verifyOTP(dto: VerifyOtpDto) {
const otp = await this.OtpRepository.findOne({
where: { phoneNumber: dto.phoneNumber, status: OtpStatus.PENDING },
order: {
createdAt: 'DESC',
},
});
if (!otp) {
throw new BadRequestException(OTPMessages.OTP_NOT_FOUND);
}
if (otp.expirationDate < new Date()) {
otp.status = OtpStatus.EXPIRED;
await this.OtpRepository.save(otp);
throw new BadRequestException(OTPMessages.OTP_EXPIRED);
}
if (otp.code !== dto.code) {
otp.numberOfTries++;
if (otp.numberOfTries >= 3) {
otp.status = OtpStatus.EXPIRED;
await this.OtpRepository.save(otp);
throw new BadRequestException(OTPMessages.OTP_TOO_MANY_TRIES);
}
await this.OtpRepository.save(otp);
throw new BadRequestException(OTPMessages.OTP_INVALID);
}
otp.status = OtpStatus.VERIFIED;
await this.OtpRepository.save(otp);
}
private generateOtp(): string {
return Math.floor(100000 + Math.random() * 900000).toString();
}
async checkLoginCredentials(credential: LoginCredentialDto): Promise<void> {
const { phoneNumber, password } = credential;
const user = await this.userService.findOneByPhoneOrFail(phoneNumber);
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
throw new BadRequestException(UserMessages.USER_PASSWORD_INVALID);
}
await this.sendOTP(phoneNumber);
}
// async VerifyOtpAndLogin(dto: VerifyOtpDto) {
// await this.verifyOTP(dto);
// const user = await this.userService.findOneByPhoneOrFail(dto.phoneNumber);
// }
}
+11 -1
View File
@@ -1,3 +1,13 @@
export enum UserMessages {
USER_ALREADY_EXISTS = 'کاربر مورد نظر در حال حاضر وجود دارد. لطفا وارد حساب شوید!'
USER_ALREADY_EXISTS = 'کاربر مورد نظر با این ایمیل یا شماره همراه در حال حاضر وجود دارد. لطفا وارد حساب شوید!',
USER_NOT_FOUND = 'کاربر مورد نظر پیدا نشد!',
USER_PASSWORD_INVALID = 'رمز وارد شده اشتباده است!',
USER_VALID_CREDENTIALS = 'اطلاعات وارد شده صحیح میباشد!'
}
export enum OTPMessages {
OTP_NOT_FOUND = 'این کد پیدا نشد! لطفا دوباره درخواست بدهید!',
OTP_EXPIRED = 'این کد منسوخ شده است لطفا دوباره درخواست بدهید!',
OTP_TOO_MANY_TRIES = 'تعداد تلاش ها بیش از حد مجاز است لطفا دوباره درخواست کد بدهید!',
OTP_INVALID = 'کد وارد شده نا معتبر است!'
}
@@ -23,7 +23,7 @@ export class ResponseInterceptor<T> implements NestInterceptor<
map((data: T) => ({
success: true,
statusCode: response.statusCode,
message: 'درخواست با موفقیت پاسخ داده شد!',
message: 'درخواست موفقیت آمیز بود!',
timestamp: new Date().toISOString(),
data
}))
+7 -1
View File
@@ -15,5 +15,11 @@ export const validationSchema = Joi.object({
SMS_API_KEY: Joi.string().required(),
SMS_PATTERN_OTP: Joi.string().required(),
NODE_ENV: Joi.string().required()
NODE_ENV: Joi.string().required(),
// JWT Config
JWT_ACCESS_SECRET: Joi.string().required(),
JWT_REFRESH_SECRET: Joi.string().required(),
JWT_ACCESS_EXPIRES: Joi.string().required(),
JWT_REFRESH_EXPIRES: Joi.string().required()
});
-1
View File
@@ -17,7 +17,6 @@ export class User extends BaseEntity {
phone: string;
@Column({
select: false
})
@Exclude()
password: string;
+25 -1
View File
@@ -1,4 +1,8 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { UserRepository } from './user.repository';
import { User } from './entities/user.entity';
import * as bcrypt from 'bcrypt';
@@ -9,6 +13,26 @@ import { UserMessages } from 'src/common/enums/messages.enum';
export class UsersService {
constructor(private readonly userRepository: UserRepository) {}
async findOneOrFail(id: string): Promise<User> {
const user = await this.userRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(UserMessages.USER_NOT_FOUND);
}
return user;
}
async findOneByPhoneOrFail(phone: string): Promise<User> {
const user = await this.userRepository.findOne({ where: { phone } });
if (!user) {
throw new NotFoundException(UserMessages.USER_NOT_FOUND);
}
return user;
}
async createUser(userDto: CreateUserDto): Promise<User> {
const existingUser = await this.userRepository.findOne({
where: [