super-productivity/packages/super-sync-server/Caddyfile
Johannes Millan 904d523e8a
fix(sync): Caddy WS-token log redaction (#8343) + batched failed-op retry (#8305) (#8404)
* fix(sync): redact WebSocket auth token from Caddy access logs (#8343)

The WS endpoint authenticates via ?token=<JWT> (browsers can't set
headers on a WebSocket), and that JWT is valid for 365 days with no
rotation. The shipped Caddyfile enables access logging, so every WS
upgrade wrote a year-valid, full-sync credential to stdout / docker
logs. Use a filter log encoder to redact the token query param from the
logged URI while keeping the request line for observability.

* fix(sync): retry failed remote ops as one seq-ordered batch (#8305)

retryFailedRemoteOps applied each failed op in its own single-op
applyOperations call. That turned every retry into a single-op batch, so
the same-batch archive pre-scan (collectArchivingOrDeletingEntityIdsFrom
Batch) no-op'd and silently weakened the #7330 orphan-resurrection
guard. Retry all failed ops as one batch instead, ordered by seq, and
mirror the primary remote-apply path's applied/slice-from-failure result
handling (keeping the MAX_CONFLICT_RETRY_ATTEMPTS reject cap).

* fixup! fix(sync): redact WebSocket auth token from Caddy access logs (#8343)

* refactor(sync): share slice-from-failure op-id logic across apply paths

Extract getFailedOpIdsFromBatch() — the 'failed op plus every op after it'
slice that retryFailedRemoteOps, ConflictResolutionService, and the
sync-core remote-apply path each derived inline. Removes the app-side
duplication flagged in review and fixes a latent bug: conflict-resolution
sliced from the findIndex result with no -1 guard, so an unfound failed op
would slice(-1) and mark only the last op failed. The shared helper guards
that case (marks just the failed op). Behavior is otherwise unchanged.

* test(sync): integration-test failed-op retry against real IndexedDB (#8305)

The retryFailedRemoteOps unit spec mocks the store, so it can't verify the
real status transitions the retry relies on. Add an integration spec that
drives retryFailedRemoteOps through the real OperationLogStoreService with
only the applier controlled, asserting: full-batch success clears ops to
applied, the batch is applied in one call, partial failure re-marks the
failed op plus every op after it, and a permanently-failing op is rejected
at MAX_CONFLICT_RETRY_ATTEMPTS across reboots and then no longer returned.
2026-06-15 17:40:20 +02:00

48 lines
1.7 KiB
Caddyfile

# NOTE: docker-compose.yml healthcheck probes the Caddy admin API at
# 127.0.0.1:2019 (Caddy's default admin listen is `localhost:2019`, i.e.
# IPv4 loopback; the probe uses the literal IP so busybox wget can't try
# ::1 first). Do NOT add a global `{ admin off }` block or rebind admin
# without also updating the healthcheck, or Caddy will restart-loop.
{$DOMAIN} {
reverse_proxy supersync:1900 {
# Allow up to 85 seconds for long-running operations
# (snapshot generation, large imports)
# MUST be greater than Fastify timeout (80s) but less than client timeout (90s)
transport http {
dial_timeout 10s
response_header_timeout 85s
}
}
# Security headers
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
# Enable compression
encode gzip zstd
# Logging
#
# Redact the `token` query param from the logged URI. The WebSocket endpoint
# authenticates via `?token=<JWT>` (browsers can't set headers on a
# WebSocket), and that JWT is valid for 365 days with no rotation — so
# without this filter every WS upgrade writes a year-valid, full-sync
# credential to stdout / `docker logs`. `token` is only ever a query param
# carrying an auth secret (the WS JWT, plus the email-verification,
# passkey-recovery and magic-login links), never a benign field, so
# redacting it across this site block is targeted and safe. See #8343.
log {
output stdout
format filter {
wrap console
request>uri query {
replace token REDACTED
}
}
}
}