Admin kann Nutzer inaktiv schalten (kein Login mehr, Daten bleiben vollstaendig erhalten) und wieder aktivieren #221

Closed
opened 2026-09-29 08:53:40 +02:00 by lena · 3 comments
Collaborator

Story: Nutzer voruebergehend deaktivieren statt loeschen

As a Admin,
I want to einen Nutzer in der Nutzeruebersicht auf "inaktiv" schalten und spaeter wieder auf "aktiv" zuruecksetzen koennen,
so that ich einem Nutzer den Zugang sperren kann, als waere sein Konto geloescht - ohne dass dabei irgendwelche Daten verloren gehen, falls er spaeter wieder Zugang bekommen soll.

Wortlaut des Nutzers: "Ich moechte als Admin in der Lage sein, einen User als "inaktiv" zu schalten, d.h. der User kann sich nicht mehr einloggen / es ist, als waere sein account geloescht. Man kann ihn aber wieder "aktiv" schalten und seine Daten sind vollstaendig erhalten."

Hintergrund: Die Admin-Nutzeruebersicht mit endgueltiger Loeschung gibt es seit #206, das Zuruecksetzen des Passworts durch den Admin seit #210. Fuer das sofortige Abmelden auf allen Geraeten gibt es bereits einen Mechanismus (SessionsRevokedBeforeUtc, wird von #210 genutzt) - der soll hier wiederverwendet werden.

