47 lines
1.8 KiB
TypeScript
47 lines
1.8 KiB
TypeScript
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<void> => {
|
|
await tx.execute(sql`select pg_advisory_xact_lock(${ADMIN_LOCK_ID})`);
|
|
};
|
|
|
|
export const hasUsableAdministrator = async (tx: Transaction, excludedUserId?: string): Promise<boolean> => {
|
|
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<boolean> => {
|
|
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<void> => {
|
|
await tx.delete(verifications).where(and(
|
|
eq(verifications.value, userId),
|
|
or(
|
|
like(verifications.identifier, "trust-device-%"),
|
|
like(verifications.identifier, "2fa-%"),
|
|
),
|
|
));
|
|
};
|