From 716b8d03e0e11c70c98f225c264d6c2522d313cb Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Fri, 4 Sep 2026 09:53:55 +0200 Subject: [PATCH] test(e2e): a medewerker login must not reuse a spent TOTP counter (refs #132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two beheer specs log in as bram-beheerder back to back, so both submit the code for the same 30-second counter. Keycloak's otpPolicyCodeReusable defaults to false, so it refuses the second one as invalid credentials and the beheer portal never loads — which is how verify-e2e went red on #158. Pins the counter choice as a pure function of "now" and the last counter this medewerker spent, so the guard is checkable without a browser. Co-Authored-By: Claude Opus 5 (1M context) --- tests/e2e/medewerker-login.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/e2e/medewerker-login.spec.ts diff --git a/tests/e2e/medewerker-login.spec.ts b/tests/e2e/medewerker-login.spec.ts new file mode 100644 index 0000000..b4e83bb --- /dev/null +++ b/tests/e2e/medewerker-login.spec.ts @@ -0,0 +1,14 @@ +import { expect, test } from '@playwright/test'; +import { OTP_PERIOD_MS, nextUnusedCounter } from './medewerker-login'; + +// Pure check of the TOTP counter guard in loginMedewerker — no browser, no stack. Keycloak refuses +// a code it has already accepted (its otpPolicyCodeReusable defaults to false), so two logins as +// the same medewerker inside one 30-second window must not spend the same counter twice (#132). +test('a login never spends a TOTP counter this medewerker already used', () => { + const now = 3 * OTP_PERIOD_MS + 1_000; // 1 second into counter 3 + + expect(nextUnusedCounter(now, -1)).toBe(3); // nothing spent yet → the current counter + expect(nextUnusedCounter(now, 3)).toBe(4); // the current counter is spent → the next one + expect(nextUnusedCounter(now, 4)).toBe(5); // two logins already in this window → the one after + expect(nextUnusedCounter(now + OTP_PERIOD_MS, 3)).toBe(4); // window moved on → current again +});