This commit is contained in:
2025-11-09 11:25:26 +03:30
commit 06f814b331
62 changed files with 31547 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { RequestOtpDto } from './dto/request-otp.dto';
import { ApiTags, ApiOperation, ApiBody, ApiResponse } from '@nestjs/swagger';
import { VerifyOtpDto } from './dto/verify-otp.dto copy';
import { Throttle } from '@nestjs/throttler';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Throttle({ default: { limit: 3, ttl: 180_000 } })
@Post('otp/request')
@ApiOperation({ summary: 'Request OTP for login or signup' })
@ApiBody({ type: RequestOtpDto, description: 'Mobile number to receive OTP' })
@ApiResponse({ status: 201, description: 'OTP requested successfully' })
@ApiResponse({ status: 400, description: 'Invalid mobile number' })
otpRequest(@Body() dto: RequestOtpDto) {
return this.authService.requestOtp(dto);
}
@Post('otp/verify')
@ApiOperation({ summary: 'Verify OTP code' })
@ApiBody({ type: VerifyOtpDto, description: 'Mobile number and OTP code' })
@ApiResponse({ status: 200, description: 'OTP verified successfully' })
@ApiResponse({ status: 400, description: 'Invalid OTP or expired' })
otpVerify(@Body() dto: VerifyOtpDto) {
return this.authService.verifyOtp(dto.phone, dto.otp); // assuming dto has `otp` property
}
@Post('admin/otp/request')
@ApiOperation({ summary: 'Request OTP for login or signup' })
@ApiBody({ type: RequestOtpDto, description: 'Mobile number to receive OTP' })
@ApiResponse({ status: 201, description: 'OTP requested successfully' })
@ApiResponse({ status: 400, description: 'Invalid mobile number' })
adminOtpRequest(@Body() dto: RequestOtpDto) {
return this.authService.requestOtpAdmin(dto);
}
@Post('admin/otp/verify')
@ApiOperation({ summary: 'Verify OTP code' })
@ApiBody({ type: VerifyOtpDto, description: 'Mobile number and OTP code' })
@ApiResponse({ status: 200, description: 'OTP verified successfully' })
@ApiResponse({ status: 400, description: 'Invalid OTP or expired' })
adminOtpVerify(@Body() dto: VerifyOtpDto) {
return this.authService.verifyOtpAdmin(dto.phone, dto.otp); // assuming dto has `otp` property
}
}
+29
View File
@@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UtilsModule } from '../utils/utils.module';
import { UserModule } from '../user/user.module';
import { JwtModule } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { AdminModule } from '../admin/admin.module';
@Module({
imports: [
UtilsModule,
UserModule,
JwtModule.registerAsync({
useFactory: (configService: ConfigService) => ({
global: true,
secret: configService.getOrThrow<string>('JWT_SECRET'),
signOptions: {
expiresIn: configService.getOrThrow<number>('JWT_EXPIRATION_TIME'),
},
}),
inject: [ConfigService],
}),
AdminModule,
],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
+91
View File
@@ -0,0 +1,91 @@
import { Injectable, BadRequestException, UnauthorizedException, NotFoundException } from '@nestjs/common';
import { RequestOtpDto } from './dto/request-otp.dto';
import { CacheService } from '../utils/cache.service';
import { SmsService } from '../utils/sms.service';
import { UserService } from '../user/user.service';
import { randomInt } from 'crypto';
import { JwtService } from '@nestjs/jwt';
import { AdminService } from '../admin/admin.service';
import { AuthEntityType } from 'src/common/guards/auth.guard';
@Injectable()
export class AuthService {
constructor(
private readonly cacheService: CacheService,
private readonly smsService: SmsService,
private readonly userService: UserService,
private readonly jwtService: JwtService,
private readonly adminService: AdminService,
) {}
async requestOtp(dto: RequestOtpDto) {
const { phone } = dto;
const code = this.generateOtpCode();
await this.cacheService.set(`otp:${phone}`, code, 160);
await this.smsService.sendOtp(phone, code);
return { success: true, message: ' OTP sent successfully' };
}
async requestOtpAdmin(dto: RequestOtpDto) {
const { phone } = dto;
const admin = await this.adminService.findByPhone(phone);
if (!admin) {
throw new NotFoundException();
}
const code = this.generateOtpCode();
await this.cacheService.set(`otp-admin:${phone}`, code, 160);
await this.smsService.sendOtp(phone, code);
return { success: true, message: ' OTP sent successfully' };
}
async verifyOtp(phone: string, code: string) {
const cachedCode = await this.cacheService.get(`otp:${phone}`);
if (!cachedCode) throw new BadRequestException('OTP expired or not found');
if (cachedCode !== code) throw new BadRequestException('Invalid OTP');
await this.cacheService.del(`otp:${phone}`);
const user = await this.userService.findOrCreateByPhone(phone);
const accessToken = await this.issueToken(user.id.toString(), AuthEntityType.USER);
return { success: true, message: 'User registered successfully!', accessToken };
}
async verifyOtpAdmin(phone: string, code: string) {
const cachedCode = await this.cacheService.get(`otp-admin:${phone}`);
if (!cachedCode) throw new BadRequestException('OTP expired or not found');
if (cachedCode !== code) throw new BadRequestException('Invalid OTP');
await this.cacheService.del(`otp:${phone}`);
const admin = await this.adminService.findByPhone(phone);
if (!admin) {
throw new UnauthorizedException();
}
const accessToken = await this.issueToken(admin.id.toString(), AuthEntityType.ADMIN);
return { success: true, message: 'successfully!', accessToken };
}
private generateOtpCode(): string {
const code = randomInt(10000, 100000);
return code.toString();
}
private issueToken(id: string, entityType: AuthEntityType) {
const payload = {
sub: id,
type: entityType,
};
return this.jwtService.signAsync(payload);
}
}
+13
View File
@@ -0,0 +1,13 @@
import { IsNotEmpty, IsString, Matches } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class RequestOtpDto {
@IsNotEmpty()
@IsString()
@ApiProperty({ example: '09362532122', description: 'Mobile number' })
@Matches(/^09\d{9}$/, {
message: 'Mobile number must be a valid Iranian phone number (e.g., 09123456789)',
})
// @IsMobilePhone('fa-IR')
phone: string;
}
@@ -0,0 +1,19 @@
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class VerifyOtpDto {
@IsNotEmpty()
@IsString()
@ApiProperty({ example: '09362532122', description: 'Mobile number' })
@Matches(/^09\d{9}$/, {
message: 'Mobile number must be a valid Iranian phone number (e.g., 09123456789)',
})
// @IsMobilePhone('fa-IR')
phone: string;
@IsNotEmpty()
@IsString()
@ApiProperty({ example: '12345', description: 'Otp' })
@Length(5)
otp: string;
}