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

@ -1,5 +1,6 @@
# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32` # Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
AUTH_SECRET=**** BETTER_AUTH_SECRET=****
BETTER_AUTH_URL=****
# The following keys below are automatically created and # The following keys below are automatically created and
# added to your environment when you deploy on Vercel # added to your environment when you deploy on Vercel

View file

@ -1,10 +1,7 @@
"use server"; "use server";
import { z } from "zod"; import { z } from "zod";
import { auth } from "@/lib/auth";
import { createUser, getUser } from "@/lib/db/queries";
import { signIn } from "./auth";
const authFormSchema = z.object({ const authFormSchema = z.object({
email: z.string().email(), email: z.string().email(),
@ -25,10 +22,11 @@ export const login = async (
password: formData.get("password"), password: formData.get("password"),
}); });
await signIn("credentials", { await auth.api.signInEmail({
body: {
email: validatedData.email, email: validatedData.email,
password: validatedData.password, password: validatedData.password,
redirect: false, },
}); });
return { status: "success" }; return { status: "success" };
@ -61,24 +59,29 @@ export const register = async (
password: formData.get("password"), password: formData.get("password"),
}); });
const [user] = await getUser(validatedData.email); const result = await auth.api.signUpEmail({
body: {
if (user) {
return { status: "user_exists" } as RegisterActionState;
}
await createUser(validatedData.email, validatedData.password);
await signIn("credentials", {
email: validatedData.email, email: validatedData.email,
password: validatedData.password, password: validatedData.password,
redirect: false, name: validatedData.email,
},
}); });
if (!result) {
return { status: "failed" };
}
return { status: "success" }; return { status: "success" };
} catch (error) { } catch (error) {
if (error instanceof z.ZodError) { if (error instanceof z.ZodError) {
return { status: "invalid_data" }; return { status: "invalid_data" };
} }
const message = error instanceof Error ? error.message : "";
if (message.includes("already exists") || message.includes("UNIQUE")) {
return { status: "user_exists" };
}
return { status: "failed" }; return { status: "failed" };
} }
}; };

View file

@ -1 +0,0 @@
export { GET, POST } from "@/app/(auth)/auth";

View file

@ -1,21 +0,0 @@
import { NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import { signIn } from "@/app/(auth)/auth";
import { isDevelopmentEnvironment } from "@/lib/constants";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const redirectUrl = searchParams.get("redirectUrl") || "/";
const token = await getToken({
req: request,
secret: process.env.AUTH_SECRET,
secureCookie: !isDevelopmentEnvironment,
});
if (token) {
return NextResponse.redirect(new URL("/", request.url));
}
return signIn("guest", { redirect: true, redirectTo: redirectUrl });
}

View file

@ -1,14 +0,0 @@
import type { NextAuthConfig } from "next-auth";
export const authConfig = {
basePath: "/api/auth",
pages: {
signIn: "/login",
newUser: "/",
},
providers: [
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
// while this file is also used in non-Node.js environments
],
callbacks: {},
} satisfies NextAuthConfig;

View file

@ -1,94 +0,0 @@
import { compare } from "bcrypt-ts";
import NextAuth, { type DefaultSession } from "next-auth";
import type { DefaultJWT } from "next-auth/jwt";
import Credentials from "next-auth/providers/credentials";
import { DUMMY_PASSWORD } from "@/lib/constants";
import { createGuestUser, getUser } from "@/lib/db/queries";
import { authConfig } from "./auth.config";
export type UserType = "guest" | "regular";
declare module "next-auth" {
interface Session extends DefaultSession {
user: {
id: string;
type: UserType;
} & DefaultSession["user"];
}
interface User {
id?: string;
email?: string | null;
type: UserType;
}
}
declare module "next-auth/jwt" {
interface JWT extends DefaultJWT {
id: string;
type: UserType;
}
}
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
...authConfig,
providers: [
Credentials({
credentials: {},
async authorize({ email, password }: any) {
const users = await getUser(email);
if (users.length === 0) {
await compare(password, DUMMY_PASSWORD);
return null;
}
const [user] = users;
if (!user.password) {
await compare(password, DUMMY_PASSWORD);
return null;
}
const passwordsMatch = await compare(password, user.password);
if (!passwordsMatch) {
return null;
}
return { ...user, type: "regular" };
},
}),
Credentials({
id: "guest",
credentials: {},
async authorize() {
const [guestUser] = await createGuestUser();
return { ...guestUser, type: "guest" };
},
}),
],
callbacks: {
jwt({ token, user }) {
if (user) {
token.id = user.id as string;
token.type = user.type;
}
return token;
},
session({ session, token }) {
if (session.user) {
session.user.id = token.id;
session.user.type = token.type;
}
return session;
},
},
});

View file

