feat(server): scope admin RBAC to organizations + per-org 2FA (PR2/4)#70
Merged
Conversation
Re-scope the admin layer to the organization tenant (migration 013): an owner is now a superuser WITHIN its own org, never across the box. - authz: canViewProject/canManageProject gate owner access on the project belonging to the user's org (new projectInOrg helper). - admin routes: every owner-scoped query filters by organization_id — project list/create, user + invite listings, membership grants, and the per-org last-active-owner guard. Cross-org project/user ids return 404 (not 403) so a tenant can't even confirm another's resources exist. - project-access middleware: the session data-view path is org-scoped too (404 on cross-org X-Project-Id). - 2FA policy is now per-org (organizations.require_2fa); isTwoFactorRequired / setTwoFactorRequired take an orgId, threaded through login, /me, invite accept, enforce-2fa, and the settings route. - invites carry the inviter's org so an accepted account joins that company. Adds an org-isolation integration suite (two orgs, cross-tenant 404s, scoped user/invite listings, invite org-inheritance, per-org last-owner guard) and makes the existing admin/RBAC test helpers org-aware via a shared test-support/org helper. 135 server tests pass.
A newly-published high-severity advisory (GHSA-vxpw-j846-p89q, undici DoS via WebSocket fragment-count bypass, fixed in 6.27.0) broke the CI audit step. undici 6.24.0 is a dev-only transitive dependency of @redocly/cli (the OpenAPI linter) and never ships in the prod Docker image. Pin it via pnpm.overrides, mirroring the earlier ws/form-data/protobufjs overrides.
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Why
PR2 of 4 for the organizations tenant layer. PR1 added the
organizationsentity + nullable FKs + backfill (inert). This PR is where isolation is actually enforced: the adminownerrole stops being a global superuser and becomes a superuser within its own org only — so on a multi-customer box, Company A's owner can never see Company B's data.What
admin/authz.ts):canViewProject/canManageProjectgate owner access on the project belonging to the user's org (newprojectInOrghelper). Members are same-org by construction.routes/admin.ts): every owner-scoped query filters byorganization_id— project list/create, user + invite listings, membership grants, and the per-org last-active-owner guard. Cross-org project/user ids return 404, not 403 (no cross-tenant existence leak).middleware/project-access.ts): org-scoped too (404 on a cross-orgX-Project-Id).admin/settings.ts): policy moves from the install-wideadmin_settings.require_2fatoorganizations.require_2fa;isTwoFactorRequired/setTwoFactorRequiredtake anorgId, threaded through login,/me, invite-accept, enforce-2fa, and the settings route. One tenant's toggle no longer affects another.admin/invite.ts): carry the inviter's org so an accepted account joins that company.Tests
test-support/orghelper.type-checkclean;lintclean (one pre-existing warning unrelated to this PR).Note
organization_idstays nullable; the NOT NULL backstop lands in PR3 once the bootstrap scripts (create-admin/create-project) and remaining data-only test helpers are org-aware.