File
Blob: src/worker/auth/ban.ts
| 1 | export 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`). |
| 20 | export 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 | }; |