287 lines
7.2 KiB
TypeScript
287 lines
7.2 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { PrismaClient } from '@prisma/client';
|
|
import * as bcrypt from 'bcryptjs';
|
|
import { ROLE, USER_STATUS } from '../../../common/utils/constants/common.constant';
|
|
import ApiError from '../../../common/utils/helper/ApiError';
|
|
import { OtpGeneratorSixDigit } from '../../../common/utils/helper/OtpGenerator';
|
|
|
|
type OperatorSignupInput = {
|
|
firstName?: string;
|
|
lastName?: string;
|
|
emailAddress?: string;
|
|
isdCode?: string;
|
|
mobileNumber?: string;
|
|
};
|
|
|
|
@Injectable()
|
|
export class OperatorAuthService {
|
|
constructor(private prisma: PrismaClient) {}
|
|
|
|
async findInvitedOperator(input: {
|
|
emailAddress?: string;
|
|
mobileNumber?: string;
|
|
}) {
|
|
const emailAddress = input.emailAddress?.trim().toLowerCase();
|
|
const mobileNumber = input.mobileNumber?.trim();
|
|
|
|
if (!emailAddress && !mobileNumber) {
|
|
throw new ApiError(400, 'Email address or mobile number is required');
|
|
}
|
|
|
|
const invitedOperator = await this.prisma.user.findFirst({
|
|
where: {
|
|
roleXid: ROLE.OPERATOR,
|
|
isActive: true,
|
|
OR: [
|
|
...(emailAddress ? [{ emailAddress }] : []),
|
|
...(mobileNumber ? [{ mobileNumber }] : []),
|
|
],
|
|
},
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
emailAddress: true,
|
|
isdCode: true,
|
|
mobileNumber: true,
|
|
roleXid: true,
|
|
userPassword: true,
|
|
isEmailVerfied: true,
|
|
userStatus: true,
|
|
},
|
|
});
|
|
|
|
if (!invitedOperator) {
|
|
throw new ApiError(
|
|
403,
|
|
'Operator record not found. Please contact your host.',
|
|
);
|
|
}
|
|
|
|
return invitedOperator;
|
|
}
|
|
|
|
async signUpOperator(input: OperatorSignupInput) {
|
|
const emailAddress = input.emailAddress?.trim().toLowerCase();
|
|
const mobileNumber = input.mobileNumber?.trim();
|
|
const invitedOperator = await this.findInvitedOperator({
|
|
emailAddress,
|
|
mobileNumber,
|
|
});
|
|
|
|
if (invitedOperator.userPassword) {
|
|
return {
|
|
userId: invitedOperator.id,
|
|
emailAddress: emailAddress || invitedOperator.emailAddress,
|
|
mobileNumber: mobileNumber || invitedOperator.mobileNumber,
|
|
otp: null,
|
|
expiresOn: null,
|
|
isNewOperator: false,
|
|
};
|
|
}
|
|
|
|
const otp = OtpGeneratorSixDigit.generateOtp();
|
|
const hashedOtp = await bcrypt.hash(otp, 10);
|
|
const expiry = new Date(Date.now() + 5 * 60 * 1000);
|
|
|
|
await this.prisma.$transaction(async (tx) => {
|
|
await tx.user.update({
|
|
where: { id: invitedOperator.id },
|
|
data: {
|
|
firstName: input.firstName?.trim() || invitedOperator.firstName || null,
|
|
lastName: input.lastName?.trim() || invitedOperator.lastName || null,
|
|
emailAddress: emailAddress || invitedOperator.emailAddress,
|
|
isdCode: input.isdCode?.trim() || invitedOperator.isdCode || null,
|
|
mobileNumber: mobileNumber || invitedOperator.mobileNumber,
|
|
userStatus: USER_STATUS.INVITED,
|
|
},
|
|
});
|
|
|
|
await tx.userOtp.updateMany({
|
|
where: {
|
|
userXid: invitedOperator.id,
|
|
otpType: 'Register',
|
|
isActive: true,
|
|
},
|
|
data: {
|
|
isActive: false,
|
|
isVerified: true,
|
|
},
|
|
});
|
|
|
|
await tx.userOtp.create({
|
|
data: {
|
|
userXid: invitedOperator.id,
|
|
otpType: 'Register',
|
|
otpCode: hashedOtp,
|
|
expiresOn: expiry,
|
|
isVerified: false,
|
|
isActive: true,
|
|
},
|
|
});
|
|
});
|
|
|
|
return {
|
|
isNewOperator: true,
|
|
};
|
|
}
|
|
|
|
async verifyOperatorOtp(input: { emailAddress?: string; mobileNumber?: string; otp: string }) {
|
|
const emailAddress = input.emailAddress?.trim().toLowerCase();
|
|
const mobileNumber = input.mobileNumber?.trim();
|
|
const otp = input.otp.trim();
|
|
|
|
const invitedOperator = await this.findInvitedOperator({
|
|
emailAddress,
|
|
mobileNumber,
|
|
});
|
|
|
|
const latestOtp = await this.prisma.userOtp.findFirst({
|
|
where: {
|
|
userXid: invitedOperator.id,
|
|
otpType: 'Register',
|
|
isActive: true,
|
|
isVerified: false,
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
|
|
if (!latestOtp) {
|
|
throw new ApiError(400, 'No OTP found.');
|
|
}
|
|
|
|
if (new Date() > latestOtp.expiresOn) {
|
|
throw new ApiError(400, 'OTP has expired.');
|
|
}
|
|
|
|
const isMatch = await bcrypt.compare(otp, latestOtp.otpCode);
|
|
|
|
if (!isMatch) {
|
|
throw new ApiError(400, 'Invalid OTP.');
|
|
}
|
|
|
|
await this.prisma.userOtp.update({
|
|
where: { id: latestOtp.id },
|
|
data: {
|
|
isVerified: true,
|
|
verifiedOn: new Date(),
|
|
isActive: false,
|
|
},
|
|
});
|
|
|
|
await this.prisma.user.update({
|
|
where: { id: invitedOperator.id },
|
|
data: {
|
|
isEmailVerfied: emailAddress ? true : invitedOperator.isEmailVerfied,
|
|
},
|
|
});
|
|
|
|
return invitedOperator;
|
|
}
|
|
|
|
async createOperatorPassword(userId: number, password: string) {
|
|
const user = await this.prisma.user.findFirst({
|
|
where: {
|
|
id: userId,
|
|
roleXid: ROLE.OPERATOR,
|
|
isActive: true,
|
|
},
|
|
select: {
|
|
id: true,
|
|
emailAddress: true,
|
|
userPassword: true,
|
|
},
|
|
});
|
|
|
|
if (!user) {
|
|
throw new ApiError(404, 'Operator not found');
|
|
}
|
|
|
|
if (user.userPassword) {
|
|
throw new ApiError(400, 'Password already exists. Please login.');
|
|
}
|
|
|
|
const saltRounds = parseInt(process.env.SALT_ROUNDS || '10', 10);
|
|
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
|
|
|
await this.prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
userPassword: hashedPassword,
|
|
userStatus: USER_STATUS.ACTIVE,
|
|
isEmailVerfied: true,
|
|
},
|
|
});
|
|
|
|
return {
|
|
id: user.id,
|
|
emailAddress: user.emailAddress,
|
|
};
|
|
}
|
|
|
|
async loginForOperator(emailAddress: string, userPassword: string) {
|
|
const existingOperator = await this.prisma.user.findFirst({
|
|
where: {
|
|
emailAddress,
|
|
roleXid: ROLE.OPERATOR,
|
|
isActive: true,
|
|
userStatus: USER_STATUS.ACTIVE,
|
|
},
|
|
select: {
|
|
id: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
emailAddress: true,
|
|
mobileNumber: true,
|
|
roleXid: true,
|
|
isActive: true,
|
|
userStatus: true,
|
|
userPassword: true,
|
|
},
|
|
});
|
|
|
|
if (!existingOperator) {
|
|
throw new ApiError(404, 'Operator not found');
|
|
}
|
|
|
|
const isPasswordMatched = await bcrypt.compare(
|
|
userPassword,
|
|
existingOperator.userPassword || '',
|
|
);
|
|
|
|
if (!isPasswordMatched) {
|
|
throw new ApiError(401, 'Invalid credentials');
|
|
}
|
|
|
|
delete existingOperator.userPassword;
|
|
|
|
return existingOperator;
|
|
}
|
|
|
|
async verifyPasswordForOperator(emailAddress: string, userPassword: string) {
|
|
const existingOperator = await this.prisma.user.findFirst({
|
|
where: {
|
|
emailAddress,
|
|
roleXid: ROLE.OPERATOR,
|
|
isActive: true,
|
|
userStatus: USER_STATUS.ACTIVE,
|
|
},
|
|
select: {
|
|
id: true,
|
|
userPassword: true,
|
|
},
|
|
});
|
|
|
|
if (!existingOperator) {
|
|
throw new ApiError(404, 'Operator not found');
|
|
}
|
|
|
|
const isPasswordMatched = await bcrypt.compare(
|
|
userPassword,
|
|
existingOperator.userPassword || '',
|
|
);
|
|
|
|
return isPasswordMatched;
|
|
}
|
|
}
|