Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for optional authentication #144

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,33 @@ import { useEffect } from "react";
import { addAssistantIdToUser } from "@/lib/supabase/add_assistant_id_to_user";
import { CanvasLoading } from "@/components/CanvasLoading";
import { useUser } from "@/hooks/useUser";
import { isAuthEnabled } from "@/lib/auth-config";

export default function Home() {
const { user, getUser } = useUser();

useEffect(() => {
getUser();
if (isAuthEnabled()) {
getUser();
}
}, []);

useEffect(() => {
if (typeof window === "undefined" || !user) return;
addAssistantIdToUser();
if (isAuthEnabled()) {
addAssistantIdToUser();
}
}, [user]);

// If auth is disabled, render Canvas directly with mock user
if (!isAuthEnabled()) {
return <Canvas user={{ id: 'anonymous' } as any} />;
}

// Otherwise, show loading until user is loaded
if (!user) {
return <CanvasLoading />;
}

return <Canvas user={user} />;
}
}
24 changes: 13 additions & 11 deletions src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import {
} from "@/types";
import { User } from "@supabase/supabase-js";
import { useEffect, useState } from "react";
import { isAuthEnabled } from "@/lib/auth-config";

interface CanvasProps {
user: User;
}

export function Canvas(props: CanvasProps) {
const { toast } = useToast();
const effectiveUserId = isAuthEnabled() ? props.user.id : 'anonymous';
const {
threadId,
assistantId,
Expand All @@ -35,7 +37,7 @@ export function Canvas(props: CanvasProps) {
setThreadId,
getOrCreateAssistant,
clearThreadsWithNoValues,
} = useThread(props.user.id);
} = useThread(effectiveUserId);
const [chatStarted, setChatStarted] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const {
Expand All @@ -56,7 +58,7 @@ export function Canvas(props: CanvasProps) {
firstTokenReceived,
selectedBlocks,
} = useGraph({
userId: props.user.id,
userId: effectiveUserId,
threadId,
assistantId,
});
Expand All @@ -67,14 +69,14 @@ export function Canvas(props: CanvasProps) {
isLoadingReflections,
} = useStore({
assistantId,
userId: props.user.id,
userId: effectiveUserId,
});

useEffect(() => {
if (typeof window === "undefined") return;

if (!threadId) {
searchOrCreateThread(props.user.id);
searchOrCreateThread(effectiveUserId);
}

if (!assistantId) {
Expand All @@ -85,14 +87,14 @@ export function Canvas(props: CanvasProps) {
useEffect(() => {
if (!threadId) return;
// Clear threads with no values
clearThreadsWithNoValues(props.user.id);
clearThreadsWithNoValues(effectiveUserId);
}, [threadId]);

useEffect(() => {
if (typeof window == "undefined" || !props.user.id || userThreads.length)
if (typeof window == "undefined" || !effectiveUserId || userThreads.length)
return;
getUserThreads(props.user.id);
}, [props.user.id]);
getUserThreads(effectiveUserId);
}, [effectiveUserId]);

useEffect(() => {
if (!assistantId || typeof window === "undefined") return;
Expand All @@ -109,7 +111,7 @@ export function Canvas(props: CanvasProps) {
const createThreadWithChatStarted = async () => {
setChatStarted(false);
clearState();
return createThread(props.user.id);
return createThread(effectiveUserId);
};

const handleQuickStart = (
Expand Down Expand Up @@ -165,7 +167,7 @@ export function Canvas(props: CanvasProps) {
)}
>
<ContentComposerChatInterface
userId={props.user.id}
userId={effectiveUserId}
getUserThreads={getUserThreads}
isUserThreadsLoading={isUserThreadsLoading}
userThreads={userThreads}
Expand Down Expand Up @@ -195,7 +197,7 @@ export function Canvas(props: CanvasProps) {
{chatStarted && (
<div className="w-full ml-auto">
<ArtifactRenderer
userId={props.user.id}
userId={effectiveUserId}
firstTokenReceived={firstTokenReceived}
isArtifactSaved={isArtifactSaved}
artifact={artifact}
Expand Down
19 changes: 13 additions & 6 deletions src/hooks/useThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
THREAD_ID_COOKIE_NAME,
} from "@/constants";
import { Thread } from "@langchain/langgraph-sdk";
import { isAuthEnabled } from "@/lib/auth-config";

export function useThread(userId: string) {
const [assistantId, setAssistantId] = useState<string>();
Expand All @@ -19,10 +20,13 @@ export function useThread(userId: string) {
): Promise<Thread | undefined> => {
const client = createClient();
try {
// For non-auth mode, we still need to pass some identifier
const metadata = isAuthEnabled()
? { supabase_user_id: supabaseUserId }
: { anonymous_user_id: 'anonymous' };

const thread = await client.threads.create({
metadata: {
supabase_user_id: supabaseUserId,
},
metadata,
});
setThreadId(thread.thread_id);
setCookie(THREAD_ID_COOKIE_NAME, thread.thread_id);
Expand Down Expand Up @@ -55,11 +59,14 @@ export function useThread(userId: string) {
setIsUserThreadsLoading(true);
try {
const client = createClient();

// Adjust the search criteria based on auth status
const searchMetadata = isAuthEnabled()
? { supabase_user_id: id }
: { anonymous_user_id: 'anonymous' };

const userThreads = await client.threads.search({
metadata: {
supabase_user_id: id,
},
metadata: searchMetadata,
limit: 100,
});

Expand Down
18 changes: 17 additions & 1 deletion src/hooks/useUser.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { useState } from "react";
import { createSupabaseClient } from "@/lib/supabase/client";
import { User } from "@supabase/supabase-js";
import { isAuthEnabled } from "@/lib/auth-config";

export function useUser() {
const [user, setUser] = useState<User>();
const [loading, setLoading] = useState(true);

async function getUser() {
if (!isAuthEnabled()) {
const mockUser = {
id: 'anonymous',
email: '[email protected]',
// Add other required User properties
} as User;
setUser(mockUser);
setLoading(false);
return;
}

const supabase = createSupabaseClient();
if (!supabase) {
setLoading(false);
return;
}

const {
data: { user },
Expand All @@ -21,4 +37,4 @@ export function useUser() {
user,
loading,
};
}
}
4 changes: 4 additions & 0 deletions src/lib/auth-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const isAuthEnabled = () => {
return !!(process.env.NEXT_PUBLIC_SUPABASE_URL &&
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY);
};
4 changes: 4 additions & 0 deletions src/lib/supabase/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { createBrowserClient } from "@supabase/ssr";
import { isAuthEnabled } from "../auth-config";

export function createSupabaseClient() {
if (!isAuthEnabled()) {
return null;
}
if (!process.env.NEXT_PUBLIC_SUPABASE_URL) {
throw new Error("NEXT_PUBLIC_SUPABASE_URL is not defined");
}
Expand Down
5 changes: 5 additions & 0 deletions src/lib/supabase/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { isAuthEnabled } from "../auth-config";

export function createClient() {
if (!isAuthEnabled()) {
return null;
}

if (!process.env.NEXT_PUBLIC_SUPABASE_URL) {
throw new Error("NEXT_PUBLIC_SUPABASE_URL is not defined");
}
Expand Down
14 changes: 13 additions & 1 deletion src/lib/supabase/verify_user_server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { User } from "@supabase/supabase-js";
import { createClient } from "./server";
import { isAuthEnabled } from "../auth-config";

export async function verifyUserAuthenticated(): Promise<User | undefined> {
if (!isAuthEnabled()) {
// Return a mock user when auth is disabled
return {
id: 'anonymous',
email: '[email protected]',
// Add other required User properties
} as User;
}

const supabase = createClient();
if (!supabase) return undefined;

const {
data: { user },
} = await supabase.auth.getUser();
return user || undefined;
}
}
8 changes: 8 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import { type NextRequest } from "next/server";
import { updateSession } from "@/lib/supabase/middleware";

export async function middleware(request: NextRequest) {
// Check if Supabase credentials are configured
const isAuthEnabled = process.env.NEXT_PUBLIC_SUPABASE_URL &&
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

if (!isAuthEnabled) {
return; // Skip auth middleware if Supabase is not configured
}

return await updateSession(request);
}

Expand Down