fix(frontend): correctly resolve token object from revealWorkspaceApiKey to prevent unauthorized access

This commit is contained in:
2026-06-04 11:45:42 -07:00
parent 9def97c3a5
commit 486d4449b2

View File

@@ -6,27 +6,42 @@
* - vibn_sk_... API key: returns just the one workspace the key is bound to * - vibn_sk_... API key: returns just the one workspace the key is bound to
*/ */
import { NextResponse } from 'next/server'; import { NextResponse } from "next/server";
import { authSession } from '@/lib/auth/session-server'; import { authSession } from "@/lib/auth/session-server";
import { queryOne } from '@/lib/db-postgres'; import { queryOne } from "@/lib/db-postgres";
import { ensureWorkspaceForUser, listWorkspacesForUser } from '@/lib/workspaces'; import {
import { requireWorkspacePrincipal, listWorkspaceApiKeys, mintWorkspaceApiKey, revealWorkspaceApiKey } from '@/lib/auth/workspace-auth'; ensureWorkspaceForUser,
listWorkspacesForUser,
} from "@/lib/workspaces";
import {
requireWorkspacePrincipal,
listWorkspaceApiKeys,
mintWorkspaceApiKey,
revealWorkspaceApiKey,
} from "@/lib/auth/workspace-auth";
export async function GET(request: Request) { export async function GET(request: Request) {
if (request.headers.get('authorization')?.toLowerCase().startsWith('bearer vibn_sk_')) { if (
request.headers
.get("authorization")
?.toLowerCase()
.startsWith("bearer vibn_sk_")
) {
const principal = await requireWorkspacePrincipal(request); const principal = await requireWorkspacePrincipal(request);
if (principal instanceof NextResponse) return principal; if (principal instanceof NextResponse) return principal;
return NextResponse.json({ workspaces: [serializeWorkspace(principal.workspace)] }); return NextResponse.json({
workspaces: [serializeWorkspace(principal.workspace)],
});
} }
const session = await authSession(); const session = await authSession();
if (!session?.user?.email) { if (!session?.user?.email) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const userRow = await queryOne<{ id: string }>( const userRow = await queryOne<{ id: string }>(
`SELECT id FROM fs_users WHERE data->>'email' = $1 LIMIT 1`, `SELECT id FROM fs_users WHERE data->>'email' = $1 LIMIT 1`,
[session.user.email] [session.user.email],
); );
if (!userRow) { if (!userRow) {
return NextResponse.json({ workspaces: [] }); return NextResponse.json({ workspaces: [] });
@@ -45,37 +60,57 @@ export async function GET(request: Request) {
}); });
list = await listWorkspacesForUser(userRow.id); list = await listWorkspacesForUser(userRow.id);
} catch (err) { } catch (err) {
console.error('[api/workspaces] lazy ensure failed', err); console.error("[api/workspaces] lazy ensure failed", err);
} }
} }
const url = new URL(request.url); const url = new URL(request.url);
const includeDefaultToken = url.searchParams.get('include_default_token') === 'true'; const includeDefaultToken =
url.searchParams.get("include_default_token") === "true";
if (includeDefaultToken && list.length > 0) { if (includeDefaultToken && list.length > 0) {
const ws = list[0]; const ws = list[0];
let defaultToken: string | null = null; let defaultToken: string | null = null;
try { try {
const keys = await listWorkspaceApiKeys(ws.id); const keys = await listWorkspaceApiKeys(ws.id);
let defaultKey = keys.find((k: any) => k.name === 'default' && !k.revoked_at); let defaultKey = keys.find(
(k: any) => k.name === "default" && !k.revoked_at,
);
if (!defaultKey) { if (!defaultKey) {
const minted = await mintWorkspaceApiKey({ workspaceId: ws.id, name: 'default', createdBy: userRow!.id, scopes: ['workspace:*'] }); const minted = await mintWorkspaceApiKey({
workspaceId: ws.id,
name: "default",
createdBy: userRow!.id,
scopes: ["workspace:*"],
});
defaultToken = minted.token; defaultToken = minted.token;
} else { } else {
defaultToken = await revealWorkspaceApiKey(ws.id, defaultKey.id); const revealed = await revealWorkspaceApiKey(ws.id, defaultKey.id);
if (!defaultToken) { if (revealed) {
const minted = await mintWorkspaceApiKey({ workspaceId: ws.id, name: 'default', createdBy: userRow!.id, scopes: ['workspace:*'] }); defaultToken = revealed.token;
} else {
const minted = await mintWorkspaceApiKey({
workspaceId: ws.id,
name: "default",
createdBy: userRow!.id,
scopes: ["workspace:*"],
});
defaultToken = minted.token; defaultToken = minted.token;
} }
} }
} catch { /* non-fatal */ } } catch {
return NextResponse.json({ workspaces: list.map(serializeWorkspace), defaultToken }); /* non-fatal */
}
return NextResponse.json({
workspaces: list.map(serializeWorkspace),
defaultToken,
});
} }
return NextResponse.json({ workspaces: list.map(serializeWorkspace) }); return NextResponse.json({ workspaces: list.map(serializeWorkspace) });
} }
function serializeWorkspace(w: import('@/lib/workspaces').VibnWorkspace) { function serializeWorkspace(w: import("@/lib/workspaces").VibnWorkspace) {
return { return {
id: w.id, id: w.id,
slug: w.slug, slug: w.slug,