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:
1 parent
956cb6147b
commit
ffe2d450eb
3 files changed
+13
-3
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user