Repository navigation
Add the member visibility setting and the service that decides it - #1664
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First step of "member visibility". A workspace admin will be able to switch the workspace to restricted mode. A member who is not an admin then sees in the dashboard and the REST API only what their AI client can use.
This PR adds the setting, the service that decides it, and two settings routes. No existing endpoint calls the service yet, so no response changes. The later PRs use the service in the audit, connectors, tools, MCP servers and File Search controllers.
Changes
MemberVisibilityServiceinpackages/backend/src/settings/(AGPL, outsideee/).scopeFor(req.user)returns{ restricted: false }or{ restricted: true, userId, allowedToolIds }.{ restricted: false }before any database read.org_settingskeymember_visibility, read throughOrgSettingsService. No row, or any value other thanrestricted, means open.allowedToolIdscomes fromRolesService.getAllowedToolIdson every call, never cached.nullmeans the roles narrow nothing.connectorWhere,canSeeConnector,visibleTools,serverWhere(relationstoolsandconnectors.connector, as inschema.prisma). They are always combined with theorganizationIdfilter by AND.GET /api/settings/member-visibility, any signed-in member:{ mode, restrictedForMe }.PUT /api/admin/settings/member-visibility, ADMIN only, body{ mode: 'open' | 'restricted' }checked with@IsIn. Always writes the caller's workspace. A read-only management key gets 403 from the existing guard. A change writes the security eventMEMBER_VISIBILITY_CHANGEDwith actor and{ from, to }. No change, no write and no event.SecurityEvents. It is a string constant, not a Prisma enum, so no migration.amcp apiexample. CLI operations regenerated.MemberVisibilityModuleis exported for the later PRs and registered inAppModule.Why a separate module and controllers instead of
SiteSettingsMemberControllerandSiteSettingsAdminController: the File Search worker importsSettingsModulewithoutRolesModuleandAuditModule. A dependency onRolesServiceorSecurityEventServiceinsideSettingsModulewould stop the worker at boot. The routes, tag and guards are the same as planned.RolesModuleandAuditModuleare@Global, so the new module imports onlySettingsModuleand creates no cycle. A spec checks that the service stays out ofSettingsModule.Type
Testing
New specs:
member-visibility.service.spec.ts: mode parsing, ADMIN short-circuit (a Prisma proxy proves no database access), open and restricted results, null and empty tool lists, no cache for roles, 30 s cache and drop on write, fail closed when roles or the setting throw, and the four helpers (including a check thatcanSeeConnectoragrees withconnectorWhere).member-visibility.controller.spec.ts: HTTP level with the realJwtOrManagementKeyGuard,RolesGuardand theValidationPipeoptions ofmain.ts. ADMIN can write and the event is logged. EDITOR and VIEWER get 403. AMANAGEMENT_READkey of an ADMIN gets 403. Invalid values and extra fields get 400. GET works for every role. Also the OpenAPI output.member-visibility.module.spec.ts: module wiring with the services from global modules, imported by a module shaped likeAuditModule.Commands, all in the worktree on
origin/main2b31050:packages/backend:npx jest src/settings src/audit src/main-cookie-parser.spec.ts src/auth/mcp-auth.middleware.spec.ts src/common/openapi.spec.ts --maxWorkers=4: 25 suites passed, 1 skipped; 1308 tests passed, 9 skipped.packages/backend:npx jest src/settings src/audit/security-event.service.spec.ts src/auth/jwt-or-management-key.guard.boot.spec.ts src/auth/roles.guard src/common/openapi.spec.ts src/identity-providers src/roles --maxWorkers=4: 30 suites passed, 1 skipped; 1422 tests passed, 12 skipped.packages/backend:npx jest src/auth/jwt-or-management-key.guard.spec.ts: fails in my environment inbeforeAll(prisma.organization.create). It is a real-database spec and the local database is not the CI one. The file and the guard are not changed by this PR. CI runs it against a migrated database.packages/backend:npx eslinton the changed files: 0 errors, 1 old warning (ClassSerializerInterceptorunused inapp.module.ts).packages/backend:npx tsc --noEmit -p tsconfig.json: pass.node scripts/generate-cli-operations.mjs(213 operations), then--check: up to date.packages/cli:npm test: 25 passed, 2 skipped, 0 failed.Cloud and self-hosted: identical, off by default. No workspace has the key, so every workspace reads
open. No migration.Checklist
npm test). Only the touched areas and their neighbours were run, see above.Related Issues
None.