107 lines · 4.1 KB
import { env } from "cloudflare:workers";
import { all, first, run } from "./db";
import { forbidden, notFoundError } from "./errors";
import { disabledMessage } from "~/lib/account-disable";
import type { DisableReason, DisabledNotice } from "~/lib/account-disable";
import { userChanged } from "./user-row";

export type AccountDisableState = { reason: string; at: number; note: string | null };

export type DisabledAccount = {
  id: string;
  name: string;
  kind: string;
  reason: string;
  note: string | null;
  disabledAt: number;
};

/** SUPPORT_EMAIL, else the legal contact, so a forge without a dedicated inbox still shows a way back in. */
export function supportEmail(): string | null {
  return env.SUPPORT_EMAIL?.trim() || env.LEGAL_CONTACT_EMAIL?.trim() || null;
}

export function disabledNotice(reason: string): DisabledNotice {
  const email = supportEmail();
  return { reason, message: disabledMessage(reason, email), supportEmail: email };
}

export async function accountDisableState(accountId: string): Promise<AccountDisableState | null> {
  const row = await first<{ disabled_at: number | null; disabled_reason: string | null; disabled_note: string | null }>(
    "SELECT disabled_at, disabled_reason, disabled_note FROM accounts WHERE id = ?",
    accountId,
  );
  if (!row || row.disabled_at === null) return null;
  return { reason: row.disabled_reason ?? "admin", at: row.disabled_at, note: row.disabled_note };
}

/** Refuses sign-in (and anything else that would hand out a credential) for a disabled account. */
export async function assertAccountActive(accountId: string): Promise<void> {
  const state = await accountDisableState(accountId);