Skip to content

[finding] sys_member "Add Member" action targets POST /organization/add-member, which better-auth 1.7.0-rc.2 never mounts (server-only) — on multi-org there is NO remaining UI path to attach an existing user to an org #9941

Description

@baozhoutao

Symptom

The sys_member list-toolbar action Add Member (add_member) always fails with HTTP 404. Its target route POST /api/v1/auth/organization/add-member is never mounted.

Route-mount probe against a deployed 17.0.0 stack (no credentials — contrast with a sibling route that IS mounted):

POST /api/v1/auth/organization/add-member    -> 404   (route not mounted)
POST /api/v1/auth/organization/remove-member -> 400   (mounted; body validation fired)

Root cause

better-auth 1.7.0-rc.2 declares addMember without an HTTP path — it is a server-only endpoint (auth.api.addMember), while every sibling member route keeps its path (better-auth/dist/plugins/organization/routes/crud-members.mjs):

const addMember = (option) => { ... return createAuthEndpoint({ method: "POST", ... })      // no path → server-only
const removeMember = (options) => createAuthEndpoint("/organization/remove-member", { ... }) // mounted
const updateMemberRole = (option) => createAuthEndpoint("/organization/update-member-role", { ... })

So POST /organization/add-member does not exist over HTTP, yet three places in the platform still claim it does (all current on main @ d7c42405):

  1. packages/platform-objects/src/identity/sys-member.object.ts:54 — the add_member action: target: '/api/v1/auth/organization/add-member';
  2. packages/plugins/plugin-auth/src/adopt-membership.ts:72 — doc contract: "POST /organization/add-member pre-checks and refuses first (… a 400)";
  3. by omission: packages/plugins/plugin-auth/src/auth-route-ledger.ts correctly does NOT list organization/add-member (only add-team-member) — the ledger and the action metadata contradict each other. The ledger even has a 'server-only' disposition arm that nothing uses for this endpoint.

Why this is a hard blocker (not a cosmetic 404)

On a multi-org posture there is no path left, in any UI, to attach an existing user to an organization:

  • POST /api/v1/auth/admin/create-userbindUserToSoleOrganizationreconcileMembership resolves the target org via tenancy.defaultOrgId(), which returns null when the posture enforces the org wall (by design — the framework must not guess an org). Outcome no-target-org, no sys_member row.
  • The freshly created user signs in with zero memberships and is greeted by the "create a workspace" bootstrap prompt (they then either mint a stray org or cancel into a dead-end shell).
  • The one advertised remediation — sys_member.object.ts's own comment: "The add_member toolbar action covers the admin 'attach an existing user directly without sending an invitation' flow" — 404s as above.
  • Generic CRUD on sys_member is suppressed (managedBy: "better-auth", ADR-0010 full lock), so an admin cannot hand-create the row either.
  • The invite flow requires an email round-trip, which phone-number-only users (created via the phoneNumber plugin path of create-user) cannot complete.

Expected

Either plugin-auth exposes an admin-guarded HTTP route that wraps auth.api.addMember (and the route ledger lists it), or the add_member action metadata is retargeted to whatever route is the sanctioned "attach existing user" entry — plus the stale adopt-membership.ts claim updated. As shipped, action metadata, route ledger, and better-auth reality are three different stories.

Versions

  • @objectstack/* 17.0.0 (GA); reproduced on main @ d7c424055 (2026-08-19) by inspection — same better-auth pin (1.7.0-rc.2), same action target, same ledger omission.
  • better-auth 1.7.0-rc.2.

What we did NOT do

No app-side workaround: no patched action metadata, no direct sys_member writes shipped. The app-side issue is parked as blocked on this one.

Part of steedos-labs/os-project-titanwind-ehr#1615

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions