Commit Graph

3 Commits

Author SHA1 Message Date
d9d3514647 fix(gitea-bot): mint PAT via Basic auth, not Sudo header
Gitea's POST /users/{name}/tokens is explicitly Basic-auth only;
neither the admin token nor Sudo header is accepted. Keep the random
password we generate at createUser time and pass it straight into
createAccessTokenFor as Basic auth.

For bots that already exist from a half-failed previous provision
run, reset their password via PATCH /admin/users/{name} so we can
Basic-auth as them and mint a fresh token.

Made-with: Cursor
2026-04-21 10:58:25 -07:00
b9511601bc feat(ai-access): per-workspace Gitea bot + tenant-safe Coolify proxy + MCP
Ship Phases 1–3 of the multi-tenant AI access plan so an AI agent can
act on a Vibn workspace with one bearer token and zero admin reach.

Phase 1 — Gitea bot per workspace
- Add gitea_bot_username / gitea_bot_user_id / gitea_bot_token_encrypted
  columns to vibn_workspaces (migrate route).
- New lib/auth/secret-box.ts (AES-256-GCM, VIBN_SECRETS_KEY) for PAT at rest.
- Extend lib/gitea.ts with createUser, createAccessTokenFor (Sudo PAT),
  createOrgTeam, addOrgTeamMember, ensureOrgTeamMembership.
- ensureWorkspaceProvisioned now mints a vibn-bot-<slug> user, adds it to
  a Writers team (write perms only) on the workspace's org, and stores
  its PAT encrypted.
- GET /api/workspaces/[slug]/gitea-credentials returns a workspace-scoped
  bot PAT + clone URL template; session or vibn_sk_ bearer auth.

Phase 2 — Tenant-safe Coolify proxy + real MCP
- lib/coolify.ts: projectUuidOf, listApplicationsInProject,
  getApplicationInProject, TenantError, env CRUD, deployments list.
- Workspace-scoped REST endpoints (all filtered by coolify_project_uuid):
  GET/POST /api/workspaces/[slug]/apps/[uuid](/deploy|/envs|/deployments),
  GET /api/workspaces/[slug]/deployments/[deploymentUuid]/logs.
- Full rewrite of /api/mcp off legacy Firebase onto Postgres vibn_sk_
  keys, exposing workspace.describe, gitea.credentials, projects.*,
  apps.* (list/get/deploy/deployments, envs.list/upsert/delete).

Phase 3 — Settings UI AI bundle
- GET /api/workspaces/[slug]/bootstrap.sh: curl|sh installer that writes
  .cursor/rules, .cursor/mcp.json and appends VIBN_* to .env.local.
  Embeds the caller's vibn_sk_ token when invoked with bearer auth.
- WorkspaceKeysPanel: single AiAccessBundleCard with system-prompt block,
  one-line bootstrap, Reveal-bot-PAT button, collapsible manual-setup
  fallback. Minted-key modal also shows the bootstrap one-liner.

Ops prerequisites:
  - Set VIBN_SECRETS_KEY (>=16 chars) on the frontend.
  - Run /api/admin/migrate to add the three bot columns.
  - GITEA_API_TOKEN must be a site-admin token (needed for admin/users
    + Sudo PAT mint); otherwise provision_status lands on 'partial'.

Made-with: Cursor
2026-04-21 10:49:17 -07:00
acb63a2a5a feat(workspaces): per-account tenancy + AI access keys + Cursor integration
Adds logical multi-tenancy on top of Coolify + Gitea so every Vibn
account gets its own isolated tenant boundary, and exposes that
boundary to AI agents (Cursor, Claude Code, scripts) through
per-workspace bearer tokens.

Schema (additive, idempotent — run /api/admin/migrate once after deploy)
  - vibn_workspaces: slug, name, owner, coolify_project_uuid,
    coolify_team_id (reserved for when Coolify ships POST /teams),
    gitea_org, provision_status
  - vibn_workspace_members: room for multi-user workspaces later
  - vibn_workspace_api_keys: sha256-hashed bearer tokens
  - fs_projects.vibn_workspace_id: nullable FK linking projects
    to their workspace

