diff --git a/app/(auth)/api/auth/guest/route.ts b/app/(auth)/api/auth/guest/route.ts
new file mode 100644
index 0000000000..ab890d4da8
--- /dev/null
+++ b/app/(auth)/api/auth/guest/route.ts
@@ -0,0 +1,13 @@
+import { redirect } from 'next/navigation';
+import { auth, signIn } from '@/app/(auth)/auth';
+
+export async function GET() {
+ const session = await auth();
+
+ if (!session?.user?.id) {
+ await signIn('guest', { redirect: false });
+ redirect('/');
+ }
+
+ return new Response('Unauthorized', { status: 401 });
+}
diff --git a/app/(auth)/auth.config.ts b/app/(auth)/auth.config.ts
index cf1ecdd89e..b7d7d50bf1 100644
--- a/app/(auth)/auth.config.ts
+++ b/app/(auth)/auth.config.ts
@@ -9,31 +9,5 @@ export const authConfig = {
// 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: {
- authorized({ auth, request: { nextUrl } }) {
- const isLoggedIn = !!auth?.user;
- const isOnChat = nextUrl.pathname.startsWith('/');
- const isOnRegister = nextUrl.pathname.startsWith('/register');
- const isOnLogin = nextUrl.pathname.startsWith('/login');
-
- if (isLoggedIn && (isOnLogin || isOnRegister)) {
- return Response.redirect(new URL('/', nextUrl as unknown as URL));
- }
-
- if (isOnRegister || isOnLogin) {
- return true; // Always allow access to register and login pages
- }
-
- if (isOnChat) {
- if (isLoggedIn) return true;
- return false; // Redirect unauthenticated users to login page
- }
-
- if (isLoggedIn) {
- return Response.redirect(new URL('/', nextUrl as unknown as URL));
- }
-
- return true;
- },
- },
+ callbacks: {},
} satisfies NextAuthConfig;
diff --git a/app/(auth)/auth.ts b/app/(auth)/auth.ts
index d8a436988a..ea59d6fad9 100644
--- a/app/(auth)/auth.ts
+++ b/app/(auth)/auth.ts
@@ -2,7 +2,7 @@ import { compare } from 'bcrypt-ts';
import NextAuth, { type User, type Session } from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
-import { getUser } from '@/lib/db/queries';
+import { createAnonymousUser, getUser } from '@/lib/db/queries';
import { authConfig } from './auth.config';
@@ -21,12 +21,24 @@ export const {
Credentials({
credentials: {},
async authorize({ email, password }: any) {
- const users = await getUser(email);
- if (users.length === 0) return null;
- // biome-ignore lint: Forbidden non-null assertion.
- const passwordsMatch = await compare(password, users[0].password!);
+ const [user] = await getUser(email);
+
+ if (!user) return null;
+ if (!user.password) return null;
+
+ const passwordsMatch = await compare(password, user.password);
+
if (!passwordsMatch) return null;
- return users[0] as any;
+
+ return user;
+ },
+ }),
+ Credentials({
+ id: 'guest',
+ credentials: {},
+ async authorize() {
+ const [anonymousUser] = await createAnonymousUser();
+ return anonymousUser;
},
}),
],
diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx
index ba987c280d..33e9e82709 100644
--- a/app/(auth)/login/page.tsx
+++ b/app/(auth)/login/page.tsx
@@ -9,6 +9,7 @@ import { AuthForm } from '@/components/auth-form';
import { SubmitButton } from '@/components/submit-button';
import { login, type LoginActionState } from '../actions';
+import { useSession } from 'next-auth/react';
export default function Page() {
const router = useRouter();
@@ -23,6 +24,8 @@ export default function Page() {
},
);
+ const { update: updateSession } = useSession();
+
useEffect(() => {
if (state.status === 'failed') {
toast({
@@ -36,6 +39,7 @@ export default function Page() {
});
} else if (state.status === 'success') {
setIsSuccessful(true);
+ updateSession();
router.refresh();
}
}, [state.status]);
diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx
index 649278e24f..ab2ee82667 100644
--- a/app/(auth)/register/page.tsx
+++ b/app/(auth)/register/page.tsx
@@ -9,6 +9,7 @@ import { SubmitButton } from '@/components/submit-button';
import { register, type RegisterActionState } from '../actions';
import { toast } from '@/components/toast';
+import { useSession } from 'next-auth/react';
export default function Page() {
const router = useRouter();
@@ -23,6 +24,8 @@ export default function Page() {
},
);
+ const { update: updateSession } = useSession();
+
useEffect(() => {
if (state.status === 'user_exists') {
toast({ type: 'error', description: 'Account already exists!' });
@@ -37,6 +40,7 @@ export default function Page() {
toast({ type: 'success', description: 'Account created successfully!' });
setIsSuccessful(true);
+ updateSession();
router.refresh();
}
}, [state]);
diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts
index e574d7dc50..b1a9a535f8 100644
--- a/app/(chat)/api/chat/route.ts
+++ b/app/(chat)/api/chat/route.ts
@@ -1,5 +1,5 @@
import {
- UIMessage,
+ type UIMessage,
appendResponseMessages,
createDataStreamResponse,
smoothStream,
@@ -10,6 +10,7 @@ import { systemPrompt } from '@/lib/ai/prompts';
import {
deleteChatById,
getChatById,
+ getMessageCountByUserId,
saveChat,
saveMessages,
} from '@/lib/db/queries';
@@ -23,8 +24,12 @@ import { createDocument } from '@/lib/ai/tools/create-document';
import { updateDocument } from '@/lib/ai/tools/update-document';
import { requestSuggestions } from '@/lib/ai/tools/request-suggestions';
import { getWeather } from '@/lib/ai/tools/get-weather';
-import { isProductionEnvironment } from '@/lib/constants';
+import { anonymousRegex, isProductionEnvironment } from '@/lib/constants';
import { myProvider } from '@/lib/ai/providers';
+import {
+ entitlementsByMembershipTier,
+ type MembershipTier,
+} from '@/lib/ai/capabilities';
export const maxDuration = 60;
@@ -42,10 +47,33 @@ export async function POST(request: Request) {
const session = await auth();
- if (!session || !session.user || !session.user.id) {
+ if (!session?.user?.id) {
return new Response('Unauthorized', { status: 401 });
}
+ const membershipTier: MembershipTier = anonymousRegex.test(
+ session.user.email ?? '',
+ )
+ ? 'guest'
+ : 'free';
+
+ const messageCount = await getMessageCountByUserId({
+ id: session.user.id,
+ differenceInHours: 24,
+ });
+
+ if (
+ messageCount >
+ entitlementsByMembershipTier[membershipTier].maxMessagesPerDay
+ ) {
+ return new Response(
+ 'You have exceeded your maximum number of messages for the day',
+ {
+ status: 429,
+ },
+ );
+ }
+
const userMessage = getMostRecentUserMessage(messages);
if (!userMessage) {
diff --git a/app/layout.tsx b/app/layout.tsx
index 8a6ad5d448..1813e0f71b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -4,6 +4,7 @@ import { Geist, Geist_Mono } from 'next/font/google';
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'),
@@ -77,7 +78,7 @@ export default async function RootLayout({
disableTransitionOnChange
>
- {children}
+ {children}