import { betterAuth } from "better-auth" import { drizzleAdapter } from "better-auth/adapters/drizzle" import { nextCookies } from "better-auth/next-js" import { admin } from "better-auth/plugins" import { db } from "@/lib/db" import { user, session, account, verification, profiles } from "@/lib/db/schema" import { sendEmail } from "@/lib/email/send" // Bootstrap superadmins from env — no API path lets a user self-promote. const ADMIN_USER_IDS = (process.env.ADMIN_USER_IDS ?? "") .split(",") .map((s) => s.trim()) .filter(Boolean) export const auth = betterAuth({ baseURL: process.env.BETTER_AUTH_URL, secret: process.env.BETTER_AUTH_SECRET, database: drizzleAdapter(db, { provider: "pg", schema: { user, session, account, verification }, }), emailAndPassword: { enabled: true, // Env-gated so production can require a verified email without breaking // local dev (where RESEND is typically unconfigured). Set // REQUIRE_EMAIL_VERIFICATION=true in production to enforce. requireEmailVerification: process.env.REQUIRE_EMAIL_VERIFICATION === "true", minPasswordLength: 8, sendResetPassword: async ({ user: u, url }) => { await sendEmail({ to: u.email, subject: "Reset your Property Management Network password", html: resetPasswordHtml(url), }) }, }, // Send a verification email on sign-up. Enforcement of verified-email login // is gated by REQUIRE_EMAIL_VERIFICATION (see emailAndPassword above). emailVerification: { sendOnSignUp: true, sendVerificationEmail: async ({ user: u, url }) => { await sendEmail({ to: u.email, subject: "Verify your email — Property Management Network", html: verifyEmailHtml(url), }) }, }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID ?? "", clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "", }, }, // Throttle auth endpoints (per IP) to slow brute-force / credential stuffing. rateLimit: { enabled: true, window: 60, // seconds max: 20, // requests per window per IP for auth endpoints customRules: { // Tighter limit on the password sign-in endpoint to slow credential // stuffing / brute-force attempts. "/sign-in/email": { window: 60, max: 10 }, }, }, // Auto-create the app `profiles` row whenever Better Auth creates a user // (replaces the old `handle_new_user` Postgres trigger). databaseHooks: { user: { create: { after: async (u) => { try { await db .insert(profiles) .values({ id: u.id, email: u.email, full_name: u.name ?? null }) .onConflictDoNothing() } catch { // Never block sign-up on profile creation. } }, }, }, }, // `admin` enables role/ban/impersonation; `nextCookies` MUST stay last. plugins: [ admin({ adminUserIds: ADMIN_USER_IDS }), nextCookies(), ], }) function resetPasswordHtml(url: string) { return `
Click the button below to choose a new password. If you didn't request this, you can ignore this email.
Reset PasswordProperty Management Network
Confirm your email address to finish setting up your account. If you didn't create an account, you can ignore this email.
Verify EmailProperty Management Network