Skip to content
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
302 changes: 168 additions & 134 deletions apps/web/app/(auth)/signin/page.tsx

Large diffs are not rendered by default.

394 changes: 217 additions & 177 deletions apps/web/app/(auth)/signup/page.tsx

Large diffs are not rendered by default.

225 changes: 225 additions & 0 deletions apps/web/app/ai-agents/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
"use client";

import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { Sidebar } from "@/components/sidebar";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Bot, ServerCog, Loader2, CheckCircle2, XCircle, CircleDot } from "lucide-react";

interface Agent {
id: string;
name: string;
status: "connected" | "disconnected" | "warning";
}

interface McpConfig {
url: string;
apiKey: string;
}

export default function AiAgentsPage() {
const router = useRouter();
const { data: session, status } = useSession();
const [mounted, setMounted] = useState(false);

const [agents, setAgents] = useState<Agent[]>([]);
const [loadingAgents, setLoadingAgents] = useState(true);
const [savingAgentId, setSavingAgentId] = useState<string | null>(null);

const [config, setConfig] = useState<McpConfig>({ url: "", apiKey: "" });
const [savingConfig, setSavingConfig] = useState(false);

const [recent, setRecent] = useState<string[]>([]);

useEffect(() => setMounted(true), []);

useEffect(() => {
if (status === "loading") return;
if (!session) router.push("/signin");
}, [status, session, router]);

// Fetch dynamic data
useEffect(() => {
async function fetchData() {
try {
setLoadingAgents(true);
const [a, c] = await Promise.all([
fetch("/api/ai/agents").then((r) => r.json()),
fetch("/api/ai/mcp-config").then((r) => r.json()),
]);
setAgents(a.agents ?? []);
setConfig({ url: c.url ?? "", apiKey: c.apiKey ?? "" });
setRecent(c.recent ?? []);
} catch (e) {
console.error(e);
} finally {
setLoadingAgents(false);
}
}
if (mounted) fetchData();
}, [mounted]);
Comment on lines +46 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Improve error handling and user feedback for data fetching.

The data fetching logic has minimal error handling:

  1. Silent failures: Errors are only logged to console (line 58), leaving users without feedback
  2. No response validation: Missing check for response.ok before parsing JSON
  3. No retry logic: Transient network errors will fail permanently
 useEffect(() => {
   async function fetchData() {
     try {
       setLoadingAgents(true);
       const [a, c] = await Promise.all([
-        fetch("/api/ai/agents").then((r) => r.json()),
-        fetch("/api/ai/mcp-config").then((r) => r.json()),
+        fetch("/api/ai/agents").then(async (r) => {
+          if (!r.ok) throw new Error(`Failed to fetch agents: ${r.status}`);
+          return r.json();
+        }),
+        fetch("/api/ai/mcp-config").then(async (r) => {
+          if (!r.ok) throw new Error(`Failed to fetch config: ${r.status}`);
+          return r.json();
+        }),
       ]);
       setAgents(a.agents ?? []);
       setConfig({ url: c.url ?? "", apiKey: c.apiKey ?? "" });
       setRecent(c.recent ?? []);
     } catch (e) {
       console.error(e);
+      // Add user-visible error state
+      // setError("Failed to load AI agents. Please refresh the page.");
     } finally {
       setLoadingAgents(false);
     }
   }
   if (mounted) fetchData();
 }, [mounted]);

Consider adding an error state and displaying it to users in the UI.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
async function fetchData() {
try {
setLoadingAgents(true);
const [a, c] = await Promise.all([
fetch("/api/ai/agents").then((r) => r.json()),
fetch("/api/ai/mcp-config").then((r) => r.json()),
]);
setAgents(a.agents ?? []);
setConfig({ url: c.url ?? "", apiKey: c.apiKey ?? "" });
setRecent(c.recent ?? []);
} catch (e) {
console.error(e);
} finally {
setLoadingAgents(false);
}
}
if (mounted) fetchData();
}, [mounted]);
useEffect(() => {
async function fetchData() {
try {
setLoadingAgents(true);
const [a, c] = await Promise.all([
fetch("/api/ai/agents").then(async (r) => {
if (!r.ok) throw new Error(`Failed to fetch agents: ${r.status}`);
return r.json();
}),
fetch("/api/ai/mcp-config").then(async (r) => {
if (!r.ok) throw new Error(`Failed to fetch config: ${r.status}`);
return r.json();
}),
]);
setAgents(a.agents ?? []);
setConfig({ url: c.url ?? "", apiKey: c.apiKey ?? "" });
setRecent(c.recent ?? []);
} catch (e) {
console.error(e);
// Add user-visible error state
// setError("Failed to load AI agents. Please refresh the page.");
} finally {
setLoadingAgents(false);
}
}
if (mounted) fetchData();
}, [mounted]);


