import { first } from "./db"; import { HttpError } from "./errors"; import { EMAIL_UNVERIFIED, EMAIL_UNVERIFIED_MESSAGE, VERIFY_EMAIL_PATH, canWriteAsViewer, } from "~/lib/email-verification"; import type { Viewer } from "~/lib/types"; /** * The one gate for writing and paying (AUTH-B1). It passes for a verified primary email, a linked GitHub * account, site admins and instances without email (all folded into `viewer.emailVerified` by toViewer, the * same rule as metering's TRUSTED_USER). Account self-service (profile, emails, password, 2FA, sessions, * deletion) and starring/following stay open so the holder can always fix or leave the account. */ export function assertVerifiedForWrites(viewer: Pick): void { if (canWriteAsViewer(viewer)) return; throw new HttpError(403, EMAIL_UNVERIFIED_MESSAGE, { code: EMAIL_UNVERIFIED, verifyPath: VERIFY_EMAIL_PATH }); } /** Deleting a repository is self-service only for its owning user; deleting one as a collaborator or org admin is a write. */ export function assertMayDeleteRepo( repo: { owner_id: string }, viewer: Pick, ): void { if (repo.owner_id !== viewer.id) assertVerifiedForWrites(viewer); } /** Deleting an organization is self-service only when the viewer is its sole member. */ export async function assertMayDeleteOrg( orgId: string, viewer: Pick, ): Promise { if (canWriteAsViewer(viewer)) return; const others = await first<{ ok: number }>( "SELECT 1 AS ok FROM org_members WHERE org_id = ? AND user_id != ? LIMIT 1", orgId, viewer.id, ); const member = await first<{ ok: number }>( "SELECT 1 AS ok FROM org_members WHERE org_id = ? AND user_id = ?", orgId, viewer.id, ); if (others || !member) assertVerifiedForWrites(viewer); }