@proofkit/cli
Version:
Create web application with the ProofKit stack
153 lines (138 loc) • 4.58 kB
text/typescript
import { encodeHexLowerCase } from "@oslojs/encoding";
import { generateRandomOTP } from "./index";
import { sha256 } from "@oslojs/crypto/sha2";
import { cookies } from "next/headers";
import { passwordResetLayout } from "../db/client";
import { TpasswordReset } from "../db/passwordReset";
import type { User } from "./user";
import { sendEmail } from "../email";
type PasswordResetSession = Omit<
TpasswordReset,
| "proofkit_auth_users::email"
| "proofkit_auth_users::emailVerified"
| "proofkit_auth_users::username"
>;
export async function createPasswordResetSession(
token: string,
id_user: string,
email: string
): Promise<PasswordResetSession> {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const session: PasswordResetSession = {
id: sessionId,
id_user,
email,
expires_at: Math.floor(
new Date(Date.now() + 1000 * 60 * 10).getTime() / 1000
),
code: generateRandomOTP(),
email_verified: 0,
};
await passwordResetLayout.create({ fieldData: session });
return session;
}
/**
* Validate a password reset session token.
* @param token - The password reset session token.
* @returns The password reset session, or null if it doesn't exist.
*/
export async function validatePasswordResetSessionToken(
token: string
): Promise<PasswordResetSessionValidationResult> {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const row = await passwordResetLayout.maybeFindFirst({
query: { id: `==${sessionId}` },
});
if (row === null) {
return { session: null, user: null };
}
const session: PasswordResetSession = {
id: row.data.fieldData.id,
id_user: row.data.fieldData.id_user,
email: row.data.fieldData.email,
code: row.data.fieldData.code,
expires_at: row.data.fieldData.expires_at,
email_verified: row.data.fieldData.email_verified,
};
const user: User = {
id: row.data.fieldData.id_user,
email: row.data.fieldData["proofkit_auth_users::email"],
username: row.data.fieldData["proofkit_auth_users::username"],
emailVerified: Boolean(
row.data.fieldData["proofkit_auth_users::emailVerified"]
),
};
if (session.expires_at && Date.now() >= session.expires_at * 1000) {
await passwordResetLayout.delete({ recordId: row.data.recordId });
return { session: null, user: null };
}
return { session, user };
}
async function fetchPasswordResetSession(sessionId: string) {
return (
await passwordResetLayout.findOne({ query: { id: `==${sessionId}` } })
).data;
}
export async function setPasswordResetSessionAsEmailVerified(
sessionId: string
): Promise<void> {
const { recordId } = await fetchPasswordResetSession(sessionId);
await passwordResetLayout.update({
recordId,
fieldData: { email_verified: 1 },
});
}
export async function invalidateUserPasswordResetSessions(
userId: string
): Promise<void> {
const sessions = await passwordResetLayout.find({
query: { id_user: `==${userId}` },
ignoreEmptyResult: true,
});
for (const session of sessions.data) {
await passwordResetLayout.delete({ recordId: session.recordId });
}
}
export async function validatePasswordResetSessionRequest(): Promise<PasswordResetSessionValidationResult> {
const token = (await cookies()).get("password_reset_session")?.value ?? null;
if (token === null) {
return { session: null, user: null };
}
const result = await validatePasswordResetSessionToken(token);
if (result.session === null) {
deletePasswordResetSessionTokenCookie();
}
return result;
}
export async function setPasswordResetSessionTokenCookie(
token: string,
expiresAt: number | null
): Promise<void> {
(await cookies()).set("password_reset_session", token, {
expires: expiresAt
? new Date(expiresAt * 1000)
: new Date(Date.now() + 60 * 60 * 1000),
sameSite: "lax",
httpOnly: true,
path: "/",
secure: process.env.NODE_ENV === "production",
});
}
export async function deletePasswordResetSessionTokenCookie(): Promise<void> {
(await cookies()).set("password_reset_session", "", {
maxAge: 0,
sameSite: "lax",
httpOnly: true,
path: "/",
secure: process.env.NODE_ENV === "production",
});
}
export async function sendPasswordResetEmail(
email: string,
code: string
): Promise<void> {
await sendEmail({ to: email, code, type: "password-reset" });
}
export type PasswordResetSessionValidationResult =
| { session: PasswordResetSession; user: User }
| { session: null; user: null };