migrate: replace NextAuth.js with Better Auth

- Install better-auth and @better-auth/drizzle-adapter
- Create src/lib/auth.ts with Better Auth configuration using bcrypt
- Update database schema: change auth table IDs from uuid to text
- Update route handler from /api/auth/[...nextauth] to /api/auth/[...all]
- Update tRPC context and middleware for Better Auth session handling
- Update client components to use Better Auth APIs (signIn, signOut)
- Update seed script with text-based IDs and correct account schema
- Fix type errors in wizard components (robotId, optional chaining)
- Fix API paths: api.robots.initialize -> api.robots.plugins.initialize
- Update auth router to use text IDs for Better Auth compatibility

Note: Auth tables were reset - users will need to re-register.
This commit is contained in:
2026-03-21 23:03:55 -04:00
parent 4bed537943
commit 20d6d3de1a
37 changed files with 460 additions and 1182 deletions
+20 -16
View File
@@ -16,19 +16,10 @@ import { Separator } from "~/components/ui/separator";
import { PageHeader } from "~/components/ui/page-header";
import { useBreadcrumbsEffect } from "~/components/ui/breadcrumb-provider";
import { formatRole, getRoleDescription } from "~/lib/auth-client";
import {
User,
Shield,
Download,
Trash2,
ExternalLink,
Lock,
UserCog,
Mail,
Fingerprint,
} from "lucide-react";
import { useSession } from "next-auth/react";
import { User, Shield, Download, Trash2, Lock, UserCog } from "lucide-react";
import { useSession } from "~/lib/auth-client";
import { cn } from "~/lib/utils";
import { api } from "~/trpc/react";
interface ProfileUser {
id: string;
@@ -37,7 +28,8 @@ interface ProfileUser {
image: string | null;
roles?: Array<{
role: "administrator" | "researcher" | "wizard" | "observer";
grantedAt: string | Date;
grantedAt: Date;
grantedBy: string | null;
}>;
}
@@ -213,14 +205,20 @@ function ProfileContent({ user }: { user: ProfileUser }) {
}
export default function ProfilePage() {
const { data: session, status } = useSession();
const { data: session, isPending } = useSession();
const { data: userData, isPending: isUserPending } = api.auth.me.useQuery(
undefined,
{
enabled: !!session?.user,
},
);
useBreadcrumbsEffect([
{ label: "Dashboard", href: "/dashboard" },
{ label: "Profile" },
]);
if (status === "loading") {
if (isPending || isUserPending) {
return (
<div className="text-muted-foreground animate-pulse p-8">
Loading profile...
@@ -232,7 +230,13 @@ export default function ProfilePage() {
redirect("/auth/signin");
}
const user = session.user;
const user: ProfileUser = {
id: session.user.id,
name: userData?.name ?? session.user.name ?? null,
email: userData?.email ?? session.user.email,
image: userData?.image ?? session.user.image ?? null,
roles: userData?.systemRoles as ProfileUser["roles"],
};
return <ProfileContent user={user} />;
}