async function toggleAgent(agent: Agent) {
setSavingAgentId(agent.id);
try {
const res = await fetch("/api/ai/agents", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: agent.id, action: agent.status === "connected" ? "disconnect" : "connect" }),
});
const data = await res.json();
setAgents(data.agents);
} catch (e) {
console.error(e);
} finally {
setSavingAgentId(null);
}
}

async function saveConfig() {
setSavingConfig(true);
try {
const res = await fetch("/api/ai/mcp-config", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(config),
});
const data = await res.json();
setRecent(data.recent ?? []);
} catch (e) {
console.error(e);
} finally {
setSavingConfig(false);
}
}
Comment on lines +66 to +98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add response validation, optimistic updates, and error feedback.

Both action handlers (toggleAgent and saveConfig) share similar issues:

  1. No response validation: Missing response.ok checks before parsing JSON
  2. Poor UX: No optimistic updates make interactions feel slow
  3. Silent failures: Errors only logged to console (lines 77, 94)
  4. No input validation: saveConfig doesn't validate URL format or API key presence

Suggested improvements for toggleAgent:

 async function toggleAgent(agent: Agent) {
   setSavingAgentId(agent.id);
+  
+  // Optimistic update
+  const previousAgents = agents;
+  const newStatus = agent.status === "connected" ? "disconnected" : "connected";
+  setAgents(agents.map(a => a.id === agent.id ? { ...a, status: newStatus } : a));
+  
   try {
     const res = await fetch("/api/ai/agents", {
       method: "POST",
       headers: { "Content-Type": "application/json" },
       body: JSON.stringify({ id: agent.id, action: agent.status === "connected" ? "disconnect" : "connect" }),
     });
+    
+    if (!res.ok) {
+      throw new Error(`Failed to toggle agent: ${res.status}`);
+    }
+    
     const data = await res.json();
     setAgents(data.agents);
   } catch (e) {
     console.error(e);
+    // Rollback optimistic update
+    setAgents(previousAgents);
+    // Show error to user
+    alert("Failed to toggle agent. Please try again.");
   } finally {
     setSavingAgentId(null);
   }
 }

Similar improvements should be applied to saveConfig with URL validation.


function StatusDot({ s }: { s: Agent["status"] }) {
const color = s === "connected" ? "bg-emerald-500" : s === "warning" ? "bg-amber-500" : "bg-rose-500";
return <span className={`inline-block h-2.5 w-2.5 rounded-full ${color}`} />;
}

if (!mounted || status === "loading") {
return (
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="flex flex-col items-center gap-4">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<div className="text-lg text-muted-foreground">Loading AI Agents...</div>
</div>
</div>
);
}

if (!session) return null;

return (
<div className="min-h-screen bg-background relative overflow-hidden">
<div className="fixed inset-0 -z-10 grid-background opacity-20" />
<div className="fixed inset-0 -z-10">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-primary/10 rounded-full blur-3xl pulse-glow" />
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-secondary/10 rounded-full blur-3xl pulse-glow" style={{ animationDelay: "1s" }} />
</div>

<Sidebar />

<main className="ml-64 min-h-screen transition-all duration-300">
<div className="container mx-auto px-8 py-8">
{/* Header area to mirror dashboard top spacing */}
<div className="flex items-center justify-between mb-6">
<h1 className="text-xl font-semibold">AI Agents</h1>
<div className="flex items-center gap-3">
<button className="h-9 w-9 rounded-md bg-card border border-border flex items-center justify-center text-muted-foreground">πŸ””</button>
<div className="h-9 w-9 rounded-full bg-card border border-border flex items-center justify-center text-muted-foreground">R</div>
</div>
</div>

<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left: Agents list (2 cols) */}
<Card className="lg:col-span-2 border-border bg-card/50">
<div className="p-4 md:p-6">
<div className="flex items-center gap-2 mb-4">
<Bot className="h-5 w-5 text-primary" />
<h2 className="text-sm font-medium">AI Coding Agents</h2>
</div>

