219 lines
5.9 KiB
TypeScript
219 lines
5.9 KiB
TypeScript
import { Injectable, BadRequestException } from '@nestjs/common';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { RedisService } from '../redis/redis.service';
|
|
import { SendOtpDto } from './dto/send-otp.dto';
|
|
import { VerifyOtpDto } from './dto/verify-otp.dto';
|
|
import { SmsService } from '../common/services/sms.service';
|
|
import * as bcrypt from 'bcryptjs';
|
|
|
|
@Injectable()
|
|
export class AuthService {
|
|
constructor(
|
|
private prisma: PrismaService,
|
|
private jwtService: JwtService,
|
|
private redisService: RedisService,
|
|
private smsService: SmsService,
|
|
) {}
|
|
|
|
async sendOtp(sendOtpDto: SendOtpDto) {
|
|
const { phoneNumber } = sendOtpDto;
|
|
|
|
const code = Math.floor(10000 + Math.random() * 90000).toString();
|
|
|
|
await this.redisService.set(`otp:${phoneNumber}`, code, 120);
|
|
|
|
// Dispatch OTP via MeliPayamak Pattern SMS
|
|
await this.smsService.sendOtp(phoneNumber, code);
|
|
|
|
return {
|
|
success: true,
|
|
message: 'کد تایید با موفقیت به شماره شما پیامک شد.',
|
|
};
|
|
}
|
|
|
|
async verifyOtp(verifyOtpDto: VerifyOtpDto) {
|
|
const { phoneNumber, code } = verifyOtpDto;
|
|
|
|
const savedCode = await this.redisService.get(`otp:${phoneNumber}`);
|
|
|
|
if (!savedCode) {
|
|
throw new BadRequestException({
|
|
message: 'کد تایید منقضی شده است',
|
|
error: 'OTP_EXPIRED',
|
|
});
|
|
}
|
|
|
|
if (savedCode !== code) {
|
|
throw new BadRequestException({
|
|
message: 'کد تایید اشتباه است',
|
|
error: 'OTP_INVALID',
|
|
});
|
|
}
|
|
|
|
await this.redisService.del(`otp:${phoneNumber}`);
|
|
|
|
let user = await this.prisma.user.findUnique({
|
|
where: { mobile: phoneNumber },
|
|
});
|
|
|
|
if (!user) {
|
|
user = await this.prisma.user.create({
|
|
data: {
|
|
mobile: phoneNumber,
|
|
firstName: 'کاربر',
|
|
lastName: 'جدید',
|
|
email: `${phoneNumber}@temp.local`,
|
|
},
|
|
});
|
|
}
|
|
|
|
const payload = { sub: user.id, phoneNumber: user.mobile };
|
|
const accessToken = this.jwtService.sign(payload);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user,
|
|
accessToken,
|
|
},
|
|
};
|
|
}
|
|
|
|
async register(registerDto: any) {
|
|
const { firstName, lastName, email, mobile, password } = registerDto;
|
|
|
|
const existingUser = await this.prisma.user.findUnique({
|
|
where: { mobile },
|
|
});
|
|
if (existingUser) {
|
|
throw new BadRequestException({
|
|
message: 'کاربری با این شماره موبایل قبلا ثبت نام کرده است',
|
|
error: 'MOBILE_EXISTS',
|
|
});
|
|
}
|
|
|
|
if (email) {
|
|
const existingEmail = await this.prisma.user.findUnique({
|
|
where: { email },
|
|
});
|
|
if (existingEmail) {
|
|
throw new BadRequestException({
|
|
message: 'کاربری با این ایمیل قبلا ثبت نام کرده است',
|
|
error: 'EMAIL_EXISTS',
|
|
});
|
|
}
|
|
}
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
|
|
const user = await this.prisma.user.create({
|
|
data: {
|
|
firstName,
|
|
lastName,
|
|
email: email || null,
|
|
mobile,
|
|
password: hashedPassword,
|
|
},
|
|
});
|
|
|
|
const payload = { sub: user.id, phoneNumber: user.mobile };
|
|
const accessToken = this.jwtService.sign(payload);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user,
|
|
accessToken,
|
|
},
|
|
};
|
|
}
|
|
|
|
async login(loginDto: any) {
|
|
const { mobile, password } = loginDto;
|
|
|
|
const user = await this.prisma.user.findUnique({ where: { mobile } });
|
|
if (!user) {
|
|
throw new BadRequestException({
|
|
message: 'نام کاربری یا رمز عبور اشتباه است',
|
|
error: 'INVALID_CREDENTIALS',
|
|
});
|
|
}
|
|
|
|
if (!user.password) {
|
|
throw new BadRequestException({
|
|
message: 'شما با رمز عبور ثبت نام نکردهاید. لطفاً با موبایل وارد شوید',
|
|
error: 'NO_PASSWORD',
|
|
});
|
|
}
|
|
|
|
const isMatch = await bcrypt.compare(password, user.password);
|
|
if (!isMatch) {
|
|
throw new BadRequestException({
|
|
message: 'نام کاربری یا رمز عبور اشتباه است',
|
|
error: 'INVALID_CREDENTIALS',
|
|
});
|
|
}
|
|
|
|
const payload = { sub: user.id, phoneNumber: user.mobile };
|
|
const accessToken = this.jwtService.sign(payload);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user,
|
|
accessToken,
|
|
},
|
|
};
|
|
}
|
|
|
|
async adminLogin(body: any) {
|
|
const adminEmail = process.env.ADMIN_EMAIL || 'admin@canina-iran.com';
|
|
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
|
|
|
|
// First check database for user with role 'Admin' or 'SUPER_ADMIN'
|
|
const dbAdmin = await this.prisma.user.findFirst({
|
|
where: { email: body.email, role: { in: ['Admin', 'SUPER_ADMIN', 'ADMIN'] } },
|
|
});
|
|
|
|
if (dbAdmin && dbAdmin.password) {
|
|
const isMatch = await bcrypt.compare(body.password, dbAdmin.password);
|
|
if (isMatch) {
|
|
const payload = { sub: dbAdmin.id, email: dbAdmin.email, role: dbAdmin.role };
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user: dbAdmin,
|
|
accessToken: this.jwtService.sign(payload),
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
// Fallback to validated environment variable credentials
|
|
if (body.email === adminEmail && body.password === adminPassword) {
|
|
const payload = {
|
|
sub: '12345678-1234-1234-1234-123456789012',
|
|
email: body.email,
|
|
role: 'Admin',
|
|
};
|
|
return {
|
|
success: true,
|
|
data: {
|
|
user: {
|
|
id: '12345678-1234-1234-1234-123456789012',
|
|
email: body.email,
|
|
role: 'Admin',
|
|
},
|
|
accessToken: this.jwtService.sign(payload),
|
|
},
|
|
};
|
|
}
|
|
|
|
throw new BadRequestException({
|
|
message: 'ایمیل یا رمز عبور اشتباه است',
|
|
error: 'INVALID_CREDENTIALS',
|
|
});
|
|
}
|
|
}
|