338 lines · 14.4 KB
import { env } from "cloudflare:workers";
import { all, batch, first, run, stmt } from "./db";
import { hashPassword, randomId } from "./crypto";
import { badRequest, conflict, forbidden, notFoundError } from "./errors";
import { canAdministerAccount } from "./repos";
import { scheduleRevocationSweep, sweepRevokedOrgAccess } from "./pull-guard";
import type { OrgDefaultPermission } from "./repos";
import { assertBelowCap } from "./limits";
import { purgeOrgFollowers } from "./access-revocation";
import type { Viewer } from "~/lib/types";
import { isReservedName } from "~/lib/reserved-names";
import { cleanDisplayName } from "~/lib/validation";

/** Names that still redirect stay retired so old URLs and git remotes can't be taken over; only their owner may reclaim one. */
export async function accountNameTaken(name: string, reclaimingAccountId: string | null = null): Promise<boolean> {
  if (isReservedName(name)) return true;
  if ((await first<{ id: string }>("SELECT id FROM accounts WHERE name = ?", name)) !== null) return true;
  if ((await first("SELECT 1 AS ok FROM retired_account_names WHERE name = ? COLLATE NOCASE", name)) !== null) {
    return true;
  }
  const redirect = await first<{ account_id: string }>(
    "SELECT account_id FROM account_redirects WHERE old_name = ? COLLATE NOCASE",
    name,
  );
  return redirect !== null && redirect.account_id !== reclaimingAccountId;
}

/**
 * `SIGNUPS_ENABLED` is a boolean: `1`/`true`/`yes`/`on` (any case) open signups, anything else closes them
 * (AUTH-A7). Unset keeps the historical default of open.
 */
export function parseSignupsEnabled(raw: string | undefined): boolean {
  const value = raw?.trim();
  if (value === undefined || value === "") return true;
  return /^(1|true|yes|on)$/i.test(value);
}

export function signupsEnabled(): boolean {
  return parseSignupsEnabled(env.SIGNUPS_ENABLED);
}