@ -2,12 +2,12 @@
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useActionState, useEffect, useState } from "react"; import { useActionState, useEffect, useState } from "react";
import { AuthForm } from "@/components/auth-form"; import { AuthForm } from "@/components/auth-form";
import { SubmitButton } from "@/components/submit-button"; import { SubmitButton } from "@/components/submit-button";
import { toast } from "@/components/toast"; import { toast } from "@/components/toast";
import { useSession } from "@/lib/client";
import { type LoginActionState, login } from "../actions"; import { type LoginActionState, login } from "../actions";
export default function Page() { export default function Page() {
@ -23,9 +23,9 @@ export default function Page() {
} }
); );
const { update: updateSession } = useSession(); const { refetch } = useSession();
// biome-ignore lint/correctness/useExhaustiveDependencies: router and updateSession are stable refs // biome-ignore lint/correctness/useExhaustiveDependencies: router and refetch are stable refs
useEffect(() => { useEffect(() => {
if (state.status === "failed") { if (state.status === "failed") {
toast({ toast({
@ -39,7 +39,7 @@ export default function Page() {
}); });
} else if (state.status === "success") { } else if (state.status === "success") {
setIsSuccessful(true); setIsSuccessful(true);
updateSession(); refetch();
router.refresh(); router.refresh();
} }
}, [state.status]); }, [state.status]);

View file

@ -2,11 +2,11 @@
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useActionState, useEffect, useState } from "react"; import { useActionState, useEffect, useState } from "react";
import { AuthForm } from "@/components/auth-form"; import { AuthForm } from "@/components/auth-form";
import { SubmitButton } from "@/components/submit-button"; import { SubmitButton } from "@/components/submit-button";
import { toast } from "@/components/toast"; import { toast } from "@/components/toast";
import { useSession } from "@/lib/client";
import { type RegisterActionState, register } from "../actions"; import { type RegisterActionState, register } from "../actions";
export default function Page() { export default function Page() {
@ -22,9 +22,9 @@ export default function Page() {
} }
); );
const { update: updateSession } = useSession(); const { refetch } = useSession();
// biome-ignore lint/correctness/useExhaustiveDependencies: router and updateSession are stable refs // biome-ignore lint/correctness/useExhaustiveDependencies: router and refetch are stable refs
useEffect(() => { useEffect(() => {
if (state.status === "user_exists") { if (state.status === "user_exists") {
toast({ type: "error", description: "Account already exists!" }); toast({ type: "error", description: "Account already exists!" });
@ -39,7 +39,7 @@ export default function Page() {
toast({ type: "success", description: "Account created successfully!" }); toast({ type: "success", description: "Account created successfully!" });
setIsSuccessful(true); setIsSuccessful(true);
updateSession(); refetch();
router.refresh(); router.refresh();
} }
}, [state.status]); }, [state.status]);

View file

@ -0,0 +1,4 @@
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth";
export const { GET, POST } = toNextJsHandler(auth);

View file

@ -10,7 +10,6 @@ import {
import { checkBotId } from "botid/server"; import { checkBotId } from "botid/server";
import { after } from "next/server"; import { after } from "next/server";
import { createResumableStreamContext } from "resumable-stream"; import { createResumableStreamContext } from "resumable-stream";
import { auth, type UserType } from "@/app/(auth)/auth";
import { entitlementsByUserType } from "@/lib/ai/entitlements"; import { entitlementsByUserType } from "@/lib/ai/entitlements";
import { allowedModelIds } from "@/lib/ai/models"; import { allowedModelIds } from "@/lib/ai/models";
import { type RequestHints, systemPrompt } from "@/lib/ai/prompts"; import { type RequestHints, systemPrompt } from "@/lib/ai/prompts";
@ -19,6 +18,7 @@ import { createDocument } from "@/lib/ai/tools/create-document";
import { getWeather } from "@/lib/ai/tools/get-weather"; import { getWeather } from "@/lib/ai/tools/get-weather";
import { requestSuggestions } from "@/lib/ai/tools/request-suggestions"; import { requestSuggestions } from "@/lib/ai/tools/request-suggestions";
import { updateDocument } from "@/lib/ai/tools/update-document"; import { updateDocument } from "@/lib/ai/tools/update-document";
import { getSession, getUserType, type UserType } from "@/lib/auth";
import { isProductionEnvironment } from "@/lib/constants"; import { isProductionEnvironment } from "@/lib/constants";
import { import {
createStreamId, createStreamId,
@ -67,7 +67,7 @@ export async function POST(request: Request) {
const [, session] = await Promise.all([ const [, session] = await Promise.all([
checkBotId().catch(() => null), checkBotId().catch(() => null),
auth(), getSession(),
]); ]);
if (!session?.user) { if (!session?.user) {
@ -80,7 +80,7 @@ export async function POST(request: Request) {
await checkIpRateLimit(ipAddress(request)); await checkIpRateLimit(ipAddress(request));
const userType: UserType = session.user.type; const userType: UserType = getUserType(session.user);
const messageCount = await getMessageCountByUserId({ const messageCount = await getMessageCountByUserId({
id: session.user.id, id: session.user.id,
@ -295,7 +295,7 @@ export async function DELETE(request: Request) {
return new ChatbotError("bad_request:api").toResponse(); return new ChatbotError("bad_request:api").toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:chat").toResponse(); return new ChatbotError("unauthorized:chat").toResponse();

View file

@ -1,5 +1,5 @@
import { auth } from "@/app/(auth)/auth";
import type { ArtifactKind } from "@/components/artifact"; import type { ArtifactKind } from "@/components/artifact";
import { getSession } from "@/lib/auth";
import { import {
deleteDocumentsByIdAfterTimestamp, deleteDocumentsByIdAfterTimestamp,
getDocumentsById, getDocumentsById,
@ -18,7 +18,7 @@ export async function GET(request: Request) {
).toResponse(); ).toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:document").toResponse(); return new ChatbotError("unauthorized:document").toResponse();
@ -50,7 +50,7 @@ export async function POST(request: Request) {
).toResponse(); ).toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("not_found:document").toResponse(); return new ChatbotError("not_found:document").toResponse();
@ -103,7 +103,7 @@ export async function DELETE(request: Request) {
).toResponse(); ).toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:document").toResponse(); return new ChatbotError("unauthorized:document").toResponse();

View file

@ -2,7 +2,7 @@ import { put } from "@vercel/blob";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { z } from "zod"; import { z } from "zod";
import { auth } from "@/app/(auth)/auth"; import { getSession } from "@/lib/auth";
// Use Blob instead of File since File is not available in Node.js environment // Use Blob instead of File since File is not available in Node.js environment
const FileSchema = z.object({ const FileSchema = z.object({
@ -18,7 +18,7 @@ const FileSchema = z.object({
}); });
export async function POST(request: Request) { export async function POST(request: Request) {
const session = await auth(); const session = await getSession();
if (!session) { if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

View file

@ -1,5 +1,5 @@
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { auth } from "@/app/(auth)/auth"; import { getSession } from "@/lib/auth";
import { deleteAllChatsByUserId, getChatsByUserId } from "@/lib/db/queries"; import { deleteAllChatsByUserId, getChatsByUserId } from "@/lib/db/queries";
import { ChatbotError } from "@/lib/errors"; import { ChatbotError } from "@/lib/errors";
@ -17,7 +17,7 @@ export async function GET(request: NextRequest) {
).toResponse(); ).toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:chat").toResponse(); return new ChatbotError("unauthorized:chat").toResponse();
@ -34,7 +34,7 @@ export async function GET(request: NextRequest) {
} }
export async function DELETE() { export async function DELETE() {
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:chat").toResponse(); return new ChatbotError("unauthorized:chat").toResponse();

View file

@ -1,4 +1,4 @@
import { auth } from "@/app/(auth)/auth"; import { getSession } from "@/lib/auth";
import { getSuggestionsByDocumentId } from "@/lib/db/queries"; import { getSuggestionsByDocumentId } from "@/lib/db/queries";
import { ChatbotError } from "@/lib/errors"; import { ChatbotError } from "@/lib/errors";
@ -13,7 +13,7 @@ export async function GET(request: Request) {
).toResponse(); ).toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:suggestions").toResponse(); return new ChatbotError("unauthorized:suggestions").toResponse();

View file

@ -1,4 +1,4 @@
import { auth } from "@/app/(auth)/auth"; import { getSession } from "@/lib/auth";
import { getChatById, getVotesByChatId, voteMessage } from "@/lib/db/queries"; import { getChatById, getVotesByChatId, voteMessage } from "@/lib/db/queries";
import { ChatbotError } from "@/lib/errors"; import { ChatbotError } from "@/lib/errors";
@ -13,7 +13,7 @@ export async function GET(request: Request) {
).toResponse(); ).toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:vote").toResponse(); return new ChatbotError("unauthorized:vote").toResponse();
@ -49,7 +49,7 @@ export async function PATCH(request: Request) {
).toResponse(); ).toResponse();
} }
const session = await auth(); const session = await getSession();
if (!session?.user) { if (!session?.user) {
return new ChatbotError("unauthorized:vote").toResponse(); return new ChatbotError("unauthorized:vote").toResponse();

View file

@ -1,11 +1,10 @@
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { notFound, redirect } from "next/navigation"; import { notFound, redirect } from "next/navigation";
import { Suspense } from "react"; import { Suspense } from "react";
import { auth } from "@/app/(auth)/auth";
import { Chat } from "@/components/chat"; import { Chat } from "@/components/chat";
import { DataStreamHandler } from "@/components/data-stream-handler"; import { DataStreamHandler } from "@/components/data-stream-handler";
import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models"; import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models";
import { getSession } from "@/lib/auth";
import { getChatById, getMessagesByChatId } from "@/lib/db/queries"; import { getChatById, getMessagesByChatId } from "@/lib/db/queries";
import { convertToUIMessages } from "@/lib/utils"; import { convertToUIMessages } from "@/lib/utils";
@ -25,10 +24,10 @@ async function ChatPage({ params }: { params: Promise<{ id: string }> }) {
redirect("/"); redirect("/");
} }
const session = await auth(); const session = await getSession();
if (!session) { if (!session) {
redirect("/api/auth/guest"); redirect("/login");
} }
if (chat.visibility === "private") { if (chat.visibility === "private") {

View file

@ -4,7 +4,7 @@ import { Suspense } from "react";
import { AppSidebar } from "@/components/app-sidebar"; import { AppSidebar } from "@/components/app-sidebar";
import { DataStreamProvider } from "@/components/data-stream-provider"; import { DataStreamProvider } from "@/components/data-stream-provider";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { auth } from "../(auth)/auth"; import { getSession } from "@/lib/auth";
export default function Layout({ children }: { children: React.ReactNode }) { export default function Layout({ children }: { children: React.ReactNode }) {
return ( return (
@ -23,7 +23,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
} }
async function SidebarWrapper({ children }: { children: React.ReactNode }) { async function SidebarWrapper({ children }: { children: React.ReactNode }) {
const [session, cookieStore] = await Promise.all([auth(), cookies()]); const [session, cookieStore] = await Promise.all([getSession(), cookies()]);
const isCollapsed = cookieStore.get("sidebar_state")?.value !== "true"; const isCollapsed = cookieStore.get("sidebar_state")?.value !== "true";
return ( return (

View file

@ -6,7 +6,6 @@ import { ThemeProvider } from "@/components/theme-provider";
import "katex/dist/katex.min.css"; import "katex/dist/katex.min.css";
import "./globals.css"; import "./globals.css";
import { SessionProvider } from "next-auth/react";
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL("https://chat.vercel.ai"), metadataBase: new URL("https://chat.vercel.ai"),
@ -81,7 +80,7 @@ export default function RootLayout({
enableSystem enableSystem
> >
<Toaster position="top-center" /> <Toaster position="top-center" />
<SessionProvider>{children}</SessionProvider> {children}
</ThemeProvider> </ThemeProvider>
<Analytics /> <Analytics />
</body> </body>

View file

@ -2,7 +2,6 @@
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import type { User } from "next-auth";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { useSWRConfig } from "swr"; import { useSWRConfig } from "swr";
@ -22,6 +21,7 @@ import {
SidebarMenu, SidebarMenu,
useSidebar, useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import type { AuthUser } from "@/lib/auth";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@ -34,7 +34,7 @@ import {
} from "./ui/alert-dialog"; } from "./ui/alert-dialog";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
export function AppSidebar({ user }: { user: User | undefined }) { export function AppSidebar({ user }: { user: AuthUser | undefined }) {
const router = useRouter(); const router = useRouter();
const { setOpenMobile } = useSidebar(); const { setOpenMobile } = useSidebar();
const { mutate } = useSWRConfig(); const { mutate } = useSWRConfig();

View file

@ -176,7 +176,7 @@ export function Chat({
}, [query, sendMessage, hasAppendedQuery, id]); }, [query, sendMessage, hasAppendedQuery, id]);
const { data: votes } = useSWR<Vote[]>( const { data: votes } = useSWR<Vote[]>(
messages.length >= 2 !isReadonly && messages.length >= 2
? `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/vote?chatId=${id}` ? `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/vote?chatId=${id}`
: null, : null,
fetcher fetcher

View file

@ -32,6 +32,7 @@ import {
DEFAULT_CHAT_MODEL, DEFAULT_CHAT_MODEL,
modelsByProvider, modelsByProvider,
} from "@/lib/ai/models"; } from "@/lib/ai/models";
import { signIn, useSession } from "@/lib/client";
import type { Attachment, ChatMessage } from "@/lib/types"; import type { Attachment, ChatMessage } from "@/lib/types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
@ -86,6 +87,8 @@ function PureMultimodalInput({
}) { }) {
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { width } = useWindowSize(); const { width } = useWindowSize();
const { data: session, refetch: refetchSession } = useSession();
const [isSigningIn, setIsSigningIn] = useState(false);
const hasAutoFocused = useRef(false); const hasAutoFocused = useRef(false);
useEffect(() => { useEffect(() => {
@ -304,10 +307,21 @@ function PureMultimodalInput({
<PromptInput <PromptInput
className="[&>div]:rounded-xl" className="[&>div]:rounded-xl"
onSubmit={() => { onSubmit={async () => {
if (!input.trim() && attachments.length === 0) { if (!input.trim() && attachments.length === 0) {
return; return;
} }
if (!session && !isSigningIn) {
setIsSigningIn(true);
const { error } = await signIn.anonymous();
if (error) {
toast.error("Failed to create session, please try again!");
setIsSigningIn(false);
return;
}
await refetchSession();
setIsSigningIn(false);
}
if (status === "ready") { if (status === "ready") {
submitForm(); submitForm();
} else { } else {

View file

@ -3,7 +3,6 @@
import { isToday, isYesterday, subMonths, subWeeks } from "date-fns"; import { isToday, isYesterday, subMonths, subWeeks } from "date-fns";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import type { User } from "next-auth";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import useSWRInfinite from "swr/infinite"; import useSWRInfinite from "swr/infinite";
@ -23,6 +22,7 @@ import {
SidebarMenu, SidebarMenu,
useSidebar, useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import type { AuthUser } from "@/lib/auth";
import type { Chat } from "@/lib/db/schema"; import type { Chat } from "@/lib/db/schema";
import { fetcher } from "@/lib/utils"; import { fetcher } from "@/lib/utils";
import { LoaderIcon } from "./icons"; import { LoaderIcon } from "./icons";
@ -97,7 +97,7 @@ export function getChatHistoryPaginationKey(
return `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/history?ending_before=${firstChatFromPage.id}&limit=${PAGE_SIZE}`; return `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/history?ending_before=${firstChatFromPage.id}&limit=${PAGE_SIZE}`;
} }
export function SidebarHistory({ user }: { user: User | undefined }) { export function SidebarHistory({ user }: { user: AuthUser | undefined }) {
const { setOpenMobile } = useSidebar(); const { setOpenMobile } = useSidebar();
const pathname = usePathname(); const pathname = usePathname();
const id = pathname?.startsWith("/chat/") ? pathname.split("/")[2] : null; const id = pathname?.startsWith("/chat/") ? pathname.split("/")[2] : null;
@ -108,9 +108,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) {
isValidating, isValidating,
isLoading, isLoading,
mutate, mutate,
} = useSWRInfinite<ChatHistory>(getChatHistoryPaginationKey, fetcher, { } = useSWRInfinite<ChatHistory>(
fallbackData: [], user ? getChatHistoryPaginationKey : () => null,
}); fetcher,
{ fallbackData: [] }
);
const router = useRouter(); const router = useRouter();
const [deleteId, setDeleteId] = useState<string | null>(null); const [deleteId, setDeleteId] = useState<string | null>(null);

View file

@ -3,8 +3,6 @@
import { ChevronUp } from "lucide-react"; import { ChevronUp } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import type { User } from "next-auth";
import { signOut, useSession } from "next-auth/react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { import {
DropdownMenu, DropdownMenu,
@ -18,23 +16,27 @@ import {
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { guestRegex } from "@/lib/constants"; import { signOut, useSession } from "@/lib/client";
import { LoaderIcon } from "./icons"; import { LoaderIcon } from "./icons";
import { toast } from "./toast"; import { toast } from "./toast";
export function SidebarUserNav({ user }: { user: User }) { export function SidebarUserNav({
user,
}: {
user: { email?: string | null; isAnonymous?: boolean | null };
}) {
const router = useRouter(); const router = useRouter();
const { data, status } = useSession(); const { data, isPending } = useSession();
const { setTheme, resolvedTheme } = useTheme(); const { setTheme, resolvedTheme } = useTheme();
const isGuest = guestRegex.test(data?.user?.email ?? ""); const isGuest = data?.user?.isAnonymous ?? user.isAnonymous ?? false;
return ( return (
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
{status === "loading" ? ( {isPending ? (
<SidebarMenuButton className="h-10 justify-between bg-background data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"> <SidebarMenuButton className="h-10 justify-between bg-background data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<div className="size-6 animate-pulse rounded-full bg-zinc-500/30" /> <div className="size-6 animate-pulse rounded-full bg-zinc-500/30" />
@ -84,7 +86,7 @@ export function SidebarUserNav({ user }: { user: User }) {
<button <button
className="w-full cursor-pointer" className="w-full cursor-pointer"
onClick={() => { onClick={() => {
if (status === "loading") { if (isPending) {
toast({ toast({
type: "error", type: "error",
description: description:
@ -98,7 +100,12 @@ export function SidebarUserNav({ user }: { user: User }) {
router.push("/login"); router.push("/login");
} else { } else {
signOut({ signOut({
redirectTo: "/", fetchOptions: {
onSuccess: () => {
router.push("/");
router.refresh();
},
},
}); });
} }
}} }}

View file

@ -1,6 +1,7 @@
import Form from "next/form"; import Form from "next/form";
import { headers } from "next/headers";
import { signOut } from "@/app/(auth)/auth"; import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
export const SignOutForm = () => { export const SignOutForm = () => {
return ( return (
@ -8,9 +9,11 @@ export const SignOutForm = () => {
action={async () => { action={async () => {
"use server"; "use server";
await signOut({ await auth.api.signOut({
redirectTo: "/", headers: await headers(),
}); });
redirect("/");
}} }}
className="w-full" className="w-full"
> >

View file

@ -1,4 +1,4 @@
import type { UserType } from "@/app/(auth)/auth"; import type { UserType } from "@/lib/auth";
type Entitlements = { type Entitlements = {
maxMessagesPerHour: number; maxMessagesPerHour: number;

View file

@ -1,15 +1,15 @@
import { tool, type UIMessageStreamWriter } from "ai"; import { tool, type UIMessageStreamWriter } from "ai";
import type { Session } from "next-auth";
import { z } from "zod"; import { z } from "zod";
import { import {
artifactKinds, artifactKinds,
documentHandlersByArtifactKind, documentHandlersByArtifactKind,
} from "@/lib/artifacts/server"; } from "@/lib/artifacts/server";
import type { AuthSession } from "@/lib/auth";
import type { ChatMessage } from "@/lib/types"; import type { ChatMessage } from "@/lib/types";
import { generateUUID } from "@/lib/utils"; import { generateUUID } from "@/lib/utils";
type CreateDocumentProps = { type CreateDocumentProps = {
session: Session; session: NonNullable<AuthSession>;
dataStream: UIMessageStreamWriter<ChatMessage>; dataStream: UIMessageStreamWriter<ChatMessage>;
}; };

View file

@ -1,6 +1,6 @@
import { Output, streamText, tool, type UIMessageStreamWriter } from "ai"; import { Output, streamText, tool, type UIMessageStreamWriter } from "ai";
import type { Session } from "next-auth";
import { z } from "zod"; import { z } from "zod";
import type { AuthSession } from "@/lib/auth";
import { getDocumentById, saveSuggestions } from "@/lib/db/queries"; import { getDocumentById, saveSuggestions } from "@/lib/db/queries";
import type { Suggestion } from "@/lib/db/schema"; import type { Suggestion } from "@/lib/db/schema";
import type { ChatMessage } from "@/lib/types"; import type { ChatMessage } from "@/lib/types";
@ -8,7 +8,7 @@ import { generateUUID } from "@/lib/utils";
import { getArtifactModel } from "../providers"; import { getArtifactModel } from "../providers";
type RequestSuggestionsProps = { type RequestSuggestionsProps = {
session: Session; session: NonNullable<AuthSession>;
dataStream: UIMessageStreamWriter<ChatMessage>; dataStream: UIMessageStreamWriter<ChatMessage>;
}; };

View file

@ -1,12 +1,12 @@
import { tool, type UIMessageStreamWriter } from "ai"; import { tool, type UIMessageStreamWriter } from "ai";
import type { Session } from "next-auth";
import { z } from "zod"; import { z } from "zod";
import { documentHandlersByArtifactKind } from "@/lib/artifacts/server"; import { documentHandlersByArtifactKind } from "@/lib/artifacts/server";
import type { AuthSession } from "@/lib/auth";
import { getDocumentById } from "@/lib/db/queries"; import { getDocumentById } from "@/lib/db/queries";
import type { ChatMessage } from "@/lib/types"; import type { ChatMessage } from "@/lib/types";
type UpdateDocumentProps = { type UpdateDocumentProps = {
session: Session; session: NonNullable<AuthSession>;
dataStream: UIMessageStreamWriter<ChatMessage>; dataStream: UIMessageStreamWriter<ChatMessage>;
}; };

View file

@ -1,9 +1,9 @@
import type { UIMessageStreamWriter } from "ai"; import type { UIMessageStreamWriter } from "ai";
import type { Session } from "next-auth";
import { codeDocumentHandler } from "@/artifacts/code/server"; import { codeDocumentHandler } from "@/artifacts/code/server";
import { sheetDocumentHandler } from "@/artifacts/sheet/server"; import { sheetDocumentHandler } from "@/artifacts/sheet/server";
import { textDocumentHandler } from "@/artifacts/text/server"; import { textDocumentHandler } from "@/artifacts/text/server";
import type { ArtifactKind } from "@/components/artifact"; import type { ArtifactKind } from "@/components/artifact";
import type { AuthSession } from "@/lib/auth";
import { saveDocument } from "../db/queries"; import { saveDocument } from "../db/queries";
import type { Document } from "../db/schema"; import type { Document } from "../db/schema";
import type { ChatMessage } from "../types"; import type { ChatMessage } from "../types";
@ -20,14 +20,14 @@ export type CreateDocumentCallbackProps = {
id: string; id: string;
title: string; title: string;
dataStream: UIMessageStreamWriter<ChatMessage>; dataStream: UIMessageStreamWriter<ChatMessage>;
session: Session; session: NonNullable<AuthSession>;
}; };
export type UpdateDocumentCallbackProps = { export type UpdateDocumentCallbackProps = {
document: Document; document: Document;
description: string; description: string;
dataStream: UIMessageStreamWriter<ChatMessage>; dataStream: UIMessageStreamWriter<ChatMessage>;
session: Session; session: NonNullable<AuthSession>;
}; };
export type DocumentHandler<T = ArtifactKind> = { export type DocumentHandler<T = ArtifactKind> = {

58
lib/auth.ts Normal file
View file

@ -0,0 +1,58 @@
import { compare, genSaltSync, hashSync } from "bcrypt-ts";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { nextCookies } from "better-auth/next-js";
import { anonymous } from "better-auth/plugins";
import { drizzle } from "drizzle-orm/postgres-js";
import { headers } from "next/headers";
import postgres from "postgres";
import { account, session, user, verification } from "./db/schema";
// biome-ignore lint: Forbidden non-null assertion.
const client = postgres(process.env.POSTGRES_URL!);
const db = drizzle(client);
export const auth = betterAuth({
basePath: "/api/auth",
baseURL: process.env.BETTER_AUTH_URL,
database: drizzleAdapter(db, {
provider: "pg",
schema: { user, session, account, verification },
}),
emailAndPassword: {
enabled: true,
password: {
hash: async (password) => hashSync(password, genSaltSync(10)),
verify: async ({ hash, password }) => compare(password, hash),
},
},
...(process.env.VERCEL_CLIENT_ID && process.env.VERCEL_CLIENT_SECRET
? {
socialProviders: {
vercel: {
clientId: process.env.VERCEL_CLIENT_ID,
clientSecret: process.env.VERCEL_CLIENT_SECRET,
},
},
}
: {}),
advanced: {
database: {
generateId: () => crypto.randomUUID(),
},
},
plugins: [anonymous(), nextCookies()],
});
export type UserType = "guest" | "regular";
export function getUserType(user: Record<string, unknown>): UserType {
return (user as { isAnonymous?: boolean }).isAnonymous ? "guest" : "regular";
}
export async function getSession() {
return auth.api.getSession({ headers: await headers() });
}
export type AuthSession = Awaited<ReturnType<typeof getSession>>;
export type AuthUser = NonNullable<AuthSession>["user"];

9
lib/client.ts Normal file
View file

@ -0,0 +1,9 @@
import { anonymousClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
basePath: `${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/api/auth`,
plugins: [anonymousClient()],
});
export const { useSession, signIn, signUp, signOut } = authClient;

View file

@ -1,5 +1,3 @@
import { generateDummyPassword } from "./db/utils";
export const isProductionEnvironment = process.env.NODE_ENV === "production"; export const isProductionEnvironment = process.env.NODE_ENV === "production";
export const isDevelopmentEnvironment = process.env.NODE_ENV === "development"; export const isDevelopmentEnvironment = process.env.NODE_ENV === "development";
export const isTestEnvironment = Boolean( export const isTestEnvironment = Boolean(
@ -7,7 +5,3 @@ export const isTestEnvironment = Boolean(
process.env.PLAYWRIGHT || process.env.PLAYWRIGHT ||
process.env.CI_PLAYWRIGHT process.env.CI_PLAYWRIGHT
); );
export const guestRegex = /^guest-\d+$/;
export const DUMMY_PASSWORD = generateDummyPassword();

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 { ArtifactKind } from "@/components/artifact";
import type { VisibilityType } from "@/components/visibility-selector"; import type { VisibilityType } from "@/components/visibility-selector";
import { ChatbotError } from "../errors"; import { ChatbotError } from "../errors";
import { generateUUID } from "../utils";
import { import {
type Chat, type Chat,
chat, chat,
@ -27,58 +27,13 @@ import {
type Suggestion, type Suggestion,
stream, stream,
suggestion, suggestion,
type User,
user,
vote, vote,
} from "./schema"; } 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. // biome-ignore lint: Forbidden non-null assertion.
const client = postgres(process.env.POSTGRES_URL!); const client = postgres(process.env.POSTGRES_URL!);
const db = drizzle(client); 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({ export async function saveChat({
id, id,
userId, userId,

View file

@ -15,10 +15,62 @@ export const user = pgTable("User", {
id: uuid("id").primaryKey().notNull().defaultRandom(), id: uuid("id").primaryKey().notNull().defaultRandom(),
email: varchar("email", { length: 64 }).notNull(), email: varchar("email", { length: 64 }).notNull(),
password: varchar("password", { length: 64 }), 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 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", { export const chat = pgTable("Chat", {
id: uuid("id").primaryKey().notNull().defaultRandom(), id: uuid("id").primaryKey().notNull().defaultRandom(),
createdAt: timestamp("createdAt").notNull(), 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;
}

View file

@ -8,7 +8,6 @@ const nextConfig: NextConfig = {
assetPrefix: "/demo-assets", assetPrefix: "/demo-assets",
env: { env: {
NEXT_PUBLIC_BASE_PATH: basePath, NEXT_PUBLIC_BASE_PATH: basePath,
NEXTAUTH_URL: `http://localhost${basePath}/api/auth`,
}, },
cacheComponents: true, cacheComponents: true,
images: { images: {

View file

@ -38,6 +38,7 @@
"@vercel/otel": "^1.12.0", "@vercel/otel": "^1.12.0",
"ai": "6.0.37", "ai": "6.0.37",
"bcrypt-ts": "^5.0.2", "bcrypt-ts": "^5.0.2",
"better-auth": "^1.5.5",
"botid": "^1.5.11", "botid": "^1.5.11",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"classnames": "^2.5.1", "classnames": "^2.5.1",
@ -55,7 +56,6 @@
"motion": "^12.23.26", "motion": "^12.23.26",
"nanoid": "^5.1.3", "nanoid": "^5.1.3",
"next": "16.0.10", "next": "16.0.10",
"next-auth": "5.0.0-beta.25",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"orderedmap": "^2.1.1", "orderedmap": "^2.1.1",
"papaparse": "^5.5.2", "papaparse": "^5.5.2",

462
pnpm-lock.yaml generated
View file

@ -68,6 +68,9 @@ importers:
bcrypt-ts: bcrypt-ts:
specifier: ^5.0.2 specifier: ^5.0.2
version: 5.0.3 version: 5.0.3
better-auth:
specifier: ^1.5.5
version: 1.5.5(drizzle-kit@0.25.0)(drizzle-orm@0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1))(mongodb@7.1.0)(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react-dom@19.0.1(react@19.0.1))(react@19.0.1)
botid: botid:
specifier: ^1.5.11 specifier: ^1.5.11
version: 1.5.11(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1) version: 1.5.11(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1)
@ -97,7 +100,7 @@ importers:
version: 16.4.7 version: 16.4.7
drizzle-orm: drizzle-orm:
specifier: ^0.34.0 specifier: ^0.34.0
version: 0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(postgres@3.4.5)(react@19.0.1) version: 0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1)
fast-deep-equal: fast-deep-equal:
specifier: ^3.1.3 specifier: ^3.1.3
version: 3.1.3 version: 3.1.3
@ -119,9 +122,6 @@ importers:
next: next:
specifier: 16.0.10 specifier: 16.0.10
version: 16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1) version: 16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)
next-auth:
specifier: 5.0.0-beta.25
version: 5.0.0-beta.25(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1)
next-themes: next-themes:
specifier: ^0.3.0 specifier: ^0.3.0
version: 0.3.0(react-dom@19.0.1(react@19.0.1))(react@19.0.1) version: 0.3.0(react-dom@19.0.1(react@19.0.1))(react@19.0.1)
@ -287,19 +287,73 @@ packages:
'@antfu/install-pkg@1.1.0': '@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@auth/core@0.37.2': '@better-auth/core@1.5.5':
resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} resolution: {integrity: sha512-1oR/2jAp821Dcf67kQYHUoyNcdc1TcShfw4QMK0YTVntuRES5mUOyvEJql5T6eIuLfaqaN4LOF78l0FtF66HXA==}
peerDependencies: peerDependencies:
'@simplewebauthn/browser': ^9.0.1 '@better-auth/utils': 0.3.1
'@simplewebauthn/server': ^9.0.2 '@better-fetch/fetch': 1.1.21
nodemailer: ^6.8.0 '@cloudflare/workers-types': '>=4'
better-call: 1.3.2
jose: ^6.1.0
kysely: ^0.28.5
nanostores: ^1.0.1
peerDependenciesMeta: peerDependenciesMeta:
'@simplewebauthn/browser': '@cloudflare/workers-types':
optional: true optional: true
'@simplewebauthn/server':
'@better-auth/drizzle-adapter@1.5.5':
resolution: {integrity: sha512-HAi9xAP40oDt48QZeYBFTcmg3vt1Jik90GwoRIfangd7VGbxesIIDBJSnvwMbZ52GBIc6+V4FRw9lasNiNrPfw==}
peerDependencies:
'@better-auth/core': 1.5.5
'@better-auth/utils': ^0.3.0
drizzle-orm: '>=0.41.0'
peerDependenciesMeta:
drizzle-orm:
optional: true optional: true
nodemailer:
'@better-auth/kysely-adapter@1.5.5':
resolution: {integrity: sha512-LmHffIVnqbfsxcxckMOoE8MwibWrbVFch+kwPKJ5OFDFv6lin75ufN7ZZ7twH0IMPLT/FcgzaRjP8jRrXRef9g==}
peerDependencies:
'@better-auth/core': 1.5.5
'@better-auth/utils': ^0.3.0
kysely: ^0.27.0 || ^0.28.0
'@better-auth/memory-adapter@1.5.5':
resolution: {integrity: sha512-4X0j1/2L+nsgmObjmy9xEGUFWUv38Qjthp558fwS3DAp6ueWWyCaxaD6VJZ7m5qPNMrsBStO5WGP8CmJTEWm7g==}
peerDependencies:
'@better-auth/core': 1.5.5
'@better-auth/utils': ^0.3.0
'@better-auth/mongo-adapter@1.5.5':
resolution: {integrity: sha512-P1J9ljL5X5k740I8Rx1esPWNgWYPdJR5hf2CY7BwDSrQFPUHuzeCg0YhtEEP55niNateTXhBqGAcy0fVOeamZg==}
peerDependencies:
'@better-auth/core': 1.5.5
'@better-auth/utils': ^0.3.0
mongodb: ^6.0.0 || ^7.0.0
'@better-auth/prisma-adapter@1.5.5':
resolution: {integrity: sha512-CliDd78CXHzzwQIXhCdwGr5Ml53i6JdCHWV7PYwTIJz9EAm6qb2RVBdpP3nqEfNjINGM22A6gfleCgCdZkTIZg==}
peerDependencies:
'@better-auth/core': 1.5.5
'@better-auth/utils': ^0.3.0
'@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
peerDependenciesMeta:
'@prisma/client':
optional: true optional: true
prisma:
optional: true
'@better-auth/telemetry@1.5.5':
resolution: {integrity: sha512-1+lklxArn4IMHuU503RcPdXrSG2tlXt4jnGG3omolmspQ7tktg/Y9XO/yAkYDurtvMn1xJ8X1Ov01Ji/r5s9BQ==}
peerDependencies:
'@better-auth/core': 1.5.5
'@better-auth/utils@0.3.1':
resolution: {integrity: sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg==}
'@better-fetch/fetch@1.1.21':
resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==}
'@biomejs/biome@2.3.11': '@biomejs/biome@2.3.11':
resolution: {integrity: sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==} resolution: {integrity: sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==}
@ -1051,6 +1105,9 @@ packages:
'@mermaid-js/parser@1.0.0': '@mermaid-js/parser@1.0.0':
resolution: {integrity: sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==} resolution: {integrity: sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==}
'@mongodb-js/saslprep@1.4.6':
resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==}
'@neondatabase/serverless@0.9.5': '@neondatabase/serverless@0.9.5':
resolution: {integrity: sha512-siFas6gItqv6wD/pZnvdu34wEqgG3nSE6zWZdq5j2DEsa+VvX8i/5HXJOo06qrw5axPXn+lGCxeR+NLaSPIXug==} resolution: {integrity: sha512-siFas6gItqv6wD/pZnvdu34wEqgG3nSE6zWZdq5j2DEsa+VvX8i/5HXJOo06qrw5axPXn+lGCxeR+NLaSPIXug==}
@ -1105,6 +1162,14 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@noble/ciphers@2.1.1':
resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==}
engines: {node: '>= 20.19.0'}
'@noble/hashes@2.0.1':
resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
engines: {node: '>= 20.19.0'}
'@opentelemetry/api-logs@0.200.0': '@opentelemetry/api-logs@0.200.0':
resolution: {integrity: sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q==} resolution: {integrity: sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
@ -1157,9 +1222,6 @@ packages:
resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==}
engines: {node: '>=14'} engines: {node: '>=14'}
'@panva/hkdf@1.2.1':
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
'@playwright/test@1.51.0': '@playwright/test@1.51.0':
resolution: {integrity: sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==} resolution: {integrity: sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -2028,9 +2090,6 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=5.7.2' typescript: '>=5.7.2'
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/d3-array@3.2.2': '@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
@ -2192,6 +2251,12 @@ packages:
'@types/unist@3.0.3': '@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
'@types/webidl-conversions@7.0.3':
resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
'@types/whatwg-url@13.0.0':
resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==}
'@ungap/structured-clone@1.3.0': '@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
@ -2292,6 +2357,76 @@ packages:
resolution: {integrity: sha512-2FcgD12xPbwCoe5i9/HK0jJ1xA1m+QfC1e6htG9Bl/hNOnLyaFmQSlqLKcfe3QdnoMPKpKEGFCbESBTg+SJNOw==} resolution: {integrity: sha512-2FcgD12xPbwCoe5i9/HK0jJ1xA1m+QfC1e6htG9Bl/hNOnLyaFmQSlqLKcfe3QdnoMPKpKEGFCbESBTg+SJNOw==}
engines: {node: '>=18'} engines: {node: '>=18'}
better-auth@1.5.5:
resolution: {integrity: sha512-GpVPaV1eqr3mOovKfghJXXk6QvlcVeFbS3z+n+FPDid5rK/2PchnDtiaVCzWyXA9jH2KkirOfl+JhAUvnja0Eg==}
peerDependencies:
'@lynx-js/react': '*'
'@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
'@sveltejs/kit': ^2.0.0
'@tanstack/react-start': ^1.0.0
'@tanstack/solid-start': ^1.0.0
better-sqlite3: ^12.0.0
drizzle-kit: '>=0.31.4'
drizzle-orm: '>=0.41.0'
mongodb: ^6.0.0 || ^7.0.0
mysql2: ^3.0.0
next: ^14.0.0 || ^15.0.0 || ^16.0.0
pg: ^8.0.0
prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
solid-js: ^1.0.0
svelte: ^4.0.0 || ^5.0.0
vitest: ^2.0.0 || ^3.0.0 || ^4.0.0
vue: ^3.0.0
peerDependenciesMeta:
'@lynx-js/react':
optional: true
'@prisma/client':
optional: true
'@sveltejs/kit':
optional: true
'@tanstack/react-start':
optional: true
'@tanstack/solid-start':
optional: true
better-sqlite3:
optional: true
drizzle-kit:
optional: true
drizzle-orm:
optional: true
mongodb:
optional: true
mysql2:
optional: true
next:
optional: true
pg:
optional: true
prisma:
optional: true
react:
optional: true
react-dom:
optional: true
solid-js:
optional: true
svelte:
optional: true
vitest:
optional: true
vue:
optional: true
better-call@1.3.2:
resolution: {integrity: sha512-4cZIfrerDsNTn3cm+MhLbUePN0gdwkhSXEuG7r/zuQ8c/H7iU0/jSK5TD3FW7U0MgKHce/8jGpPYNO4Ve+4NBw==}
peerDependencies:
zod: ^4.0.0
peerDependenciesMeta:
zod:
optional: true
botid@1.5.11: botid@1.5.11:
resolution: {integrity: sha512-KOO1A3+/vFVJk5aFoG3sNwiogKFPVR+x4aw4gQ1b2e0XoE+i5xp48/EZn+WqR07jRHeDGwHWQUOtV5WVm7xiww==} resolution: {integrity: sha512-KOO1A3+/vFVJk5aFoG3sNwiogKFPVR+x4aw4gQ1b2e0XoE+i5xp48/EZn+WqR07jRHeDGwHWQUOtV5WVm7xiww==}
peerDependencies: peerDependencies:
@ -2303,6 +2438,10 @@ packages:
react: react:
optional: true optional: true
bson@7.2.0:
resolution: {integrity: sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==}
engines: {node: '>=20.19.0'}
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@ -2397,10 +2536,6 @@ packages:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0} engines: {node: ^14.18.0 || >=16.10.0}
cookie@0.7.1:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'}
cose-base@1.0.3: cose-base@1.0.3:
resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
@ -2605,6 +2740,9 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
delaunator@5.0.1: delaunator@5.0.1:
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
@ -2936,8 +3074,8 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true hasBin: true
jose@5.10.0: jose@6.2.1:
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} resolution: {integrity: sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==}
json-schema@0.4.0: json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
@ -2952,6 +3090,10 @@ packages:
khroma@2.1.0: khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
kysely@0.28.11:
resolution: {integrity: sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==}
engines: {node: '>=20.0.0'}
langium@4.2.1: langium@4.2.1:
resolution: {integrity: sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==} resolution: {integrity: sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==}
engines: {node: '>=20.10.0', npm: '>=10.2.3'} engines: {node: '>=20.10.0', npm: '>=10.2.3'}
@ -3133,6 +3275,9 @@ packages:
mdurl@2.0.0: mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
memory-pager@1.5.0:
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
mermaid@11.12.3: mermaid@11.12.3:
resolution: {integrity: sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==} resolution: {integrity: sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==}
@ -3266,6 +3411,37 @@ packages:
module-details-from-path@1.0.4: module-details-from-path@1.0.4:
resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
mongodb-connection-string-url@7.0.1:
resolution: {integrity: sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==}
engines: {node: '>=20.19.0'}
mongodb@7.1.0:
resolution: {integrity: sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@aws-sdk/credential-providers': ^3.806.0
'@mongodb-js/zstd': ^7.0.0
gcp-metadata: ^7.0.1
kerberos: ^7.0.0
mongodb-client-encryption: '>=7.0.0 <7.1.0'
snappy: ^7.3.2
socks: ^2.8.6
peerDependenciesMeta:
'@aws-sdk/credential-providers':
optional: true
'@mongodb-js/zstd':
optional: true
gcp-metadata:
optional: true
kerberos:
optional: true
mongodb-client-encryption:
optional: true
snappy:
optional: true
socks:
optional: true
motion-dom@11.18.1: motion-dom@11.18.1:
resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==}
@ -3310,21 +3486,9 @@ packages:
engines: {node: ^18 || >=20} engines: {node: ^18 || >=20}
hasBin: true hasBin: true
next-auth@5.0.0-beta.25: nanostores@1.1.1:
resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} resolution: {integrity: sha512-EYJqS25r2iBeTtGQCHidXl1VfZ1jXM7Q04zXJOrMlxVVmD0ptxJaNux92n1mJ7c5lN3zTq12MhH/8x59nP+qmg==}
peerDependencies: engines: {node: ^20.0.0 || >=22.0.0}
'@simplewebauthn/browser': ^9.0.1
'@simplewebauthn/server': ^9.0.2
next: ^14.0.0-0 || ^15.0.0-0
nodemailer: ^6.6.5
react: ^18.2.0 || ^19.0.0-0
peerDependenciesMeta:
'@simplewebauthn/browser':
optional: true
'@simplewebauthn/server':
optional: true
nodemailer:
optional: true
next-themes@0.3.0: next-themes@0.3.0:
resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
@ -3362,9 +3526,6 @@ packages:
engines: {node: ^14.16.0 || >=16.10.0} engines: {node: ^14.16.0 || >=16.10.0}
hasBin: true hasBin: true
oauth4webapi@3.3.1:
resolution: {integrity: sha512-ZwX7UqYrP3Lr+Glhca3a1/nF2jqf7VVyJfhGuW5JtrfDUxt0u+IoBPzFjZ2dd7PJGkdM6CFPVVYzuDYKHv101A==}
obuf@1.1.2: obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
@ -3477,17 +3638,6 @@ packages:
resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==} resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==}
engines: {node: '>=12'} engines: {node: '>=12'}
preact-render-to-string@5.2.3:
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==}
peerDependencies:
preact: '>=10'
preact@10.11.3:
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==}
pretty-format@3.8.0:
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
property-information@6.5.0: property-information@6.5.0:
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
@ -3543,6 +3693,10 @@ packages:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'} engines: {node: '>=6'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
radix-ui@1.4.3: radix-ui@1.4.3:
resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
peerDependencies: peerDependencies:
@ -3689,6 +3843,9 @@ packages:
rope-sequence@1.3.4: rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
roughjs@4.6.6: roughjs@4.6.6:
resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
@ -3709,6 +3866,9 @@ packages:
server-only@0.0.1: server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
set-cookie-parser@3.0.1:
resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==}
sharp@0.34.5: sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@ -3742,6 +3902,9 @@ packages:
space-separated-tokens@2.0.2: space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
sparse-bitfield@3.0.3:
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
streamdown@2.3.0: streamdown@2.3.0:
resolution: {integrity: sha512-OqS3by/lt91lSicE8RQP2nTsYI6Q/dQgGP2vcyn9YesCmRHhNjswAuBAZA1z0F4+oBU3II/eV51LqjCqwTb1lw==} resolution: {integrity: sha512-OqS3by/lt91lSicE8RQP2nTsYI6Q/dQgGP2vcyn9YesCmRHhNjswAuBAZA1z0F4+oBU3II/eV51LqjCqwTb1lw==}
peerDependencies: peerDependencies:
@ -3811,6 +3974,10 @@ packages:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'} engines: {node: '>=18'}
tr46@5.1.1:
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
engines: {node: '>=18'}
trim-lines@3.0.1: trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@ -3988,6 +4155,14 @@ packages:
web-namespaces@2.0.1: web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
whatwg-url@14.2.0:
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
engines: {node: '>=18'}
ws@8.18.1: ws@8.18.1:
resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@ -4006,6 +4181,9 @@ packages:
zod@4.3.5: zod@4.3.5:
resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==}
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
zwitch@2.0.4: zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@ -4046,15 +4224,55 @@ snapshots:
package-manager-detector: 1.6.0 package-manager-detector: 1.6.0
tinyexec: 1.0.2 tinyexec: 1.0.2
'@auth/core@0.37.2': '@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)':
dependencies: dependencies:
'@panva/hkdf': 1.2.1 '@better-auth/utils': 0.3.1
'@types/cookie': 0.6.0 '@better-fetch/fetch': 1.1.21
cookie: 0.7.1 '@standard-schema/spec': 1.1.0
jose: 5.10.0 better-call: 1.3.2(zod@4.3.6)
oauth4webapi: 3.3.1 jose: 6.2.1
preact: 10.11.3 kysely: 0.28.11
preact-render-to-string: 5.2.3(preact@10.11.3) nanostores: 1.1.1
zod: 4.3.6
'@better-auth/drizzle-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1))':
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1
optionalDependencies:
drizzle-orm: 0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1)
'@better-auth/kysely-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)':
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1
kysely: 0.28.11
'@better-auth/memory-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)':
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1
'@better-auth/mongo-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)':
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1
mongodb: 7.1.0
'@better-auth/prisma-adapter@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)':
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1
'@better-auth/telemetry@1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))':
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
'@better-auth/utils@0.3.1': {}
'@better-fetch/fetch@1.1.21': {}
'@biomejs/biome@2.3.11': '@biomejs/biome@2.3.11':
optionalDependencies: optionalDependencies:
@ -4584,6 +4802,10 @@ snapshots:
dependencies: dependencies:
langium: 4.2.1 langium: 4.2.1
'@mongodb-js/saslprep@1.4.6':
dependencies:
sparse-bitfield: 3.0.3
'@neondatabase/serverless@0.9.5': '@neondatabase/serverless@0.9.5':
dependencies: dependencies:
'@types/pg': 8.11.6 '@types/pg': 8.11.6
@ -4615,6 +4837,10 @@ snapshots:
'@next/swc-win32-x64-msvc@16.0.10': '@next/swc-win32-x64-msvc@16.0.10':
optional: true optional: true
'@noble/ciphers@2.1.1': {}
'@noble/hashes@2.0.1': {}
'@opentelemetry/api-logs@0.200.0': '@opentelemetry/api-logs@0.200.0':
dependencies: dependencies:
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
@ -4670,8 +4896,6 @@ snapshots:
'@opentelemetry/semantic-conventions@1.28.0': {} '@opentelemetry/semantic-conventions@1.28.0': {}
'@panva/hkdf@1.2.1': {}
'@playwright/test@1.51.0': '@playwright/test@1.51.0':
dependencies: dependencies:
playwright: 1.51.0 playwright: 1.51.0
@ -5594,8 +5818,6 @@ snapshots:
dependencies: dependencies:
typescript: 5.8.2 typescript: 5.8.2
'@types/cookie@0.6.0': {}
'@types/d3-array@3.2.2': {} '@types/d3-array@3.2.2': {}
'@types/d3-axis@3.0.6': '@types/d3-axis@3.0.6':
@ -5783,6 +6005,12 @@ snapshots:
'@types/unist@3.0.3': {} '@types/unist@3.0.3': {}
'@types/webidl-conversions@7.0.3': {}
'@types/whatwg-url@13.0.0':
dependencies:
'@types/webidl-conversions': 7.0.3
'@ungap/structured-clone@1.3.0': {} '@ungap/structured-clone@1.3.0': {}
'@vercel/analytics@1.5.0(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1)': '@vercel/analytics@1.5.0(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1)':
@ -5855,11 +6083,51 @@ snapshots:
bcrypt-ts@5.0.3: {} bcrypt-ts@5.0.3: {}
better-auth@1.5.5(drizzle-kit@0.25.0)(drizzle-orm@0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1))(mongodb@7.1.0)(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react-dom@19.0.1(react@19.0.1))(react@19.0.1):
dependencies:
'@better-auth/core': 1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1)
'@better-auth/drizzle-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1))
'@better-auth/kysely-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)
'@better-auth/memory-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)
'@better-auth/mongo-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0)
'@better-auth/prisma-adapter': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)
'@better-auth/telemetry': 1.5.5(@better-auth/core@1.5.5(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@3.25.76))(jose@6.2.1)(kysely@0.28.11)(nanostores@1.1.1))
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
'@noble/ciphers': 2.1.1
'@noble/hashes': 2.0.1
better-call: 1.3.2(zod@4.3.6)
defu: 6.1.4
jose: 6.2.1
kysely: 0.28.11
nanostores: 1.1.1
zod: 4.3.6
optionalDependencies:
drizzle-kit: 0.25.0
drizzle-orm: 0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1)
mongodb: 7.1.0
next: 16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)
react: 19.0.1
react-dom: 19.0.1(react@19.0.1)
transitivePeerDependencies:
- '@cloudflare/workers-types'
better-call@1.3.2(zod@4.3.6):
dependencies:
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
rou3: 0.7.12
set-cookie-parser: 3.0.1
optionalDependencies:
zod: 4.3.6
botid@1.5.11(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1): botid@1.5.11(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1):
optionalDependencies: optionalDependencies:
next: 16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1) next: 16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)
react: 19.0.1 react: 19.0.1
bson@7.2.0: {}
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
bufferutil@4.0.9: bufferutil@4.0.9:
@ -5949,8 +6217,6 @@ snapshots:
consola@3.4.2: {} consola@3.4.2: {}
cookie@0.7.1: {}
cose-base@1.0.3: cose-base@1.0.3:
dependencies: dependencies:
layout-base: 1.0.2 layout-base: 1.0.2
@ -6167,6 +6433,8 @@ snapshots:
deepmerge@4.3.1: {} deepmerge@4.3.1: {}
defu@6.1.4: {}
delaunator@5.0.1: delaunator@5.0.1:
dependencies: dependencies:
robust-predicates: 3.0.2 robust-predicates: 3.0.2
@ -6198,13 +6466,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
drizzle-orm@0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(postgres@3.4.5)(react@19.0.1): drizzle-orm@0.34.1(@neondatabase/serverless@0.9.5)(@opentelemetry/api@1.9.0)(@types/pg@8.11.6)(@types/react@18.3.18)(@vercel/postgres@0.10.0)(kysely@0.28.11)(postgres@3.4.5)(react@19.0.1):
optionalDependencies: optionalDependencies:
'@neondatabase/serverless': 0.9.5 '@neondatabase/serverless': 0.9.5
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@types/pg': 8.11.6 '@types/pg': 8.11.6
'@types/react': 18.3.18 '@types/react': 18.3.18
'@vercel/postgres': 0.10.0 '@vercel/postgres': 0.10.0
kysely: 0.28.11
postgres: 3.4.5 postgres: 3.4.5
react: 19.0.1 react: 19.0.1
@ -6531,7 +6800,7 @@ snapshots:
jiti@2.6.1: {} jiti@2.6.1: {}
jose@5.10.0: {} jose@6.2.1: {}
json-schema@0.4.0: {} json-schema@0.4.0: {}
@ -6543,6 +6812,8 @@ snapshots:
khroma@2.1.0: {} khroma@2.1.0: {}
kysely@0.28.11: {}
langium@4.2.1: langium@4.2.1:
dependencies: dependencies:
chevrotain: 11.1.1 chevrotain: 11.1.1
@ -6812,6 +7083,8 @@ snapshots:
mdurl@2.0.0: {} mdurl@2.0.0: {}
memory-pager@1.5.0: {}
mermaid@11.12.3: mermaid@11.12.3:
dependencies: dependencies:
'@braintree/sanitize-url': 7.1.2 '@braintree/sanitize-url': 7.1.2
@ -7083,6 +7356,17 @@ snapshots:
module-details-from-path@1.0.4: {} module-details-from-path@1.0.4: {}
mongodb-connection-string-url@7.0.1:
dependencies:
'@types/whatwg-url': 13.0.0
whatwg-url: 14.2.0
mongodb@7.1.0:
dependencies:
'@mongodb-js/saslprep': 1.4.6
bson: 7.2.0
mongodb-connection-string-url: 7.0.1
motion-dom@11.18.1: motion-dom@11.18.1:
dependencies: dependencies:
motion-utils: 11.18.1 motion-utils: 11.18.1
@ -7111,11 +7395,7 @@ snapshots:
nanoid@5.1.3: {} nanoid@5.1.3: {}
next-auth@5.0.0-beta.25(next@16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1))(react@19.0.1): nanostores@1.1.1: {}
dependencies:
'@auth/core': 0.37.2
next: 16.0.10(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.1(react@19.0.1))(react@19.0.1)
react: 19.0.1
next-themes@0.3.0(react-dom@19.0.1(react@19.0.1))(react@19.0.1): next-themes@0.3.0(react-dom@19.0.1(react@19.0.1))(react@19.0.1):
dependencies: dependencies:
@ -7158,8 +7438,6 @@ snapshots:
pkg-types: 2.3.0 pkg-types: 2.3.0
tinyexec: 1.0.2 tinyexec: 1.0.2
oauth4webapi@3.3.1: {}
obuf@1.1.2: obuf@1.1.2:
optional: true optional: true
@ -7287,15 +7565,6 @@ snapshots:
postgres@3.4.5: {} postgres@3.4.5: {}
preact-render-to-string@5.2.3(preact@10.11.3):
dependencies:
preact: 10.11.3
pretty-format: 3.8.0
preact@10.11.3: {}
pretty-format@3.8.0: {}
property-information@6.5.0: {} property-information@6.5.0: {}
property-information@7.0.0: {} property-information@7.0.0: {}
@ -7393,6 +7662,8 @@ snapshots:
punycode.js@2.3.1: {} punycode.js@2.3.1: {}
punycode@2.3.1: {}
radix-ui@1.4.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.1(react@19.0.1))(react@19.0.1): radix-ui@1.4.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.1(react@19.0.1))(react@19.0.1):
dependencies: dependencies:
'@radix-ui/primitive': 1.1.3 '@radix-ui/primitive': 1.1.3
@ -7628,6 +7899,8 @@ snapshots:
rope-sequence@1.3.4: {} rope-sequence@1.3.4: {}
rou3@0.7.12: {}
roughjs@4.6.6: roughjs@4.6.6:
dependencies: dependencies:
hachure-fill: 0.5.2 hachure-fill: 0.5.2
@ -7645,6 +7918,8 @@ snapshots:
server-only@0.0.1: {} server-only@0.0.1: {}
set-cookie-parser@3.0.1: {}
sharp@0.34.5: sharp@0.34.5:
dependencies: dependencies:
'@img/colour': 1.0.0 '@img/colour': 1.0.0
@ -7708,6 +7983,10 @@ snapshots:
space-separated-tokens@2.0.2: {} space-separated-tokens@2.0.2: {}
sparse-bitfield@3.0.3:
dependencies:
memory-pager: 1.5.0
streamdown@2.3.0(react-dom@19.0.1(react@19.0.1))(react@19.0.1): streamdown@2.3.0(react-dom@19.0.1(react@19.0.1))(react@19.0.1):
dependencies: dependencies:
clsx: 2.1.1 clsx: 2.1.1
@ -7776,6 +8055,10 @@ snapshots:
tinyexec@1.0.2: {} tinyexec@1.0.2: {}
tr46@5.1.1:
dependencies:
punycode: 2.3.1
trim-lines@3.0.1: {} trim-lines@3.0.1: {}
trough@2.2.0: {} trough@2.2.0: {}
@ -7942,6 +8225,13 @@ snapshots:
web-namespaces@2.0.1: {} web-namespaces@2.0.1: {}
webidl-conversions@7.0.0: {}
whatwg-url@14.2.0:
dependencies:
tr46: 5.1.1
webidl-conversions: 7.0.0
ws@8.18.1(bufferutil@4.0.9): ws@8.18.1(bufferutil@4.0.9):
optionalDependencies: optionalDependencies:
bufferutil: 4.0.9 bufferutil: 4.0.9
@ -7951,4 +8241,6 @@ snapshots:
zod@4.3.5: {} zod@4.3.5: {}
zod@4.3.6: {}
zwitch@2.0.4: {} zwitch@2.0.4: {}

View file

@ -1,8 +1,7 @@
import { getSessionCookie } from "better-auth/cookies";
import { type NextRequest, NextResponse } from "next/server"; import { type NextRequest, NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import { guestRegex, isDevelopmentEnvironment } from "./lib/constants";
export async function proxy(request: NextRequest) { export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl; const { pathname } = request.nextUrl;
/* /*
@ -17,24 +16,9 @@ export async function proxy(request: NextRequest) {
return NextResponse.next(); return NextResponse.next();
} }
const token = await getToken({ const sessionCookie = getSessionCookie(request);
req: request,
secret: process.env.AUTH_SECRET,
secureCookie: !isDevelopmentEnvironment,
});
if (!token) { if (sessionCookie && ["/login", "/register"].includes(pathname)) {
const redirectUrl = encodeURIComponent(request.url);
const url = request.nextUrl.clone();
url.pathname = "/api/auth/guest";
url.search = `?redirectUrl=${redirectUrl}`;
return NextResponse.redirect(url);
}
const isGuest = guestRegex.test(token?.email ?? "");
if (token && !isGuest && ["/login", "/register"].includes(pathname)) {
const url = request.nextUrl.clone(); const url = request.nextUrl.clone();
url.pathname = "/"; url.pathname = "/";
return NextResponse.redirect(url); return NextResponse.redirect(url);