266 lines · 12.6 KB
import { batch, first, run, stmt } from "./db";
import { hashPassword, passwordNeedsRehash, randomToken, verifyPassword } from "./crypto";
import { consumeAuthToken, findAuthToken, storeAuthToken } from "./auth-tokens";
import { deleteUserSessions, sessionCreatedAt } from "./auth";
import { enforceRateLimit } from "./limits";
import { emailTemplates, queueEmail } from "./email";
import { publicUrl } from "./events/emit";
import { badRequest, conflict } from "./errors";
import { reclaimUnverifiedEmail } from "./accounts";
import { invalidateUserSignatures } from "./security/signatures";

const VERIFY_TTL_SECONDS = 60 * 60 * 24;
const RESET_TTL_SECONDS = 60 * 60;

type VerifyPayload = { email: string };
type SpentReason = "used" | "superseded";
/** `viaPrimary` is false when the link went to one of the user's other verified addresses. */
type ResetPayload = { passwordHash: string | null; viaPrimary: boolean };

function parseVerify(raw: string): VerifyPayload {
  const parsed: unknown = JSON.parse(raw);
  const email = typeof parsed === "object" && parsed !== null && "email" in parsed ? parsed.email : null;
  if (typeof email !== "string") throw badRequest("This verification link is invalid");
  return { email };
}

function parseReset(raw: string): ResetPayload {
  const parsed: unknown = JSON.parse(raw);
  const hash = typeof parsed === "object" && parsed !== null && "passwordHash" in parsed ? parsed.passwordHash : null;
  const viaPrimary =
    typeof parsed === "object" && parsed !== null && "viaPrimary" in parsed ? parsed.viaPrimary !== false : true;
  return { passwordHash: typeof hash === "string" ? hash : null, viaPrimary };
}

async function displayNameFor(userId: string): Promise<string> {
  return (await first<{ name: string }>("SELECT name FROM accounts WHERE id = ?", userId))?.name ?? "there";
}

/** Sends a single-use verification link for the primary address or one of the user's other addresses. */
export async function sendEmailVerification(userId: string, email: string): Promise<void> {