init
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user