Files
property-management-network/lib/auth.ts
T

91 lines
3.2 KiB
TypeScript
Raw Normal View History

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,
// Login works immediately; flip to true once verification email is desired.
// Recommended for production: set requireEmailVerification to true.
requireEmailVerification: false,
minPasswordLength: 8,
sendResetPassword: async ({ user: u, url }) => {
await sendEmail({
to: u.email,
subject: "Reset your Property Management Network password",
html: resetPasswordHtml(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
},
// 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 `
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; background: #09090b; color: #fff; padding: 40px 20px; max-width: 560px; margin: 0 auto;">
<div style="background: #16161f; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 32px;">
<h1 style="font-size: 20px; margin: 0 0 8px; color: #fff;">Reset your password</h1>
<p style="color: rgba(255,255,255,0.6); margin: 0 0 24px;">
Click the button below to choose a new password. If you didn't request this, you can ignore this email.
</p>
<a href="${url}" style="display: inline-block; background: #6366f1; color: #fff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">
Reset Password
</a>
<p style="color: rgba(255,255,255,0.4); font-size: 12px; margin: 24px 0 0;">Property Management Network</p>
</div>
</body>
</html>`
}