Skip to content

Latest commit

 

History

History
96 lines (86 loc) · 13.2 KB

File metadata and controls

96 lines (86 loc) · 13.2 KB

Contact, mail, and logging (Feature Draft)

Status: Draft
Updated: 2026-05-25
Owner: Core
Purpose: Draft for contact forms, mail delivery, logging, statistics, and GeoIP-aware operational insights.

Overview

  • Defines basic contact-form behavior and mail delivery expectations.
  • Covers logging, statistics, GeoIP enrichment, privacy considerations, and operational visibility.
  • Depends on error handling, security, and configuration decisions.
  • Provides practical public interaction and operational insight without turning analytics into a surveillance feature.

Outline

The CMS should provide a reliable contact form and mail delivery foundation for project websites. Contact workflows should be protected by validation, CSRF where applicable, rate limiting, optional captcha, and safe error messages that do not leak delivery internals.

Logging and statistics should help operators understand system behavior, failed workflows, content activity, and abuse patterns. GeoIP enrichment can be useful, but it must be treated as optional operational metadata with privacy and retention controls. If GeoIP is not configured, logs and statistics should continue without hard errors and should use N/A or an equivalent empty value for location fields.

The first implementation should prefer Symfony Mailer, Monolog, Rate Limiter, Validator, and explicit storage decisions. Module-provided integrations can add newsletter systems, CRM handoff, or alternative logging sinks later. Statistics may evaluate rotated logs over longer time ranges, but rotated logs should anonymize IP addresses while preserving acceptable GeoIP-derived location data.

Security logging should distinguish normal operational events from high-signal abuse patterns. Known probe paths, repeated 404s, repeated validation failures, and repeated rate-limit hits may feed suspicious-event statistics, but they should not automatically become permanent block rules without a separate security decision. The logging layer should provide insight; the security layer decides whether to throttle, challenge, temporarily block, or hard-block.

Audit logging should start with a simple proposed event set and remain easy to adjust. The first audit-worthy events should include global configuration changes, protected configuration changes, provider selection changes, package activation/deactivation/update/uninstall, schema changes, import apply, backup restore, user/ACL changes, API token changes, and high-impact maintenance actions. Whether this lives in the general logger or a separate admin audit log can stay open until log retention and log categories are implemented.

Technical Specifications

  • Use Symfony Mailer for outbound mail.
  • Use Symfony Forms and Validator for contact forms.
  • Use Symfony Rate Limiter for contact form submissions.
  • Use the captcha provider contract once available for anonymous public forms.
  • Use translated user-facing messages.
  • Do not expose mail transport errors directly to users.
  • Log delivery failures with safe context.
  • Prefer filesystem-backed structured logs over database-backed operational logs for access, error, and security events.
  • Use stable structured log records that can be emitted as JSONL or converted to JSONL for UI filtering, scripted analysis, and retention jobs.
  • Start with separate channels for access, error, and security; keep anonymized statistics output separate from raw request-derived logs.
  • Define a mail template structure that themes may style but not silently break.
  • Keep statistics events additive and observable.
  • Use GeoIP through a replaceable adapter or optional service boundary.
  • Use the installed MaxMind/GeoIP2 package as the first GeoIP provider implementation.
  • Treat GeoIP as a provider-backed service with local database storage, scheduled update support, and clear failure handling.
  • Configure the MaxMind API key through a protected admin configuration UI and store it in database-backed configuration with access restricted to authorized administrators.
  • If no MaxMind API key is configured, disable GeoIP lookup, GeoIP database updates, and Geo-blocking without throwing hard errors.
  • Use N/A, null, or an equivalent normalized empty value for GeoIP log/statistic fields when GeoIP is disabled or unavailable.
  • Never write the MaxMind API key to logs, exported diagnostics, screenshots, fixtures, or public configuration.
  • Define retention controls before storing request-derived statistics.
  • Keep raw access logs with retraceable IP addresses for at most 30 days by default.
  • Anonymize IP addresses during rotation when logs are retained beyond the raw retention window, or write a parallel anonymized statistics log from the start.
  • Use log rotation for longer-running statistics. Rotated logs should anonymize IP addresses while retaining non-sensitive aggregate or GeoIP location data where allowed.
  • Keep access/security event logs separate from aggregated statistics where practical.
  • Use a separate audit channel for high-impact administrator actions, starting with authentication events, backend maintenance/package lifecycle events, and settings saves that log changed keys but not submitted values. Keep audit logging configurable through Security settings with an enabled production default and selectable event categories.
  • Draft first audit-worthy events: global configuration changes, protected configuration changes, provider selection changes, theme lifecycle actions, module lifecycle actions, schema changes, import apply, backup restore, user/ACL changes, API token changes, and high-impact maintenance actions.
  • Keep log level, retention, rotation, and category/channel rules configurable and easy to change.
  • Let the future operational logger consume structured message or operation entries through an explicit recorder/service boundary before adding generic message events.
  • Do not introduce a generic OnOperationsMessage event as the first logging API. A recorder may dispatch internal events after persistence if another subsystem needs to observe recorded log entries.
  • Track suspicious signals such as known probe paths, repeated 404s, repeated rate-limit hits, and repeated failed public form submissions.
  • Keep probe-path detection as a high-signal diagnostic input rather than a mandatory hard-block rule.
  • Avoid storing unnecessary personal data.
  • Let modules observe contact submission lifecycle events only when documented.

