import db from "@server/db"; import { resourceOtp } from "@server/db/schema"; import { and, eq } from "drizzle-orm"; import { createDate, isWithinExpirationDate, TimeSpan } from "oslo"; import { alphabet, generateRandomString, sha256 } from "oslo/crypto"; import { sendEmail } from "@server/emails"; import ResourceOTPCode from "@server/emails/templates/ResourceOTPCode"; import config from "@server/lib/config"; import { verifyPassword } from "./password"; import { hashPassword } from "./password"; export async function sendResourceOtpEmail( email: string, resourceId: number, resourceName: string, orgName: string ): Promise { const otp = await generateResourceOtpCode(resourceId, email); await sendEmail( ResourceOTPCode({ email, resourceName, orgName, otp }), { to: email, from: config.getNoReplyEmail(), subject: `Your one-time code to access ${resourceName}` } ); } export async function generateResourceOtpCode( resourceId: number, email: string ): Promise { const otp = generateRandomString(8, alphabet("0-9", "A-Z", "a-z")); await db.transaction(async (trx) => { await trx .delete(resourceOtp) .where( and( eq(resourceOtp.email, email), eq(resourceOtp.resourceId, resourceId) ) ); const otpHash = await hashPassword(otp); await trx.insert(resourceOtp).values({ resourceId, email, otpHash, expiresAt: createDate(new TimeSpan(15, "m")).getTime() }); }); return otp; } export async function isValidOtp( email: string, resourceId: number, otp: string ): Promise { const record = await db .select() .from(resourceOtp) .where( and( eq(resourceOtp.email, email), eq(resourceOtp.resourceId, resourceId) ) ) .limit(1); if (record.length === 0) { return false; } const validCode = await verifyPassword(otp, record[0].otpHash); if (!validCode) { return false; } if (!isWithinExpirationDate(new Date(record[0].expiresAt))) { return false; } return true; }