better-chatbot / src /lib /auth /auth-instance.ts
Bot
Initial commit for HF Spaces
05c5ed5
Raw
History Blame Contribute Delete
4.31 kB
// Base auth instance without "server-only" - can be used in seed scripts
import { betterAuth, type BetterAuthOptions } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { nextCookies } from "better-auth/next-js";
import { admin as adminPlugin } from "better-auth/plugins";
import { pgDb } from "lib/db/pg/db.pg";
import { headers } from "next/headers";
import {
AccountTable,
SessionTable,
UserTable,
VerificationTable,
} from "lib/db/pg/schema.pg";
import { getAuthConfig } from "./config";
import logger from "logger";
import { userRepository } from "lib/db/repository";
import { DEFAULT_USER_ROLE, USER_ROLES } from "app-types/roles";
import { admin, editor, user, ac } from "./roles";
const {
emailAndPasswordEnabled,
signUpEnabled,
socialAuthenticationProviders,
} = getAuthConfig();
const options = {
secret: process.env.BETTER_AUTH_SECRET!,
plugins: [
adminPlugin({
defaultRole: DEFAULT_USER_ROLE,
adminRoles: [USER_ROLES.ADMIN],
ac,
roles: {
admin,
editor,
user,
},
}),
nextCookies(),
],
baseURL: process.env.BETTER_AUTH_URL || process.env.NEXT_PUBLIC_BASE_URL,
user: {
changeEmail: {
enabled: true,
},
deleteUser: {
enabled: true,
},
},
database: drizzleAdapter(pgDb, {
provider: "pg",
schema: {
user: UserTable,
session: SessionTable,
account: AccountTable,
verification: VerificationTable,
},
}),
databaseHooks: {
user: {
create: {
before: async (user) => {
// This hook ONLY runs during user creation (sign-up), not on sign-in
// Use our optimized getIsFirstUser function with caching
const isFirstUser = await getIsFirstUser();
// Set role based on whether this is the first user
const role = isFirstUser ? USER_ROLES.ADMIN : DEFAULT_USER_ROLE;
logger.info(
`User creation hook: ${user.email} will get role: ${role} (isFirstUser: ${isFirstUser})`,
);
return {
data: {
...user,
role,
},
};
},
},
},
},
emailAndPassword: {
enabled: emailAndPasswordEnabled,
disableSignUp: !signUpEnabled,
},
session: {
cookieCache: {
enabled: true,
maxAge: 60 * 60,
},
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day (every 1 day the session expiration is updated)
},
advanced: {
useSecureCookies:
process.env.NO_HTTPS == "1"
? false
: process.env.NODE_ENV === "production",
database: {
generateId: false,
},
},
account: {
accountLinking: {
trustedProviders: (
Object.keys(
socialAuthenticationProviders,
) as (keyof typeof socialAuthenticationProviders)[]
).filter((key) => socialAuthenticationProviders[key]),
},
},
socialProviders: socialAuthenticationProviders,
} satisfies BetterAuthOptions;
export const auth = betterAuth({
...options,
plugins: [...(options.plugins ?? [])],
});
export const getSession = async () => {
const reqHeaders = await headers();
try {
const session = await auth.api.getSession({
headers: reqHeaders,
});
return session ?? null;
} catch (error) {
logger.error("Error getting session:", error);
return null;
}
};
// Cache the first user check to avoid repeated DB queries
let isFirstUserCache: boolean | null = null;
export const getIsFirstUser = async () => {
// If we already know there's at least one user, return false immediately
// This in-memory cache prevents any DB calls once we know users exist
if (isFirstUserCache === false) {
return false;
}
try {
// Direct database query - simple and reliable
const userCount = await userRepository.getUserCount();
const isFirstUser = userCount === 0;
// Once we have at least one user, cache it permanently in memory
if (!isFirstUser) {
isFirstUserCache = false;
}
return isFirstUser;
} catch (error) {
logger.error("Error checking if first user:", error);
// Cache as false on error to prevent repeated attempts
isFirstUserCache = false;
return false;
}
};