diff --git a/README.md b/README.md index 798725e860..797d5c7418 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,9 @@ - Data Persistence - [Neon Serverless Postgres](https://vercel.com/marketplace/neon) for saving chat history and user data - [Vercel Blob](https://vercel.com/storage/blob) for efficient file storage -- [Auth.js](https://authjs.dev) - - Simple and secure authentication +- [BetterAuth](https://www.better-auth.com) + - Modern, type-safe authentication with email/password support + - Built-in session management with customizable password hashing ## Model Providers diff --git a/app/(auth)/actions.ts b/app/(auth)/actions.ts index 024ff518ed..c18633053a 100644 --- a/app/(auth)/actions.ts +++ b/app/(auth)/actions.ts @@ -1,19 +1,18 @@ -"use server"; +"use server" -import { z } from "zod"; - -import { createUser, getUser } from "@/lib/db/queries"; - -import { signIn } from "./auth"; +import { z } from "zod" +import { auth } from "@/lib/auth" +import { createUser, getUser } from "@/lib/db/queries" +import { headers } from "next/headers" const authFormSchema = z.object({ email: z.string().email(), password: z.string().min(6), -}); +}) export type LoginActionState = { - status: "idle" | "in_progress" | "success" | "failed" | "invalid_data"; -}; + status: "idle" | "in_progress" | "success" | "failed" | "invalid_data" +} export const login = async ( _: LoginActionState, @@ -23,23 +22,26 @@ export const login = async ( const validatedData = authFormSchema.parse({ email: formData.get("email"), password: formData.get("password"), - }); + }) - await signIn("credentials", { - email: validatedData.email, - password: validatedData.password, - redirect: false, - }); + const response = await auth.api.signInEmail({ + body: validatedData, + headers: await headers(), + asResponse: true + }) - return { status: "success" }; + if (!response.ok) { + return { status: "failed" } + } + + return { status: "success" } } catch (error) { if (error instanceof z.ZodError) { - return { status: "invalid_data" }; + return { status: "invalid_data" } } - - return { status: "failed" }; + return { status: "failed" } } -}; +} export type RegisterActionState = { status: @@ -48,8 +50,8 @@ export type RegisterActionState = { | "success" | "failed" | "user_exists" - | "invalid_data"; -}; + | "invalid_data" +} export const register = async ( _: RegisterActionState, @@ -59,26 +61,33 @@ export const register = async ( const validatedData = authFormSchema.parse({ email: formData.get("email"), password: formData.get("password"), - }); + }) - const [user] = await getUser(validatedData.email); + // Check if user exists + const [existingUser] = await getUser(validatedData.email) + if (existingUser) { + return { status: "user_exists" } + } + + // Create user in database first + await createUser(validatedData.email, validatedData.password) - if (user) { - return { status: "user_exists" } as RegisterActionState; + // Then sign them in + const response = await auth.api.signInEmail({ + body: validatedData, + headers: await headers(), + asResponse: true + }) + + if (!response.ok) { + return { status: "failed" } } - await createUser(validatedData.email, validatedData.password); - await signIn("credentials", { - email: validatedData.email, - password: validatedData.password, - redirect: false, - }); - return { status: "success" }; + return { status: "success" } } catch (error) { if (error instanceof z.ZodError) { - return { status: "invalid_data" }; + return { status: "invalid_data" } } - - return { status: "failed" }; + return { status: "failed" } } -}; +} diff --git a/app/(auth)/api/auth/[...nextauth]/route.ts b/app/(auth)/api/auth/[...nextauth]/route.ts deleted file mode 100644 index 588ff6a5ca..0000000000 --- a/app/(auth)/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,2 +0,0 @@ -// biome-ignore lint/performance/noBarrelFile: "Required" -export { GET, POST } from "@/app/(auth)/auth"; diff --git a/app/(auth)/api/auth/guest/route.ts b/app/(auth)/api/auth/guest/route.ts index dca565c5ab..40881ce8a6 100644 --- a/app/(auth)/api/auth/guest/route.ts +++ b/app/(auth)/api/auth/guest/route.ts @@ -1,21 +1,51 @@ -import { NextResponse } from "next/server"; -import { getToken } from "next-auth/jwt"; -import { signIn } from "@/app/(auth)/auth"; -import { isDevelopmentEnvironment } from "@/lib/constants"; +import { headers } from "next/headers"; +import { NextResponse } from "next/server" +import { auth } from "@/lib/auth" +import { createGuestUser } from "@/lib/db/queries" export async function GET(request: Request) { - const { searchParams } = new URL(request.url); - const redirectUrl = searchParams.get("redirectUrl") || "/"; + const { searchParams } = new URL(request.url) + const redirectUrl = searchParams.get("redirectUrl") || "/" - const token = await getToken({ - req: request, - secret: process.env.AUTH_SECRET, - secureCookie: !isDevelopmentEnvironment, - }); + try { + // Check if already authenticated + const session = await auth.api.getSession({ + headers: request.headers + }) - if (token) { - return NextResponse.redirect(new URL("/", request.url)); - } + if (session) { + return NextResponse.redirect(new URL("/", request.url)) + } + + // Create guest user + const [guestUser] = await createGuestUser() + + // Sign in the guest user programmatically + // This approach manually creates a session for the guest user + const signInResponse = await auth.api.signInEmail({ + body: { + email: guestUser.email!, + password: "guest-password", // Use a consistent guest password + }, + asResponse: true + }) + + if (signInResponse.ok) { + // Forward the session cookies from the sign-in response + const response = NextResponse.redirect(new URL(redirectUrl, request.url)) - return signIn("guest", { redirect: true, redirectTo: redirectUrl }); + // Copy auth cookies from the sign-in response + const authCookies = signInResponse.headers.get('set-cookie') + if (authCookies) { + response.headers.set('set-cookie', authCookies) + } + + return response + } + + throw new Error('Guest sign-in failed') + } catch (error) { + console.error('Guest authentication error:', error) + return NextResponse.redirect(new URL("/login", request.url)) + } } diff --git a/app/(auth)/auth.config.ts b/app/(auth)/auth.config.ts deleted file mode 100644 index b8bc9e1f17..0000000000 --- a/app/(auth)/auth.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { NextAuthConfig } from "next-auth"; - -export const authConfig = { - 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; diff --git a/app/(auth)/auth.ts b/app/(auth)/auth.ts deleted file mode 100644 index dbebb1d98d..0000000000 --- a/app/(auth)/auth.ts +++ /dev/null @@ -1,95 +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"]; - } - - // biome-ignore lint/nursery/useConsistentTypeDefinitions: "Required" - 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; - }, - }, -}); diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 1140cbe14d..1ba05f644d 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; -import { useSession } from "next-auth/react"; +import { useSession } from "@/lib/auth-client"; import { useActionState, useEffect, useState } from "react"; import { AuthForm } from "@/components/auth-form"; @@ -23,7 +23,7 @@ export default function Page() { } ); - const { update: updateSession } = useSession(); + const { data: session, isPending } = useSession(); useEffect(() => { if (state.status === "failed") { @@ -38,11 +38,10 @@ export default function Page() { }); } else if (state.status === "success") { setIsSuccessful(true); - updateSession(); router.refresh(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.status, router.refresh, updateSession]); + }, [state.status, router]); const handleSubmit = (formData: FormData) => { setEmail(formData.get("email") as string); diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx index 950f67b96b..295333f8c3 100644 --- a/app/(auth)/register/page.tsx +++ b/app/(auth)/register/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; -import { useSession } from "next-auth/react"; +import { useSession } from "@/lib/auth-client"; import { useActionState, useEffect, useState } from "react"; import { AuthForm } from "@/components/auth-form"; import { SubmitButton } from "@/components/submit-button"; @@ -22,7 +22,7 @@ export default function Page() { } ); - const { update: updateSession } = useSession(); + const { data: session, isPending } = useSession(); useEffect(() => { if (state.status === "user_exists") { @@ -38,11 +38,10 @@ export default function Page() { toast({ type: "success", description: "Account created successfully!" }); setIsSuccessful(true); - updateSession(); router.refresh(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.status, router.refresh, updateSession]); + }, [state.status, router]); const handleSubmit = (formData: FormData) => { setEmail(formData.get("email") as string); diff --git a/app/(chat)/api/chat/[id]/stream/route.ts b/app/(chat)/api/chat/[id]/stream/route.ts index 48352e9763..c076948af1 100644 --- a/app/(chat)/api/chat/[id]/stream/route.ts +++ b/app/(chat)/api/chat/[id]/stream/route.ts @@ -1,6 +1,7 @@ +import { headers } from "next/headers"; import { createUIMessageStream, JsonToSseTransformStream } from "ai"; import { differenceInSeconds } from "date-fns"; -import { auth } from "@/app/(auth)/auth"; +import { auth } from "@/lib/auth"; import { getChatById, getMessagesByChatId, @@ -28,7 +29,7 @@ export async function GET( return new ChatSDKError("bad_request:api").toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:chat").toResponse(); diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 8808ea6fff..f750daf110 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -1,3 +1,4 @@ +import { headers } from "next/headers"; import { geolocation } from "@vercel/functions"; import { convertToModelMessages, @@ -16,7 +17,7 @@ import { import type { ModelCatalog } from "tokenlens/core"; import { fetchModels } from "tokenlens/fetch"; import { getUsage } from "tokenlens/helpers"; -import { auth, type UserType } from "@/app/(auth)/auth"; +import { auth, type UserType } from "@/lib/auth"; import type { VisibilityType } from "@/components/visibility-selector"; import { entitlementsByUserType } from "@/lib/ai/entitlements"; import type { ChatModel } from "@/lib/ai/models"; @@ -107,13 +108,13 @@ export async function POST(request: Request) { selectedVisibilityType: VisibilityType; } = requestBody; - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:chat").toResponse(); } - const userType: UserType = session.user.type; + const userType: UserType = /^guest-\d+$/.test(session.user.email) ? "guest" : "regular"; const messageCount = await getMessageCountByUserId({ id: session.user.id, @@ -315,7 +316,7 @@ export async function DELETE(request: Request) { return new ChatSDKError("bad_request:api").toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:chat").toResponse(); diff --git a/app/(chat)/api/document/route.ts b/app/(chat)/api/document/route.ts index 0ea78ff553..382f3d6145 100644 --- a/app/(chat)/api/document/route.ts +++ b/app/(chat)/api/document/route.ts @@ -1,4 +1,5 @@ -import { auth } from "@/app/(auth)/auth"; +import { headers } from "next/headers"; +import { auth } from "@/lib/auth"; import type { ArtifactKind } from "@/components/artifact"; import { deleteDocumentsByIdAfterTimestamp, @@ -18,7 +19,7 @@ export async function GET(request: Request) { ).toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:document").toResponse(); @@ -50,7 +51,7 @@ export async function POST(request: Request) { ).toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("not_found:document").toResponse(); @@ -103,7 +104,7 @@ export async function DELETE(request: Request) { ).toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:document").toResponse(); diff --git a/app/(chat)/api/files/upload/route.ts b/app/(chat)/api/files/upload/route.ts index 4e4e4f3caf..c26114fb05 100644 --- a/app/(chat)/api/files/upload/route.ts +++ b/app/(chat)/api/files/upload/route.ts @@ -1,8 +1,9 @@ +import { headers } from "next/headers"; import { put } from "@vercel/blob"; import { NextResponse } from "next/server"; import { z } from "zod"; -import { auth } from "@/app/(auth)/auth"; +import { auth } from "@/lib/auth"; // Use Blob instead of File since File is not available in Node.js environment const FileSchema = z.object({ @@ -18,7 +19,7 @@ const FileSchema = z.object({ }); export async function POST(request: Request) { - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); diff --git a/app/(chat)/api/history/route.ts b/app/(chat)/api/history/route.ts index 412f10f6ed..658102397a 100644 --- a/app/(chat)/api/history/route.ts +++ b/app/(chat)/api/history/route.ts @@ -1,5 +1,6 @@ +import { headers } from "next/headers"; import type { NextRequest } from "next/server"; -import { auth } from "@/app/(auth)/auth"; +import { auth } from "@/lib/auth"; import { getChatsByUserId } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; @@ -17,7 +18,7 @@ export async function GET(request: NextRequest) { ).toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:chat").toResponse(); diff --git a/app/(chat)/api/suggestions/route.ts b/app/(chat)/api/suggestions/route.ts index 8801004eff..ae932b14aa 100644 --- a/app/(chat)/api/suggestions/route.ts +++ b/app/(chat)/api/suggestions/route.ts @@ -1,4 +1,5 @@ -import { auth } from "@/app/(auth)/auth"; +import { headers } from "next/headers"; +import { auth } from "@/lib/auth"; import { getSuggestionsByDocumentId } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; @@ -13,7 +14,7 @@ export async function GET(request: Request) { ).toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:suggestions").toResponse(); diff --git a/app/(chat)/api/vote/route.ts b/app/(chat)/api/vote/route.ts index 2c0ce3f788..28a9ef0323 100644 --- a/app/(chat)/api/vote/route.ts +++ b/app/(chat)/api/vote/route.ts @@ -1,4 +1,5 @@ -import { auth } from "@/app/(auth)/auth"; +import { headers } from "next/headers"; +import { auth } from "@/lib/auth"; import { getChatById, getVotesByChatId, voteMessage } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; @@ -13,7 +14,7 @@ export async function GET(request: Request) { ).toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:vote").toResponse(); @@ -49,7 +50,7 @@ export async function PATCH(request: Request) { ).toResponse(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session?.user) { return new ChatSDKError("unauthorized:vote").toResponse(); diff --git a/app/(chat)/chat/[id]/page.tsx b/app/(chat)/chat/[id]/page.tsx index f208fac603..2b886497fe 100644 --- a/app/(chat)/chat/[id]/page.tsx +++ b/app/(chat)/chat/[id]/page.tsx @@ -1,7 +1,8 @@ +import { headers } from "next/headers"; import { cookies } from "next/headers"; import { notFound, redirect } from "next/navigation"; -import { auth } from "@/app/(auth)/auth"; +import { auth } from "@/lib/auth"; import { Chat } from "@/components/chat"; import { DataStreamHandler } from "@/components/data-stream-handler"; import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models"; @@ -17,7 +18,7 @@ export default async function Page(props: { params: Promise<{ id: string }> }) { notFound(); } - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session) { redirect("/api/auth/guest"); diff --git a/app/(chat)/layout.tsx b/app/(chat)/layout.tsx index c5e44e5a8b..fb37d7433d 100644 --- a/app/(chat)/layout.tsx +++ b/app/(chat)/layout.tsx @@ -3,7 +3,8 @@ import Script from "next/script"; import { AppSidebar } from "@/components/app-sidebar"; import { DataStreamProvider } from "@/components/data-stream-provider"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; -import { auth } from "../(auth)/auth"; +import { headers } from "next/headers"; +import { auth } from "@/lib/auth"; export const experimental_ppr = true; @@ -12,7 +13,10 @@ export default async function Layout({ }: { children: React.ReactNode; }) { - const [session, cookieStore] = await Promise.all([auth(), cookies()]); + const [session, cookieStore] = await Promise.all([ + auth.api.getSession({ headers: await headers() }), + cookies() + ]); const isCollapsed = cookieStore.get("sidebar_state")?.value !== "true"; return ( diff --git a/app/(chat)/page.tsx b/app/(chat)/page.tsx index 225d2acaff..1bceb07586 100644 --- a/app/(chat)/page.tsx +++ b/app/(chat)/page.tsx @@ -1,13 +1,14 @@ +import { headers } from "next/headers"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import { Chat } from "@/components/chat"; import { DataStreamHandler } from "@/components/data-stream-handler"; import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models"; import { generateUUID } from "@/lib/utils"; -import { auth } from "../(auth)/auth"; +import { auth } from "@/lib/auth"; export default async function Page() { - const session = await auth(); + const session = await auth.api.getSession({ headers: await headers() }); if (!session) { redirect("/api/auth/guest"); diff --git a/app/api/auth/[...all]/route.ts b/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000000..3ae740d1a3 --- /dev/null +++ b/app/api/auth/[...all]/route.ts @@ -0,0 +1,4 @@ +import { toNextJsHandler } from "better-auth/next-js" +import { auth } from "@/lib/auth" + +export const { POST, GET } = toNextJsHandler(auth) \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 66db5da925..16d56cb91f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,7 +4,6 @@ import { Toaster } from "sonner"; import { ThemeProvider } from "@/components/theme-provider"; import "./globals.css"; -import { SessionProvider } from "next-auth/react"; export const metadata: Metadata = { metadataBase: new URL("https://chat.vercel.ai"), @@ -79,7 +78,7 @@ export default function RootLayout({ enableSystem > - {children} + {children} diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 7e9b3447fc..c4128f74c6 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; -import type { User } from "next-auth"; +import type { User } from "better-auth"; import { PlusIcon } from "@/components/icons"; import { SidebarHistory } from "@/components/sidebar-history"; import { SidebarUserNav } from "@/components/sidebar-user-nav"; diff --git a/components/model-selector.tsx b/components/model-selector.tsx index 65134c8099..038d6e3068 100644 --- a/components/model-selector.tsx +++ b/components/model-selector.tsx @@ -1,6 +1,6 @@ "use client"; -import type { Session } from "next-auth"; +import type { Session, UserType } from "@/lib/auth"; import { startTransition, useMemo, useOptimistic, useState } from "react"; import { saveChatModelAsCookie } from "@/app/(chat)/actions"; import { Button } from "@/components/ui/button"; @@ -27,7 +27,7 @@ export function ModelSelector({ const [optimisticModelId, setOptimisticModelId] = useOptimistic(selectedModelId); - const userType = session.user.type; + const userType: UserType = /^guest-\d+$/.test(session.user.email) ? "guest" : "regular"; const { availableChatModelIds } = entitlementsByUserType[userType]; const availableChatModels = chatModels.filter((chatModel) => diff --git a/components/sidebar-history.tsx b/components/sidebar-history.tsx index fa6a1518af..e8045573af 100644 --- a/components/sidebar-history.tsx +++ b/components/sidebar-history.tsx @@ -3,10 +3,10 @@ import { isToday, isYesterday, subMonths, subWeeks } from "date-fns"; import { motion } from "framer-motion"; import { useParams, useRouter } from "next/navigation"; -import type { User } from "next-auth"; import { useState } from "react"; import { toast } from "sonner"; import useSWRInfinite from "swr/infinite"; +import type { Session } from "@/lib/auth"; import { AlertDialog, AlertDialogAction, @@ -97,7 +97,7 @@ export function getChatHistoryPaginationKey( return `/api/history?ending_before=${firstChatFromPage.id}&limit=${PAGE_SIZE}`; } -export function SidebarHistory({ user }: { user: User | undefined }) { +export function SidebarHistory({ user }: { user: Session["user"] | null }) { const { setOpenMobile } = useSidebar(); const { id } = useParams(); diff --git a/components/sidebar-user-nav.tsx b/components/sidebar-user-nav.tsx index 81b52d8d10..17dac8bf03 100644 --- a/components/sidebar-user-nav.tsx +++ b/components/sidebar-user-nav.tsx @@ -3,9 +3,9 @@ import { ChevronUp } from "lucide-react"; import Image from "next/image"; import { useRouter } from "next/navigation"; -import type { User } from "next-auth"; -import { signOut, useSession } from "next-auth/react"; +import { signOut, useSession } from "@/lib/auth-client"; import { useTheme } from "next-themes"; +import type { Session } from "@/lib/auth"; import { DropdownMenu, DropdownMenuContent, @@ -22,19 +22,19 @@ import { guestRegex } from "@/lib/constants"; import { LoaderIcon } from "./icons"; import { toast } from "./toast"; -export function SidebarUserNav({ user }: { user: User }) { +export function SidebarUserNav({ user }: { user: Session["user"] | null }) { const router = useRouter(); - const { data, status } = useSession(); + const { data: session, isPending } = useSession(); const { setTheme, resolvedTheme } = useTheme(); - const isGuest = guestRegex.test(data?.user?.email ?? ""); + const isGuest = guestRegex.test(session?.user?.email ?? ""); return ( - {status === "loading" ? ( + {isPending ? (
@@ -84,7 +84,7 @@ export function SidebarUserNav({ user }: { user: User }) { - + ); }; diff --git a/lib/ai/entitlements.ts b/lib/ai/entitlements.ts index 4577974a0f..e8bda92d7c 100644 --- a/lib/ai/entitlements.ts +++ b/lib/ai/entitlements.ts @@ -1,4 +1,4 @@ -import type { UserType } from "@/app/(auth)/auth"; +import type { UserType } from "@/lib/auth"; import type { ChatModel } from "./models"; type Entitlements = { diff --git a/lib/ai/tools/create-document.ts b/lib/ai/tools/create-document.ts index bc9bc72021..d853ea68bb 100644 --- a/lib/ai/tools/create-document.ts +++ b/lib/ai/tools/create-document.ts @@ -1,5 +1,5 @@ import { tool, type UIMessageStreamWriter } from "ai"; -import type { Session } from "next-auth"; +import type { Session } from "@/lib/auth"; import { z } from "zod"; import { artifactKinds, diff --git a/lib/ai/tools/request-suggestions.ts b/lib/ai/tools/request-suggestions.ts index f1a9d160eb..260835a279 100644 --- a/lib/ai/tools/request-suggestions.ts +++ b/lib/ai/tools/request-suggestions.ts @@ -1,5 +1,5 @@ import { streamObject, tool, type UIMessageStreamWriter } from "ai"; -import type { Session } from "next-auth"; +import type { Session } from "@/lib/auth"; import { z } from "zod"; import { getDocumentById, saveSuggestions } from "@/lib/db/queries"; import type { Suggestion } from "@/lib/db/schema"; diff --git a/lib/ai/tools/update-document.ts b/lib/ai/tools/update-document.ts index cae5c801dc..4167868420 100644 --- a/lib/ai/tools/update-document.ts +++ b/lib/ai/tools/update-document.ts @@ -1,5 +1,5 @@ import { tool, type UIMessageStreamWriter } from "ai"; -import type { Session } from "next-auth"; +import type { Session } from "@/lib/auth"; import { z } from "zod"; import { documentHandlersByArtifactKind } from "@/lib/artifacts/server"; import { getDocumentById } from "@/lib/db/queries"; diff --git a/lib/artifacts/server.ts b/lib/artifacts/server.ts index a11ecfddf6..02ca66b7b3 100644 --- a/lib/artifacts/server.ts +++ b/lib/artifacts/server.ts @@ -1,5 +1,5 @@ import type { UIMessageStreamWriter } from "ai"; -import type { Session } from "next-auth"; +import type { Session } from "@/lib/auth"; import { codeDocumentHandler } from "@/artifacts/code/server"; import { sheetDocumentHandler } from "@/artifacts/sheet/server"; import { textDocumentHandler } from "@/artifacts/text/server"; diff --git a/lib/auth-client.ts b/lib/auth-client.ts new file mode 100644 index 0000000000..197674f095 --- /dev/null +++ b/lib/auth-client.ts @@ -0,0 +1,13 @@ +import { createAuthClient } from "better-auth/react" + +export const authClient = createAuthClient({ + baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || process.env.BETTER_AUTH_URL || "http://localhost:3000", +}) + +// Export commonly used functions +export const { + signIn, + signOut, + signUp, + useSession, +} = authClient \ No newline at end of file diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000000..1cf8f03fed --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,42 @@ +import { betterAuth } from "better-auth" +import { drizzleAdapter } from "better-auth/adapters/drizzle" +import { db } from "./db" +import { user } from "./db/schema" + +export const auth = betterAuth({ + database: drizzleAdapter(db, { + provider: "pg", + schema: { + user: user, + } + }), + user: { + additionalFields: { + type: { + type: "string", + input: false, + defaultValue: "regular" + } + } + }, + emailAndPassword: { + enabled: true, + requireEmailVerification: false, + // Custom password verification to maintain existing bcrypt logic + password: { + hash: async (password) => { + const bcrypt = await import("bcrypt-ts") + return bcrypt.hash(password, 10) + }, + verify: async ({ password, hash }) => { + const bcrypt = await import("bcrypt-ts") + return bcrypt.compare(password, hash) + } + } + }, + // Note: Guest user system will be handled through custom API routes + // BetterAuth doesn't have built-in "guest" provider like NextAuth +}) + +export type Session = typeof auth.$Infer.Session +export type UserType = "guest" | "regular" \ No newline at end of file diff --git a/lib/db/index.ts b/lib/db/index.ts new file mode 100644 index 0000000000..ace7d1332d --- /dev/null +++ b/lib/db/index.ts @@ -0,0 +1,8 @@ +import "server-only"; + +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +// biome-ignore lint: Forbidden non-null assertion. +const client = postgres(process.env.POSTGRES_URL!); +export const db = drizzle(client); \ No newline at end of file diff --git a/lib/db/queries.ts b/lib/db/queries.ts index 7772160e2e..0dfce75aea 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -12,8 +12,6 @@ import { lt, type SQL, } from "drizzle-orm"; -import { drizzle } from "drizzle-orm/postgres-js"; -import postgres from "postgres"; import type { ArtifactKind } from "@/components/artifact"; import type { VisibilityType } from "@/components/visibility-selector"; import { ChatSDKError } from "../errors"; @@ -33,15 +31,12 @@ import { vote, } from "./schema"; import { generateHashedPassword } from "./utils"; +import { db } from "./index"; // 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 { try { return await db.select().from(user).where(eq(user.email, email)); diff --git a/middleware.ts b/middleware.ts index 228ced03d4..e993996c72 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,46 +1,70 @@ -import { type NextRequest, NextResponse } from "next/server"; -import { getToken } from "next-auth/jwt"; -import { guestRegex, isDevelopmentEnvironment } from "./lib/constants"; +import { type NextRequest, NextResponse } from "next/server" +import { auth } from "@/lib/auth" +import { guestRegex } from "./lib/constants" export async function middleware(request: NextRequest) { - const { pathname } = request.nextUrl; + const { pathname } = request.nextUrl /* * Playwright starts the dev server and requires a 200 status to * begin the tests, so this ensures that the tests can start */ if (pathname.startsWith("/ping")) { - return new Response("pong", { status: 200 }); + return new Response("pong", { status: 200 }) } if (pathname.startsWith("/api/auth")) { - return NextResponse.next(); + return NextResponse.next() } - const token = await getToken({ - req: request, - secret: process.env.AUTH_SECRET, - secureCookie: !isDevelopmentEnvironment, - }); + try { + const session = await auth.api.getSession({ + headers: request.headers + }) - if (!token) { - const redirectUrl = encodeURIComponent(request.url); + if (!session) { + const redirectUrl = encodeURIComponent(request.url) + return NextResponse.redirect( + new URL(`/api/auth/guest?redirectUrl=${redirectUrl}`, request.url) + ) + } - return NextResponse.redirect( - new URL(`/api/auth/guest?redirectUrl=${redirectUrl}`, request.url) - ); - } + const isGuest = guestRegex.test(session.user?.email ?? "") - const isGuest = guestRegex.test(token?.email ?? ""); + if (session && !isGuest && ["/login", "/register"].includes(pathname)) { + return NextResponse.redirect(new URL("/", request.url)) + } - if (token && !isGuest && ["/login", "/register"].includes(pathname)) { - return NextResponse.redirect(new URL("/", request.url)); - } + return NextResponse.next() + } catch (error) { + console.error("Auth middleware error:", error) + + // Handle specific auth-related errors + if (error instanceof Error) { + // For token validation errors, redirect to guest auth + if (error.message.includes("token") || error.message.includes("jwt") || error.message.includes("session")) { + const redirectUrl = encodeURIComponent(request.url) + return NextResponse.redirect( + new URL(`/api/auth/guest?redirectUrl=${redirectUrl}`, request.url) + ) + } - return NextResponse.next(); + // For network/database errors, return error response instead of redirecting + if (error.message.includes("network") || error.message.includes("database") || error.message.includes("connection")) { + return new Response("Service temporarily unavailable", { status: 503 }) + } + } + + // For unknown errors, redirect to guest auth as fallback + const redirectUrl = encodeURIComponent(request.url) + return NextResponse.redirect( + new URL(`/api/auth/guest?redirectUrl=${redirectUrl}`, request.url) + ) + } } export const config = { + runtime: "nodejs", // Required for BetterAuth session handling matcher: [ "/", "/chat/:id", @@ -56,4 +80,4 @@ export const config = { */ "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", ], -}; +} diff --git a/package.json b/package.json index 7a3dffae0f..3790134e30 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@vercel/postgres": "^0.10.0", "ai": "5.0.26", "bcrypt-ts": "^5.0.2", + "better-auth": "^1.3.23", "class-variance-authority": "^0.7.1", "classnames": "^2.5.1", "clsx": "^2.1.1", @@ -56,7 +57,6 @@ "lucide-react": "^0.446.0", "nanoid": "^5.0.8", "next": "15.3.0-canary.31", - "next-auth": "5.0.0-beta.25", "next-themes": "^0.3.0", "orderedmap": "^2.1.1", "papaparse": "^5.5.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 992543ce9f..d1095c883b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: bcrypt-ts: specifier: ^5.0.2 version: 5.0.3 + better-auth: + specifier: ^1.3.23 + version: 1.3.23(next@15.3.0-canary.31(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -100,7 +103,7 @@ importers: version: 16.4.7 drizzle-orm: 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.0-rc-45804af1-20241021) + 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.7)(postgres@3.4.5)(react@19.0.0-rc-45804af1-20241021) embla-carousel-react: specifier: ^8.6.0 version: 8.6.0(react@19.0.0-rc-45804af1-20241021) @@ -122,9 +125,6 @@ importers: next: specifier: 15.3.0-canary.31 version: 15.3.0-canary.31(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) - next-auth: - specifier: 5.0.0-beta.25 - version: 5.0.0-beta.25(next@15.3.0-canary.31(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) @@ -321,24 +321,19 @@ packages: '@antfu/utils@9.2.0': resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==} - '@auth/core@0.37.2': - resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - nodemailer: ^6.8.0 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - '@babel/runtime@7.28.3': resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} engines: {node: '>=6.9.0'} + '@better-auth/core@1.3.23': + resolution: {integrity: sha512-l0ICSSu8N4tOcdbDX6DRetFaX/wBHLwB4mSp9LD9DfjgDywKWWbzTFr3GP+T5HfvNq+vmH4NKjr2MJtI+sTqIw==} + + '@better-auth/utils@0.3.0': + resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} + + '@better-fetch/fetch@1.1.18': + resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==} + '@biomejs/biome@2.2.2': resolution: {integrity: sha512-j1omAiQWCkhuLgwpMKisNKnsM6W8Xtt1l0WZmqY/dFj8QPNkIoTvk4tSsi40FaAAkBE1PU0AFG2RWFBWenAn+w==} engines: {node: '>=14.21.3'} @@ -899,6 +894,9 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@hexagon/base64@1.1.28': + resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} + '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -1035,6 +1033,9 @@ packages: '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@levischuck/tiny-cbor@0.2.11': + resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==} + '@lezer/common@1.2.3': resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} @@ -1110,6 +1111,14 @@ packages: cpu: [x64] os: [win32] + '@noble/ciphers@2.0.1': + resolution: {integrity: sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==} + 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': resolution: {integrity: sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q==} engines: {node: '>=8.0.0'} @@ -1146,8 +1155,41 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} - '@panva/hkdf@1.2.1': - resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@peculiar/asn1-android@2.5.0': + resolution: {integrity: sha512-t8A83hgghWQkcneRsgGs2ebAlRe54ns88p7ouv8PW2tzF1nAW4yHcL4uZKrFpIU+uszIRzTkcCuie37gpkId0A==} + + '@peculiar/asn1-cms@2.5.0': + resolution: {integrity: sha512-p0SjJ3TuuleIvjPM4aYfvYw8Fk1Hn/zAVyPJZTtZ2eE9/MIer6/18ROxX6N/e6edVSfvuZBqhxAj3YgsmSjQ/A==} + + '@peculiar/asn1-csr@2.5.0': + resolution: {integrity: sha512-ioigvA6WSYN9h/YssMmmoIwgl3RvZlAYx4A/9jD2qaqXZwGcNlAxaw54eSx2QG1Yu7YyBC5Rku3nNoHrQ16YsQ==} + + '@peculiar/asn1-ecc@2.5.0': + resolution: {integrity: sha512-t4eYGNhXtLRxaP50h3sfO6aJebUCDGQACoeexcelL4roMFRRVgB20yBIu2LxsPh/tdW9I282gNgMOyg3ywg/mg==} + + '@peculiar/asn1-pfx@2.5.0': + resolution: {integrity: sha512-Vj0d0wxJZA+Ztqfb7W+/iu8Uasw6hhKtCdLKXLG/P3kEPIQpqGI4P4YXlROfl7gOCqFIbgsj1HzFIFwQ5s20ug==} + + '@peculiar/asn1-pkcs8@2.5.0': + resolution: {integrity: sha512-L7599HTI2SLlitlpEP8oAPaJgYssByI4eCwQq2C9eC90otFpm8MRn66PpbKviweAlhinWQ3ZjDD2KIVtx7PaVw==} + + '@peculiar/asn1-pkcs9@2.5.0': + resolution: {integrity: sha512-UgqSMBLNLR5TzEZ5ZzxR45Nk6VJrammxd60WMSkofyNzd3DQLSNycGWSK5Xg3UTYbXcDFyG8pA/7/y/ztVCa6A==} + + '@peculiar/asn1-rsa@2.5.0': + resolution: {integrity: sha512-qMZ/vweiTHy9syrkkqWFvbT3eLoedvamcUdnnvwyyUNv5FgFXA3KP8td+ATibnlZ0EANW5PYRm8E6MJzEB/72Q==} + + '@peculiar/asn1-schema@2.5.0': + resolution: {integrity: sha512-YM/nFfskFJSlHqv59ed6dZlLZqtZQwjRVJ4bBAiWV08Oc+1rSd5lDZcBEx0lGDHfSoH3UziI2pXt2UM33KerPQ==} + + '@peculiar/asn1-x509-attr@2.5.0': + resolution: {integrity: sha512-9f0hPOxiJDoG/bfNLAFven+Bd4gwz/VzrCIIWc1025LEI4BXO0U5fOCTNDPbbp2ll+UzqKsZ3g61mpBp74gk9A==} + + '@peculiar/asn1-x509@2.5.0': + resolution: {integrity: sha512-CpwtMCTJvfvYTFMuiME5IH+8qmDe3yEWzKHe7OOADbGfq7ohxeLaXwQo0q4du3qs0AII3UbLCvb9NF/6q0oTKQ==} + + '@peculiar/x509@1.14.0': + resolution: {integrity: sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg==} '@playwright/test@1.51.0': resolution: {integrity: sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==} @@ -2052,6 +2094,13 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@simplewebauthn/browser@13.2.0': + resolution: {integrity: sha512-N3fuA1AAnTo5gCStYoIoiasPccC+xPLx2YU88Dv0GeAmPQTWHETlZQq5xZ0DgUq1H9loXMWQH5qqUjcI7BHJ1A==} + + '@simplewebauthn/server@13.2.1': + resolution: {integrity: sha512-Inmfye5opZXe3HI0GaksqBnQiM7glcNySoG6DH1GgkO1Lh9dvuV4XSV9DK02DReUVX39HpcDob9nxHELjECoQw==} + engines: {node: '>=20.0.0'} + '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -2174,9 +2223,6 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -2455,6 +2501,10 @@ packages: resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} engines: {node: '>=10'} + asn1js@3.0.6: + resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==} + engines: {node: '>=12.0.0'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2469,6 +2519,38 @@ packages: resolution: {integrity: sha512-2FcgD12xPbwCoe5i9/HK0jJ1xA1m+QfC1e6htG9Bl/hNOnLyaFmQSlqLKcfe3QdnoMPKpKEGFCbESBTg+SJNOw==} engines: {node: '>=18'} + better-auth@1.3.23: + resolution: {integrity: sha512-wskWRCUu9pCmGzVMht2+ZkO33CzztJUdtuBttq7YDxBSR5/gaa3WaTx9TLLgUz/wBNCqyAW71uCqeArI5M6Fpg==} + peerDependencies: + '@lynx-js/react': '*' + '@sveltejs/kit': '*' + next: '*' + react: '*' + react-dom: '*' + solid-js: '*' + svelte: '*' + vue: '*' + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@sveltejs/kit': + optional: true + next: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + + better-call@1.0.19: + resolution: {integrity: sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2600,10 +2682,6 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} 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: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -2812,6 +2890,9 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} @@ -3189,8 +3270,8 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true - jose@5.10.0: - resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + jose@6.1.0: + resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -3211,6 +3292,10 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + kysely@0.28.7: + resolution: {integrity: sha512-u/cAuTL4DRIiO2/g4vNGRgklEKNIj5Q3CG7RoUB5DV5SfEC2hMvPxKi0GWPmnzwL2ryIeud2VTcEEmqzTzEPNw==} + engines: {node: '>=20.0.0'} + langium@3.3.1: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} @@ -3527,21 +3612,9 @@ packages: engines: {node: ^18 || >=20} hasBin: true - next-auth@5.0.0-beta.25: - resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} - peerDependencies: - '@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 + nanostores@1.0.1: + resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==} + engines: {node: ^20.0.0 || >=22.0.0} next-themes@0.3.0: resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} @@ -3579,9 +3652,6 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true - oauth4webapi@3.3.1: - resolution: {integrity: sha512-ZwX7UqYrP3Lr+Glhca3a1/nF2jqf7VVyJfhGuW5JtrfDUxt0u+IoBPzFjZ2dd7PJGkdM6CFPVVYzuDYKHv101A==} - obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} @@ -3706,17 +3776,6 @@ packages: resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==} 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==} - prismjs@1.27.0: resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} engines: {node: '>=6'} @@ -3783,6 +3842,13 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -3865,6 +3931,9 @@ packages: resolution: {integrity: sha512-J/fzf0cYeFw5NP4aYIvv9owYOcNUsaDqF4qmwbSuaV4yKBLaIHJQIFbAKLgjn99GDXdJBbfqCRXE7+BIlkpATA==} engines: {node: '>= 18'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + refractor@3.6.0: resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} @@ -3919,6 +3988,9 @@ packages: rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + rou3@0.5.1: + resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==} + roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} @@ -3939,6 +4011,9 @@ packages: server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -4106,6 +4181,9 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -4114,6 +4192,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tsyringe@4.10.0: + resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} + engines: {node: '>= 6.0.0'} + typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -4129,6 +4211,9 @@ packages: resolution: {integrity: sha512-85rzcGk+KKF3jhBGLYQj57JYAQ50Mun6PAifN4B6cCmCI8GNjgvUbp0f4DlpOfl92mq8CzouCzzcVxwcNNmTmw==} hasBin: true + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} @@ -4406,18 +4491,14 @@ snapshots: '@antfu/utils@9.2.0': {} - '@auth/core@0.37.2': - dependencies: - '@panva/hkdf': 1.2.1 - '@types/cookie': 0.6.0 - cookie: 0.7.1 - jose: 5.10.0 - oauth4webapi: 3.3.1 - preact: 10.11.3 - preact-render-to-string: 5.2.3(preact@10.11.3) - '@babel/runtime@7.28.3': {} + '@better-auth/core@1.3.23': {} + + '@better-auth/utils@0.3.0': {} + + '@better-fetch/fetch@1.1.18': {} + '@biomejs/biome@2.2.2': optionalDependencies: '@biomejs/cli-darwin-arm64': 2.2.2 @@ -4799,6 +4880,8 @@ snapshots: '@floating-ui/utils@0.2.9': {} + '@hexagon/base64@1.1.28': {} + '@iconify/types@2.0.0': {} '@iconify/utils@3.0.1': @@ -4916,6 +4999,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@levischuck/tiny-cbor@0.2.11': {} + '@lezer/common@1.2.3': {} '@lezer/highlight@1.2.1': @@ -4974,6 +5059,10 @@ snapshots: '@next/swc-win32-x64-msvc@15.3.0-canary.31': optional: true + '@noble/ciphers@2.0.1': {} + + '@noble/hashes@2.0.1': {} + '@opentelemetry/api-logs@0.200.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -5006,7 +5095,101 @@ snapshots: '@opentelemetry/semantic-conventions@1.28.0': {} - '@panva/hkdf@1.2.1': {} + '@peculiar/asn1-android@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-cms@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + '@peculiar/asn1-x509-attr': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-csr@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-ecc@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-pfx@2.5.0': + dependencies: + '@peculiar/asn1-cms': 2.5.0 + '@peculiar/asn1-pkcs8': 2.5.0 + '@peculiar/asn1-rsa': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs8@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-pkcs9@2.5.0': + dependencies: + '@peculiar/asn1-cms': 2.5.0 + '@peculiar/asn1-pfx': 2.5.0 + '@peculiar/asn1-pkcs8': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + '@peculiar/asn1-x509-attr': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-rsa@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-schema@2.5.0': + dependencies: + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509-attr@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + asn1js: 3.0.6 + tslib: 2.8.1 + + '@peculiar/asn1-x509@2.5.0': + dependencies: + '@peculiar/asn1-schema': 2.5.0 + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/x509@1.14.0': + dependencies: + '@peculiar/asn1-cms': 2.5.0 + '@peculiar/asn1-csr': 2.5.0 + '@peculiar/asn1-ecc': 2.5.0 + '@peculiar/asn1-pkcs9': 2.5.0 + '@peculiar/asn1-rsa': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + pvtsutils: 1.3.6 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + tsyringe: 4.10.0 '@playwright/test@1.51.0': dependencies: @@ -5913,6 +6096,19 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@simplewebauthn/browser@13.2.0': {} + + '@simplewebauthn/server@13.2.1': + dependencies: + '@hexagon/base64': 1.1.28 + '@levischuck/tiny-cbor': 0.2.11 + '@peculiar/asn1-android': 2.5.0 + '@peculiar/asn1-ecc': 2.5.0 + '@peculiar/asn1-rsa': 2.5.0 + '@peculiar/asn1-schema': 2.5.0 + '@peculiar/asn1-x509': 2.5.0 + '@peculiar/x509': 1.14.0 + '@standard-schema/spec@1.0.0': {} '@swc/counter@0.1.3': {} @@ -6024,8 +6220,6 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 - '@types/cookie@0.6.0': {} - '@types/d3-array@3.2.1': {} '@types/d3-axis@3.0.6': @@ -6314,6 +6508,12 @@ snapshots: dependencies: tslib: 2.8.1 + asn1js@3.0.6: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.3 + tslib: 2.8.1 + assertion-error@2.0.1: {} async-retry@1.3.3: @@ -6324,6 +6524,34 @@ snapshots: bcrypt-ts@5.0.3: {} + better-auth@1.3.23(next@15.3.0-canary.31(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021): + dependencies: + '@better-auth/core': 1.3.23 + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.18 + '@noble/ciphers': 2.0.1 + '@noble/hashes': 2.0.1 + '@simplewebauthn/browser': 13.2.0 + '@simplewebauthn/server': 13.2.1 + better-call: 1.0.19 + defu: 6.1.4 + jose: 6.1.0 + kysely: 0.28.7 + nanostores: 1.0.1 + zod: 4.1.11 + optionalDependencies: + next: 15.3.0-canary.31(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) + react: 19.0.0-rc-45804af1-20241021 + react-dom: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) + + better-call@1.0.19: + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.18 + rou3: 0.5.1 + set-cookie-parser: 2.7.1 + uncrypto: 0.1.3 + buffer-from@1.1.2: {} bufferutil@4.0.9: @@ -6444,8 +6672,6 @@ snapshots: consola@3.4.2: {} - cookie@0.7.1: {} - cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -6664,6 +6890,8 @@ snapshots: deepmerge@4.3.1: {} + defu@6.1.4: {} + delaunator@5.0.1: dependencies: robust-predicates: 3.0.2 @@ -6698,13 +6926,14 @@ snapshots: transitivePeerDependencies: - 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.0-rc-45804af1-20241021): + 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.7)(postgres@3.4.5)(react@19.0.0-rc-45804af1-20241021): optionalDependencies: '@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.7 postgres: 3.4.5 react: 19.0.0-rc-45804af1-20241021 @@ -7060,7 +7289,7 @@ snapshots: jiti@2.5.1: {} - jose@5.10.0: {} + jose@6.1.0: {} js-tokens@9.0.1: {} @@ -7076,6 +7305,8 @@ snapshots: kolorist@1.8.0: {} + kysely@0.28.7: {} + langium@3.3.1: dependencies: chevrotain: 11.0.3 @@ -7611,11 +7842,7 @@ snapshots: nanoid@5.1.3: {} - next-auth@5.0.0-beta.25(next@15.3.0-canary.31(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021): - dependencies: - '@auth/core': 0.37.2 - next: 15.3.0-canary.31(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) - react: 19.0.0-rc-45804af1-20241021 + nanostores@1.0.1: {} next-themes@0.3.0(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021): dependencies: @@ -7659,8 +7886,6 @@ snapshots: pkg-types: 2.3.0 tinyexec: 1.0.1 - oauth4webapi@3.3.1: {} - obuf@1.1.2: {} oniguruma-parser@0.12.1: {} @@ -7792,15 +8017,6 @@ snapshots: 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: {} - prismjs@1.27.0: {} prismjs@1.30.0: {} @@ -7906,6 +8122,12 @@ snapshots: punycode.js@2.3.1: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.3: {} + quansync@0.2.11: {} 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.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021): @@ -8052,6 +8274,8 @@ snapshots: '@redis/search': 5.0.0(@redis/client@5.0.0) '@redis/time-series': 5.0.0(@redis/client@5.0.0) + reflect-metadata@0.2.2: {} + refractor@3.6.0: dependencies: hastscript: 6.0.0 @@ -8165,6 +8389,8 @@ snapshots: rope-sequence@1.3.4: {} + rou3@0.5.1: {} + roughjs@4.6.6: dependencies: hachure-fill: 0.5.2 @@ -8183,6 +8409,8 @@ snapshots: server-only@0.0.1: {} + set-cookie-parser@2.7.1: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -8370,6 +8598,8 @@ snapshots: ts-dedent@2.2.0: {} + tslib@1.14.1: {} + tslib@2.8.1: {} tsx@4.19.3: @@ -8379,6 +8609,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tsyringe@4.10.0: + dependencies: + tslib: 1.14.1 + typescript@5.8.2: {} uc.micro@2.1.0: {} @@ -8418,6 +8652,8 @@ snapshots: - typescript - yaml + uncrypto@0.1.3: {} + undici-types@6.20.0: {} undici@5.28.5: