diff --git a/backend/src/lib/authz.ts b/backend/src/lib/authz.ts index 1f98178..3cdd121 100644 --- a/backend/src/lib/authz.ts +++ b/backend/src/lib/authz.ts @@ -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; } diff --git a/backend/src/routes/organizations.ts b/backend/src/routes/organizations.ts index 678f7cc..61cea27 100644 --- a/backend/src/routes/organizations.ts +++ b/backend/src/routes/organizations.ts @@ -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 }; }); diff --git a/features/PROJ-3-mitgliederverwaltung.md b/features/PROJ-3-mitgliederverwaltung.md index 40e4a06..be27978 100644 --- a/features/PROJ-3-mitgliederverwaltung.md +++ b/features/PROJ-3-mitgliederverwaltung.md @@ -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