mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-30 14:39:29 +02:00
add otp flow to resource auth portal
This commit is contained in:
parent
acab2ff3a7
commit
b331bc85ea
14 changed files with 1159 additions and 376 deletions
102
server/auth/resourceOtp.ts
Normal file
102
server/auth/resourceOtp.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
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 { encodeHex } from "oslo/encoding";
|
||||
import { sendEmail } from "@server/emails";
|
||||
import ResourceOTPCode from "@server/emails/templates/ResourceOTPCode";
|
||||
import config from "@server/config";
|
||||
import { hash, verify } from "@node-rs/argon2";
|
||||
|
||||
export async function sendResourceOtpEmail(
|
||||
email: string,
|
||||
resourceId: number,
|
||||
resourceName: string,
|
||||
orgName: string
|
||||
): Promise<void> {
|
||||
const otp = await generateResourceOtpCode(resourceId, email);
|
||||
|
||||
await sendEmail(
|
||||
ResourceOTPCode({
|
||||
email,
|
||||
resourceName,
|
||||
orgName,
|
||||
otp
|
||||
}),
|
||||
{
|
||||
to: email,
|
||||
from: config.email?.no_reply,
|
||||
subject: `Your one-time code to access ${resourceName}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateResourceOtpCode(
|
||||
resourceId: number,
|
||||
email: string
|
||||
): Promise<string> {
|
||||
await db
|
||||
.delete(resourceOtp)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceOtp.email, email),
|
||||
eq(resourceOtp.resourceId, resourceId)
|
||||
)
|
||||
);
|
||||
|
||||
const otp = generateRandomString(8, alphabet("0-9", "A-Z", "a-z"));
|
||||
|
||||
const otpHash = await hash(otp, {
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
outputLen: 32,
|
||||
parallelism: 1,
|
||||
});
|
||||
|
||||
await db.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<boolean> {
|
||||
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 verify(record[0].otpHash, otp, {
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
outputLen: 32,
|
||||
parallelism: 1
|
||||
});
|
||||
if (!validCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isWithinExpirationDate(new Date(record[0].expiresAt))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue