fix(api-keys): gültige Schlüssel nie durch die Fehlversuchs-Sperre abweisen
Befunde aus dem Sicherheitsreview des Branches: - MEDIUM: Die Sperre lief vor der Schlüsselsuche und zählte auch falsch aufgebaute Tokens. Wer die gemeinsame Adresse (Cluster-Ausgang, Proxy) nutzt oder per X-Forwarded-For vortäuscht, hätte ERPNext und den Shop 15 Minuten aussperren können. Jetzt: erst Suche über den Hash; ein gültiger, nicht widerrufener, nicht abgelaufener Schlüssel kommt immer durch; 429 nur für unbekannte, widerrufene und abgelaufene Schlüssel; falsch aufgebaute Tokens sofort 401 ohne Zählung. Beim Start warnt ApiCore, wenn TRUST_PROXY in Produktion fehlt (im Cluster gesetzt). - LOW: Bei vollem Speicher verdrängt der Limiter zuerst nicht gesperrte Einträge, gesperrte erst zuletzt. - LOW: Bewegungs-Cursor mit Jahr außerhalb 2000-2100 ergab 500, jetzt 400. - LOW/INFO: Vertrag legt fest, dass ERPNext Produkte und Chargen bei jedem Lauf vollständig neu liest (Voll-Spiegel) und Bewegungen inkrementell; remainingQuantity ist live, Bewegungen kommen 5 Minuten verzögert. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
e28cc8e0f3
commit
fe7b61c3c1
11 files changed
+300
-81
No files matched your search
@@ -577,6 +577,22 @@ describe('ERP-Inventar-Export (e2e)', () => {
|
||||
await get(`bewegungen?nach=${productCursor}`, erpKey).expect(400);
|
||||
});
|
||||
|
||||
it('lehnt einen Bewegungs-Cursor mit unmöglichem Jahr ab (400 statt 500)', async () => {
|
||||
const cursor = encodeCursor({
|
||||
k: 'bewegungen',
|
||||
ts: '+275760-09-13T00:00:00.000Z',
|
||||
id: movementIds.hofladen,
|
||||
});
|
||||
await get(`bewegungen?nach=${cursor}`, erpKey).expect(400);
|
||||
});
|
||||
|
||||
it('verträgt ein fernes seit (leere Seite statt Fehler)', async () => {
|
||||
const body = (
|
||||
await get('bewegungen?seit=9999-12-31T00:00:00Z', erpKey).expect(200)
|
||||
).body as PageBody<Item>;
|
||||
expect(body.items).toEqual([]);
|
||||
});
|
||||
|
||||
it('lehnt seit und nach zusammen ab (400)', async () => {
|
||||
const cursor = encodeCursor({
|
||||
k: 'bewegungen',
|
||||
@@ -645,7 +661,7 @@ describe('ERP-Inventar-Export (e2e)', () => {
|
||||
});
|
||||
|
||||
describe('Fehlversuche', () => {
|
||||
it('sperrt eine Adresse nach zu vielen falschen Schlüsseln mit 429, andere nicht', async () => {
|
||||
it('sperrt nach zu vielen unbekannten Schlüsseln weitere ungültige mit 429, andere Adressen nicht', async () => {
|
||||
const attacker = '203.0.113.50';
|
||||
const wrong = `ack_abcd1234_${'Y'.repeat(43)}`;
|
||||
for (let i = 0; i < API_KEY_FAILURE_LIMIT.maxFailures; i++) {
|
||||
@@ -656,9 +672,32 @@ describe('ERP-Inventar-Export (e2e)', () => {
|
||||
statusCode: 429,
|
||||
message: expect.stringContaining('Fehlversuche') as string,
|
||||
});
|
||||
// Auch ein richtiger Schlüssel verrät von dort nichts mehr.
|
||||
await get('produkte', erpKey, attacker).expect(429);
|
||||
await get('produkte', erpKey, '203.0.113.51').expect(200);
|
||||
await get('produkte', wrong, '203.0.113.51').expect(401);
|
||||
});
|
||||
|
||||
it('lässt einen gültigen Schlüssel auch von einer gesperrten Adresse durch', async () => {
|
||||
// Sonst könnte jeder, der die gemeinsame Adresse (Cluster-Ausgang,
|
||||
// Proxy) nutzt oder fälscht, ERPNext oder den Shop aussperren.
|
||||
const shared = '203.0.113.60';
|
||||
const wrong = `ack_abcd1234_${'X'.repeat(43)}`;
|
||||
for (let i = 0; i < API_KEY_FAILURE_LIMIT.maxFailures; i++) {
|
||||
await get('produkte', wrong, shared).expect(401);
|
||||
}
|
||||
await get('produkte', wrong, shared).expect(429);
|
||||
await get('produkte', erpKey, shared).expect(200);
|
||||
await request(server())
|
||||
.get('/api/v1/shop-sync/stock')
|
||||
.set('Authorization', `Bearer ${shopKey}`)
|
||||
.set('X-Forwarded-For', shared)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('zählt fehlerhaft aufgebaute Schlüssel nicht', async () => {
|
||||
const ip = '203.0.113.70';
|
||||
for (let i = 0; i < API_KEY_FAILURE_LIMIT.maxFailures + 5; i++) {
|
||||
await get('produkte', 'ack_x', ip).expect(401);
|
||||
}
|
||||
await get('produkte', `ack_abcd1234_${'W'.repeat(43)}`, ip).expect(401);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user