<div className="space-y-4">
{(loadingAgents ? [1,2,3].map(n => ({ id: String(n), name: "", status: "disconnected" as const })) : agents).map((agent, idx) => (
<div key={agent.id || idx} className="flex items-center justify-between rounded-md border border-border bg-card px-4 py-3">
<div className="flex items-center gap-3">
<div className="h-9 w-9 rounded-md bg-primary/10 flex items-center justify-center text-primary">
<Bot className="h-5 w-5" />
</div>
<div>
<div className="font-medium text-foreground">{agent.name || "Loading..."}</div>
</div>
</div>
<div className="flex items-center gap-3">
<StatusDot s={agent.status} />
<Button
size="sm"
variant={agent.status === "connected" ? "secondary" : "default"}
className={agent.status === "connected" ? "" : "bg-primary"}
onClick={() => toggleAgent(agent)}
disabled={savingAgentId === agent.id || loadingAgents}
>
{savingAgentId === agent.id ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : agent.status === "connected" ? (
"Disconnect"
) : (
"Connect"
)}
</Button>
</div>
</div>
))}
</div>
</div>
</Card>

{/* Right: MCP server config */}
<Card className="border-border bg-card/50">
<div className="p-4 md:p-6 space-y-4">
<div className="flex items-center gap-2">
<ServerCog className="h-5 w-5 text-primary" />
<h2 className="text-sm font-medium">MCP Server Configuration</h2>
</div>
<div className="space-y-2">
<Label htmlFor="mcpUrl">MCP Server URL</Label>
<Input id="mcpUrl" placeholder="https://..." value={config.url} onChange={(e) => setConfig({ ...config, url: e.target.value })} />
</div>
<div className="space-y-2">
<Label htmlFor="mcpKey">API Key</Label>
<Input id="mcpKey" type="password" placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" value={config.apiKey} onChange={(e) => setConfig({ ...config, apiKey: e.target.value })} />
</div>
<div>
<Button onClick={saveConfig} disabled={savingConfig} className="bg-primary">
{savingConfig ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
Save Configuration
</Button>
</div>
</div>
</Card>
</div>

{/* Recent configs */}
<Card className="mt-6 border-border bg-card/50">
<div className="p-4 md:p-6">
<h3 className="text-sm font-medium mb-3">Recent MCP Server Configurations</h3>
<ul className="list-disc pl-5 space-y-1 text-sm text-muted-foreground">
{recent.length === 0 ? (
<li>No recent configurations.</li>
) : (
recent.map((r, i) => <li key={i}>{r}</li>)
)}
</ul>
</div>
</Card>
</div>
</main>
</div>
);
}
78 changes: 78 additions & 0 deletions apps/web/app/api/_state/workspaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Shared in-memory workspace state for dev/demo. Not for production use.

export type WorkspaceState = {
id: string;
name: string;
provider: string; // aws | gcp | azure | local
size: string; // small | medium | large
region: string; // us-east | etc
status: "running" | "stopped";
metrics: {
cpu: number; // percent
memory: { usedGb: number; totalGb: number };
disk: { usedGb: number; totalGb: number };
network: { inMb: number; outMb: number };
};
terminal: string[];
snapshots: Array<{ id: string; createdAt: number; location: string }>;
assistant: { tips: string[]; note: string };
lastUpdate: number;
};

const store = new Map<string, WorkspaceState>();

let seed = Date.now() % 100000;
function rnd() {
seed = (seed * 1664525 + 1013904223) % 4294967296;
return seed / 4294967296;
}

export function jitter(n: number, pct = 0.15, min = 0, max = Number.POSITIVE_INFINITY) {
const j = 1 + (rnd() * 2 - 1) * pct;
const v = Math.max(min, Math.min(max, n * j));
return Math.round(v * 100) / 100;
}

function defaultTips(name: string) {
return [
"You can improve startup time by updating packages.",
"Enable hot-reload caching for faster builds.",
`Run tests in watch mode inside ${name} for quicker feedback.`,
];
}

