Skip to content

Show restricted members only the connectors their roles reach - #1666

Merged
helpcodeai merged 1 commit into
mainfrom
feat/member-visibility-connectors
Nov 13, 2026
Merged

helpcodeai merged 1 commit into
mainfrom
feat/member-visibility-connectors

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

Summary

Third step of "member visibility" (after #1664 and #1665). In a workspace set to restricted mode, a member who is not an admin now sees on the connectors, tools and File Search routes only what their AI client can use: the connectors they own, and the connectors that hold at least one tool their roles allow. Of another member's connector they see only the allowed tools.

Admins and open workspaces take today's code path. No workspace has the setting yet and the switch in the UI comes last, so nothing changes on deploy. Writes keep their owner-or-admin rule.

Changes

Every handler has the same shape: scopeFor(req.user), and when the scope is not restricted, today's code unchanged. The rules per route:

Connectors controller (api/connectors):

  • GET / and GET /health-check: visible connectors only, filtered in the Prisma where (new ConnectorsService.findVisibleByOrg, workspace filter AND visibility filter), so limit and offset page over visible rows. tools narrowed to the allowed ones unless the caller owns the connector. The setup state is still computed from all tools.
  • GET /:id: 404 for a hidden connector, with exactly the body an unknown id gets (Connector <id> not found). Tools narrowed. mcpServers filtered with serverWhere. A server that holds a visible connector is visible by that rule, so today the filter keeps every entry; it is there so the two rules cannot drift.
  • GET /:id/oauth-config, GET /:id/my-credential, DELETE /:id/my-credential, POST /:id/oauth/authorize: 404 for a hidden connector. Per-user sign-in keeps working for a member whose role allows the connector.
  • GET /:id/webhook/url, GET /:id/webhook/events, GET /:id/catalog-diff: these were already owner-or-admin. Now a hidden connector answers 404 first, then today's owner-or-admin check.
  • GET /export-all: owned connectors only. GET /:id/export: 404 (Connector not found, as for an unknown id) for a connector the member does not own.
  • GET /proxy-availability, GET /oauth/redirect-uri: no connector data, unchanged.
  • Another workspace's connector keeps today's 403 in every mode.

Tools controller (api/connectors/:connectorId/tools):

  • GET /: 404 for a hidden connector; the list is filtered in SQL with the new helper toolWhere (allowed ids, or all tools for the owner).
  • GET /:toolId/annotations, GET /:toolId/response-mapping: already owner-or-admin. Now a hidden connector answers 404 first. Owners see every tool of their connector, so no tool-level rule is needed there.

File Search controller (api/file-search):

  • GET connectors/:id, connectors/:id/scans/:scanId, connectors/:id/tree, connectors/:id/problems, connectors/:id/runs, connectors/:id/access-rules: 404 (File Search connector not found.) for a hidden connector, before any File Search data is read.
  • POST connectors/:id/sync: same 404. It is the one File Search write that is not admin-only (any EDITOR can start a sync), so a member could otherwise act on a connector they cannot see.
  • GET capabilities, GET policy: workspace settings, no connector data, unchanged.

Wiring:

  • ConnectorsModule and FileSearchModule import MemberVisibilityModule, never SettingsModule.
  • files-worker loads only SettingsModule and FileSearchCoreModule. A new module spec walks its import graph (no MemberVisibilityModule, FileSearchModule or ConnectorsModule) and compiles FilesWorkerModule without RolesModule. I checked that both tests fail when MemberVisibilityModule is added to FileSearchCoreModule.
  • The visibility service is a required Nest dependency of the three controllers (no @Optional), so the app does not boot without it. In the connectors and tools controllers it is the last constructor parameter. Existing unit specs build those controllers by hand without it; there the controller takes today's path. This keeps every existing spec untouched.
  • One OpenAPI sentence on each changed operation. The generated CLI operations do not change.

Type

  • Bug fix
  • New feature
  • Documentation
  • Refactoring
  • Performance improvement
  • Other (describe below)

Testing

  • Added/updated unit tests
  • Tested manually (describe steps below)
  • No tests needed (explain why)

New specs:

  • connectors.member-visibility.spec.ts (1511 tests): route coverage, so the suite fails when a GET route of the connectors, tools or File Search controller has no declared rule. Then the matrix: ADMIN / EDITOR / VIEWER x open / restricted x (no whitelist, role with tools, role without tools), with the caller owning one connector of each kind. 404 bodies are compared with the body an unknown id gets. Pagination with the filter, per-user sign-in, and fail closed (a failing role lookup fails the request and reads nothing). I checked that removing one route's check, or the list filter, makes 12 tests fail.
  • connectors.member-visibility.integration.spec.ts (38 tests, real PostgreSQL through AUDIT_TEST_DATABASE_URL, as in CI): the same matrix over HTTP with the real ConnectorsService, RolesService and org_settings rows. Includes pages of a filtered list and a 500 when the role lookup throws.
  • file-search.module.spec.ts (4 tests): the wiring described above.
  • member-visibility.service.spec.ts: three tests for the new toolWhere helper.

Commands, in the worktree rebased on origin/main 45e0fdf (packages/backend unless noted). The real-DB runs used a database created for this PR on the local 5433 server, migrated with npx prisma migrate deploy and dropped afterwards.

  • npx jest src/connectors src/file-search src/settings src/roles src/audit src/common/openapi.spec.ts src/auth/jwt-or-management-key.guard.boot.spec.ts --maxWorkers=4 with AUDIT_TEST_DATABASE_URL set: 163 suites passed, 2 skipped, 1 failed; 5280 tests passed, 11 skipped, 1 failed. The failure is audit/tool-performance.integration.spec.ts ("weights ERROR and TIMEOUT counts"): local Postgres returns 284.99999999999994 where the spec expects 285. This PR does not touch audit; the spec reads only the audit query.
  • Full backend npx jest --maxWorkers=4 (before the rebase): 1096 suites passed, 7 skipped, 2 failed. The failures are the one above and auth/jwt-or-management-key.guard.spec.ts, which needs DATABASE_URL pointing at a migrated database and fails in its setup locally. Neither file is touched here; CI runs both against its database.
  • npx eslint <changed files>: 0 errors, 0 warnings.
  • npx tsc --noEmit -p tsconfig.json: pass.
  • Root: node scripts/generate-cli-operations.mjs, then --check: up to date (213 operations), no diff.

What tree and problems return (unchanged, now behind the visibility check): tree is the folder tree of the index, 4 levels deep, with file count and bytes per folder. problems is a page of files that failed or were skipped (path, status, last error, size, attempts, updated at). Neither applies the File Search folder rules by role, which only apply on the MCP tools today. So a member who can see a File Search connector sees its whole folder tree and problem list. That is unchanged by this PR and could be a follow-up.

Not changed, on purpose:

  • Write routes still answer 403 to a non-owner for a hidden connector, where a GET answers 404. Connector ids are random cuids, so this only confirms an id the caller already has. A follow-up could return 404 there too.
  • The connect page /auth/connect/:connectorId and the OAuth callbacks (plan §10, separate PR).
  • No backend onboarding endpoint calls a connectors route. The frontend OnboardingRedirect reads GET /api/connectors; it is handled in the frontend PR, together with the switch in the UI.

Cloud and self-hosted: identical, off by default. No workspace has the setting, so every workspace reads open. No migration.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • I have updated documentation (if applicable). The docs come with the frontend PR.
  • All existing tests pass (npm test). All pass except the two local database failures above, which are not touched here.
  • This PR has a descriptive title

Related Issues

Relates to #1664 and #1665 (same feature, earlier steps).

@helpcodeai
helpcodeai merged commit c7667b3 into main Nov 13, 2026
17 checks passed
@helpcodeai
helpcodeai deleted the feat/member-visibility-connectors branch November 13, 2026 17:36
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant