# fail2ban filter for credential-stuffing attempts against the
# Familienarchiv authentication endpoints.
#
# Parses Caddy JSON access log entries (configured in
# infra/caddy/Caddyfile via the (access_log) snippet).
#
# Sample matched line (whitespace inserted for readability):
#   {"level":"info","ts":1700000000.12,"logger":"http.log.access",
#    "msg":"handled request",
#    "request":{"remote_ip":"203.0.113.42","method":"POST",
#               "host":"archiv.raddatz.cloud",
#               "uri":"/api/auth/login",…},
#    "status":401,…}
#
# Watched endpoints:
#   - /api/auth/login            — credential stuffing
#   - /api/auth/forgot-password  — email enumeration + slow brute-force
#                                  against accounts whose addresses leak
#
# Watched statuses:
#   - 401 — bad credentials
#   - 429 — server-side rate limit (in case a future in-app limiter
#           returns 429 before fail2ban catches the volume)
#
# Caddy emits remote_ip *inside* the request object and status at the
# top level. The order within the request object is stable
# (remote_ip → … → uri) across Caddy 2.7+. Lazy `.*?` keeps the regex
# robust to header-dict size growth.

[INCLUDES]
before = common.conf

[Definition]
failregex = ^\s*\{.*?"remote_ip":"<HOST>".*?"uri":"/api/auth/(login|forgot-password).*?"status":\s*4(01|29)\b

ignoreregex =

# Caddy's ts field is a Unix epoch with sub-second precision.
datepattern = "ts":{EPOCH}
