diff --git a/vibn-frontend/app/api/chat/route.ts b/vibn-frontend/app/api/chat/route.ts index 7514ef8e..53fe661a 100644 --- a/vibn-frontend/app/api/chat/route.ts +++ b/vibn-frontend/app/api/chat/route.ts @@ -15,8 +15,8 @@ * data: {"type":"error","error":"..."} */ import { NextResponse } from "next/server"; -import { authSession } from "@/lib/auth/session-server"; -import { query } from "@/lib/db-postgres"; +import { requireWorkspacePrincipal } from "@/lib/auth/workspace-auth"; +import { query, queryOne } from "@/lib/db-postgres"; import { callVibnChat } from "@/lib/ai/vibn-chat-model"; import { VIBN_TOOL_DEFINITIONS, executeMcpTool } from "@/lib/ai/vibn-tools"; import { @@ -394,10 +394,17 @@ function lastToolResultsHadFailure(messages: ChatMessage[], lookback = 3) { export async function POST(request: Request) { await ensureChatTables(); - const session = await authSession(); - if (!session?.user?.email) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + const principal = await requireWorkspacePrincipal(request); + if (principal instanceof NextResponse) return principal; + + const userRow = await queryOne<{ data: any }>( + `SELECT data FROM fs_users WHERE id = $1 LIMIT 1`, + [principal.userId] + ); + if (!userRow?.data?.email) { + return NextResponse.json({ error: "Unauthorized user" }, { status: 401 }); } + const sessionEmail = userRow.data.email; let body: { thread_id: string; @@ -428,7 +435,7 @@ export async function POST(request: Request) { ); } - const email = session.user.email; + const email = sessionEmail; // Verify thread belongs to user, and capture its project scope (if any). const threads = await query<{ id: string; project_id: string | null }>( diff --git a/vibn-frontend/app/api/chat/threads/[id]/route.ts b/vibn-frontend/app/api/chat/threads/[id]/route.ts index ce71fae4..9a68db9a 100644 --- a/vibn-frontend/app/api/chat/threads/[id]/route.ts +++ b/vibn-frontend/app/api/chat/threads/[id]/route.ts @@ -4,18 +4,25 @@ * DELETE /api/chat/threads/[id] — delete a thread */ import { NextResponse } from 'next/server'; -import { authSession } from '@/lib/auth/session-server'; -import { query } from '@/lib/db-postgres'; +import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth'; +import { query, queryOne } from '@/lib/db-postgres'; export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { - const session = await authSession(); - if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const principal = await requireWorkspacePrincipal(request); + if (principal instanceof NextResponse) return principal; + + const userRow = await queryOne<{ data: any }>( + `SELECT data FROM fs_users WHERE id = $1 LIMIT 1`, + [principal.userId] + ); + if (!userRow?.data?.email) return NextResponse.json({ error: 'Unauthorized user' }, { status: 401 }); + const sessionEmail = userRow.data.email; const { id } = await params; const threads = await query( `SELECT id, data, created_at, updated_at FROM fs_chat_threads WHERE id = $1 AND user_id = $2`, - [id, session.user.email], + [id, sessionEmail], ); if (!threads.length) return NextResponse.json({ error: 'Not found' }, { status: 404 }); @@ -32,8 +39,15 @@ export async function GET(request: Request, { params }: { params: Promise<{ id: } export async function PATCH(request: Request, { params }: { params: Promise<{ id: string }> }) { - const session = await authSession(); - if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const principal = await requireWorkspacePrincipal(request); + if (principal instanceof NextResponse) return principal; + + const userRow = await queryOne<{ data: any }>( + `SELECT data FROM fs_users WHERE id = $1 LIMIT 1`, + [principal.userId] + ); + if (!userRow?.data?.email) return NextResponse.json({ error: 'Unauthorized user' }, { status: 401 }); + const sessionEmail = userRow.data.email; const { id } = await params; const { title } = await request.json().catch(() => ({})); @@ -41,16 +55,23 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id await query( `UPDATE fs_chat_threads SET data = data || $3, updated_at = NOW() WHERE id = $1 AND user_id = $2`, - [id, session.user.email, JSON.stringify({ title })], + [id, sessionEmail, JSON.stringify({ title })], ); return NextResponse.json({ ok: true }); } export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) { - const session = await authSession(); - if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const principal = await requireWorkspacePrincipal(request); + if (principal instanceof NextResponse) return principal; + + const userRow = await queryOne<{ data: any }>( + `SELECT data FROM fs_users WHERE id = $1 LIMIT 1`, + [principal.userId] + ); + if (!userRow?.data?.email) return NextResponse.json({ error: 'Unauthorized user' }, { status: 401 }); + const sessionEmail = userRow.data.email; const { id } = await params; - await query(`DELETE FROM fs_chat_threads WHERE id = $1 AND user_id = $2`, [id, session.user.email]); + await query(`DELETE FROM fs_chat_threads WHERE id = $1 AND user_id = $2`, [id, sessionEmail]); return NextResponse.json({ ok: true }); } diff --git a/vibn-frontend/app/api/chat/threads/route.ts b/vibn-frontend/app/api/chat/threads/route.ts index bb51cbf6..c18ce52f 100644 --- a/vibn-frontend/app/api/chat/threads/route.ts +++ b/vibn-frontend/app/api/chat/threads/route.ts @@ -12,8 +12,8 @@ * after deploy (no manual migration needed). */ import { NextResponse } from 'next/server'; -import { authSession } from '@/lib/auth/session-server'; -import { query } from '@/lib/db-postgres'; +import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth'; +import { query, queryOne } from '@/lib/db-postgres'; let chatTablesReady = false; async function ensureChatTables() { @@ -54,8 +54,15 @@ async function ensureChatTables() { export async function GET(request: Request) { await ensureChatTables(); - const session = await authSession(); - if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const principal = await requireWorkspacePrincipal(request); + if (principal instanceof NextResponse) return principal; + + const userRow = await queryOne<{ data: any }>( + `SELECT data FROM fs_users WHERE id = $1 LIMIT 1`, + [principal.userId] + ); + if (!userRow?.data?.email) return NextResponse.json({ error: 'Unauthorized user' }, { status: 401 }); + const sessionEmail = userRow.data.email; const { searchParams } = new URL(request.url); const workspace = searchParams.get('workspace') || ''; @@ -90,8 +97,8 @@ export async function GET(request: Request) { WHERE t.user_id = $1 AND t.workspace = $2 AND t.project_id IS NULL ORDER BY t.updated_at DESC LIMIT 50`; const args = projectId - ? [session.user.email, workspace, projectId] - : [session.user.email, workspace]; + ? [sessionEmail, workspace, projectId] + : [sessionEmail, workspace]; const rows = await query(sql, args); @@ -110,8 +117,15 @@ export async function GET(request: Request) { export async function POST(request: Request) { await ensureChatTables(); - const session = await authSession(); - if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const principal = await requireWorkspacePrincipal(request); + if (principal instanceof NextResponse) return principal; + + const userRow = await queryOne<{ data: any }>( + `SELECT data FROM fs_users WHERE id = $1 LIMIT 1`, + [principal.userId] + ); + if (!userRow?.data?.email) return NextResponse.json({ error: 'Unauthorized user' }, { status: 401 }); + const sessionEmail = userRow.data.email; const { workspace, title, projectId } = await request.json().catch(() => ({})); if (!workspace) return NextResponse.json({ error: 'workspace required' }, { status: 400 }); @@ -125,17 +139,17 @@ export async function POST(request: Request) { `SELECT p.id FROM fs_projects p JOIN fs_users u ON u.id = p.user_id WHERE p.id = $1 AND u.data->>'email' = $2 LIMIT 1`, - [projectId, session.user.email], - ); - if (owned.length > 0) safeProjectId = projectId; - } + [projectId, sessionEmail], + ); + if (owned.length > 0) safeProjectId = projectId; + } - const rows = await query( - `INSERT INTO fs_chat_threads (user_id, workspace, project_id, data) - VALUES ($1, $2, $3, $4) - RETURNING id, project_id, data, created_at, updated_at`, - [ - session.user.email, + const rows = await query( + `INSERT INTO fs_chat_threads (user_id, workspace, project_id, data) + VALUES ($1, $2, $3, $4) + RETURNING id, project_id, data, created_at, updated_at`, + [ + sessionEmail, workspace, safeProjectId, JSON.stringify({ title: title || 'New conversation', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }),