Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9279135

Browse files
feat: support guest session (#919)
1 parent 24cb2ce commit 9279135

34 files changed

+741
-288
lines changed

app/(auth)/api/auth/guest/route.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { signIn } from '@/app/(auth)/auth';
2+
import { isDevelopmentEnvironment } from '@/lib/constants';
3+
import { getToken } from 'next-auth/jwt';
4+
import { NextResponse } from 'next/server';
5+
6+
export async function GET(request: Request) {
7+
const { searchParams } = new URL(request.url);
8+
const redirectUrl = searchParams.get('redirectUrl') || '/';
9+
10+
const token = await getToken({
11+
req: request,
12+
secret: process.env.AUTH_SECRET,
13+
secureCookie: !isDevelopmentEnvironment,
14+
});
15+
16+
if (token) {
17+
return NextResponse.redirect(new URL('/', request.url));
18+
}
19+
20+
return signIn('guest', { redirect: true, redirectTo: redirectUrl });
21+
}

app/(auth)/auth.config.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,5 @@ export const authConfig = {
99
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
1010
// while this file is also used in non-Node.js environments
1111
],
12-
callbacks: {
13-
authorized({ auth, request: { nextUrl } }) {
14-
const isLoggedIn = !!auth?.user;
15-
const isOnChat = nextUrl.pathname.startsWith('/');
16-
const isOnRegister = nextUrl.pathname.startsWith('/register');
17-
const isOnLogin = nextUrl.pathname.startsWith('/login');
18-
19-
if (isLoggedIn && (isOnLogin || isOnRegister)) {
20-
return Response.redirect(new URL('/', nextUrl as unknown as URL));
21-
}
22-
23-
if (isOnRegister || isOnLogin) {
24-
return true; // Always allow access to register and login pages
25-
}
26-
27-
if (isOnChat) {
28-
if (isLoggedIn) return true;
29-
return false; // Redirect unauthenticated users to login page
30-
}
31-
32-
if (isLoggedIn) {
33-
return Response.redirect(new URL('/', nextUrl as unknown as URL));
34-
}
35-
36-
return true;
37-
},
38-
},
12+
callbacks: {},
3913
} satisfies NextAuthConfig;

app/(auth)/auth.ts

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
import { compare } from 'bcrypt-ts';
2-
import NextAuth, { type User, type Session } from 'next-auth';
2+
import NextAuth, { type DefaultSession } from 'next-auth';
33
import Credentials from 'next-auth/providers/credentials';
4-
5-
import { getUser } from '@/lib/db/queries';
6-
4+
import { createGuestUser, getUser } from '@/lib/db/queries';
75
import { authConfig } from './auth.config';
86
import { DUMMY_PASSWORD } from '@/lib/constants';
7+
import type { DefaultJWT } from 'next-auth/jwt';
8+
9+
export type UserType = 'guest' | 'regular';
10+
11+
declare module 'next-auth' {
12+
interface Session extends DefaultSession {
13+
user: {
14+
id: string;
15+
type: UserType;
16+
} & DefaultSession['user'];
17+
}
18+
19+
interface User {
20+
id?: string;
21+
email?: string | null;
22+
type: UserType;
23+
}
24+
}
925

