LoginRateLimiter uses two Caffeine LoadingCaches of Bucket4j buckets — one keyed on IP:email (10 attempts/15 min) and one on IP alone (20/15 min backstop). Exceeding either throws DomainException(TOO_MANY_LOGIN_ATTEMPTS) and emits LOGIN_RATE_LIMITED audit. Successful login invalidates both buckets via invalidateOnSuccess. Buckets expire after windowMinutes of inactivity (no clock advance needed — Caffeine handles eviction). AuthService integrates it as an optional @Autowired field so non-web test contexts still work without a Caffeine dependency. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
159 lines
4.6 KiB
YAML
159 lines
4.6 KiB
YAML
spring:
|
|
application:
|
|
name: Familienarchiv
|
|
|
|
datasource:
|
|
url: ${SPRING_DATASOURCE_URL}
|
|
username: ${SPRING_DATASOURCE_USERNAME}
|
|
password: ${SPRING_DATASOURCE_PASSWORD}
|
|
driver-class-name: org.postgresql.Driver
|
|
|
|
flyway:
|
|
enabled: false # Managed explicitly via FlywayConfig bean
|
|
|
|
jpa:
|
|
open-in-view: false # Prevents holding DB connections for the full HTTP request lifecycle
|
|
hibernate:
|
|
ddl-auto: none
|
|
properties:
|
|
hibernate:
|
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
|
show-sql: false
|
|
|
|
servlet:
|
|
multipart:
|
|
max-file-size: 50MB
|
|
max-request-size: 500MB # supports 10-file chunk at max per-file size; see #317
|
|
file-size-threshold: 2KB
|
|
|
|
mail:
|
|
host: ${MAIL_HOST:}
|
|
port: ${MAIL_PORT:587}
|
|
username: ${MAIL_USERNAME:}
|
|
password: ${MAIL_PASSWORD:}
|
|
properties:
|
|
mail:
|
|
smtp:
|
|
auth: true
|
|
starttls:
|
|
enable: true
|
|
|
|
session:
|
|
timeout: 28800s # 8 h idle timeout (MaxInactiveIntervalInSeconds)
|
|
jdbc:
|
|
initialize-schema: never # Flyway owns schema creation (V67)
|
|
# Cookie name, SameSite, and Secure are configured via SpringSessionConfig#cookieSerializer
|
|
# (spring.session.cookie.* is not supported in Spring Boot 4.x).
|
|
|
|
server:
|
|
# Behind Caddy/reverse proxy: trust X-Forwarded-{Proto,For,Host} so that
|
|
# request.getScheme(), redirect URLs, and Spring Session "Secure" cookies
|
|
# reflect the original https client request, not the http hop from Caddy.
|
|
forward-headers-strategy: native
|
|
|
|
management:
|
|
server:
|
|
# Management port is separate from the app port so that:
|
|
# (a) Caddy never proxies /actuator/* (it only routes :8080 → the app port)
|
|
# (b) Prometheus scrapes backend:8081 directly inside archiv-net, not via Caddy
|
|
# Note: in Spring Boot 4.0 the management port shares the security filter chain; /actuator/health
|
|
# and /actuator/prometheus must be explicitly permitted in SecurityConfig — see SecurityConfig.java.
|
|
port: 8081
|
|
endpoints:
|
|
web:
|
|
exposure:
|
|
include: health,info,prometheus,metrics
|
|
endpoint:
|
|
prometheus:
|
|
enabled: true
|
|
# Spring Boot 4.0: metrics export is disabled by default — explicitly opt in for Prometheus
|
|
prometheus:
|
|
metrics:
|
|
export:
|
|
enabled: true
|
|
metrics:
|
|
tags:
|
|
# Common tag applied to every metric so Grafana's Spring Boot dashboard can filter by application name.
|
|
# Override via MANAGEMENT_METRICS_TAGS_APPLICATION env var.
|
|
application: ${spring.application.name}
|
|
health:
|
|
mail:
|
|
enabled: false
|
|
tracing:
|
|
sampling:
|
|
probability: 1.0 # 100% in dev; override via MANAGEMENT_TRACING_SAMPLING_PROBABILITY in prod compose
|
|
|
|
# OpenTelemetry trace export — failures are non-fatal (app starts cleanly without Tempo running)
|
|
# Port 4318 = OTLP HTTP (the default transport for Spring Boot's HttpExporter).
|
|
# Port 4317 is gRPC-only; sending HTTP/1.1 to it produces "Connection reset".
|
|
otel:
|
|
service:
|
|
name: familienarchiv-backend
|
|
exporter:
|
|
otlp:
|
|
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4318}
|
|
logs:
|
|
exporter: none # Promtail captures Docker logs; disable OTLP log export (Tempo only accepts traces)
|
|
metrics:
|
|
exporter: none # Prometheus scrapes /actuator/prometheus; disable OTLP metric export to Tempo
|
|
|
|
springdoc:
|
|
api-docs:
|
|
enabled: false
|
|
swagger-ui:
|
|
enabled: false
|
|
|
|
app:
|
|
s3:
|
|
endpoint: ${S3_ENDPOINT}
|
|
access-key: ${S3_ACCESS_KEY}
|
|
secret-key: ${S3_SECRET_KEY}
|
|
bucket: ${S3_BUCKET_NAME}
|
|
region: ${S3_REGION}
|
|
|
|
base-url: ${APP_BASE_URL:http://localhost:3000}
|
|
|
|
mail:
|
|
from: ${APP_MAIL_FROM:noreply@familienarchiv.local}
|
|
|
|
admin:
|
|
# Key must be `email`, not `username` — UserDataInitializer reads
|
|
# `${app.admin.email:...}`. The env-var name stays APP_ADMIN_USERNAME
|
|
# to match the existing Gitea secrets and DEPLOYMENT.md §3.3.
|
|
# See #513.
|
|
email: ${APP_ADMIN_USERNAME:admin@familienarchiv.local}
|
|
password: ${APP_ADMIN_PASSWORD:admin123}
|
|
|
|
import:
|
|
col:
|
|
index: 0
|
|
box: 1
|
|
folder: 2
|
|
sender: 3
|
|
receivers: 5
|
|
date: 7
|
|
location: 9
|
|
tags: 10
|
|
summary: 11
|
|
transcription: 13
|
|
|
|
ocr:
|
|
sender-model:
|
|
activation-threshold: 100
|
|
retrain-delta: 50
|
|
|
|
sentry:
|
|
dsn: ${SENTRY_DSN:}
|
|
environment: ${SPRING_PROFILES_ACTIVE:dev}
|
|
traces-sample-rate: ${SENTRY_TRACES_SAMPLE_RATE:1.0}
|
|
send-default-pii: false
|
|
enable-tracing: true
|
|
ignored-exceptions-for-type:
|
|
- org.raddatz.familienarchiv.exception.DomainException
|
|
|
|
rate-limit:
|
|
login:
|
|
max-attempts-per-ip-email: 10
|
|
max-attempts-per-ip: 20
|
|
window-minutes: 15
|