Shop Review A I4: Shop-Limit 300 je Stunde, hoechstens 3 offene Bestellungen je IP und Shop in 30 Minuten
Die IP wird nur als gesalzener Kurz-Hash (sha256, 16 Hex-Zeichen) im Bestell-JSON abgelegt; der Tagesjob entfernt ihn bei der Anonymisierung mit. Die Pruefung laeuft unter der Bestandssperre, Honigtopf und Fehlversuche legen nichts an und zaehlen nicht. Antwort 429 zu_viele_offen mit Retry-After 1800. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
55fe1c567f
commit
cb6291410d
5 files changed
+71
-11
No files matched your search
@@ -331,7 +331,7 @@ test('bestellung: fremde Origin 403 und unbekannter Shop aktiv:false; kein Rumpf
|
||||
assert.equal(orders().length, 0)
|
||||
})
|
||||
|
||||
test('bestellung: Grenze 10 je IP und 10 Minuten (Retry-After 600), Grenze 60 je Shop und Stunde (3600)', async () => {
|
||||
test('bestellung: Grenze 10 je IP und 10 Minuten (Retry-After 600), Grenze 300 je Shop und Stunde (3600) für alle Anfragen', async () => {
|
||||
const eigene = `10.8.8.${(n % 250) + 1}`
|
||||
for (let i = 0; i < 10; i += 1) assert.equal((await bestellen({ kontakt_url: 'x' }, { ip: eigene })).status, 200, `Versuch ${i + 1}`)
|
||||
const zuViele = await bestellen({ kontakt_url: 'x' }, { ip: eigene })
|
||||
@@ -340,16 +340,56 @@ test('bestellung: Grenze 10 je IP und 10 Minuten (Retry-After 600), Grenze 60 je
|
||||
|
||||
resetTermineLimitsForTests()
|
||||
let ok = 0
|
||||
for (let i = 0; i < 60; i += 1) {
|
||||
for (let i = 0; i < 300; i += 1) {
|
||||
const res = await bestellen({ kontakt_url: 'x' }, { ip: `10.7.${Math.floor(i / 200)}.${(i % 200) + 1}` })
|
||||
if (res.status === 200) ok += 1
|
||||
}
|
||||
assert.equal(ok, 60)
|
||||
assert.equal(ok, 300)
|
||||
const shopGrenze = await bestellen({ kontakt_url: 'x' }, { ip: '10.7.9.9' })
|
||||
assert.equal(shopGrenze.status, 429)
|
||||
assert.equal(kopf(shopGrenze, 'retry-after'), '3600')
|
||||
})
|
||||
|
||||
test('bestellung I4: höchstens 3 offene Bestellungen je IP und Shop in 30 Minuten; andere IPs, abgeschlossene und alte Bestellungen zählen nicht', async () => {
|
||||
await aufbau({ produkte: [artikel('a1', {})] })
|
||||
const eigene = `10.9.9.${(n % 250) + 1}`
|
||||
for (let i = 0; i < 3; i += 1) assert.equal((await bestellen({}, { ip: eigene })).status, 200, `Bestellung ${i + 1}`)
|
||||
assert.equal(orders().length, 3)
|
||||
const vierte = await bestellen({}, { ip: eigene })
|
||||
assert.equal(vierte.status, 429)
|
||||
assert.equal(vierte.body.code, 'zu_viele_offen')
|
||||
assert.equal(vierte.body.fehler._, 'Sie haben bereits offene Bestellungen. Bitte schließen Sie diese zuerst ab oder warten Sie 30 Minuten.')
|
||||
assert.equal(kopf(vierte, 'retry-after'), '1800')
|
||||
assert.equal(orders().length, 3, 'keine vierte Bestellung')
|
||||
assert.equal(sessionCalls().length, 3, 'keine vierte Stripe-Sitzung')
|
||||
|
||||
// Der Hash steht in der Bestellung, die Adresse nicht
|
||||
const json = orders()[0].bestellung
|
||||
assert.match(JSON.parse(json).ipHash, /^[0-9a-f]{16}$/)
|
||||
assert.ok(!JSON.stringify(orders()).includes(eigene), 'die IP-Adresse selbst wird nirgends gespeichert')
|
||||
assert.equal(new Set(orders().map((o) => JSON.parse(o.bestellung).ipHash)).size, 1)
|
||||
|
||||
// Eine andere IP ist nicht betroffen
|
||||
assert.equal((await bestellen({}, { ip: '10.9.8.1' })).status, 200)
|
||||
// Eine storniert/abgeschlossene Bestellung zählt nicht mehr
|
||||
const erste = orders()[0]
|
||||
aw.seed('shopOrders', { ...erste, status: 'storniert' })
|
||||
assert.equal((await bestellen({}, { ip: eigene })).status, 200)
|
||||
assert.equal((await bestellen({}, { ip: eigene })).status, 429)
|
||||
// Nach 30 Minuten sind die alten Bestellungen aus dem Fenster
|
||||
setzeUhrFuerTests(() => new Date(JETZT.getTime() + 31 * 60 * 1000))
|
||||
const spaeter = await bestellen({ formToken: formToken(SITE_KEY, new Date(JETZT.getTime() + 20 * 60 * 1000)) }, { ip: eigene })
|
||||
assert.equal(spaeter.status, 200)
|
||||
})
|
||||
|
||||
test('bestellung I4: Honigtopf-Anfragen und Fehlversuche legen nichts an und zählen nicht als offene Bestellung', async () => {
|
||||
const eigene = `10.9.7.${(n % 250) + 1}`
|
||||
for (let i = 0; i < 5; i += 1) await bestellen({ kontakt_url: 'x' }, { ip: eigene })
|
||||
await bestellen({ positionen: [] }, { ip: eigene })
|
||||
assert.equal(orders().length, 0)
|
||||
assert.equal((await bestellen({}, { ip: eigene })).status, 200)
|
||||
})
|
||||
|
||||
test('bestellung: Mindestantwortzeit gilt auch bei Fehlern', async () => {
|
||||
config.shop.minAntwortMs = 200
|
||||
const t0 = performance.now()
|
||||
|
||||
@@ -109,7 +109,7 @@ test('anonymisierGrenze: 1. Januar Berlin, drei Jahre nach Ende des Bestelljahre
|
||||
|
||||
test('Bestellung aus 2026 wird genau am 1. Januar 2030 (Berlin) anonymisiert, nicht eine Sekunde vorher', async () => {
|
||||
shop('s_a')
|
||||
const o = bestellung('s_a', { erstelltAt: '2026-06-01T10:00:00.000Z' })
|
||||
const o = bestellung('s_a', { erstelltAt: '2026-06-01T10:00:00.000Z' }, { ipHash: 'abcdef0123456789' })
|
||||
const vorher = await taeglicherJob(new Date('2029-12-31T22:59:59.000Z')) // 23:59:59 Berlin
|
||||
assert.equal(vorher.bestellungenAnonymisiert, 0)
|
||||
assert.equal(order(o).anonymisiertAt, undefined)
|
||||
@@ -122,6 +122,7 @@ test('Bestellung aus 2026 wird genau am 1. Januar 2030 (Berlin) anonymisiert, ni
|
||||
assert.equal(doc.kaeuferEmail, '')
|
||||
assert.ok(doc.anonymisiertAt)
|
||||
const d = daten(o)
|
||||
assert.equal(d.ipHash, undefined, 'der IP-Hash wird mit anonymisiert')
|
||||
assert.deepEqual(d.kaeufer, { name: '', email: '', telefon: '' })
|
||||
assert.equal('lieferadresse' in d, false)
|
||||
assert.equal('rechnungsadresse' in d, false)
|
||||
|
||||
Reference in new issue
Block a user