Skip to content
File

Blob: src/worker/auth/ban.ts

typescript27 lines
1export type BannableUser = Record<string, unknown> & {
2 banned?: boolean | null;
3 banExpires?: Date | string | number | null;
4};
5 
6// Defense-in-depth: tessera's `handleBanUser` flips `users.banned` and
7// drops sessions + OAuth tokens atomically, so a banned user normally
8// has no live session or token row. This predicate covers the case
9// where `banned` is flipped outside that path — manual D1 fixes, admin
10// tooling that misses the wrapper, or Better Auth's plugin
11// /admin/ban-user route. An expired temporary ban (`banExpires` in the
12// past) still allows the request, matching Better Auth's auto-clear
13// behavior for expired bans.
14//
15// Single source of truth so the predicate stays consistent across the
16// session-cookie middleware (`worker/middleware/auth.ts`) and the
17// Better Auth hook surfaces (`worker/auth/index.ts`: `hooks.before`,
18// `customTokenResponseFields`, `customAccessTokenClaims`,
19// `customUserInfoClaims`).
20export const isActiveBan = (user: BannableUser): boolean => {
21 if (user.banned !== true) return false;
22 if (user.banExpires == null) return true;
23 const expiresMs = user.banExpires instanceof Date ? user.banExpires.getTime() : new Date(user.banExpires).getTime();
24 if (!Number.isFinite(expiresMs)) return true;
25 return expiresMs > Date.now();
26};