Testing & Validation

  • Functional-test contact form validation and success/failure flows.
  • Test rate limiting behavior.
  • Test captcha provider integration once implemented.
  • Test mail dispatch with Symfony test transports.
  • Test logs do not include secrets or full sensitive payloads.
  • Test rotated logs anonymize IP addresses while preserving allowed aggregate/GeoIP data.
  • Test GeoIP adapter behavior with missing or invalid data.
  • Test missing MaxMind API key disables GeoIP lookup and Geo-blocking without hard errors.
  • Test GeoIP-disabled logs use normalized empty location values such as N/A.
  • Test MaxMind API key configuration access is restricted to authorized administrators.
  • Test GeoIP update failure handling once scheduled updates are implemented.
  • Test suspicious-event aggregation for probe paths, repeated 404s, and rate-limit hits.
  • Test access/security logs remain separable from anonymized aggregate statistics.
  • Test audit-worthy events are emitted with actor, action, target, timestamp, and safe redaction where implemented.
  • Run translation comparison after user-facing copy changes.

Implementation Notes

  • Decision recorded: Implement contact forms with Symfony Forms, Validator, Mailer, Rate Limiter, and optional captcha.
  • Decision recorded: Contact form storage is configurable: mail-only by default, with optional stored submissions.
  • Decision recorded: Treat GeoIP as optional and replaceable, with MaxMind/GeoIP2 as the first provider implementation because it is already installed.
  • Decision recorded: GeoIP should use a provider boundary with local database storage, scheduled update support, and safe failure behavior.
  • Decision recorded: The initial GeoIP boundary is GeoIpResolverInterface with a NullGeoIpResolver that returns normalized n/a fields. Access logging and statistics depend on the interface now, while MaxMind/local database lookup remains a later provider implementation.
  • Decision recorded: The MaxMind API key is configured through protected admin configuration and stored in database-backed configuration with restricted access.
  • Decision recorded: Missing MaxMind API key disables GeoIP lookup, GeoIP database updates, and Geo-blocking gracefully. Logs should continue with normalized empty GeoIP values such as N/A.
  • Decision recorded: Keep statistics operational and privacy-conscious.
  • Decision recorded: Core statistics and access logging may use only first-party technical cookies. The system_visitor cookie identifies a browser/device visitor for internal statistics and future security buckets, uses a 30-day lifetime aligned with raw access-log retention, is not a cross-site cookie, and is not available for advertising or external analytics modules. If the cookie is missing or disabled, statistics use an APP_SECRET-derived IP/user-agent fallback ID instead of creating a new unique visitor on every request. Fresh responses still receive random signed visitor-cookie tokens so cookie-capable clients get real per-browser/device uniqueness after the cookie roundtrip. The short-lived visitor identity store keeps cookie hashes and IP/user-agent fallback hashes separate so a newly issued cookie can bridge the first request without making later same-IP/same-browser cookies share one persistent visitor. A future consent interface can allow packages to register their own cookie policies for advertising or external analytics without weakening the core technical-cookie boundary.
  • Decision recorded: Prefer filesystem-backed Monolog channels with stable structured context for UI filtering.
  • Implemented baseline: Admin Logs browsing is split into source discovery, reverse log-line reading, filter matching, entry presentation, and pagination collaborators behind LogFileBrowser so future export/API/support tooling can reuse smaller pieces.
  • Decision recorded: Separate message, audit, and access file channels; live-operation terminal summaries use the message channel with operation-specific message keys, and raw request-derived logs stay separate from anonymized statistics output. Log files use var/log/{APP_ENV}/{message|audit|access}-{rotation_date}.log so filenames stay descriptive without product or system owner prefixes.
  • Decision recorded: Access statistics run as a separate database-backed model in parallel to raw access files. Each request may write a short-lived raw access-log entry plus one access_statistic_event row with an internally generated request id, first-party cookie-derived anonymized visitor id, method, requested path, resolved route, surface, status, duration, referrer host, preferred language, content metadata, coarse browser family, device type, bot classification, and GeoIP placeholder fields. Raw access logs keep the full user-agent, IP/proxy hints, and an optional safe inbound correlation_id for operational review, but IP addresses, user-agents, inbound correlation ids, and raw visitor-cookie tokens are not stored in the statistics table. X-Request-ID and X-Correlation-ID are never reused as the internal request id. Request id, visitor id, requested path, and resolved route are exposed to Twig error pages as a visitor-shareable debug reference. The latest Admin Logs snapshot is still written below var/statistics/{environment}/access/latest.json as a cache/output boundary so a later scheduler or richer statistics module can replace the producer without changing the Admin Logs UI.
  • Decision recorded: The first Admin Logs statistics view supports fixed windows (1h, 24h, 7d, 30d, all) over stored anonymized statistic events. Long-term compaction into daily/monthly aggregates remains deferred until the statistics feature grows beyond this foundation slice.
  • Decision recorded: Use log rotation for longer statistics windows. Rotated logs should anonymize IP addresses while retaining allowed aggregate and GeoIP-derived location data.
  • Decision recorded: Suspicious events such as probe paths, repeated 404s, rate-limit hits, and repeated failed submissions should feed diagnostics, not automatically become permanent block rules.
  • Decision recorded: First audit-worthy events should include configuration, protected setting, provider, package lifecycle, schema, import, backup restore, user/ACL, API token, and high-impact maintenance changes. The first implemented events are login success, login failure, logout, backend maintenance action starts/completions, package ZIP verification starts, and package lifecycle action starts/completions.
  • Decision recorded: Defer a generic operations-message event until after the logger is implemented; start with explicit recorder/service boundaries and keep audit/access/security logs separate from the live ActionLog overlay.
  • Decision recorded: Built-in raw log channels use 30-day rotating files by default. Long-term statistics stay separate and may move to database-backed aggregates.