Acceptance criteria:

  • In der Admin-Nutzeruebersicht hat jeder Nutzer eine Aktion "Inaktiv schalten" bzw. - wenn er bereits inaktiv ist - "Wieder aktivieren". Der aktuelle Status ist in der Uebersicht sichtbar (z. B. Badge "inaktiv").
  • Inaktiv schalten fragt vorher nach einer Bestaetigung.
  • Ein inaktiver Nutzer kann sich nicht einloggen - weder mit Passwort noch ueber eine gespeicherte Anmeldung ("angemeldet bleiben"). Bestehende Sitzungen auf allen Geraeten enden sofort beim Inaktiv-Schalten.
  • Ein inaktiver Nutzer kann auch "Passwort vergessen" nicht nutzen, um sich wieder Zugang zu verschaffen.
  • Beim fehlgeschlagenen Login verhaelt sich die App fuer einen inaktiven Nutzer so, als gaebe es das Konto nicht (gleiche Meldung wie bei unbekanntem Konto/falschem Passwort) - kein Hinweis, dass das Konto existiert.
  • Ein inaktiver Nutzer bekommt keine E-Mails und keine Push-Benachrichtigungen mehr.
  • Beim Inaktiv-Schalten wird nichts geloescht oder veraendert: eigene Listen, Mitgliedschaften, Eintraege, Kommentare, Zuweisungen, Einstellungen und Avatar bleiben vollstaendig erhalten.
  • Nach "Wieder aktivieren" kann sich der Nutzer mit seinem bisherigen Passwort wieder einloggen und findet alles so vor wie vorher.
  • Ein Admin kann weder sich selbst noch ein anderes Admin-Konto inaktiv schalten (gleiche Schutzregel wie beim Loeschen in #206).
  • Das Inaktiv-/Aktiv-Schalten ist serverseitig nur fuer Admins erlaubt.

Out of scope for this story:

  • Automatisches Inaktiv-Schalten (z. B. nach laengerer Nichtnutzung).
  • Ein Grund-/Notizfeld zum Inaktiv-Schalten.

Open questions: (escalate to human if unanswered)

  • Was sehen andere Nutzer, die mit dem inaktiven Nutzer Listen teilen? Vorschlag PO: Geteilte Listen, die dem inaktiven Nutzer gehoeren, bleiben fuer die anderen Mitglieder normal nutzbar; der inaktive Nutzer bleibt in Mitgliederlisten/Zuweisungen sichtbar (evtl. mit Hinweis "inaktiv"), damit beim Reaktivieren nichts neu verknuepft werden muss. Er kann nicht neu eingeladen oder neu zugewiesen werden, solange er inaktiv ist.

Als Backlog-Item vom Nutzer eingereicht (per Chat, nicht im laufenden Loop-Zyklus geclaimt).

## Story: Nutzer voruebergehend deaktivieren statt loeschen **As a** Admin, **I want to** einen Nutzer in der Nutzeruebersicht auf "inaktiv" schalten und spaeter wieder auf "aktiv" zuruecksetzen koennen, **so that** ich einem Nutzer den Zugang sperren kann, als waere sein Konto geloescht - ohne dass dabei irgendwelche Daten verloren gehen, falls er spaeter wieder Zugang bekommen soll. Wortlaut des Nutzers: "Ich moechte als Admin in der Lage sein, einen User als "inaktiv" zu schalten, d.h. der User kann sich nicht mehr einloggen / es ist, als waere sein account geloescht. Man kann ihn aber wieder "aktiv" schalten und seine Daten sind vollstaendig erhalten." Hintergrund: Die Admin-Nutzeruebersicht mit endgueltiger Loeschung gibt es seit #206, das Zuruecksetzen des Passworts durch den Admin seit #210. Fuer das sofortige Abmelden auf allen Geraeten gibt es bereits einen Mechanismus (`SessionsRevokedBeforeUtc`, wird von #210 genutzt) - der soll hier wiederverwendet werden. **Acceptance criteria:** - [ ] In der Admin-Nutzeruebersicht hat jeder Nutzer eine Aktion "Inaktiv schalten" bzw. - wenn er bereits inaktiv ist - "Wieder aktivieren". Der aktuelle Status ist in der Uebersicht sichtbar (z. B. Badge "inaktiv"). - [ ] Inaktiv schalten fragt vorher nach einer Bestaetigung. - [ ] Ein inaktiver Nutzer kann sich nicht einloggen - weder mit Passwort noch ueber eine gespeicherte Anmeldung ("angemeldet bleiben"). Bestehende Sitzungen auf allen Geraeten enden sofort beim Inaktiv-Schalten. - [ ] Ein inaktiver Nutzer kann auch "Passwort vergessen" nicht nutzen, um sich wieder Zugang zu verschaffen. - [ ] Beim fehlgeschlagenen Login verhaelt sich die App fuer einen inaktiven Nutzer so, als gaebe es das Konto nicht (gleiche Meldung wie bei unbekanntem Konto/falschem Passwort) - kein Hinweis, dass das Konto existiert. - [ ] Ein inaktiver Nutzer bekommt keine E-Mails und keine Push-Benachrichtigungen mehr. - [ ] Beim Inaktiv-Schalten wird **nichts** geloescht oder veraendert: eigene Listen, Mitgliedschaften, Eintraege, Kommentare, Zuweisungen, Einstellungen und Avatar bleiben vollstaendig erhalten. - [ ] Nach "Wieder aktivieren" kann sich der Nutzer mit seinem bisherigen Passwort wieder einloggen und findet alles so vor wie vorher. - [ ] Ein Admin kann weder sich selbst noch ein anderes Admin-Konto inaktiv schalten (gleiche Schutzregel wie beim Loeschen in #206). - [ ] Das Inaktiv-/Aktiv-Schalten ist serverseitig nur fuer Admins erlaubt. **Out of scope for this story:** - Automatisches Inaktiv-Schalten (z. B. nach laengerer Nichtnutzung). - Ein Grund-/Notizfeld zum Inaktiv-Schalten. **Open questions:** (escalate to human if unanswered) - Was sehen **andere** Nutzer, die mit dem inaktiven Nutzer Listen teilen? Vorschlag PO: Geteilte Listen, die dem inaktiven Nutzer gehoeren, bleiben fuer die anderen Mitglieder normal nutzbar; der inaktive Nutzer bleibt in Mitgliederlisten/Zuweisungen sichtbar (evtl. mit Hinweis "inaktiv"), damit beim Reaktivieren nichts neu verknuepft werden muss. Er kann nicht neu eingeladen oder neu zugewiesen werden, solange er inaktiv ist. --- Als Backlog-Item vom Nutzer eingereicht (per Chat, nicht im laufenden Loop-Zyklus geclaimt).
lena self-assigned this 2026-09-29 09:56:50 +02:00
Author
Collaborator

Claimed by the autonomous loop (go-cycle 2026-09-29). Design first; the open question about what other members see, plus a few further scope decisions, will be asked to the human up front before implementation starts.

Claimed by the autonomous loop (go-cycle 2026-09-29). Design first; the open question about what other members see, plus a few further scope decisions, will be asked to the human up front before implementation starts.
Author
Collaborator

Decisions by the human (2026-09-29, asked up front):

  • Other members: the inactive user stays visible in member lists and on existing assignments, with an "inaktiv" hint next to the name; lists owned by them stay fully usable for the others.
  • Assignment: inactive users are hidden from the assignment picker and the server rejects new assignments to them; existing assignments stay.
  • API keys: blocked while inactive, kept stored, work again after reactivation.
  • Emails: no mail on deactivation; one short mail on reactivation ("you can log in again"). Otherwise no mails/pushes while inactive.
Decisions by the human (2026-09-29, asked up front): - Other members: the inactive user stays visible in member lists and on existing assignments, with an "inaktiv" hint next to the name; lists owned by them stay fully usable for the others. - Assignment: inactive users are hidden from the assignment picker and the server rejects new assignments to them; existing assignments stay. - API keys: blocked while inactive, kept stored, work again after reactivation. - Emails: no mail on deactivation; one short mail on reactivation ("you can log in again"). Otherwise no mails/pushes while inactive.
Author
Collaborator

Done, pushed to master (f5ec8abb..e61bd1db).

Scope

  • New per-user DeactivatedAtUtc (migration AddDeactivatedAtToUser); nothing else about the user is touched, so reactivation restores everything.
  • Admin overview: action "Inaktiv schalten" (confirmation dialog, states that nothing is deleted) / "Wieder aktivieren" (one click), badge "Inaktiv" with "since" tooltip, filter "Inaktiv geschaltet". The older login-recency labels were renamed to "Kuerzlich aktiv" / "Laenger nicht da" so they don't clash with the new lock.
  • Access: login refused with the unknown-account message (checked before the password); all sessions end immediately (SessionsRevokedBeforeUtc + session check returns revoked for deactivated users); API keys stop working but stay stored; forgot-password behaves like an unknown address, open reset links are voided and the reset endpoint refuses deactivated users.
  • No emails (digest, instant, admin password-reset notice) and no pushes while inactive; one mail on reactivation (human decision).
  • Other members: deactivated user stays in member lists with an "inaktiv" hint and on existing assignments (greyed badge, "(inaktiv)" tooltip); not offered in the todo/shopping assignment pickers or ownership transfer, server rejects new assignments/transfers; recurring-todo rotation skips them; a non-rotating recurring assignment is carried forward unchanged.
  • Admin accounts can't be deactivated; endpoint admin-gated server-side.

Tests: backend 1087 + 63 + 119 green (new: SetUserActiveAsAdminCommandHandlerTests incl. data-preservation round trip, plus login/session/API key/forgot/reset/email/push/assignment/transfer/rotation/member/overview cases); frontend 149 files / 1495 tests green (admin deactivate/reactivate/cancel/filter, pickers, members hint, assignee badge, transfer dialog).

Known gap (pre-existing, shared with #59/#210): an already-open live-update WebSocket isn't closed by session revocation; a passive open tab keeps receiving list updates until its next request/reconnect. Documented in docs/SECURITY_NOTES.md, suggested as follow-up.
Deliberate: deleting a deactivated account still sends the GDPR deletion confirmation mail.

Done, pushed to master (f5ec8abb..e61bd1db). **Scope** - New per-user `DeactivatedAtUtc` (migration `AddDeactivatedAtToUser`); nothing else about the user is touched, so reactivation restores everything. - Admin overview: action "Inaktiv schalten" (confirmation dialog, states that nothing is deleted) / "Wieder aktivieren" (one click), badge "Inaktiv" with "since" tooltip, filter "Inaktiv geschaltet". The older login-recency labels were renamed to "Kuerzlich aktiv" / "Laenger nicht da" so they don't clash with the new lock. - Access: login refused with the unknown-account message (checked before the password); all sessions end immediately (SessionsRevokedBeforeUtc + session check returns revoked for deactivated users); API keys stop working but stay stored; forgot-password behaves like an unknown address, open reset links are voided and the reset endpoint refuses deactivated users. - No emails (digest, instant, admin password-reset notice) and no pushes while inactive; one mail on reactivation (human decision). - Other members: deactivated user stays in member lists with an "inaktiv" hint and on existing assignments (greyed badge, "(inaktiv)" tooltip); not offered in the todo/shopping assignment pickers or ownership transfer, server rejects new assignments/transfers; recurring-todo rotation skips them; a non-rotating recurring assignment is carried forward unchanged. - Admin accounts can't be deactivated; endpoint admin-gated server-side. **Tests**: backend 1087 + 63 + 119 green (new: SetUserActiveAsAdminCommandHandlerTests incl. data-preservation round trip, plus login/session/API key/forgot/reset/email/push/assignment/transfer/rotation/member/overview cases); frontend 149 files / 1495 tests green (admin deactivate/reactivate/cancel/filter, pickers, members hint, assignee badge, transfer dialog). **Known gap (pre-existing, shared with #59/#210)**: an already-open live-update WebSocket isn't closed by session revocation; a passive open tab keeps receiving list updates until its next request/reconnect. Documented in docs/SECURITY_NOTES.md, suggested as follow-up. **Deliberate**: deleting a deactivated account still sends the GDPR deletion confirmation mail.
lena 2026-09-29 11:07:39 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#221
No description provided.