export function ensureWorkspace(id: string): WorkspaceState {
const key = String(id);
if (store.has(key)) return store.get(key)!;
const sizes = { small: { cpu: 2, ram: 4 }, medium: { cpu: 4, ram: 8 }, large: { cpu: 8, ram: 16 } } as const;
const keys = Object.keys(sizes) as Array<keyof typeof sizes>;
const pickSize = keys[Math.floor(rnd() * keys.length)] ?? "small";
const ws: WorkspaceState = {
id: key,
name: `my-nextjs-app-${key}`,
provider: "aws",
size: String(pickSize),
region: "us-east-1",
status: "running",
metrics: {
cpu: Math.round(25 + rnd() * 40),
memory: { usedGb: Math.round(2 + rnd() * 6), totalGb: sizes[pickSize].ram },
disk: { usedGb: Math.round(20 + rnd() * 40), totalGb: 100 },
network: { inMb: Math.round(80 + rnd() * 80), outMb: Math.round(120 + rnd() * 120) },
},
terminal: [
`ritesh@cloudidex:~$ npm run dev`,
`> web@ dev`,
`Server ready on http://localhost:3000 πŸš€`,
],
snapshots: [],
assistant: { tips: defaultTips(`app-${key}`), note: "Predicted CPU load ~60% in next 10 mins" },
lastUpdate: Date.now(),
};
store.set(key, ws);
return ws;
}

export function getStore() {
return store;
}
44 changes: 44 additions & 0 deletions apps/web/app/api/account/connections/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { getServerSession } from "next-auth";
import { createAuthConfig } from "@/lib/auth-config";

type Conn = { provider: string; connected: boolean; available: boolean };

export async function GET() {
try {
// Determine provider availability from env
const googleAvailable = Boolean(process.env.AUTH_GOOGLE_ID && process.env.AUTH_GOOGLE_SECRET);
const githubAvailable = Boolean(process.env.AUTH_GITHUB_ID && process.env.AUTH_GITHUB_SECRET);

// Derive connections for the signed-in user from the Accounts table (if signed in)
const session = await getServerSession(createAuthConfig());

let connected = new Set<string>();
if (session?.user?.id) {
try {
const accounts = await prisma.account.findMany({
where: { userId: session.user.id },
select: { provider: true },
});
connected = new Set(accounts.map((a) => a.provider.toLowerCase()));
} catch (e) {
console.error("/api/account/connections prisma error", e);
}
}

const providers: Conn[] = [
{ provider: "Google", connected: connected.has("google"), available: googleAvailable },
{ provider: "GitHub", connected: connected.has("github"), available: githubAvailable },
];

return NextResponse.json({ connections: providers, updatedAt: new Date().toISOString() });
} catch (e) {
console.error("/api/account/connections route error", e);
// Always return JSON to avoid client JSON parsing errors
return NextResponse.json(
{ connections: [], error: "failed_to_load_connections" },
{ status: 500 },
);
}
}
7 changes: 7 additions & 0 deletions apps/web/app/api/account/delete/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NextResponse } from "next/server";

export async function POST() {
// TODO: hook into your real user deletion logic
// For now, just simulate success so the button works end-to-end
return NextResponse.json({ ok: true }, { status: 200 });
}
Comment on lines +3 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Critical: Missing authentication check exposes deletion endpoint.

This endpoint can be invoked by anyone without authentication, creating a critical security vulnerability. Even though the deletion logic is a placeholder, the endpoint is publicly accessible.

Apply this diff to add authentication:

 import { NextResponse } from "next/server";
+import { auth } from "@/lib/auth";

-export async function POST() {
+export async function POST(req: Request) {
+  const session = await auth();
+  if (!session?.user?.id) {
+    return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
+  }
+
   // TODO: hook into your real user deletion logic
   // For now, just simulate success so the button works end-to-end
   return NextResponse.json({ ok: true }, { status: 200 });
 }

10 changes: 10 additions & 0 deletions apps/web/app/api/account/password/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextResponse } from "next/server";

export async function POST(req: Request) {
// This is a placeholder implementation; validate and change password in your auth system here
const { current, next } = await req.json();
if (!next || next.length < 8) {
return NextResponse.json({ ok: false, error: "Password too short" }, { status: 400 });
}
return NextResponse.json({ ok: true });
}
Comment on lines +3 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Critical: Missing authentication and current password validation.

