made resend otp lambda
This commit is contained in:
@@ -325,3 +325,18 @@ updateSuggestionAsReviewed:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/update-suggestion-reviewed
|
||||
method: patch
|
||||
|
||||
resendOTPmail:
|
||||
handler: src/modules/host/handlers/resendOtp.handler
|
||||
memorySize: 512
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/host/handlers/resendOtp/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /resend-otp
|
||||
method: post
|
||||
|
||||
63
src/common/utils/helper/resendOtpHelper.ts
Normal file
63
src/common/utils/helper/resendOtpHelper.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as bcrypt from "bcryptjs";
|
||||
import { OtpGenerator, OtpGeneratorSixDigit } from "./OtpGenerator";
|
||||
import { encryptUserId } from "./CodeGenerator";
|
||||
|
||||
export interface OtpResult {
|
||||
otp: string;
|
||||
hashedOtp: string;
|
||||
expiry: Date;
|
||||
encryptedId: string;
|
||||
}
|
||||
|
||||
export async function resendOtpHelper(
|
||||
prisma: any,
|
||||
userId: number,
|
||||
email: string,
|
||||
emailPurpose: "Register" | "Login" | "ForgotPassword",
|
||||
otpLength: 4 | 6 = 4,
|
||||
expiryMinutes: number = 5
|
||||
): Promise<OtpResult> {
|
||||
|
||||
// 1️⃣ Deactivate previous OTPs
|
||||
await prisma.userOtp.updateMany({
|
||||
where: {
|
||||
userXid: userId,
|
||||
otpType: emailPurpose,
|
||||
isActive: true,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
isVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 2️⃣ Generate new OTP
|
||||
const otp =
|
||||
otpLength === 6
|
||||
? OtpGeneratorSixDigit.generateOtp()
|
||||
: OtpGenerator.generateOtp();
|
||||
|
||||
const hashedOtp = await bcrypt.hash(otp, 10);
|
||||
const expiry = new Date(Date.now() + expiryMinutes * 60000);
|
||||
const encryptedId = encryptUserId(userId.toString());
|
||||
|
||||
// 3️⃣ Insert new OTP into table
|
||||
await prisma.userOtp.create({
|
||||
data: {
|
||||
userXid: userId,
|
||||
otpType: emailPurpose,
|
||||
otpCode: hashedOtp,
|
||||
expiresOn: expiry,
|
||||
isVerified: false,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 4️⃣ Return new OTP (email will use this)
|
||||
return {
|
||||
otp,
|
||||
hashedOtp,
|
||||
expiry,
|
||||
encryptedId,
|
||||
};
|
||||
}
|
||||
81
src/modules/host/handlers/resendOtp.ts
Normal file
81
src/modules/host/handlers/resendOtp.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
|
||||
import { PrismaService } from "../../../common/database/prisma.service";
|
||||
import { safeHandler } from "../../../common/utils/handlers/safeHandler";
|
||||
import ApiError from "../../../common/utils/helper/ApiError";
|
||||
import { resendOtpHelper } from "../../../common/utils/helper/resendOtpHelper";
|
||||
import { resendOtpEmail } from "../services/resendOTPEmail.service";
|
||||
|
||||
const prisma = new PrismaService();
|
||||
|
||||
// allowed purposes
|
||||
const ALLOWED_PURPOSES = ["Register", "Login", "ForgotPassword"] as const;
|
||||
type OtpPurpose = typeof ALLOWED_PURPOSES[number];
|
||||
|
||||
export const handler = safeHandler(
|
||||
async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
|
||||
// parse body safely
|
||||
let body: { email?: string; purpose?: string } = {};
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch {
|
||||
throw new ApiError(400, "Invalid JSON in request body");
|
||||
}
|
||||
|
||||
// allow passing purpose via query string too (useful for GET requests)
|
||||
const qsPurpose = event.queryStringParameters?.purpose;
|
||||
const purposeRaw = (body.purpose || qsPurpose || "").trim();
|
||||
|
||||
if (!purposeRaw) {
|
||||
throw new ApiError(400, "purpose is required. Allowed values: Register, Login, ForgotPassword");
|
||||
}
|
||||
|
||||
if (!ALLOWED_PURPOSES.includes(purposeRaw as OtpPurpose)) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
`Invalid purpose '${purposeRaw}'. Allowed values: ${ALLOWED_PURPOSES.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
const purpose = purposeRaw as OtpPurpose;
|
||||
|
||||
const email = (body.email || "").trim();
|
||||
if (!email) throw new ApiError(400, "Email is required");
|
||||
|
||||
// find user (you can adapt the isActive / userStatus checks per your rules)
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { emailAddress: email, isActive: true },
|
||||
select: { id: true, emailAddress: true, role: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, "User not found");
|
||||
}
|
||||
const role = user.role.roleName
|
||||
|
||||
// call resend helper (old OTPs become inactive + verified, new OTP gets created)
|
||||
const otpResult = await resendOtpHelper(
|
||||
prisma,
|
||||
user.id,
|
||||
user.emailAddress,
|
||||
purpose,
|
||||
6, // 6-digit OTP
|
||||
5 // expires in 5 minutes
|
||||
);
|
||||
|
||||
// send email (use appropriate template based on 'purpose' inside the email service)
|
||||
await resendOtpEmail(user.emailAddress, otpResult.otp, role);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: "OTP resent successfully.",
|
||||
data: { purpose },
|
||||
}),
|
||||
};
|
||||
}
|
||||
);
|
||||
39
src/modules/host/services/resendOTPEmail.service.ts
Normal file
39
src/modules/host/services/resendOTPEmail.service.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { brevoService } from "@/common/email/brevoApi";
|
||||
import ApiError from "@/common/utils/helper/ApiError";
|
||||
|
||||
export async function resendOtpEmail(
|
||||
emailAddress: string,
|
||||
otp: string | number,
|
||||
role: string
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "New OTP from Minglar Team";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear ${role},</p>
|
||||
<p>Your new OTP is: <strong>${otp}</strong></p>
|
||||
<p>This code is valid for 5 minutes. Please do not share it with anyone.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = await brevoService.sendEmail({
|
||||
recipients: [{ email: emailAddress }],
|
||||
subject,
|
||||
htmlContent,
|
||||
});
|
||||
|
||||
// console.log("📧 Email sent successfully:", result);
|
||||
|
||||
return {
|
||||
sent: true,
|
||||
// messageId: result.messageId
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Brevo email send failed:", err);
|
||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user