import { createHmac } from 'node:crypto'; import type { Page } from '@playwright/test'; // The medewerker realm enforces MFA (S-15c), so a staff login is two steps: password, then a TOTP // code. The realm export seeds every medewerker with this fixture secret — Keycloak HMACs the raw // secret bytes — so the e2e can compute a valid code instead of enrolling an authenticator. const OTP_SECRET = 'BIGMEDEWERKEROTPSEED'; // RFC 6238 TOTP: HMAC-SHA1 over the 30-second counter, dynamically truncated to 6 digits. export function totp(secret = OTP_SECRET, at = Date.now()): string { const counter = Buffer.alloc(8); counter.writeBigUInt64BE(BigInt(Math.floor(at / 1000 / 30))); const mac = createHmac('sha1', secret).update(counter).digest(); const offset = mac[mac.length - 1] & 0x0f; return String((mac.readUInt32BE(offset) & 0x7fffffff) % 1_000_000).padStart(6, '0'); } export async function loginMedewerker(page: Page, username: string): Promise { await page.locator('#username').fill(username); await page.locator('#password').fill('test123'); await page.locator('#kc-login').click(); // Keycloak's conditional-OTP step. Its lookAheadWindow accepts the neighbouring counters, so a // code computed just before a 30-second boundary still validates — no retry needed. await page.locator('#otp').fill(totp()); await page.locator('#kc-login').click(); }