brackt/app/lib/auth.server.ts
Claude 4480f2e9b6
Clean up flag config code
- FlagSvg: remove redundant getDisplayFlagConfig call; config is already a
  valid FlagConfig by type, so the validation/fallback was dead code. Also
  replace the unreachable `?? "#adf661"` fallback with a non-null assertion.
- team/user models: pre-generate ID before insert so flagConfig is persisted
  immediately on creation rather than relying on display-time generation.
- auth.server.ts: add create.after hook so BetterAuth-created users also get
  a flagConfig written to the DB on signup.

https://claude.ai/code/session_014RUwPfm1qc539xLxW4m9Uy
2026-05-07 02:32:42 +00:00

107 lines
3.3 KiB
TypeScript

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { eq } from "drizzle-orm";
import bcrypt from "bcrypt";
import { Resend } from "resend";
import { db } from "~/server/db";
import * as schema from "~/database/schema";
import { generateFlagConfig } from "~/lib/flag-generator";
if (!process.env.BETTER_AUTH_SECRET) {
throw new Error("BETTER_AUTH_SECRET is required");
}
const resend = new Resend(process.env.RESEND_API_KEY);
const googleProvider = process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
? { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } }
: {};
const discordProvider = process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET
? { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET } }
: {};
const appBaseUrl = process.env.APP_URL ?? process.env.BETTER_AUTH_URL;
export const auth = betterAuth({
baseURL: appBaseUrl,
database: drizzleAdapter(db, {
provider: "pg",
usePlural: true,
}),
databaseHooks: {
user: {
create: {
after: async (user) => {
await db
.update(schema.users)
.set({ flagConfig: generateFlagConfig(user.id) })
.where(eq(schema.users.id, user.id));
},
},
update: {
before: async (data) => {
const incomingImage =
"imageUrl" in data ? data.imageUrl : "image" in data ? data.image : undefined;
const userId = typeof data.id === "string" ? data.id : null;
if (!incomingImage || !userId) return;
const existing = await db.query.users.findFirst({
where: eq(schema.users.id, userId),
});
if (existing?.avatarType === "uploaded" || existing?.avatarType === "flag") {
const next = { ...data };
delete next.imageUrl;
delete next.image;
return { data: next };
}
},
},
},
},
advanced: {
database: {
generateId: false,
},
},
user: {
fields: {
name: "displayName",
image: "imageUrl",
},
additionalFields: {
firstName: { type: "string", required: false, fieldName: "firstName" },
lastName: { type: "string", required: false, fieldName: "lastName" },
username: { type: "string", required: false, fieldName: "username" },
isAdmin: { type: "boolean", defaultValue: false, fieldName: "isAdmin" },
},
},
emailAndPassword: {
enabled: true,
password: {
hash: (password: string) => bcrypt.hash(password, 10),
verify: ({ hash, password }: { hash: string; password: string }) =>
bcrypt.compare(password, hash),
},
sendResetPassword: async ({ user, url }) => {
await resend.emails.send({
from: "Brackt <noreply@brackt.com>",
to: user.email,
subject: "Reset your Brackt password",
html: `<p>Click the link below to reset your password. This link expires in 1 hour.</p><p><a href="${url}">${url}</a></p>`,
});
},
},
socialProviders: {
...googleProvider,
...discordProvider,
},
account: {
accountLinking: {
enabled: true,
trustedProviders: ["google", "discord"],
},
},
});