Provisioning
  - On first sign-in, ensureWorkspaceForUser() inserts the row
    (no network calls — keeps signin fast).
  - On first project create, ensureWorkspaceProvisioned() lazily
    creates a Coolify Project (vibn-ws-{slug}) and a Gitea org
    (vibn-{slug}). Failures are recorded on the row, not thrown,
    and POST /api/workspaces/{slug}/provision retries.

Auth surface
  - lib/auth/workspace-auth.ts: requireWorkspacePrincipal() accepts
    either a NextAuth session or "Authorization: Bearer vibn_sk_...".
    The bearer key is hard-pinned to one workspace — it cannot
    reach any other tenant.
  - mintWorkspaceApiKey / listWorkspaceApiKeys / revokeWorkspaceApiKey

Routes
  - GET    /api/workspaces                         list
  - GET    /api/workspaces/[slug]                  details
  - POST   /api/workspaces/[slug]/provision        retry provisioning
  - GET    /api/workspaces/[slug]/keys             list keys
  - POST   /api/workspaces/[slug]/keys             mint key (token shown once)
  - DELETE /api/workspaces/[slug]/keys/[keyId]     revoke

UI
  - components/workspace/WorkspaceKeysPanel.tsx: identity card,
    keys CRUD with one-time secret reveal, and a "Connect Cursor"
    block with copy/download for:
      .cursor/rules/vibn-workspace.mdc — rule telling the agent
        about the API + workspace IDs + house rules
      ~/.cursor/mcp.json — MCP server registration with key
        embedded (server URL is /api/mcp; HTTP MCP route lands next)
      .env.local — VIBN_API_KEY + smoke-test curl
  - Slotted into existing /[workspace]/settings between Workspace
    and Notifications cards (no other layout changes).

projects/create
  - Resolves the user's workspace (creating + provisioning lazily).
  - Repos go under workspace.gitea_org (falls back to GITEA_ADMIN_USER
    for backwards compat).
  - Coolify services are created inside workspace.coolify_project_uuid
    (renamed {slug}-{appName} to stay unique within the namespace) —
    no more per-Vibn-project Coolify Project sprawl.
  - Stamps vibn_workspace_id on fs_projects.

lib/gitea
  - createOrg, getOrg, addOrgOwner, getUser
  - createRepo now routes /orgs/{owner}/repos when owner != admin

Also includes prior-turn auth hardening that was already in
authOptions.ts (CredentialsProvider for dev-local, isLocalNextAuth
cookie config) bundled in to keep the auth layer in one consistent
state.

.env.example
  - Documents GITEA_API_URL / GITEA_API_TOKEN / GITEA_ADMIN_USER /
    GITEA_WEBHOOK_SECRET and COOLIFY_URL / COOLIFY_API_TOKEN /
    COOLIFY_SERVER_UUID, with the canonical hostnames
    (git.vibnai.com, coolify.vibnai.com).

Post-deploy
  - Run once: curl -X POST https://vibnai.com/api/admin/migrate \\
      -H "x-admin-secret: \$ADMIN_MIGRATE_SECRET"
  - Existing users get a workspace row on next sign-in.
  - Existing fs_projects keep working (legacy gitea owner + their
    own per-project Coolify Projects); new projects use the
    workspace-scoped path.

Not in this commit (follow-ups)
  - Wiring requireWorkspacePrincipal into the rest of /api/projects/*
    so API keys can drive existing routes
  - HTTP MCP server at /api/mcp (the mcp.json snippet already
    points at the right URL — no client re-setup when it lands)
  - Backfill script to assign legacy fs_projects to a workspace

Made-with: Cursor
2026-04-20 17:17:12 -07:00