import "dotenv/config"; /** * Login throttling (rate-limit scope `login`, checked in verifyIdentityPassword): * (1) within the limit a wrong password returns null, the right one succeeds * (2) after exceeding the per-account limit even the correct password returns null * (3) other accounts are unaffected by one account's counter (per-account key) * (4) resetRateLimits restores access (in-memory counter) * Outside a request there is no IP → only the account counter applies (documented behaviour). */ import { prisma } from "../src/server/db"; import { verifyIdentityPassword } from "../src/server/auth"; import { RATE_LIMITS, resetRateLimits } from "../src/server/rate-limit"; import { hashPassword } from "../src/server/password"; let failures = 0; const ok = (cond: boolean, msg: string) => { console.log(`${cond ? "✓" : "✗ FEHLER"} ${msg}`); if (!cond) failures++; }; async function main() { const stamp = Date.now(); const pw = "Richtig-Passwort-2026!"; const emailA = `zz-rl-a-${stamp}@example.test`; const emailB = `zz-rl-b-${stamp}@example.test`; const hash = await hashPassword(pw); const [a, b] = await Promise.all([ prisma.identity.create({ data: { email: emailA, passwordHash: hash } }), prisma.identity.create({ data: { email: emailB, passwordHash: hash } }), ]); resetRateLimits(); try { // (1) wrong then right, far below the limit (lockout after 5 failures is a separate mechanism) ok((await verifyIdentityPassword(emailA, "falsch")) === null, "(1) falsches Passwort → null"); await prisma.identity.update({ where: { id: a.id }, data: { failedLogins: 0, lockedUntil: null } }); ok((await verifyIdentityPassword(emailA, pw))?.identityId === a.id, "(1) richtiges Passwort → Erfolg"); // (2) exhaust the per-account limit with successful attempts (no lockout involved) const limit = RATE_LIMITS.login.limit; for (let i = 0; i < limit; i++) await verifyIdentityPassword(emailA, pw); ok((await verifyIdentityPassword(emailA, pw)) === null, `(2) nach ${limit}+ Versuchen: auch richtiges Passwort → null (gedrosselt)`); // (3) other account unaffected ok((await verifyIdentityPassword(emailB, pw))?.identityId === b.id, "(3) anderes Konto nicht betroffen"); // (4) reset resetRateLimits(); ok((await verifyIdentityPassword(emailA, pw))?.identityId === a.id, "(4) nach Reset wieder möglich"); } finally { await prisma.identity.deleteMany({ where: { id: { in: [a.id, b.id] } } }); await prisma.$disconnect(); } console.log(failures ? `\n${failures} Fehler` : "\nOK — Login-Drosselung je Konto wirksam."); process.exit(failures ? 1 : 0); } main().catch((err) => { console.error(err); process.exit(1); });