Auto-mint default MCP token on workspace creation

- ensureWorkspaceForUser() now calls mintWorkspaceApiKey('default') on first workspace creation
- Legacy workspaces without a default key get one minted on first request
- GET /api/workspaces/[slug]/keys/default reveals (or mints) the default token for session users
- Chat panel fetches the token automatically on mount, caches it in localStorage
- No manual setup needed — tool calling works immediately on first sign-in

Made-with: Cursor
This commit is contained in:
2026-04-27 15:43:27 -07:00
parent 5e07bbf39d
commit 1e138d69d6
3 changed files with 96 additions and 4 deletions

View File

@@ -0,0 +1,66 @@
/**
* 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 });
}

View File

@@ -163,11 +163,22 @@ export function ChatPanel() {
document.documentElement.style.setProperty("--chat-panel-width", open ? "380px" : "0px");
}, [open]);
// Load MCP token from localStorage (set at /settings)
// Load MCP token — prefer localStorage cache, fetch from API if missing
useEffect(() => {
const t = localStorage.getItem(`vibn-mcp-token-${workspace}`);
if (t) setMcpToken(t);
}, [workspace]);
if (!workspace || status !== "authenticated") return;
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`)
.then((r) => r.ok ? r.json() : null)
.then((d) => {
if (d?.token) {
localStorage.setItem(`vibn-mcp-token-${workspace}`, d.token);
setMcpToken(d.token);
}
})
.catch(() => {/* silent — panel works in read-only mode */});
}, [workspace, status]);
// Load threads
const loadThreads = useCallback(async () => {

View File

@@ -25,6 +25,7 @@ import {
COOLIFY_DEFAULT_SERVER_UUID,
COOLIFY_DEFAULT_DESTINATION_UUID,
} from '@/lib/coolify';
import { mintWorkspaceApiKey } from '@/lib/auth/workspace-auth';
import {
createOrg,
getOrg,
@@ -178,6 +179,20 @@ export async function ensureWorkspaceForUser(opts: {
[workspace.id, opts.userId]
);
// Auto-mint a default API key so the chat panel and Cursor MCP
// integration work without any manual setup step.
try {
await mintWorkspaceApiKey({
workspaceId: workspace.id,
name: 'default',
createdBy: opts.userId,
scopes: ['workspace:*'],
});
} catch (err) {
// Non-fatal — user can mint manually in Settings if this fails.
console.error('[workspaces] auto-mint default key failed', workspace.slug, err);
}
return workspace;
}