This endpoint has multiple security issues:

  1. No authentication check - anyone can invoke this endpoint to change any user's password
  2. The current password is extracted but never validated against the user's actual password
  3. No actual password change implementation

Apply this diff to add authentication and validation:

 import { NextResponse } from "next/server";
+import { auth } from "@/lib/auth";

 export async function POST(req: Request) {
+  const session = await auth();
+  if (!session?.user?.id) {
+    return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });
+  }
+
   // This is a placeholder implementation; validate and change password in your auth system here
   const { current, next } = await req.json();
+  
+  // TODO: Validate current password against stored hash
+  // if (!await validatePassword(session.user.id, current)) {
+  //   return NextResponse.json({ ok: false, error: "Current password is incorrect" }, { status: 400 });
+  // }
+  
   if (!next || next.length < 8) {
     return NextResponse.json({ ok: false, error: "Password too short" }, { status: 400 });
   }
+  
+  // TODO: Hash and update the password in the database
+  
   return NextResponse.json({ ok: true });
 }

Committable suggestion skipped: line range outside the PR's diff.

27 changes: 27 additions & 0 deletions apps/web/app/api/ai/agents/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NextResponse } from "next/server";

type Agent = { id: string; name: string; status: "connected" | "disconnected" | "warning" };

// In-memory store for demo; replace with DB/service
let agents: Agent[] = [
{ id: "code-expo-pilot", name: "Code Expo Pilot", status: "connected" },
{ id: "cloud-code-copilot", name: "Cloud Code Copilot", status: "disconnected" },
{ id: "custom-agents", name: "Custom Agents", status: "warning" },
];
Comment on lines +5 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

Replace in-memory store before production deployment.

While the comment acknowledges this is demo code, the in-memory store has the same critical issues as the MCP config route: state will not persist across serverless function invocations in Next.js 15 deployments.

Ensure this is replaced with a database or persistent service before merging to production branches.

Run this script to check if similar in-memory patterns exist elsewhere:


🏁 Script executed:

#!/bin/bash
# Description: Find other API routes using in-memory state patterns

# Search for module-level let/var declarations in API routes
rg -n --type=ts -g 'app/api/**/*.ts' '^(let|var)\s+\w+.*=.*\[|\{' -C 2

Length of output: 148


Let me gather information about the repository structure and search for in-memory patterns more effectively.


🏁 Script executed:

#!/bin/bash
# Find TypeScript files in api-related directories
echo "=== Looking for API route files ==="
fd -e ts -e tsx 'api' --type f | head -20

echo ""
echo "=== Checking general structure ==="
fd -type d -name 'api' | head -10

echo ""
echo "=== Search for module-level let/var assignments with arrays/objects in TS files ==="
rg -n --type=ts '^\s*(let|var|const)\s+\w+\s*[:=].*[\[\{]' -A 3 | head -40

Length of output: 2413


Let me search more specifically for API routes and mutable state patterns.


🏁 Script executed:

#!/bin/bash
# Find files in API directory
echo "=== API route files in apps/web ==="
fd 'route\.(ts|tsx)$' apps/web/app/api 2>/dev/null || echo "No route files found"

echo ""
echo "=== Search for mutable state (let/var) in API routes ==="
fd -e ts apps/web/app/api 2>/dev/null | xargs grep -n '^\s*\(let\|var\)\s' 2>/dev/null | head -30

echo ""
echo "=== Specific check for agents route ==="
cat apps/web/app/api/ai/agents/route.ts 2>/dev/null || echo "File not found"

echo ""
echo "=== Check for other mutable module-level state in any API routes ==="
rg -n '^\s*(let|var)\s+\w+.*=' apps/web/app/api/ 2>/dev/null | grep -v node_modules

Length of output: 2405


Replace mutable module-level state stores across multiple API routes before production deployment.

Verification confirms the concern is critical and systemic. The agents route contains let agents that mutates via POST, and similar patterns exist in:

  • apps/web/app/api/workspaces/route.ts (let workspaces)
  • apps/web/app/api/billing/route.ts (let state)
  • apps/web/app/api/ai/mcp-config/route.ts (let config, let recent)

In Next.js 15 serverless deployments, state stored at module level is lost on each function invocation. Replace all of these with database or persistent service calls before merging to production branches.