10-
interface ExtendedSession extends Session {
11-
user: User;
26+
declare module 'next-auth/jwt' {
27+
interface JWT extends DefaultJWT {
28+
id: string;
29+
type: UserType;
30+
}
1231
}
1332

1433
export const {
@@ -40,27 +59,31 @@ export const {
4059

4160
if (!passwordsMatch) return null;
4261

43-
return user as any;
62+
return { ...user, type: 'regular' };
63+
},
64+
}),
65+
Credentials({
66+
id: 'guest',
67+
credentials: {},
68+
async authorize() {
69+
const [guestUser] = await createGuestUser();
70+
return { ...guestUser, type: 'guest' };
4471
},
4572
}),
4673
],
4774
callbacks: {
4875
async jwt({ token, user }) {
4976
if (user) {
50-
token.id = user.id;
77+
token.id = user.id as string;
78+
token.type = user.type;
5179
}
5280

5381
return token;
5482
},
55-
async session({
56-
session,
57-
token,
58-
}: {
59-
session: ExtendedSession;
60-
token: any;
61-
}) {
83+
async session({ session, token }) {
6284
if (session.user) {
63-
session.user.id = token.id as string;
85+
session.user.id = token.id;
86+
session.user.type = token.type;
6487
}
6588

6689
return session;

app/(auth)/login/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AuthForm } from '@/components/auth-form';
99
import { SubmitButton } from '@/components/submit-button';
1010

1111
import { login, type LoginActionState } from '../actions';
12+
import { useSession } from 'next-auth/react';
1213

1314
export default function Page() {
1415
const router = useRouter();
@@ -23,6 +24,8 @@ export default function Page() {
2324
},
2425
);
2526

27+
const { update: updateSession } = useSession();
28+
2629
useEffect(() => {
2730
if (state.status === 'failed') {
2831
toast({
@@ -36,6 +39,7 @@ export default function Page() {
3639
});
3740
} else if (state.status === 'success') {
3841
setIsSuccessful(true);
42+
updateSession();
3943
router.refresh();
4044
}
4145
}, [state.status]);

app/(auth)/register/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SubmitButton } from '@/components/submit-button';
99

1010
import { register, type RegisterActionState } from '../actions';
1111
import { toast } from '@/components/toast';
12+
import { useSession } from 'next-auth/react';
1213

1314
export default function Page() {
1415
const router = useRouter();
@@ -23,6 +24,8 @@ export default function Page() {
2324
},
2425
);
2526

27+
const { update: updateSession } = useSession();
28+
2629
useEffect(() => {
2730
if (state.status === 'user_exists') {
2831
toast({ type: 'error', description: 'Account already exists!' });
@@ -37,6 +40,7 @@ export default function Page() {
3740
toast({ type: 'success', description: 'Account created successfully!' });
3841

3942
setIsSuccessful(true);
43+
updateSession();
4044
router.refresh();
4145
}
4246
}, [state]);

app/(chat)/api/chat/route.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import {
55
smoothStream,
66
streamText,
77
} from 'ai';
8-
import { auth } from '@/app/(auth)/auth';
8+
import { auth, type UserType } from '@/app/(auth)/auth';
99
import { systemPrompt } from '@/lib/ai/prompts';
1010
import {
1111
deleteChatById,
1212
getChatById,
13+
getMessageCountByUserId,
1314
saveChat,
1415
saveMessages,
1516
} from '@/lib/db/queries';
@@ -25,6 +26,7 @@ import { requestSuggestions } from '@/lib/ai/tools/request-suggestions';
2526
import { getWeather } from '@/lib/ai/tools/get-weather';
2627
import { isProductionEnvironment } from '@/lib/constants';
2728
import { myProvider } from '@/lib/ai/providers';
29+
import { entitlementsByUserType } from '@/lib/ai/entitlements';
2830

2931
export const maxDuration = 60;
3032

@@ -46,6 +48,22 @@ export async function POST(request: Request) {
4648
return new Response('Unauthorized', { status: 401 });
4749
}
4850

51+
const userType: UserType = session.user.type;
52+
53+
const messageCount = await getMessageCountByUserId({
54+
id: session.user.id,
55+
differenceInHours: 24,
56+
});
57+
58+
if (messageCount > entitlementsByUserType[userType].maxMessagesPerDay) {
59+
return new Response(
60+
'You have exceeded your maximum number of messages for the day! Please try again later.',
61+
{
62+
status: 429,
63+
},
64+
);
65+
}
66+
4967
const userMessage = getMostRecentUserMessage(messages);
5068

5169
if (!userMessage) {

app/(chat)/chat/[id]/page.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { cookies } from 'next/headers';
2-
import { notFound } from 'next/navigation';
2+
import { notFound, redirect } from 'next/navigation';
33

44
import { auth } from '@/app/(auth)/auth';
55
import { Chat } from '@/components/chat';
66
import { getChatById, getMessagesByChatId } from '@/lib/db/queries';
77
import { DataStreamHandler } from '@/components/data-stream-handler';
88
import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models';
9-
import { DBMessage } from '@/lib/db/schema';
10-
import { Attachment, UIMessage } from 'ai';
9+
import type { DBMessage } from '@/lib/db/schema';
10+
import type { Attachment, UIMessage } from 'ai';
1111

1212
export default async function Page(props: { params: Promise<{ id: string }> }) {
1313
const params = await props.params;
@@ -20,8 +20,12 @@ export default async function Page(props: { params: Promise<{ id: string }> }) {
2020

2121
const session = await auth();
2222

23+
if (!session) {
24+
redirect('/api/auth/guest');
25+
}
26+
2327
if (chat.visibility === 'private') {
24-
if (!session || !session.user) {
28+
if (!session.user) {
2529
return notFound();
2630
}
2731

@@ -59,6 +63,7 @@ export default async function Page(props: { params: Promise<{ id: string }> }) {
5963
selectedChatModel={DEFAULT_CHAT_MODEL}
6064
selectedVisibilityType={chat.visibility}
6165
isReadonly={session?.user?.id !== chat.userId}
66+
session={session}
6267
/>
6368
<DataStreamHandler id={id} />
6469
</>
@@ -73,6 +78,7 @@ export default async function Page(props: { params: Promise<{ id: string }> }) {
7378
selectedChatModel={chatModelFromCookie.value}
7479
selectedVisibilityType={chat.visibility}
7580
isReadonly={session?.user?.id !== chat.userId}
81+
session={session}
7682
/>
7783
<DataStreamHandler id={id} />
7884
</>

app/(chat)/page.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@ import { Chat } from '@/components/chat';
44
import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models';
55
import { generateUUID } from '@/lib/utils';
66
import { DataStreamHandler } from '@/components/data-stream-handler';
7+
import { auth } from '../(auth)/auth';
8+
import { redirect } from 'next/navigation';
79

810
export default async function Page() {
11+
const session = await auth();
12+
13+
if (!session) {
14+
redirect('/api/auth/guest');
15+
}
16+
917
const id = generateUUID();
1018

1119
const cookieStore = await cookies();
@@ -21,6 +29,7 @@ export default async function Page() {
2129
selectedChatModel={DEFAULT_CHAT_MODEL}
2230
selectedVisibilityType="private"
2331
isReadonly={false}
32+
session={session}
2433
/>
2534
<DataStreamHandler id={id} />
2635
</>
@@ -36,6 +45,7 @@ export default async function Page() {
3645
selectedChatModel={modelIdFromCookie.value}
3746
selectedVisibilityType="private"
3847
isReadonly={false}
48+
session={session}
3949
/>
4050
<DataStreamHandler id={id} />
4151
</>

app/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Geist, Geist_Mono } from 'next/font/google';
44
import { ThemeProvider } from '@/components/theme-provider';
55

66
import './globals.css';
7+
import { SessionProvider } from 'next-auth/react';
78

89
export const metadata: Metadata = {
910
metadataBase: new URL('https://chat.vercel.ai'),
@@ -77,7 +78,7 @@ export default async function RootLayout({
7778
disableTransitionOnChange
7879
>
7980
<Toaster position="top-center" />
80-
{children}
81+
<SessionProvider>{children}</SessionProvider>
8182
</ThemeProvider>
8283
</body>
8384
</html>

components/chat-header.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ import { useSidebar } from './ui/sidebar';
1212
import { memo } from 'react';
1313
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
1414
import { type VisibilityType, VisibilitySelector } from './visibility-selector';
15+
import type { Session } from 'next-auth';
1516

1617
function PureChatHeader({
1718
chatId,
1819
selectedModelId,
1920
selectedVisibilityType,
2021
isReadonly,
22+
session,
2123
}: {
2224
chatId: string;
2325
selectedModelId: string;
2426
selectedVisibilityType: VisibilityType;
2527
isReadonly: boolean;
28+
session: Session;
2629
}) {
2730
const router = useRouter();
2831
const { open } = useSidebar();
@@ -54,6 +57,7 @@ function PureChatHeader({
5457

5558
{!isReadonly && (
5659
<ModelSelector
60+
session={session}
5761
selectedModelId={selectedModelId}
5862
className="order-1 md:order-2"
5963
/>

0 commit comments

Comments
 (0)