Shuffle/e2e/tests/throttle/krit07-drosselung.spec.ts
cschlaefke 2542679797 test(auth): add throttle and SMTP-down spike projects, start-throttle-gotrue.sh
Refs #38

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 10:30:15 +01:00

94 lines
4.4 KiB
TypeScript

/**
* KTO-01 Kriterium 7 (Drosselung), AUTH-CFG-10 und AUTH-CFG-13.
*
* Laeuft gegen den ZUSAETZLICHEN GoTrue-Container mit GOTRUE_RATE_LIMIT_HEADER=X-Forwarded-For
* (e2e/support/start-throttle-gotrue.sh, THROTTLE_GOTRUE_URL), nicht gegen die CLI: Die CLI
* drosselt nie (Spike A2, F11). Jeder Test nutzt eine eigene zufaellige IP (simuliert den Proxy,
* R13). Es wird direkt auf GoTrue zugegriffen, ohne Kong und ohne apikey.
*/
import { expect, test } from '@playwright/test';
import { authConfig, env, errorCodes } from '../../support/auth-config';
import { GoTrueClient, randomEmail, randomIp, randomPassword } from '../../support/gotrue';
const base = env.throttleGotrueUrl;
test.skip(!base, 'THROTTLE_GOTRUE_URL nicht gesetzt (siehe e2e/support/start-throttle-gotrue.sh)');
const client = (ip?: string) =>
new GoTrueClient({ baseUrl: base!, apikey: null, headers: ip ? { 'X-Forwarded-For': ip } : {} });
/** Konto, das GoTrue-Instanz und DB mit der CLI teilen; Anlage ohne Header ueber die CLI (Service-Role). */
async function createUser() {
const email = randomEmail();
const password = randomPassword();
const res = await new GoTrueClient().createConfirmedUser(email, password);
expect(res.status).toBe(200);
return { email, password };
}
test.describe('KTO-01 Kriterium 7: Drosselung der Anmeldung (throttle)', () => {
test('KTO-01 Kriterium 7: AUTH-CFG-10 30 Fehllogins einer IP -> invalid_credentials, Login #31 -> 429 over_request_rate_limit', async () => {
const { email, password } = await createUser();
const ip = randomIp();
const gotrue = client(ip);
for (let attempt = 1; attempt <= authConfig.loginBurst; attempt++) {
const res = await gotrue.signIn(email, `${password}-falsch`);
expect(res.status, `Versuch ${attempt}`).toBe(400);
expect(res.body.error_code, `Versuch ${attempt}`).toBe(errorCodes.invalidCredentials);
}
// Login #31 -> 429. Der Limiter fuellt 0,5 Token/s nach; auf langsamen Rechnern darf die
// erste 429 deshalb bis zu zwei Versuche spaeter kommen (Burst bleibt 30, AUTH-CFG-10).
let limited = await gotrue.signIn(email, `${password}-falsch`);
for (let extra = 0; extra < 2 && limited.status !== 429; extra++) {
limited = await gotrue.signIn(email, `${password}-falsch`);
}
expect(limited.status).toBe(429);
expect(limited.body.error_code).toBe(errorCodes.overRequestRateLimit);
// Auch der richtige Login dieser IP wird gedrosselt (Drosselung je IP, nicht je Passwort).
expect((await gotrue.signIn(email, password)).status).toBe(429);
});
test('KTO-01 Kriterium 7: AUTH-CFG-10 die 429-Antwort traegt weder Retry-After noch X-RateLimit-* (App nennt keine Dauer)', async () => {
const { email, password } = await createUser();
const gotrue = client(randomIp());
let limited;
for (let attempt = 1; attempt <= authConfig.loginBurst + 5; attempt++) {
const res = await gotrue.signIn(email, `${password}-falsch`);
if (res.status === 429) {
limited = res;
break;
}
}
expect(limited, 'es gab eine 429-Antwort').toBeTruthy();
expect(limited!.headers.get('retry-after')).toBeNull();
const rateHeaders = [...limited!.headers.keys()].filter((h) => h.toLowerCase().startsWith('x-ratelimit'));
expect(rateHeaders).toEqual([]);
});
test('KTO-01 Kriterium 7: AUTH-CFG-10 eine zweite IP ist von der Drosselung der ersten nicht betroffen', async () => {
const { email, password } = await createUser();
const first = client(randomIp());
for (let attempt = 1; attempt <= authConfig.loginBurst; attempt++) {
await first.signIn(email, `${password}-falsch`);
}
expect((await first.signIn(email, `${password}-falsch`)).status).toBe(429);
const second = client(randomIp());
const wrong = await second.signIn(email, `${password}-falsch`);
expect(wrong.status).toBe(400);
expect(wrong.body.error_code).toBe(errorCodes.invalidCredentials);
const right = await second.signIn(email, password);
expect(right.status).toBe(200);
expect(right.body.access_token).toBeTruthy();
});
test('KTO-01 Kriterium 7: AUTH-CFG-13 Requests ohne X-Forwarded-For werden nicht gedrosselt', async () => {
const { email, password } = await createUser();
const gotrue = client();
for (let attempt = 1; attempt <= authConfig.loginBurst + 5; attempt++) {
const res = await gotrue.signIn(email, `${password}-falsch`);
expect(res.status, `Versuch ${attempt}`).toBe(400);
}
});
});