Fix auto-token fetch: use keys?include_default_token=true instead of /keys/default
Avoids the Next.js dynamic route conflict with [keyId]. Chat panel now correctly bootstraps MCP token on mount with no user action. Made-with: Cursor
This commit is contained in:
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* GET /api/workspaces/[slug]/keys/default
|
||||
*
|
||||
* Returns the plaintext token for the workspace's "default" API key,
|
||||
* auto-minted at workspace creation. Used by the chat panel to bootstrap
|
||||
* itself without any manual setup step.
|
||||
*
|
||||
* Session-only: never callable by an API-key principal (same restriction
|
||||
* as the reveal endpoint, for the same reason).
|
||||
*
|
||||
* If no default key exists (legacy workspaces created before auto-mint),
|
||||
* one is minted on the spot and returned.
|
||||
*/
|
||||
import { NextResponse } from 'next/server';
|
||||
import {
|
||||
requireWorkspacePrincipal,
|
||||
listWorkspaceApiKeys,
|
||||
mintWorkspaceApiKey,
|
||||
revealWorkspaceApiKey,
|
||||
} from '@/lib/auth/workspace-auth';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ slug: string }> },
|
||||
) {
|
||||
const { slug } = await params;
|
||||
const principal = await requireWorkspacePrincipal(request, { targetSlug: slug });
|
||||
if (principal instanceof NextResponse) return principal;
|
||||
|
||||
if (principal.source !== 'session') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Default key can only be retrieved from a signed-in session' },
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
const ws = principal.workspace;
|
||||
const keys = await listWorkspaceApiKeys(ws.id);
|
||||
let defaultKey = keys.find((k: any) => k.name === 'default' && !k.revoked_at);
|
||||
|
||||
// Legacy workspace — mint the default key now
|
||||
if (!defaultKey) {
|
||||
const minted = await mintWorkspaceApiKey({
|
||||
workspaceId: ws.id,
|
||||
name: 'default',
|
||||
createdBy: principal.userId,
|
||||
scopes: ['workspace:*'],
|
||||
});
|
||||
return NextResponse.json({ token: minted.token, keyId: minted.id, minted: true });
|
||||
}
|
||||
|
||||
// Reveal the stored plaintext
|
||||
const token = await revealWorkspaceApiKey(ws.id, defaultKey.id);
|
||||
if (!token) {
|
||||
// Key predates encryption — rotate it
|
||||
const minted = await mintWorkspaceApiKey({
|
||||
workspaceId: ws.id,
|
||||
name: 'default',
|
||||
createdBy: principal.userId,
|
||||
scopes: ['workspace:*'],
|
||||
});
|
||||
return NextResponse.json({ token: minted.token, keyId: minted.id, rotated: true });
|
||||
}
|
||||
|
||||
return NextResponse.json({ token, keyId: defaultKey.id });
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth';
|
||||
import { listWorkspaceApiKeys, mintWorkspaceApiKey } from '@/lib/auth/workspace-auth';
|
||||
import { listWorkspaceApiKeys, mintWorkspaceApiKey, revealWorkspaceApiKey } from '@/lib/auth/workspace-auth';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
@@ -24,6 +24,39 @@ export async function GET(
|
||||
if (principal instanceof NextResponse) return principal;
|
||||
|
||||
const keys = await listWorkspaceApiKeys(principal.workspace.id);
|
||||
|
||||
// ?include_default_token=true — used by the chat panel on mount to
|
||||
// bootstrap tool-calling without any manual setup. Session-only.
|
||||
const url = new URL(request.url);
|
||||
if (url.searchParams.get('include_default_token') === 'true' && principal.source === 'session') {
|
||||
let defaultKey = keys.find((k: any) => k.name === 'default' && !k.revoked_at);
|
||||
|
||||
// Legacy workspace — mint the default key on demand
|
||||
if (!defaultKey) {
|
||||
const minted = await mintWorkspaceApiKey({
|
||||
workspaceId: principal.workspace.id,
|
||||
name: 'default',
|
||||
createdBy: principal.userId,
|
||||
scopes: ['workspace:*'],
|
||||
});
|
||||
return NextResponse.json({ keys, defaultToken: minted.token });
|
||||
}
|
||||
|
||||
const token = await revealWorkspaceApiKey(principal.workspace.id, defaultKey.id);
|
||||
if (!token) {
|
||||
// Pre-encryption key — rotate it
|
||||
const minted = await mintWorkspaceApiKey({
|
||||
workspaceId: principal.workspace.id,
|
||||
name: 'default',
|
||||
createdBy: principal.userId,
|
||||
scopes: ['workspace:*'],
|
||||
});
|
||||
return NextResponse.json({ keys, defaultToken: minted.token });
|
||||
}
|
||||
|
||||
return NextResponse.json({ keys, defaultToken: token });
|
||||
}
|
||||
|
||||
return NextResponse.json({ keys });
|
||||
}
|
||||
|
||||
|
||||
@@ -169,12 +169,12 @@ export function ChatPanel() {
|
||||
const cached = localStorage.getItem(`vibn-mcp-token-${workspace}`);
|
||||
if (cached) { setMcpToken(cached); return; }
|
||||
// Auto-fetch the workspace's default key (created at account setup)
|
||||
fetch(`/api/workspaces/${workspace}/keys/default`)
|
||||
fetch(`/api/workspaces/${workspace}/keys?include_default_token=true`)
|
||||
.then((r) => r.ok ? r.json() : null)
|
||||
.then((d) => {
|
||||
if (d?.token) {
|
||||
localStorage.setItem(`vibn-mcp-token-${workspace}`, d.token);
|
||||
setMcpToken(d.token);
|
||||
if (d?.defaultToken) {
|
||||
localStorage.setItem(`vibn-mcp-token-${workspace}`, d.defaultToken);
|
||||
setMcpToken(d.defaultToken);
|
||||
}
|
||||
})
|
||||
.catch(() => {/* silent — panel works in read-only mode */});
|
||||
|
||||
Reference in New Issue
Block a user