Fix BetterAuth ID generation, clean up review issues

- Set generateId: false so BetterAuth omits id from inserts; add
  $defaultFn(crypto.randomUUID) to accounts/sessions/verifications
  so Drizzle fills it in (fixes null id constraint violation on signup)
- Move renameTeam to static import; rename before removeTeamOwner so
  a failed rename leaves owner intact rather than leaving a stale prefix
- Clarify stripOwnerFromTeamName toTitleCase assumption in comment
- Annotate reset-password token fallback to explain loader guarantee

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-04-29 09:58:49 -07:00
parent 7e8c5afde5
commit 75dedd0a90
5 changed files with 9 additions and 8 deletions

View file

@ -25,7 +25,7 @@ export const auth = betterAuth({
}), }),
advanced: { advanced: {
database: { database: {
generateId: "uuid", generateId: false,
}, },
}, },
user: { user: {

View file

@ -15,7 +15,7 @@ import {
removeCommissionerByLeagueAndUser, removeCommissionerByLeagueAndUser,
} from "~/models/commissioner"; } from "~/models/commissioner";
import { findCurrentSeasonWithSports, updateSeason, type NewSeason } from "~/models/season"; import { findCurrentSeasonWithSports, updateSeason, type NewSeason } from "~/models/season";
import { findTeamsBySeasonId, deleteTeam, removeTeamOwner, claimTeam, findTeamById } from "~/models/team"; import { findTeamsBySeasonId, deleteTeam, removeTeamOwner, claimTeam, findTeamById, renameTeam } from "~/models/team";
import { findAllUsers, findUsersByIds, findUserById, getUserDisplayName, isUserAdmin } from "~/models/user"; import { findAllUsers, findUsersByIds, findUserById, getUserDisplayName, isUserAdmin } from "~/models/user";
import { prependOwnerToTeamName, stripOwnerFromTeamName, generateUniqueTeamNames } from "~/utils/team-names"; import { prependOwnerToTeamName, stripOwnerFromTeamName, generateUniqueTeamNames } from "~/utils/team-names";
import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport"; import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport";
@ -407,11 +407,10 @@ export async function action(args: Route.ActionArgs) {
const username = owner?.username ?? owner?.displayName; const username = owner?.username ?? owner?.displayName;
if (username) { if (username) {
const strippedName = stripOwnerFromTeamName(team.name, username); const strippedName = stripOwnerFromTeamName(team.name, username);
await removeTeamOwner(teamId);
if (strippedName !== team.name) { if (strippedName !== team.name) {
const { renameTeam } = await import("~/models/team");
await renameTeam(teamId, strippedName); await renameTeam(teamId, strippedName);
} }
await removeTeamOwner(teamId);
} else { } else {
await removeTeamOwner(teamId); await removeTeamOwner(teamId);
} }

View file

@ -22,7 +22,7 @@ export async function loader(args: Route.LoaderArgs) {
export default function ResetPasswordPage() { export default function ResetPasswordPage() {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const token = searchParams.get("token") ?? ""; const token = searchParams.get("token") ?? ""; // loader redirects to /forgot-password when absent
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);

View file

@ -93,6 +93,8 @@ export function prependOwnerToTeamName(
return `${titleName}${possessiveSuffix(titleName)} ${baseName}`; return `${titleName}${possessiveSuffix(titleName)} ${baseName}`;
} }
// Relies on the name having been set via prependOwnerToTeamName (same toTitleCase logic).
// If username casing differs from when the name was set, the strip is a safe no-op.
export function stripOwnerFromTeamName( export function stripOwnerFromTeamName(
teamName: string, teamName: string,
username: string username: string

View file

@ -19,7 +19,7 @@ export const users = pgTable("users", {
// BetterAuth session/account/verification tables // BetterAuth session/account/verification tables
export const sessions = pgTable("sessions", { export const sessions = pgTable("sessions", {
id: text("id").primaryKey(), id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
expiresAt: timestamp("expires_at").notNull(), expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(), token: text("token").notNull().unique(),
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
@ -30,7 +30,7 @@ export const sessions = pgTable("sessions", {
}); });
export const accounts = pgTable("accounts", { export const accounts = pgTable("accounts", {
id: text("id").primaryKey(), id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
accountId: text("account_id").notNull(), accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(), providerId: text("provider_id").notNull(),
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
@ -47,7 +47,7 @@ export const accounts = pgTable("accounts", {
}); });
export const verifications = pgTable("verifications", { export const verifications = pgTable("verifications", {
id: text("id").primaryKey(), id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
identifier: text("identifier").notNull(), identifier: text("identifier").notNull(),
value: text("value").notNull(), value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(), expiresAt: timestamp("expires_at").notNull(),