fix: revoke org API access immediately on member suspend/leave, not just Matrix

findMembership() and GET /api/organizations/mine ignored members.status —
a suspended or left member kept full API access to the organization (member
list, teams, roles, audit log) and still saw it in the org switcher, even
though only their Matrix account was deactivated. Both now require
status = "active".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-09-11 18:46:34 +02:00
1 parent 956cb6147b
commit ffe2d450eb
3 files changed
+13 -3

No files matched your search

+9 -1
View File
@@ -5,10 +5,18 @@ import { members, organizationRoles } from "../db/schema.js";
const ADMIN_ROLES = ["ORG_ADMIN", "ORG_MANAGER"] as const;
export async function findMembership(userId: string, organizationId: string) {
// Only "active" grants access — "suspended"/"left" must lose API access immediately,
// not just get their Matrix account deactivated.
const [membership] = await db
.select({ id: members.id })
.from(members)
.where(and(eq(members.userId, userId), eq(members.organizationId, organizationId)))
.where(
and(
eq(members.userId, userId),
eq(members.organizationId, organizationId),
eq(members.status, "active"),
),
)
.limit(1);
return membership ?? null;
}
+2 -2
View File
@@ -1,6 +1,6 @@
import type { FastifyInstance } from "fastify";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { and, eq } from "drizzle-orm";
import { db } from "../db/index.js";
import { organizations, members, organizationRoles, users } from "../db/schema.js";
import { logAudit } from "../lib/audit.js";
@@ -21,7 +21,7 @@ export async function organizationRoutes(app: FastifyInstance) {
.select({ id: organizations.id, name: organizations.name, slug: organizations.slug })
.from(members)
.innerJoin(organizations, eq(members.organizationId, organizations.id))
.where(eq(members.userId, userId));
.where(and(eq(members.userId, userId), eq(members.status, "active")));
return { organizations: rows };
});
+2
View File
@@ -11,12 +11,14 @@ Mitglieder eines Vereins verwalten: Liste, Status-Aktionen (aktivieren/sperren/A
- `frontend/src/pages/MembersPage.tsx`
- Mitglied-Status-Aktionen + Deaktivierungs-Sync (commit b38a5a4)
- **Nachtrag 2026-09-11 (commit bc4d749):** `DELETE /api/members/:memberId` — Admin-only, hartes Löschen. Audit-Log-Eintrag wird vor dem Delete geschrieben (`audit_logs.targetId` ist unconstrained text, überlebt das Löschen). Matrix-Account wird per `deactivateUser(matrixUserId, erase=true)` gelöscht. Abhängige Daten (`parent_links`, `team_members`, `org_roles`) laufen über bestehende `onDelete: cascade`-FKs mit. Frontend: Bestätigungsdialog (`AlertDialog`) im Dropdown-Menü der Mitgliederliste.
- **Nachtrag 2026-09-11 (Bugfix, ungetaggter Commit nach bc4d749):** `backend/src/lib/authz.ts` (`findMembership`) und `GET /api/organizations/mine` prüften `members.status` bisher **nicht** — ein Mitglied mit Status `suspended`/`left` behielt vollen API-Zugriff auf die Organisation (Mitgliederliste, Teams, Rollen, Audit-Log) und sah sie weiter im Organisation-Switcher, obwohl nur der Matrix-Account deaktiviert wurde. Beide Stellen filtern jetzt zusätzlich auf `status = "active"`.
## Acceptance Criteria
- [x] Mitgliederliste pro Verein abrufbar (paginiert)
- [x] Status ändern (aktiv/gesperrt/ausgetreten) durch berechtigte Rollen (`memberStatusEnum`: `active`/`suspended`/`left`)
- [x] Austritt kann eigenständig vermerkt werden ("Austritt vermerken" im Dropdown, Status → `left`), getrennt von Sperren
- [x] Sperren oder Austritt lösen Sync-Schritt Richtung Matrix aus (`deactivateUser`, ohne Erase — Account bleibt inaktiv wiederherstellbar)
- [x] Sperren oder Austritt entziehen sofort den TalkTime-API-Zugriff auf die Organisation (nicht nur den Matrix-Account) — durchgesetzt in `findMembership`/`requesterIsOrganizationMember`/`requesterIsOrganizationAdmin`, betrifft alle org-gescopten Routen
- [x] Mitglied kann durch Org-Admin endgültig gelöscht werden (mit Bestätigungsdialog im Frontend)
- [x] Löschen entfernt/erased den zugehörigen Matrix-Account und alle abhängigen Datensätze (Team-Zuordnungen, Rollen, Eltern-Verknüpfungen)
- [x] Audit-Log-Eintrag bleibt nach dem Löschen erhalten