Skip to content

revoke a session whose membership ended, so "Remove member" actually signs the person out — the courtesy half of #15409, ⛔ never the enforcement #15784

Description

@hotlong

The courtesy half of the ruling on #15409 (maintainer, 2026-09-05, verbatim 「同意」 adopting "B as the boundary, C later as the courtesy"). ⛔ This is never the enforcement. #15409 closes the hole per-request; this card only makes the product behave the way an admin expects. (The former Blocked-by: #15409 line was removed after #15409 closed completed via PR #15794.)

What an admin expects, and does not get

When someone clicks Remove member, they expect that person to be signed out. Today the membership row is deleted and the session lives on — up to 7 days by default (auth-manager.ts:1626, expiresIn default 60*60*24*7). After #15409 lands, the removed member can no longer read or write that organization's data; they are simply still signed in, with no active organization. That is safe, and it is still not what the admin was told happened.

Why the machinery is already there

sys_session.revoked_at / revoke_reason are declared and system-managed. Revoking expires the session in place (expires_at set to the past plus the reason stamped), so better-auth returns no session on the next request and the Console's existing 401 → login redirect handles it — no client change. engine.ts:8041 already writes revoked_at.

⚠️ What is new here, and why it is filed separately rather than folded into #15409: every existing reason is a timer — the field's own description enumerates idle_timeout, absolute_max, concurrent_cap, …. None is event-driven. "Membership ended" would be the first authorization-event trigger on this mechanism, and an event trigger covers exactly the paths someone remembered to wire: better-auth's /organization/remove-member, a direct sys_member delete, a seed replay, a bulk operation, an admin tool, a control-plane operation. A missed path fails open, silently.

⇒ That is precisely why it is not the wall. #15409's per-request check covers every removal path by construction. This card is layered on top of a hole that is already closed, so a missed trigger costs a stale login session — not access.

Scope

  1. Enumerate the removal paths by measurement, not by memory — every writer that can delete or invalidate a sys_member row, in this repo and in cloud's packages/organizations. Attach the census with its firing control.
  2. Revoke the affected user's sessions for that organization's claim when a membership ends, through the existing mechanism, with a new revoke_reason naming the cause.
  3. ⛔ Do not weaken, bypass or "optimise away" [decision · p0] a SESSION whose activeOrganizationId points at a left organization reads AND writes that organization — measured through better-auth's own remove-member endpoint #15409's per-request check on the strength of this trigger existing. If a future reader is tempted, the comment beside the check should say why: a trigger can be missed, an evaluation cannot.
  4. Decide and record what happens to a user who holds other memberships: revoking every session logs them out of organizations they are still legitimately in. Re-pointing activeOrganizationId instead may be kinder — that is a product call and belongs on this card before implementation.

Acceptance

An admin removes a member through better-auth's own endpoint while that member has a live session; their next request is unauthenticated and the Console redirects to login. Controls: a member of a different organization is unaffected; an intact member's session is untouched; and the #15409 pins still hold — ⛔ the per-request check must not have been removed in the process.

Refs: #15409 (the boundary, and the ruling) · #15256 (the API-key arm) · cloud#1982 / #15396 (the measurements) · sys-session.object.ts (the existing mechanism).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions