Skip to content

console: workspace members page offers Invite / Remove member to the member role; only the server 403 stops it #4475

Description

@hotlong

Moved from objectstack-ai/objectstack#8092 at triage (file-at-destination: the fix is UI affordance gating in this repo). Filed 2026-08-12 by @baozhoutao from a multi-org console walkthrough, original grading priority:p2. Batch siblings: objectstack-ai/objectstack#8090 #8091 #8093 #8094.

Triage note: this is the UI half only. The server-side question — whether org_member should be able to read the invitation ledger at all — is escalated separately as objectstack-ai/objectstack#8095 (needs-user-decision); do not couple this card to that ruling.

On /_console/organizations/<slug>/members, a user whose org role is member is shown the Invite member button and a per-row Member actions menu containing Remove member — including on the row of the workspace Owner. Nothing is disabled or hidden; the action only fails after the user commits to it.

Repro

  1. User A owns workspace 甲; user B joins it as member.
  2. As B, open /_console/organizations/acme-jia/members.

Actual

The server does gate the write — POST /api/v1/auth/organization/invite-member as the member returns:

403 {"message":"You are not allowed to invite users to this organization",
     "code":"YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION"}

so this is a UI-gating gap, not a privilege escalation. (remove-member was not exercised destructively; a probe with a non-existent member returned 400 MEMBER_NOT_FOUND, i.e. the lookup runs before the permission check, so that path's gating is unverified from the client side and worth a look while fixing.)

Expected

A member sees no invite or remove affordances — matching the Settings tab of the very same page, which already gets this right: it replaces the form with "只有所有者可以修改设置。" and disables Delete organization while leaving Leave organization enabled. The members/invitations tabs simply never got the same treatment.

Where

packages/app-shell/src/console/organizations/manage/ — MembersPage (invite button + row actions) and InviteMemberDialog. The active member's role is already available via the org context used elsewhere on the page.

Environment

Local dev server http://localhost:8080, multi-org enabled, zh locale, observed 2026-08-12. The server was started by the maintainer and its exact commit is not verified.

Activity

  1. self-assigned this
    on Aug 12, 2026
  2. yinlianghui commented on Aug 12, 2026

    @yinlianghui
    Collaborator

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4475-members-role-gating.

    Ruling (delegated authority; open veto window): gate the affordances by the active member's org role, using the same page's own Settings tab as the in-file precedent — it already does owner-gating right (form replaced by explanatory copy, destructive action disabled, Leave still enabled). Members/invitations tabs get the same treatment: a member sees no Invite button and no Remove-member row actions; the role comes from the org context the page already consumes (measure which roles CAN invite/remove rather than hardcoding "owner" — the server's better-auth role model is the truth; mirror what the Settings tab keys on and what the server's own gates enforce, and say what you keyed on).

    Scope guards: (1) UI half ONLY — the server-side "can org_member read the invitation ledger" question is escalated as objectstack#8095, do NOT couple to it. (2) The card's probe note: the remove-member server path's gating is unverified from the client (lookup runs before permission check) — MEASURE it read-only and report; if the server genuinely does not gate remove-member, that is a stop-and-report finding for the objectstack lane, not something to fix here. (3) New user-facing strings via the channel #4474 just established (PR #4496 landed — useObjectTranslation inline defaults + all-ten-packs parity; the role-label resolver resolveOrgRoleLabel and error mapper now exist, reuse them, don't fork).

    Red-first: member viewer on the members page → pre-fix Invite button enabled + Remove on every row (capture the DOM); post-fix neither affordance present, with the Settings-tab-style explanation where the design calls for one. Must-not-change (green both sides): owner/admin still see and use both affordances; Leave organization unaffected; the invitations tab's copy-link affordances follow the same measured role rule.

    Grading: patch @object-ui/app-shell. Never major.

    Surface (mutual exclusion): packages/app-shell/src/console/organizations/manage/** (MembersPage, InvitationsPage if its affordances need the same gate, InviteMemberDialog) + tests + one changeset. Disjoint from in-flight #4434 (test-support), #4464 (plugin-grid), #4465 (plugin-dashboard filter bar), #4467 (shell chrome/impersonation — dispatching in parallel, does NOT enter organizations/manage).


    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions