/** * GET /api/workspaces/[slug]/gitea-credentials * * Returns a ready-to-use git clone URL for the workspace's Gitea org, * plus the bot username/token. This is the one endpoint an AI agent * calls before doing any git work — it hides all the admin/org/bot * bookkeeping behind a single bearer-auth request. * * Auth: NextAuth session (owner) OR `Bearer vibn_sk_...` scoped to * this workspace. Never returns credentials for a different workspace. * * The plaintext PAT is decrypted on the server on every call — we * never persist it in logs or client state. */ import { NextResponse } from 'next/server'; import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth'; import { getWorkspaceBotCredentials, ensureWorkspaceProvisioned } from '@/lib/workspaces'; const GITEA_API_URL = process.env.GITEA_API_URL ?? 'https://git.vibnai.com'; 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 the bot has never been provisioned, do it now. Idempotent. let workspace = principal.workspace; if (!workspace.gitea_bot_token_encrypted || !workspace.gitea_org) { try { workspace = await ensureWorkspaceProvisioned(workspace); } catch (err) { return NextResponse.json( { error: 'Provisioning failed', details: err instanceof Error ? err.message : String(err), }, { status: 502 } ); } } const creds = getWorkspaceBotCredentials(workspace); if (!creds) { return NextResponse.json( { error: 'Workspace has no Gitea bot yet', provisionStatus: workspace.provision_status, provisionError: workspace.provision_error, hint: 'POST /api/workspaces/' + slug + '/provision to retry bot provisioning.', }, { status: 503 } ); } const apiBase = GITEA_API_URL.replace(/\/$/, ''); const host = new URL(apiBase).host; return NextResponse.json({ workspace: { slug: workspace.slug, giteaOrg: creds.org }, bot: { username: creds.username, // Full plaintext PAT — treat like a password. token: creds.token, }, gitea: { apiBase, host, // Templates for the agent. Substitute {{repo}} with the repo name. cloneUrlTemplate: `https://${creds.username}:${creds.token}@${host}/${creds.org}/{{repo}}.git`, sshRemoteTemplate: `git@${host}:${creds.org}/{{repo}}.git`, webUrlTemplate: `${apiBase}/${creds.org}/{{repo}}`, }, principal: { source: principal.source, apiKeyId: principal.apiKeyId ?? null, }, }); }