πŸ€– Prompt for AI Agents
In apps/web/app/api/ai/agents/route.ts around lines 5-10, the module-level
mutable variable `let agents` is being used as an in-memory store and mutated by
requests; replace it with calls to a persistent store (database or external
service) and remove module-level writable state. Update the POST/GET handlers to
read/write via an injected persistence layer (e.g., DB client or API client) and
return the persisted result, and apply the same refactor to the other affected
files (apps/web/app/api/workspaces/route.ts: replace `let workspaces`,
apps/web/app/api/billing/route.ts: replace `let state`,
apps/web/app/api/ai/mcp-config/route.ts: replace `let config` and `let recent`)
so no route relies on module-level mutable state that will be lost in serverless
environments. Ensure dependency initialization is safe for serverless (lazy/init
per-request or a singleton DB client) and add tests or integration checks to
confirm persistence across invocations.


export async function GET() {
return NextResponse.json({ agents });
}
Comment on lines +12 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add authentication to the GET endpoint.

The GET handler allows unauthenticated access to agent data. Consider adding session validation to ensure only authenticated users can view agents.

Apply this diff to add authentication:

+import { getServerSession } from "next-auth/next";
+import { createAuthConfig } from "@/lib/auth-config";
+
 export async function GET() {
+  const session = await getServerSession(createAuthConfig());
+  if (!session) {
+    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+  }
   return NextResponse.json({ agents });
 }

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In apps/web/app/api/ai/agents/route.ts around lines 12 to 14, the GET handler
currently returns agents without authentication; add session validation so only
authenticated users can access agents. Retrieve the current user/session from
the incoming request using the app's auth/session helper (e.g., getServerSession
or your auth utility), check that a valid user id exists, and if not return a
401/unauthorized NextResponse; if the user is authenticated, proceed to return
NextResponse.json({ agents }). Ensure any new auth helper is imported at the top
of the file and keep error/response messages minimal and consistent with other
API handlers.


export async function POST(req: Request) {
const { id, action } = await req.json();
agents = agents.map((a) =>
a.id === id
? {
...a,
status: action === "connect" ? "connected" : action === "disconnect" ? "disconnected" : a.status,
}
: a
);
return NextResponse.json({ ok: true, agents });
}
Comment on lines +16 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add input validation and improve status update logic.

The POST handler has several issues:

  1. No input validation: Request body is destructured without checking structure or types
  2. Silent failures: Invalid agent IDs are silently ignored (map continues without error)
  3. No authentication: Anyone can modify agent statuses
  4. Unclear logic: Nested ternary operator reduces readability
+import { z } from "zod";
+
+const agentActionSchema = z.object({
+  id: z.string().min(1),
+  action: z.enum(["connect", "disconnect"]),
+});
+
 export async function POST(req: Request) {
+  try {
-  const { id, action } = await req.json();
+    const body = await req.json();
+    const { id, action } = agentActionSchema.parse(body);
+    
+    const agent = agents.find(a => a.id === id);
+    if (!agent) {
+      return NextResponse.json({ error: "Agent not found" }, { status: 404 });
+    }
+    
   agents = agents.map((a) =>
     a.id === id
       ? {
           ...a,
-          status: action === "connect" ? "connected" : action === "disconnect" ? "disconnected" : a.status,
+          status: action === "connect" ? "connected" : "disconnected",
         }
       : a
   );
   return NextResponse.json({ ok: true, agents });
+  } catch (error) {
+    if (error instanceof z.ZodError) {
+      return NextResponse.json({ error: "Invalid input" }, { status: 400 });
+    }
+    return NextResponse.json({ error: "Internal error" }, { status: 500 });
+  }
 }
πŸ€– Prompt for AI Agents
In apps/web/app/api/ai/agents/route.ts around lines 16 to 27, the POST handler
currently destructures req.json() without validation, silently ignores unknown
agent IDs, lacks authentication, and uses a nested ternary for status updates;
fix by validating the request body shape and types (ensure id is a non-empty
string and action is one of "connect" or "disconnect"), authenticate/authorize
the caller before making changes, find the target agent by id and return a 404
or 400 if not found/invalid instead of silently continuing, replace the nested
ternary with clear conditional logic to set status to "connected" or
"disconnected", and return appropriate HTTP error responses on validation/auth
failures.

Loading