Skip to content

feat(api): add workspace administration to the public API - #1175

Open
realcodesiman wants to merge 2 commits into
mainfrom
feat/workspace-administration-public-api
Open

realcodesiman wants to merge 2 commits into
mainfrom
feat/workspace-administration-public-api

Conversation

@realcodesiman

Copy link
Copy Markdown
Contributor

Summary

Adds a new workspace workspace-token scope and its public routes, closing the one gap in the public API: workspace administration (settings, API tokens, member writes, templates, audit logs) was builder-session-only.

  • workspace — get/update/updateStatus, schedule/cancel deletion, support-access toggle, channel-token refresh
  • apiTokens — full CRUD + rotate, gated behind an unrestricted (all-scopes) token so a token can never mint one broader than itself; the default token ({{api_key}}) is immutable
  • workspaceMembers — new invite/update/remove write routes alongside the existing list/get
  • templates — full CRUD, share settings, installations, install auto-update toggle
  • auditLogs — enterprise-gated list

Every route: workspaceId always comes from the authenticated token's context.workspace.id (never client input), actor fields are null for a token caller (no human actor), and responses never serialize secrets (tokenHash, encryptedToken, template payload, tenantId).

Refactors (clean cutover, no duplicated logic)

  • Lifted the per-integration channel-token refresh loop out of refresh-all-channel-tokens.action.ts into channelTokenRefreshService.refreshWorkspace (packages/business/src/workspace/channel-token-refresh.ts); the builder action now calls the same service.
  • Lifted the invitation-row insert out of invite-workspace-member.action.ts into invitationService.create; the builder action now calls the same service.
  • Added findByIdForWorkspace / updateByIdForWorkspace / rotateTokenById to the workspace-API-token repository, and findTokenOrFail / updateToken / rotateToken to its service, invalidating the token-auth cache tag on both mutations.

Verification

pnpm --filter @chatbotx.io/database check-types   # pass
pnpm --filter @chatbotx.io/business check-types    # pass
pnpm --filter @chatbotx.io/business test           # 244 files / 2615 tests pass
pnpm --filter builder check-types                  # pass
pnpm --filter builder test                         # 551 files / 3904 tests pass
pnpm --filter builder i18n:check                   # pass (new `workspace` scope label added to all 20 locales)
pnpm lint                                           # pass

New test coverage: *-public-scope.test.ts / *-public-api.test.ts for each of workspace, apiTokens, templates, auditLogs, plus extended workspace-members-actions.test.ts, workspace-token-scope-registry.test.ts, and workspace-api-token.service.test.ts. The public-spec snapshot (public-spec-operations.test.ts.snap) is refreshed for the new operations.

Not included: invitation acceptance (requires the invitee's browser session) — everything else the builder UI can do in this area is covered.

Add a new "workspace" workspace-token scope covering workspace
settings, API tokens, members, templates, and audit logs — the one
area with no public surface today.

- workspace: get/update/updateStatus, schedule/cancel deletion,
  support-access toggle, and channel-token refresh
- apiTokens: full CRUD + rotate, gated behind an unrestricted
  (all-scopes) token so a token can never mint one broader than itself
- workspaceMembers: invite/update/remove write routes alongside the
  existing list/get
- templates: full CRUD, share settings, installations, and
  auto-update toggle
- auditLogs: enterprise-gated list

Lifts the per-integration channel-token refresh loop out of
refresh-all-channel-tokens.action.ts into a new
channelTokenRefreshService, and the invitation insert out of
invite-workspace-member.action.ts into invitationService.create, so
both the builder action and the new public route share one
implementation. workspaceId is always taken from the authenticated
token's context; secrets (tokenHash, encryptedToken, template
payload, tenantId) are never serialized.
@github-actions github-actions Bot added the feature New feature or request label Sep 14, 2026
- requireUnrestrictedToken (guards /v1/api-tokens) treated a missing
  apiToken context as an unrestricted token instead of rejecting it;
  now explicitly requires a present, scopes:null token.
- workspace-token-auth blocked every route once a workspace was
  scheduled for deletion, including DELETE /v1/workspace/deletion
  itself, leaving no way to cancel via the public API; the
  owner trial/quota gate now takes the request path too, exempting
  the deletion-lifecycle routes the same way workspaceActionClient
  already does for session callers (invariant #14).
- Consolidated logic duplicated between the new public routes and
  their private builder-action counterparts into shared business
  services: invitationService.create (audit failures no longer
  reject the invite), workspaceMemberService.updateMember (diff +
  role_change audit), templateService.enqueueInstallation
  (install/enqueue/fail sequence), and
  channelTokenRefreshService.refreshWorkspaces (batches every
  provider lookup across all workspace ids in one query instead of
  one query per workspace).
- Added cross-workspace-isolation (IDOR) regression tests for
  workspace API tokens, workspace members, and templates, plus
  coverage for the two auth-gate fixes above.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant