fix(nextcloud-files): WR-02/IN-04 Widerrufe auch nach Sperre, Netzfehler und abgebrochener Browser-Anmeldung

- voruebergehend gescheiterte Widerrufe (Aufrufsperre nach 429, Netz, Zeitablauf, Wartung,
  5xx) kommen in eine kleine Warteschlange im Arbeitsspeicher und werden nach dem Ende
  der Sperre bzw. nach einer Minute erneut versucht (hoechstens 200 Eintraege, 6 Versuche,
  nie geloggt)
- abgebrochene, ersetzte oder durch Adresswechsel verworfene Browser-Anmeldungen bleiben
  bis zum Ablauf als "abgebrochen" stehen; der Server fragt sie alle 10 s ab und widerruft
  ein doch noch ausgestelltes App-Passwort sofort; sie zaehlen nicht gegen die 200 offenen
- Specs fuer Sperre, Netzfehler, Aufgeben und den Login-Flow-Abbruch; Betriebshandbuch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-08 22:36:32 +02:00
parent c0b8283cf4
commit ddae9400a3
5 changed files with 453 additions and 20 deletions
@@ -168,6 +168,27 @@ describe('LoginFlowStore', () => {
expect(store.get(b.flowId, 't1', 'u1')?.pollToken).toBe('tok-b');
});
it('abgebrochene Ablaeufe sind fuer den Benutzer weg, werden aber bis zum Ablauf beobachtet (IN-04)', () => {
const { store, clock } = makeStore();
const a = store.create('t1', 'u1', 'http://c.example', 'tok-a');
store.cancel(a.flowId);
expect(store.lookup(a.flowId, 't1', 'u1')).toEqual({ state: 'missing' });
expect(store.hasCancelled()).toBe(true);
expect(store.cancelledDue(10_000).map((e) => e.pollToken)).toEqual(['tok-a']);
clock.t += FLOW_TTL_MS;
expect(store.cancelledDue(10_000)).toEqual([]);
expect(store.hasCancelled()).toBe(false);
});
it('abgebrochene Ablaeufe zaehlen nicht gegen die 200 offenen', () => {
const { store } = makeStore();
for (let i = 0; i < FLOW_MAX_TOTAL; i++) {
const e = store.create('t1', `u${i}`, 'http://c.example', `tok-${i}`);
store.cancel(e.flowId);
}
expect(() => store.create('t1', 'neu', 'http://c.example', 'tok')).not.toThrow();
});
it('fremder Benutzer oder Mandant: nichts (wie unbekannt)', () => {
const { store } = makeStore();
const a = store.create('t1', 'u1', 'http://c.example', 'tok');