diff --git a/app/actions.ts b/app/actions.ts
index 9fe805e..3c6967c 100644
--- a/app/actions.ts
+++ b/app/actions.ts
@@ -1,12 +1,12 @@
"use server";
-import { getServerSession } from "next-auth";
import { prisma } from "@/lib/prisma";
+import { getServerSession } from "@/lib/session/get-server-session";
import { revalidatePath } from "next/cache";
-import { authOptions } from "@/app/api/auth/[...nextauth]/route";
export async function removeChat({ id, path }: { id: string; path: string }) {
- const session = await getServerSession(authOptions);
+ const session = await getServerSession();
+
const userId = session?.user?.email;
if (!userId || !id) {
throw new Error("Unauthorized");
diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts
deleted file mode 100644
index dea6129..0000000
--- a/app/api/auth/[...nextauth]/route.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import NextAuth, { NextAuthOptions } from "next-auth";
-import { PrismaAdapter } from "@next-auth/prisma-adapter";
-import { prisma } from "@/lib/prisma";
-import GoogleProvider from "next-auth/providers/google";
-
-export const authOptions: NextAuthOptions = {
- adapter: PrismaAdapter(prisma),
- providers: [
- GoogleProvider({
- clientId: process.env.GOOGLE_CLIENT_ID as string,
- clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
- }),
- ],
-};
-
-const handler = NextAuth(authOptions);
-
-export { handler as GET, handler as POST };
diff --git a/app/api/auth/callback/route.ts b/app/api/auth/callback/route.ts
new file mode 100644
index 0000000..a235634
--- /dev/null
+++ b/app/api/auth/callback/route.ts
@@ -0,0 +1,62 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { serialize } from 'cookie';
+import { createSession } from '@/lib/session/create';
+import { encryptJWECookie } from '@/lib/cookies/encrypt-jwe';
+import ms from 'ms';
+import { userSessionCookieName } from '@/lib/session/constants';
+
+export const runtime = 'edge';
+
+const sharedTokenSecret = process.env.SHARE_TOKEN_ENDPOINT_SECRET;
+const cookieTTL = ms('1y');
+
+export async function GET(req: NextRequest) {
+ const { origin, searchParams } = req.nextUrl;
+ const shareableToken = searchParams.get('shareableToken');
+
+ if (!sharedTokenSecret || !shareableToken) {
+ // TODO: Make an error page
+ return NextResponse.json({ error: 'Invalid request' }, { status: 500 });
+ }
+
+ const query = new URLSearchParams({
+ sharedTokenSecret,
+ });
+
+ const response = await fetch(
+ `https://api.vercel.com/user/tokens/shared/${shareableToken}?${query.toString()}`
+ );
+
+ const { token } = (await response.json()) as { token: string };
+
+ const session = await createSession(token);
+
+ const cookieValue = await encryptJWECookie(session, '1y');
+ const next = req.cookies.get('auth-next')?.value;
+
+ return NextResponse.redirect(new URL(next ?? '/', origin), {
+ headers: [
+ [
+ 'set-cookie',
+ serialize(userSessionCookieName, cookieValue, {
+ path: '/',
+ maxAge: cookieTTL / 1000,
+ expires: new Date(Date.now() + cookieTTL),
+ httpOnly: true,
+ secure: process.env.NODE_ENV !== 'development',
+ }),
+ ],
+ // There's currently an issue with adding more than one cookie to a response: https://github.com/vercel/next.js/issues/46579
+ // the `auth-next` cookie will expire in 10 min anyway
+ // [
+ // 'set-cookie',
+ // serialize('auth-next', '', {
+ // path: '/api/auth/callback',
+ // maxAge: 0,
+ // expires: new Date(),
+ // httpOnly: true,
+ // })
+ // ]
+ ],
+ });
+}
diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts
new file mode 100644
index 0000000..1bd23cc
--- /dev/null
+++ b/app/api/auth/login/route.ts
@@ -0,0 +1,29 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { serialize } from 'cookie';
+
+export const runtime = 'edge';
+
+export async function GET(req: NextRequest) {
+ const { origin } = req.nextUrl;
+
+ const next = req.nextUrl.searchParams.get('next');
+
+ const params = new URLSearchParams({
+ redirectUrl: new URL('/api/auth/callback', origin).toString(),
+ });
+
+ return NextResponse.redirect(
+ `https://vercel.com/api/vercel-auth?${params.toString()}`,
+ {
+ headers: next
+ ? {
+ 'Set-Cookie': serialize('auth-next', next, {
+ path: '/api/auth/callback',
+ maxAge: 60 * 60 * 10,
+ httpOnly: true,
+ }),
+ }
+ : undefined,
+ }
+ );
+}
diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts
new file mode 100644
index 0000000..a8b2768
--- /dev/null
+++ b/app/api/auth/logout/route.ts
@@ -0,0 +1,25 @@
+import { userSessionCookieName } from '@/lib/session/constants';
+import { serialize } from 'cookie';
+import { NextRequest, NextResponse } from 'next/server';
+
+export const runtime = 'edge';
+
+export async function GET(req: NextRequest) {
+ const { origin, searchParams } = req.nextUrl;
+ const next = searchParams.get('next');
+
+ return NextResponse.redirect(new URL(next ?? '/', origin), {
+ headers: [
+ [
+ 'set-cookie',
+ serialize(userSessionCookieName, '', {
+ path: '/',
+ maxAge: 0,
+ expires: new Date(),
+ httpOnly: true,
+ secure: process.env.NODE_ENV !== 'development',
+ }),
+ ],
+ ],
+ });
+}
diff --git a/app/api/generate/route.ts b/app/api/generate/route.ts
index 80ae6aa..a0a9677 100644
--- a/app/api/generate/route.ts
+++ b/app/api/generate/route.ts
@@ -1,5 +1,5 @@
import { OpenAIStream, openai } from "@/lib/openai";
-import { getServerSession } from "next-auth";
+import { getServerSession } from "@/lib/session/get-server-session";
export const runtime = "edge";
diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx
index 10c6517..4129e8c 100644
--- a/app/chat/[id]/page.tsx
+++ b/app/chat/[id]/page.tsx
@@ -3,8 +3,7 @@ import { prisma } from "@/lib/prisma";
import { Chat } from "../../chat";
import { type Metadata } from "next";
-import { getServerSession } from "next-auth";
-import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { getServerSession } from "@/lib/session/get-server-session";
export interface ChatPageProps {
params: {
@@ -30,7 +29,7 @@ export const runtime = "nodejs"; // default
export const preferredRegion = "home";
export const dynamic = "force-dynamic";
export default async function ChatPage({ params }: ChatPageProps) {
- const session = await getServerSession(authOptions);
+ const session = await getServerSession();
const chat = await prisma.chat.findFirst({
where: {
id: params.id,
diff --git a/app/page.tsx b/app/page.tsx
index e7e6f0b..a3ca75a 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,7 +1,6 @@
-import { getServerSession } from "next-auth";
+import { getServerSession } from "@/lib/session/get-server-session";
import { Chat } from "./chat";
import { Sidebar } from "./sidebar";
-import { authOptions } from "@/app/api/auth/[...nextauth]/route";
// Prisma does not support Edge without the Data Proxy currently
export const runtime = "nodejs"; // default
@@ -9,7 +8,7 @@ export const preferredRegion = "home";
export const dynamic = "force-dynamic";
export default async function IndexPage() {
- const session = await getServerSession(authOptions);
+ const session = await getServerSession();
return (
diff --git a/app/sidebar.tsx b/app/sidebar.tsx
index 9db8003..013ae22 100644
--- a/app/sidebar.tsx
+++ b/app/sidebar.tsx
@@ -1,16 +1,15 @@
-import { Plus } from "lucide-react";
-import Link from "next/link";
+import { DevGPTLogo } from "@/components/ui/devgpt-logo";
import { Login } from "@/components/ui/login";
import { UserMenu } from "@/components/ui/user-menu";
import { prisma } from "@/lib/prisma";
-import { VercelLogo } from "@/components/ui/vercel-logo";
-import { SidebarItem } from "./sidebar-item";
+import { type Session } from "@/lib/session/types";
import { cn } from "@/lib/utils";
-import { type Session } from "next-auth";
-import { DevGPTLogo } from "@/components/ui/devgpt-logo";
+import { Plus } from "lucide-react";
+import Link from "next/link";
+import { SidebarItem } from "./sidebar-item";
export interface SidebarProps {
- session: Session | null;
+ session?: Session;
newChat?: boolean;
}
diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx
index f3207da..93847ec 100644
--- a/components/theme-toggle.tsx
+++ b/components/theme-toggle.tsx
@@ -4,16 +4,21 @@ import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import { Moon, Sun } from "lucide-react";
+import { useTransition } from "react";
export function ThemeToggle() {
const { setTheme, theme } = useTheme();
-
+ const [_, startTransition] = useTransition();
return (