fix(auth): migrate from next-auth to better-auth (#1453)

This commit is contained in:
dancer 2026-03-13 23:18:01 +00:00 committed by GitHub
parent 453f5bb3e6
commit b4f595a68c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 668 additions and 390 deletions

View file

@ -0,0 +1,49 @@
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "name" text;
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "emailVerified" boolean NOT NULL DEFAULT false;
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "image" text;
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "isAnonymous" boolean NOT NULL DEFAULT false;
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "createdAt" timestamp DEFAULT now() NOT NULL;
ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "updatedAt" timestamp DEFAULT now() NOT NULL;
CREATE TABLE IF NOT EXISTS "Session" (
"id" text PRIMARY KEY,
"expiresAt" timestamp NOT NULL,
"token" text NOT NULL UNIQUE,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
"ipAddress" text,
"userAgent" text,
"userId" uuid NOT NULL REFERENCES "User"("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "Account" (
"id" text PRIMARY KEY,
"accountId" text NOT NULL,
"providerId" text NOT NULL,
"userId" uuid NOT NULL REFERENCES "User"("id") ON DELETE CASCADE,
"accessToken" text,
"refreshToken" text,
"idToken" text,
"accessTokenExpiresAt" timestamp,
"refreshTokenExpiresAt" timestamp,
"scope" text,
"password" text,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS "Verification" (
"id" text PRIMARY KEY,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expiresAt" timestamp NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL
);
UPDATE "User" SET "isAnonymous" = true WHERE email ~ '^guest-\d+$';
INSERT INTO "Account" ("id", "accountId", "providerId", "userId", "password", "createdAt", "updatedAt")
SELECT gen_random_uuid()::text, id::text, 'credential', id, password, now(), now()
FROM "User" WHERE password IS NOT NULL
ON CONFLICT DO NOTHING;

View file

@ -17,7 +17,7 @@ import postgres from "postgres";
import type { ArtifactKind } from "@/components/artifact";
import type { VisibilityType } from "@/components/visibility-selector";
import { ChatbotError } from "../errors";
import { generateUUID } from "../utils";
import {
type Chat,
chat,
@ -27,58 +27,13 @@ import {
type Suggestion,
stream,
suggestion,
type User,
user,
vote,
} from "./schema";
import { generateHashedPassword } from "./utils";
// Optionally, if not using email/pass login, you can
// use the Drizzle adapter for Auth.js / NextAuth
// https://authjs.dev/reference/adapter/drizzle
// biome-ignore lint: Forbidden non-null assertion.
const client = postgres(process.env.POSTGRES_URL!);
const db = drizzle(client);
export async function getUser(email: string): Promise<User[]> {
try {
return await db.select().from(user).where(eq(user.email, email));
} catch (_error) {
throw new ChatbotError(
"bad_request:database",
"Failed to get user by email"
);
}
}
export async function createUser(email: string, password: string) {
const hashedPassword = generateHashedPassword(password);
try {
return await db.insert(user).values({ email, password: hashedPassword });
} catch (_error) {
throw new ChatbotError("bad_request:database", "Failed to create user");
}
}
export async function createGuestUser() {
const email = `guest-${Date.now()}`;
const password = generateHashedPassword(generateUUID());
try {
return await db.insert(user).values({ email, password }).returning({
id: user.id,
email: user.email,
});
} catch (_error) {
throw new ChatbotError(
"bad_request:database",
"Failed to create guest user"
);
}
}
export async function saveChat({
id,
userId,

View file

@ -15,10 +15,62 @@ export const user = pgTable("User", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
email: varchar("email", { length: 64 }).notNull(),
password: varchar("password", { length: 64 }),
name: text("name"),
emailVerified: boolean("emailVerified").notNull().default(false),
image: text("image"),
isAnonymous: boolean("isAnonymous").notNull().default(false),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type User = InferSelectModel<typeof user>;
export const session = pgTable("Session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expiresAt").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
ipAddress: text("ipAddress"),
userAgent: text("userAgent"),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
});
export type Session = InferSelectModel<typeof session>;
export const account = pgTable("Account", {
id: text("id").primaryKey(),
accountId: text("accountId").notNull(),
providerId: text("providerId").notNull(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("accessToken"),
refreshToken: text("refreshToken"),
idToken: text("idToken"),
accessTokenExpiresAt: timestamp("accessTokenExpiresAt"),
refreshTokenExpiresAt: timestamp("refreshTokenExpiresAt"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type Account = InferSelectModel<typeof account>;
export const verification = pgTable("Verification", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expiresAt").notNull(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type Verification = InferSelectModel<typeof verification>;
export const chat = pgTable("Chat", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
createdAt: timestamp("createdAt").notNull(),

View file

@ -1,16 +0,0 @@
import { generateId } from "ai";
import { genSaltSync, hashSync } from "bcrypt-ts";
export function generateHashedPassword(password: string) {
const salt = genSaltSync(10);
const hash = hashSync(password, salt);
return hash;
}
export function generateDummyPassword() {
const password = generateId();
const hashedPassword = generateHashedPassword(password);
return hashedPassword;
}