import type { Transaction } from "./auth-transaction"; import { and, eq, isNotNull, ne, or, isNull, sql, like } from "drizzle-orm"; import { accounts, users, verifications } from "../../drizzle/schema"; const ADMIN_LOCK_ID = 7_261_009; // Deletion, banning and demotion must all hold this lock until commit. export const lockAdministration = async (tx: Transaction): Promise => { await tx.execute(sql`select pg_advisory_xact_lock(${ADMIN_LOCK_ID})`); }; export const hasUsableAdministrator = async (tx: Transaction, excludedUserId?: string): Promise => { const rows = await tx.select({ id: users.id }) .from(users) .innerJoin(accounts, eq(accounts.userId, users.id)) .where(and( eq(users.role, "admin"), excludedUserId === undefined ? undefined : ne(users.id, excludedUserId), or(eq(users.banned, false), isNull(users.banned)), eq(accounts.providerId, "credential"), isNotNull(accounts.password), ne(accounts.password, ""), )) .limit(1); return rows.length > 0; }; // All credential, recovery-link and provider-config writes use the user row // as their common lock, including when no credential or recovery row exists. export const lockAccount = async (tx: Transaction, userId: string): Promise => { const rows = await tx.select({ id: users.id }).from(users) .where(eq(users.id, userId)).for("update"); return rows.length > 0; }; export const revokeTwoFactorTrust = async (tx: Transaction, userId: string): Promise => { await tx.delete(verifications).where(and( eq(verifications.value, userId), or( like(verifications.identifier, "trust-device-%"), like(verifications.identifier, "2fa-%"), ), )); };