import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { organization } from "better-auth/plugins"; import { magicLink } from "better-auth/plugins/magic-link"; import { db } from "@/lib/db"; import * as schema from "@/lib/db/schema"; const isProduction = process.env.NODE_ENV === "production"; export const auth = betterAuth({ // ------------------------------------------------------------------------- // Core // ------------------------------------------------------------------------- secret: process.env.BETTER_AUTH_SECRET!, baseURL: process.env.BETTER_AUTH_URL ?? "http://localhost:3000", // ------------------------------------------------------------------------- // Database adapter (Drizzle + bun:sqlite) // ------------------------------------------------------------------------- database: drizzleAdapter(db, { provider: "sqlite", schema: { users: schema.users, sessions: schema.sessions, accounts: schema.accounts, verifications: schema.verifications, }, usePlural: false, }), // ------------------------------------------------------------------------- // Custom user fields // ------------------------------------------------------------------------- user: { additionalFields: { role: { type: "string", required: false, defaultValue: "moderator", input: false, // Not settable by the user directly }, }, }, // ------------------------------------------------------------------------- // Email + password authentication // ------------------------------------------------------------------------- emailAndPassword: { enabled: true, requireEmailVerification: false, minPasswordLength: 8, maxPasswordLength: 128, }, // ------------------------------------------------------------------------- // Plugins // ------------------------------------------------------------------------- plugins: [ // Organization / role support organization(), // Magic link — used for invitation acceptance flows magicLink({ expiresIn: 60 * 60, // 1 hour disableSignUp: true, // magic links are only for invited users sendMagicLink: async ({ email, url, token }) => { // Delegate to the application's email module. The email module is // responsible for importing and calling Resend (or whichever mailer // is configured). We do a dynamic import so that this file does not // pull in email dependencies at auth-initialisation time on the edge. const { sendMagicLinkEmail } = await import("@/lib/email/index"); await sendMagicLinkEmail({ email, url, token }); }, }), ], // ------------------------------------------------------------------------- // Trusted origins — allow env-configured list plus localhost in dev // ------------------------------------------------------------------------- trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS ? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(",").map((o) => o.trim()) : ["http://localhost:3000"], // ------------------------------------------------------------------------- // Cookie / session security // ------------------------------------------------------------------------- advanced: { useSecureCookies: isProduction, defaultCookieAttributes: { httpOnly: true, secure: isProduction, sameSite: "strict", path: "/", }, }, }); // --------------------------------------------------------------------------- // Type helpers for use across the application // --------------------------------------------------------------------------- export type Auth = typeof auth; /** The server-side session type returned by auth.api.getSession */ export type Session = typeof auth.$Infer.Session.session; /** The user type embedded in every session */ export type User = typeof auth.$Infer.Session.user;