mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-10 07:07:21 +00:00
add OTP email sending functionality and integrate with user authentication flow
This commit is contained in:
@ -19,5 +19,7 @@ export default registerAs(
|
||||
process.env.MAILTRAP_DELETE_USER_TEMPLATE_UUID,
|
||||
MAILTRAP_EDIT_USER_TEMPLATE_UUID:
|
||||
process.env.MAILTRAP_EDIT_USER_TEMPLATE_UUID,
|
||||
MAILTRAP_SEND_OTP_TEMPLATE_UUID:
|
||||
process.env.MAILTRAP_SEND_OTP_TEMPLATE_UUID,
|
||||
}),
|
||||
);
|
||||
|
@ -181,6 +181,49 @@ export class EmailService {
|
||||
);
|
||||
}
|
||||
}
|
||||
async sendOtpEmailWithTemplate(
|
||||
email: string,
|
||||
emailEditData: any,
|
||||
): Promise<void> {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const API_TOKEN = this.configService.get<string>(
|
||||
'email-config.MAILTRAP_API_TOKEN',
|
||||
);
|
||||
const API_URL = isProduction
|
||||
? SEND_EMAIL_API_URL_PROD
|
||||
: SEND_EMAIL_API_URL_DEV;
|
||||
const TEMPLATE_UUID = this.configService.get<string>(
|
||||
'email-config.MAILTRAP_SEND_OTP_TEMPLATE_UUID',
|
||||
);
|
||||
|
||||
const emailData = {
|
||||
from: {
|
||||
email: this.smtpConfig.sender,
|
||||
},
|
||||
to: [
|
||||
{
|
||||
email: email,
|
||||
},
|
||||
],
|
||||
template_uuid: TEMPLATE_UUID,
|
||||
template_variables: emailEditData,
|
||||
};
|
||||
|
||||
try {
|
||||
await axios.post(API_URL, emailData, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
error.response?.data?.message ||
|
||||
'Error sending email using Mailtrap template',
|
||||
error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
generateUserChangesEmailBody(
|
||||
addedSpaceNames: string[],
|
||||
removedSpaceNames: string[],
|
||||
|
@ -181,79 +181,98 @@ export class UserAuthService {
|
||||
otpCode: string;
|
||||
cooldown: number;
|
||||
}> {
|
||||
const otpLimiter = new Date();
|
||||
otpLimiter.setDate(
|
||||
otpLimiter.getDate() - this.configService.get<number>('OTP_LIMITER'),
|
||||
);
|
||||
const userExists = await this.userRepository.exists({
|
||||
where: {
|
||||
region: data.regionUuid
|
||||
? {
|
||||
uuid: data.regionUuid,
|
||||
}
|
||||
: undefined,
|
||||
email: data.email,
|
||||
isUserVerified: data.type === OtpType.PASSWORD ? true : undefined,
|
||||
},
|
||||
});
|
||||
if (!userExists) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
await this.otpRepository.softDelete({ email: data.email, type: data.type });
|
||||
await this.otpRepository.delete({
|
||||
email: data.email,
|
||||
type: data.type,
|
||||
createdAt: LessThan(otpLimiter),
|
||||
});
|
||||
const countOfOtp = await this.otpRepository.count({
|
||||
withDeleted: true,
|
||||
where: {
|
||||
email: data.email,
|
||||
type: data.type,
|
||||
createdAt: MoreThan(otpLimiter),
|
||||
},
|
||||
});
|
||||
const lastOtp = await this.otpRepository.findOne({
|
||||
where: { email: data.email, type: data.type },
|
||||
order: { createdAt: 'DESC' },
|
||||
withDeleted: true,
|
||||
});
|
||||
let cooldown = 30 * Math.pow(2, countOfOtp - 1);
|
||||
if (lastOtp) {
|
||||
const now = new Date();
|
||||
const timeSinceLastOtp = differenceInSeconds(now, lastOtp.createdAt);
|
||||
|
||||
if (timeSinceLastOtp < cooldown) {
|
||||
throw new BadRequestException({
|
||||
message: `Please wait ${cooldown - timeSinceLastOtp} more seconds before requesting a new OTP.`,
|
||||
data: {
|
||||
cooldown: cooldown - timeSinceLastOtp,
|
||||
},
|
||||
});
|
||||
try {
|
||||
const otpLimiter = new Date();
|
||||
otpLimiter.setDate(
|
||||
otpLimiter.getDate() - this.configService.get<number>('OTP_LIMITER'),
|
||||
);
|
||||
const user = await this.userRepository.findOne({
|
||||
where: {
|
||||
region: data.regionUuid ? { uuid: data.regionUuid } : undefined,
|
||||
email: data.email,
|
||||
isUserVerified: data.type === OtpType.PASSWORD ? true : undefined,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
}
|
||||
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
const expiryTime = new Date();
|
||||
expiryTime.setMinutes(expiryTime.getMinutes() + 10);
|
||||
await this.otpRepository.save({
|
||||
email: data.email,
|
||||
otpCode,
|
||||
expiryTime,
|
||||
type: data.type,
|
||||
});
|
||||
const countOfOtpToReturn = await this.otpRepository.count({
|
||||
withDeleted: true,
|
||||
where: {
|
||||
await this.otpRepository.softDelete({
|
||||
email: data.email,
|
||||
type: data.type,
|
||||
createdAt: MoreThan(otpLimiter),
|
||||
},
|
||||
});
|
||||
cooldown = 30 * Math.pow(2, countOfOtpToReturn - 1);
|
||||
const subject = 'OTP send successfully';
|
||||
const message = `Your OTP code is ${otpCode}`;
|
||||
this.emailService.sendEmail(data.email, subject, message);
|
||||
return { otpCode, cooldown };
|
||||
});
|
||||
await this.otpRepository.delete({
|
||||
email: data.email,
|
||||
type: data.type,
|
||||
createdAt: LessThan(otpLimiter),
|
||||
});
|
||||
const countOfOtp = await this.otpRepository.count({
|
||||
withDeleted: true,
|
||||
where: {
|
||||
email: data.email,
|
||||
type: data.type,
|
||||
createdAt: MoreThan(otpLimiter),
|
||||
},
|
||||
});
|
||||
const lastOtp = await this.otpRepository.findOne({
|
||||
where: { email: data.email, type: data.type },
|
||||
order: { createdAt: 'DESC' },
|
||||
withDeleted: true,
|
||||
});
|
||||
let cooldown = 30 * Math.pow(2, countOfOtp - 1);
|
||||
if (lastOtp) {
|
||||
const now = new Date();
|
||||
const timeSinceLastOtp = differenceInSeconds(now, lastOtp.createdAt);
|
||||
|
||||
if (timeSinceLastOtp < cooldown) {
|
||||
throw new BadRequestException({
|
||||
message: `Please wait ${cooldown - timeSinceLastOtp} more seconds before requesting a new OTP.`,
|
||||
data: {
|
||||
cooldown: cooldown - timeSinceLastOtp,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
const expiryTime = new Date();
|
||||
expiryTime.setMinutes(expiryTime.getMinutes() + 10);
|
||||
await this.otpRepository.save({
|
||||
email: data.email,
|
||||
otpCode,
|
||||
expiryTime,
|
||||
type: data.type,
|
||||
});
|
||||
const countOfOtpToReturn = await this.otpRepository.count({
|
||||
withDeleted: true,
|
||||
where: {
|
||||
email: data.email,
|
||||
type: data.type,
|
||||
createdAt: MoreThan(otpLimiter),
|
||||
},
|
||||
});
|
||||
cooldown = 30 * Math.pow(2, countOfOtpToReturn - 1);
|
||||
|
||||
const [otp1, otp2, otp3, otp4, otp5, otp6] = otpCode.split('');
|
||||
|
||||
await this.emailService.sendOtpEmailWithTemplate(data.email, {
|
||||
name: user.firstName,
|
||||
otp1,
|
||||
otp2,
|
||||
otp3,
|
||||
otp4,
|
||||
otp5,
|
||||
otp6,
|
||||
});
|
||||
|
||||
return { otpCode, cooldown };
|
||||
} catch (error) {
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
console.error('OTP generation error:', error);
|
||||
throw new BadRequestException(
|
||||
'An unexpected error occurred while generating the OTP.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async verifyOTP(
|
||||
|
Reference in New Issue
Block a user