Skip to content

The members page judges permissions from the active organization while acting on the one in the URL #1032

Description

@DEENUU1

/orgs/{id}/members acts on the organization in the path and judges
permissions from the active one, and the two are routinely different.

Why they diverge

apiClient puts X-Organization-Id: activeOrgId on every request
(frontend/src/lib/api-client.ts:86), so GET /me/permissions answers for the
active organization. The members page reads it through usePermissions() and
decides:

Meanwhile useMembers(id), useInvitations(id) and every write name the org from
the URL.

And nothing keeps them the same: the organizations list opens any org's
members page through an overlay link (orgs/page.tsx:147), while switching the
active org is a separate button that navigates to the dashboard
(orgs/page.tsx:195). So the ordinary way to reach another org's members page is
the way that leaves the active org behind.

What breaks

  1. Be an Owner of Acme and an Admin of Globex. Have Globex active.
  2. Organizations → click the Acme card.
  3. The page lists Acme's members, and every decision about them is Globex's: the
    picker offers what an Admin may assign, so Acme's Owner cannot offer Admin on
    their own organization.

The inverse is the one that matters more: Admin of Acme, Owner of Globex
active → the picker offers admin, change_role refuses it, and the
offer-then-refuse #1028 removed is back through another door.

Worse than the picker: with members:manage in one organization and not the
other, canManage shows or hides the whole set of controls against the wrong
answer. That half is older than #1028.

Fixed when

The page's decisions and the page's writes name one organization. Two shapes, and
the choice is worth making explicitly:

  • Make the URL authoritative. The page (or the [id] layout) switches the
    active organization to the one in the path, so the header and the path agree
    for every request the page makes. Cheap, and it makes the org switcher and the
    URL one thing rather than two.
  • Ask per organization. /me/permissions takes an org id, and the page asks
    for the one it is showing. More surgery, and it leaves two notions of "current"
    in the app.

Either way a test opens org B's page with org A active and asserts the controls
describe B.

Related

Found reviewing #1031 (which closes #1028) — the automated review raised the
picker half. Not a duplicate: #1028 was "the picker offers what the service
refuses", and this is "the page asks the wrong organization what the caller may
do". #696 and #700 are the server-side ceiling in the same area.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingeffort:mA day or twofrontendNext.js app, BFF proxies, permission gatingseverity:mediumCorrectness risk under load or edge input, or a contract that lies

Type

Projects

  • Status
    Done

Relationships

None yet

Development

No branches or pull requests

Issue actions