Introduce a `max_email_attachment_size` configuration option and support
for attaching files via data URLs with CC recipients in the send_mail
function. Refactor data URL decoding into a shared utility.
Rename the misspelled `stmp_*` configuration options to `smtp_*`
and add a new `smtp_tls_mode` option (`starttls`, `tls`, `none`)
to control encryption when connecting to the SMTP server. Reject
credentials in plaintext mode.
Change `sqlpage.send_mail` to return its JSON argument unchanged
on success and update the example to use a local Mailpit SMTP
server via Docker Compose.
### Motivation
- Provide a built-in `sqlpage.send_mail(...)` SQL function so pages can send plain-text emails from SQL code.
- Allow the SMTP server to be configured via an environment / configuration option so the function can target a deployable SMTP endpoint.
### Description
- Added a new function implementation at `src/webserver/database/sqlpage_functions/functions/send_mail.rs` implementing `sqlpage.send_mail(json)` which accepts a JSON object with required `recipient`, `subject`, and `body` and optional `sender` and `reply_to`, sends the message and returns `sent` on success.
- Registered the function in the SQLPage function registry by adding `send_mail` to `src/webserver/database/sqlpage_functions/functions.rs`.
- Added a configuration option `stmp_host: Option<String>` to `AppConfig` in `src/app_config.rs`, with `parse_stmp_host`/`validate_stmp_host` helpers that accept either `host` or `host:port` and default to port 25 when none is provided; validation is run from `AppConfig::validate`.
- Added `lettre` to `Cargo.toml` and updated `Cargo.lock` to enable SMTP sending, and added official-site documentation and a migration at `examples/official-site/sqlpage/migrations/75_send_mail.sql` describing usage and parameters.
- Documented the `stmp_host` option in `configuration.md`.
### Testing
- Ran `cargo fmt --all`, which completed successfully.
- Ran `git diff --check` which reported no immediate style errors.
- Attempted `cargo clippy --all-targets --all-features -- -D warnings`, but it was blocked by a toolchain/build issue (a dependency `libsqlite3-sys` build script uses the unstable `cfg_select` feature) and did not complete.
- Attempted `cargo test`, but it was similarly blocked by the same `libsqlite3-sys` build-script error and did not complete.
* docs: warn that static serving follows symlinks under web_root
Operators control web_root contents, so a symlink there is a trusted
deployment artifact. Clarify that SQLPage follows such symlinks during
static file serving, meaning a symlink under web_root pointing to
reserved/private files (sqlpage/ config, dotfiles) or to files outside
web_root would make those targets publicly reachable.
Note added to SECURITY.md (Out of Scope), cross-referenced from the
web_root row in configuration.md and an Unreleased CHANGELOG entry.
* Update web_root description for clarity
* add OpenTelemetry distributed tracing support
When OTEL_EXPORTER_OTLP_ENDPOINT is set, enables full tracing pipeline
with OTLP export, W3C traceparent propagation, and spans for HTTP
requests, SQL file execution, DB pool acquire, and query execution.
Falls back to env_logger when unset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix OTel example: tracing init, Dockerfile, Tempo config
- Fix tracing-log bridge initialization order (set subscriber first,
then LogTracer) to avoid double-set panic
- Add dedicated Dockerfile for example using release profile (avoids
OOM with superoptimized LTO in Docker)
- Use debian:trixie-slim runtime for glibc compatibility
- Fix nginx image to nginx:otel (official image with OTel module)
- Fix nginx.conf: move otel_trace directives into location block
- Pin Tempo to 2.6.1 (latest has partition ring issues in single-node)
- Fix otel-collector exporter alias (otlp → otlp_grpc)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* propagate trace context to PostgreSQL via application_name
After acquiring a DB connection, set the W3C traceparent as the
PostgreSQL application_name (or MySQL session variable). This makes
trace IDs visible in pg_stat_activity and PostgreSQL logs, enabling
direct correlation between Grafana Tempo traces and database-side
monitoring.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* rewrite OTel example README with setup guides for all major providers
Comprehensive documentation covering:
- Step-by-step quick start for the Docker Compose example
- How OpenTelemetry works (spans, collectors, backends)
- Setup guides for Grafana Tempo, Jaeger, Grafana Cloud, Datadog,
Honeycomb, New Relic, and Axiom with exact env vars and doc links
- PostgreSQL trace correlation via application_name
- Environment variable reference
- Troubleshooting section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix todo example: use :title (POST variable) instead of $title
The form submits via POST, so the title field must be referenced with
the : prefix (POST parameter) rather than $ (GET parameter).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* set code.filepath and code.lineno span attributes to user SQL files
OTel span attributes now reference the user's .sql file path and line
number instead of the SQLPage Rust source code. Also improves span
naming, adds JSON log formatting, custom root span builder, and
Grafana dashboard provisioning for the OTel example.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* npm run fix
* use stable OTel semantic convention attribute names
- code.filepath → code.file.path, code.lineno → code.line.number
- db.statement → db.query.text, db.system → db.system.name
- Disable auto code location (.with_location(false)) so spans
reference user SQL files, not SQLPage Rust source
- Remove redundant sqlpage.file attribute (code.file.path suffices)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* use OTel semantic convention values for db.system.name
Use well-known values from the OpenTelemetry registry instead of raw
DBMS name strings. Cast line numbers to i64 for correct span recording.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* remove json-subscriber dependency
The custom logfmt layer in telemetry.rs replaces it with zero extra
dependencies and precise control over field selection and ordering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add Loki + Promtail log aggregation to OTel example
Adds two new services (Loki, Promtail) to scrape SQLPage container logs
and display them in Grafana alongside traces. The home dashboard now
shows a logs panel with trace_id derived fields linking to Tempo.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add OTel spans for request parsing, rendering, sqlpage functions, and OIDC
Add targeted spans to account for previously untraced time:
- http.parse_request: request/form parsing before SQL execution
- render: template rendering and response streaming
- subprocess: sqlpage.exec() with process.command attribute
- http.client: sqlpage.fetch()/fetch_with_meta() with OTel HTTP client
semantic conventions (http.request.method, url.full, http.response.status_code)
- sqlpage.file: sqlpage.run_sql() nested file execution
- oidc.callback + http.client: OIDC token exchange
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add oidc.jwt.verify span for OIDC token verification
This span covers JWT signature verification and claims validation,
which runs on every authenticated request via get_token_claims().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add enduser.id attribute to oidc.jwt.verify span
Records the OIDC subject claim (sub) as enduser.id after successful
JWT verification, following OTel semantic conventions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add OTel user.* attributes to oidc.jwt.verify span
Record user.id (sub), user.name (preferred_username), user.full_name
(name), and user.email from OIDC claims after JWT verification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add sqlpage.file.load span and attributes to http.parse_request
The gap before http.parse_request was the SQL file cache lookup -
now covered by the sqlpage.file.load span with code.file.path.
http.parse_request now records http.request.method and content_type,
which helps identify slow multipart/form-data parsing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix clippy pedantic warnings
* unify log format: logfmt with colors, no OTel noise
Use the custom logfmt layer for both OTel and non-OTel modes instead
of falling back to env_logger. This eliminates the tracing→log bridge
dumping all span fields (user agents, otel.kind, request_id, etc.)
and only shows: ts, level, target, msg, method, path, status,
file, client_ip, and trace_id (when valid).
Adds terminal color support (bold red for errors, green for info,
dim for timestamps/targets). Emits one log line per completed
successful request. Errors are logged once by the error handler.
Suppresses trace_id=000...0 when no real trace context exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* use .instrument() instead of .entered() for async spans
Span guards from .entered() do not propagate correctly across await
points. Switch to tracing::Instrument to ensure spans are properly
associated with their async tasks throughout their lifetime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* preserve multi-line error formatting in terminal log output
When stderr is a terminal and the log message contains newlines
(e.g. SQL syntax errors with source highlighting and arrows),
print the metadata on the first line and the message below with
its original formatting. Machine output (non-terminal) remains
single-line logfmt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* use root Dockerfile for OTel example, add CARGO_PROFILE build arg
Remove the example's custom Dockerfile and use the main one with a
CARGO_PROFILE=release build arg to avoid OOM from fat LTO in
memory-constrained Docker environments. The build scripts now
read CARGO_PROFILE from the environment, defaulting to
superoptimized for backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* add db.query.parameter and db.response.returned_rows span attributes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add official blog post about tracing
* add http.request.body.size and url.query span attributes
Add http.request.body.size to HTTP client spans (fetch, fetch_with_meta)
and to the server-side http.parse_request span (from Content-Length header).
Add url.query to the http.parse_request span.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replace Promtail with the OpenTelemetry Collector
* Refactor telemetry logging helpers
* Clamp traced fetch body size
* Clamp traced fetch_with_meta body size
* Silence noisy PostgreSQL collector logs
* make startup logs parseable
* update terminal log formats
* Ingest real PostgreSQL logs with trace IDs
* Use raw traceparent for PostgreSQL tracing
* Update opentelemetry example for PostgreSQL query events
* Add nginx logs to opentelemetry example
* Parse nginx error log severity correctly
* Log all span fields when debug logging is enabled
* Rename telemetry example directory
* Fix PostgreSQL Loki log ingestion
* `LOG_LEVEL` is now the primary environment variable for configuring SQLPage's log filter. `RUST_LOG` remains supported as an alias.
* Skip empty trace IDs in logs
* add db errors to otel traces
* add healthcheck
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Format protected and public paths with site prefix
Update protected and public paths to include site prefix.
* Fix formatting of paths in OIDC module
* Validate that paths start with '/' in config
* Refactor path handling for OIDC configuration
* Update OIDC paths configuration details
* code fix+clippy hints+formatting
Introduces a new configuration option `cache_stale_duration_ms` which
allows users to control how long files are cached before their freshness
is checked. This provides more fine-grained control over caching
behavior, especially in production environments.
* Added posibility to bypass oidc authentication for certain endpoints
* fixxed missing .clone()
* Fixxed oidc_skip_endpoints not being optional
* feat(oidc): Introduce protected path prefixes
This commit replaces the OIDC endpoint blacklist with a path prefix whitelist. This is a more intuitive and secure approach for managing protected routes.
The new `oidc_protected_paths` configuration option allows users to specify a list of URL prefixes that require OIDC authentication. By default, all paths are protected.
The documentation has been updated to reflect this change, with clear examples and more user-friendly language.
* docs(oidc): Improve OIDC documentation and examples
This commit improves the OIDC documentation and the "single sign on"
example to better demonstrate how to create a selective login system.
The main documentation now includes a section on creating a public
login page and the "single sign on" example has been updated to
reflect this pattern.
* Simplify OIDC middleware request handling for unprotected paths
* docs(oidc): Improve single sign on example
This commit improves the "single sign on" example to better
demonstrate a public information page that adapts to the users
login status and a separate protected page.
* docs(oidc): Document oidc_protected_paths in configuration.md
This commit updates the main configuration documentation to reflect the
new `oidc_protected_paths` option. It removes the outdated
`oidc_skip_endpoints` and provides a more detailed explanation of how
to create a mix of public and private pages.
* Improve SSO demo UX
- Update docker compose command to use `--watch` flag - Add watch
configuration for SQLPage development - Enhance login page with hero
component and better styling - Simplify protected page welcome message -
Fix OIDC middleware path check logic - Update protected paths in config
to use `/protected` instead of `/protected.sql`
* Skip OIDC auth for non-protected paths later in middleware
We still want to be able to access authenticated user's info in
non-authenticated parts of the app.
We crucially need to check request.path() == SQLPAGE_REDIRECT_URI before
the protected_paths check
* Added whitelist option
* Update configuration.md
* Improve OIDC public paths documentation
The documentation now provides clearer examples and explains the
interaction between public and protected paths more precisely. Also
removes the now-unused default_oidc_public_paths function since the
field's default is handled by serde's default for Vec.
* Add OidcConfig method to check public paths
The new `is_public_path` method consolidates the logic for checking if a
path should bypass OIDC authentication. This replaces the previous
inline checks for public and protected paths.
* fix default empty public paths
* Update SSO example with new image path and public access rules
- Change hero image path in login page - Remove protected.sql as it's no
longer needed - Update sqlpage.yaml to allow public access to
/protected/public
---------
Co-authored-by: Lenardt Gerhardts <lenardt.gerhardts@obi.de>
Co-authored-by: lovasoa <contact@ophir.dev>
* Extend CSP Configuration to handle user-supplied values that can contain {NONCE}
* fix RequestContext to use CSP value from AppConfig
* update documentation to describe usage
* generate nonce per request; config is a string (again); added playwright test to verify subsequent requests return a different nonce.
* fix js lint `let` -> `const`
* format
* remove some useless string copies
we are still re-parsing the csp template on every request
* implement a proper csp template struct
* parse the content-security-policy just once
* fix merge issue.
* fix docs
* fix docs
* clippy
---------
Co-authored-by: lovasoa <contact@ophir.dev>
* add oidc config variables
* setup a basic middleware
* implement an async http client that uses oidc
* initialize provider_metadata in OidcService
* better error handling in oidc config
* HTTP client initialization in oidc now follows global config
* oidc: implement redirects
- Add `host` configuration option for specifying the application's web address in configuration.md and app_config.rs.
- Update docker-compose.yaml to include SQLPAGE_HOST and SQLPAGE_OIDC_ISSUER_URL environment variables.
- Enhance OIDC middleware to utilize the new `host` setting for redirect URLs and improve cookie handling in oidc.rs.
* improve local oidc configurability
* log
* Update warning message in OIDC configuration to clarify how to disable it by providing a host setting
* Update OIDC redirect logging to use info level with client ID
* Refactor unauthenticated request handling in OIDC service
- Extracted logic for handling unauthenticated requests into a separate method `handle_unauthenticated_request`.
- Updated the main request handling flow to utilize the new method for improved readability and maintainability.
* Enhance OIDC service with callback handling and token processing
- Introduced `handle_oidc_callback` method to manage OIDC callback requests.
- Added `process_oidc_callback` and `exchange_code_for_token` methods for token exchange logic.
- Updated `handle_unauthenticated_request` to check for callback URL and redirect accordingly.
- Refactored `build_redirect_response` to improve clarity in response handling.
* in handle_oidc_callback use service_request.into_response
* fmt
* Implement oidc code exchange and token storage
* validate oidc cookies
- Updated `get_sqlpage_auth_cookie` to return a result for better error handling and validation of the SQLPage auth cookie.
- Improved logging throughout the OIDC service for better traceability of requests and responses.
- Adjusted the handling of OIDC callback parameters to include context in error messages.
* OIDC callback: redirect to the auth URL on failure.
* oidc use localhost for redirect config instead of 0.0.0.0 by default
* Enhance OIDC provider metadata discovery with improved logging and error context
* maintain the initial URL during OIDC authentication
- Added state cookie handling to maintain the initial URL during OIDC authentication.
- Refactored `build_auth_url` to accept the initial URL as a parameter.
- Enhanced `process_oidc_callback` to retrieve the state from the cookie and redirect accordingly.
* implement csrf token
* update deps
* update sso examples
* nonce verification
- Improved error logging for invalid auth cookies and ID token verification.
- Introduced nonce verification logic to ensure security during OIDC authentication.
- Adjusted parameters for nonce hashing to optimize for short-lived tokens.
* Refactor OIDC logging and improve documentation
- Updated logging statements for better clarity and context.
- Refactored code for nonce verification and error handling.
- Enhanced documentation in `app_config.rs` for clarity on `https_domain` usage.
* Remove unused app_state field from OidcService struct
* Enhance OIDC client error handling and refactor HTTP request types
- Added context to OIDC client creation error handling.
- Updated HTTP request and response types for better integration with the openidconnect library.
- Introduced AwcWrapperError for improved error management in HTTP calls.
* clippy fixes
- Changed http_client from Arc to Rc in OidcService for improved memory efficiency.
- Updated related code to reflect the new ownership model for the HTTP client.
* initialize the oidc and http clients only once
- Added OidcState struct to encapsulate OIDC configuration and client.
- Refactored OidcMiddleware to utilize OidcState for improved state management.
- Updated HTTP client handling in OIDC service methods for better integration with app data.
- Enhanced logging for OIDC middleware initialization and request processing.
* functions for accessing user claims from OIDC tokens + documentation
- Updated SQLPage authentication component documentation for clarity on usage and options.
- Removed deprecated login and redirect handler scripts to streamline the SSO implementation.
- Enhanced logout functionality to properly clear session cookies and redirect users.
- Improved request handling to include OIDC claims in the request context for better user information retrieval.
* better sso troubleshooting info
* fmt
* add sso to the changelog
This specifies a path to a UNIX socket file to listen on instead of the TCP port.
If specified, SQLPage will accept HTTP connections only on this socket and not on any TCP port. This option is mutually exclusive with the `listen_on` and `port` options.
Useful when running SQLpage behind a proxy server like Nginx, as the overhead for communication using Unix Domain Sockets is less than when using the TCP stack.