fix(nextcloud-files): CR-01 Anmeldebremse reserviert Versuche vor dem Nextcloud-Aufruf
- beginPasswordAttempt zaehlt einen Passwortversuch synchron und vor jedem await in Benutzer- und Servergrenze; release gibt den Platz bei Erfolg oder Nicht-Fehlversuch frei - gleichzeitige Fehlversuche kommen nicht mehr an den Grenzen vorbei (3 je Benutzer, 8 je Server) - Verbinden und Speichern laufen je Benutzer nacheinander (auch Browser-Anmeldung), damit zwei gleichzeitige Erfolge kein ungespeichertes, nie widerrufenes App-Passwort hinterlassen - Specs mit gleichzeitigen Promise.all-Anmeldungen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,53 @@ describe('NextcloudLoginGuard — Passwort-Fehlversuche', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('NextcloudLoginGuard — Reservierung (CR-01)', () => {
|
||||
it('laufende Versuche zaehlen sofort: der 4. gleichzeitige Versuch von u1 ist 429', () => {
|
||||
const { guard } = makeGuard();
|
||||
const running = [0, 1, 2].map(() => guard.beginPasswordAttempt('u1'));
|
||||
expect(running).toHaveLength(3);
|
||||
const blocked = codeOf(() => guard.beginPasswordAttempt('u1'));
|
||||
expect(blocked.status).toBe(429);
|
||||
expect(blocked.body.code).toBe('tooManyAttempts');
|
||||
});
|
||||
|
||||
it('release gibt den Platz frei, fail behaelt ihn', () => {
|
||||
const { guard } = makeGuard();
|
||||
const a = guard.beginPasswordAttempt('u1');
|
||||
const b = guard.beginPasswordAttempt('u1');
|
||||
const c = guard.beginPasswordAttempt('u1');
|
||||
a.release();
|
||||
expect(codeOf(() => guard.checkPasswordAttempt('u1')).status).toBeUndefined();
|
||||
b.fail();
|
||||
c.fail();
|
||||
// fail nach release (und umgekehrt) aendert nichts mehr
|
||||
a.fail();
|
||||
b.release();
|
||||
const d = guard.beginPasswordAttempt('u1');
|
||||
expect(codeOf(() => guard.checkPasswordAttempt('u1')).status).toBe(429);
|
||||
d.release();
|
||||
expect(codeOf(() => guard.checkPasswordAttempt('u1')).status).toBeUndefined();
|
||||
});
|
||||
|
||||
it('die Servergrenze gilt auch fuer gleichzeitige Versuche verschiedener Benutzer', () => {
|
||||
const { guard } = makeGuard();
|
||||
for (let i = 0; i < 8; i++) guard.beginPasswordAttempt(`u${i}`, 'http://a.example');
|
||||
expect(codeOf(() => guard.beginPasswordAttempt('u9', 'http://a.example')).status).toBe(429);
|
||||
});
|
||||
|
||||
it('recordSuccess laesst laufende Versuche desselben Benutzers stehen', () => {
|
||||
const { guard } = makeGuard();
|
||||
guard.recordFailure('u1');
|
||||
guard.recordFailure('u1');
|
||||
guard.beginPasswordAttempt('u1');
|
||||
guard.recordSuccess('u1');
|
||||
// eine abgeschlossene Fehlanmeldung weg, der laufende Versuch zaehlt noch: 1 von 3
|
||||
guard.beginPasswordAttempt('u1');
|
||||
guard.beginPasswordAttempt('u1');
|
||||
expect(codeOf(() => guard.checkPasswordAttempt('u1')).status).toBe(429);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NextcloudLoginGuard — Start der Browser-Anmeldung', () => {
|
||||
it('der 11. Start eines Benutzers binnen 10 Minuten ist 429, andere Benutzer nicht', () => {
|
||||
const { guard, clock } = makeGuard();
|
||||
|
||||
Reference in New Issue
Block a user