From ac4a3ec183cf0e324fa287108f705b0f9245865c Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Fri, 12 Jun 2026 23:44:32 +0200 Subject: [PATCH] Make JSON Schema the configuration source of truth --- .github/workflows/ci.yml | 2 + CHANGELOG.md | 4 + Cargo.toml | 1 + build.rs | 48 +++ configuration.md | 314 ++-------------- .../sqlpage/sqlpage.json | 1 + .../sqlpage/sqlpage.json | 1 + .../sqlpage/sqlpage.json | 1 + .../sqlpage/sqlpage.json | 1 + .../light-dark-toggle/sqlpage/sqlpage.json | 5 + .../light-dark-toggle/sqlpage/sqlpage.yaml | 2 - .../sqlpage/sqlpage.json | 5 +- examples/nginx/sqlpage_config/sqlpage.json | 1 + examples/official-site/configuration.sql | 51 +++ examples/official-site/llms.txt.sql | 2 +- examples/official-site/safety.sql | 2 +- .../sqlpage/migrations/01_documentation.sql | 4 +- .../sqlpage/migrations/08_functions.sql | 4 +- .../migrations/24_read_file_as_data_url.sql | 2 +- .../sqlpage/migrations/26_v0.17_release.sql | 6 +- .../migrations/69_blog_performance_guide.sql | 4 +- examples/official-site/sqlpage/sqlpage.json | 6 + examples/official-site/sqlpage/sqlpage.yaml | 7 - .../your-first-sql-website/nginx.md | 2 +- .../your-first-sql-website/tutorial.md | 4 +- .../sqlpage/sqlpage.json | 1 + .../sqlpage/sqlpage.json | 1 + examples/single sign on/sqlpage/sqlpage.json | 13 + examples/single sign on/sqlpage/sqlpage.yaml | 6 - examples/telemetry/sqlpage/sqlpage.json | 1 + .../todo application/sqlpage/sqlpage.json | 1 + .../sqlpage_config/sqlpage.json | 1 + scripts/check_configuration_examples.py | 23 ++ scripts/generate_configuration_reference.py | 54 +++ sqlpage/sqlpage.json | 1 + sqlpage/sqlpage.schema.json | 336 ++++++++++++++++++ src/app_config.rs | 188 +--------- 37 files changed, 599 insertions(+), 507 deletions(-) create mode 100644 examples/light-dark-toggle/sqlpage/sqlpage.json delete mode 100644 examples/light-dark-toggle/sqlpage/sqlpage.yaml create mode 100644 examples/official-site/configuration.sql create mode 100644 examples/official-site/sqlpage/sqlpage.json delete mode 100644 examples/official-site/sqlpage/sqlpage.yaml create mode 100644 examples/single sign on/sqlpage/sqlpage.json delete mode 100644 examples/single sign on/sqlpage/sqlpage.yaml create mode 100755 scripts/check_configuration_examples.py create mode 100755 scripts/generate_configuration_reference.py create mode 100644 sqlpage/sqlpage.schema.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c0e0d258..0a572cbcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,8 @@ jobs: - uses: actions/checkout@v6 - run: npm ci - run: npm test + - run: scripts/generate_configuration_reference.py --check + - run: scripts/check_configuration_examples.py - name: Set up cargo cache uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 env: diff --git a/CHANGELOG.md b/CHANGELOG.md index f85c2796c..c82480e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG.md +## Unreleased + +- SQLPage configuration now has a checked-in JSON Schema that powers the runtime configuration structure and the generated official-site reference. Example JSON configurations link to the live schema for editor validation and completion. + ## v0.44.1 An AI-assisted security audit found three vulnerabilities: one authentication bypass that is high severity for affected OIDC deployments, and two lower-severity issues. It also led to three hardening changes. Upgrade now if you use custom OIDC protected paths. diff --git a/Cargo.toml b/Cargo.toml index 4d8a52ad5..d952a43c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ actix-http = "3" tokio = { version = "1", features = ["rt", "time", "test-util"] } [build-dependencies] +serde_json = "1" awc = { version = "3", features = ["rustls-0_23-webpki-roots"] } rustls = "0.23" actix-rt = "2.8" diff --git a/build.rs b/build.rs index 6f9f4a963..6efb974d1 100644 --- a/build.rs +++ b/build.rs @@ -17,6 +17,7 @@ async fn main() { .unwrap(); println!("cargo:rerun-if-changed=build.rs"); + generate_app_config(); let c = Rc::new(make_client()); for h in [ @@ -35,6 +36,53 @@ async fn main() { set_odbc_rpath(); } +fn generate_app_config() { + const SCHEMA_PATH: &str = "sqlpage/sqlpage.schema.json"; + println!("cargo:rerun-if-changed={SCHEMA_PATH}"); + let schema: serde_json::Value = serde_json::from_reader( + File::open(SCHEMA_PATH).expect("Unable to open the SQLPage configuration JSON Schema"), + ) + .expect("Invalid SQLPage configuration JSON Schema"); + let properties = schema["properties"] + .as_object() + .expect("Configuration schema must have an object of properties"); + + let mut generated = String::from( + "// Generated from sqlpage/sqlpage.schema.json by build.rs. Do not edit.\n\ + #[derive(Debug, Deserialize, PartialEq, Clone)]\n\ + #[allow(clippy::doc_markdown, clippy::struct_excessive_bools)]\n\ + pub struct AppConfig {\n", + ); + for (schema_name, property) in properties { + if property["x-rust-exclude"].as_bool() == Some(true) { + continue; + } + let description = property["description"] + .as_str() + .unwrap_or_else(|| panic!("Configuration property {schema_name} needs a description")); + for line in description.lines() { + generated.push_str(" /// "); + generated.push_str(line); + generated.push('\n'); + } + if let Some(serde) = property["x-rust-serde"].as_str() { + generated.push_str(" #[serde("); + generated.push_str(serde); + generated.push_str(")]\n"); + } + let field_name = property["x-rust-field-name"] + .as_str() + .unwrap_or(schema_name); + let rust_type = property["x-rust-type"] + .as_str() + .unwrap_or_else(|| panic!("Configuration property {schema_name} needs x-rust-type")); + generated.push_str(&format!(" pub {field_name}: {rust_type},\n")); + } + generated.push_str("}\n"); + let output = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("app_config.rs"); + std::fs::write(output, generated).expect("Unable to write generated AppConfig"); +} + fn make_client() -> awc::Client { awc::ClientBuilder::new() .timeout(Duration::from_secs(10)) diff --git a/configuration.md b/configuration.md index a774dcfde..779d873b9 100644 --- a/configuration.md +++ b/configuration.md @@ -1,308 +1,34 @@ # Configuring SQLPage -SQLPage can be configured through either [environment variables](https://en.wikipedia.org/wiki/Environment_variable) -or a [JSON](https://en.wikipedia.org/wiki/JSON) file placed in `sqlpage/sqlpage.json`. +SQLPage can be configured through either environment variables or a JSON file placed at `sqlpage/sqlpage.json`. -You can find an example configuration file in [`sqlpage/sqlpage.json`](./sqlpage/sqlpage.json). -Here are the available configuration options and their default values: +The complete, generated reference for every configuration option, its type, default value, and description is available on the [official SQLPage configuration reference](https://sql-page.com/configuration.sql). +The checked-in [JSON Schema](./sqlpage/sqlpage.schema.json) is the source of truth for that reference and for SQLPage's in-memory configuration structure. -| variable | default | description | -| --------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `listen_on` | 0.0.0.0:8080 | Interface and port on which the web server should listen | -| `database_url` | `sqlite://sqlpage.db?mode=rwc` or `DSN=DuckDB` | Database connection URL, either `dbengine://user:password@host:port/dbname` or an ODBC connection string. Special characters should be [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding). See [Database connection strings](#database-connection-strings) for details and examples.| -| `database_password` | | Database password. If set, this will override any password specified in the `database_url`. This allows you to keep the password separate from the connection string for better security. | -| `port` | 8080 | Like listen_on, but specifies only the port. | -| `unix_socket` | | Path to a UNIX socket 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 `listen_on` and `port`. -| `host` | | The web address where your application is accessible (e.g., "myapp.example.com"). Used for login redirects with OIDC. | -| `max_database_pool_connections` | PostgreSQL: 50
MySql: 75
SQLite: 16
MSSQL: 100 | How many simultaneous database connections to open at most | -| `database_connection_idle_timeout_seconds` | SQLite: None
All other: 30 minutes | Automatically close database connections after this period of inactivity. Set to 0 to disable. | -| `database_connection_max_lifetime_seconds` | SQLite: None
All other: 60 minutes | Always close database connections after this amount of time. Set to 0 to disable. | -| `database_connection_retries` | 6 | Database connection attempts before giving up. Retries will happen every 5 seconds. | -| `database_connection_acquire_timeout_seconds` | 10 | How long to wait when acquiring a database connection from the pool before giving up and returning an error. | -| `sqlite_extensions` | | An array of SQLite extensions to load, such as `mod_spatialite` | -| `web_root` | `.` | The root directory of the web server, where the `index.sql` file is located. Static file serving follows symlinks, so do not place symlinks under `web_root` that point to private paths (such as the `sqlpage/` config directory) or to files outside `web_root`, as their targets would become publicly reachable (see [`SECURITY.md`](./SECURITY.md)). | -| `site_prefix` | `/` | Base path of the site. If you want to host SQLPage at `https://example.com/sqlpage/`, set this to `/sqlpage/`. When using a reverse proxy, this allows hosting SQLPage together with other applications on the same subdomain. | -| `configuration_directory` | `./sqlpage/` | The directory where the `sqlpage.json` file is located. This is used to find the path to [`templates/`](https://sql-page.com/custom_components.sql), [`migrations/`](https://sql-page.com/your-first-sql-website/migrations.sql), and `on_connect.sql`. Obviously, this configuration parameter can be set only through environment variables, not through the `sqlpage.json` file itself in order to find the `sqlpage.json` file. Be careful not to use a path that is accessible from the public WEB_ROOT | -| `allow_exec` | false | Allow usage of the `sqlpage.exec` function. Do this only if all users with write access to sqlpage query files and to the optional `sqlpage_files` table on the database are trusted. | -| `max_uploaded_file_size` | 5242880 | Maximum size of forms and uploaded files in bytes. Defaults to 5 MiB. | -| `oidc_protected_paths` | `["/"]` | A list of URL prefixes that should be protected by OIDC authentication. By default, all paths are protected (`["/"]`). If you want to make some pages public, you can restrict authentication to a sub-path, for instance `["/admin", "/users/settings"]`. All paths must start with a "/" and will be prepended by `site_prefix` if defined.| -| `oidc_public_paths` | `[]` | A list of URL prefixes that should be publicly available. By default, no paths are publicly accessible (`[]`). If you want to make some pages public, you can bypass authentication for a sub-path, for instance `["/public/", "/assets/"]`. Keep in mind that without the closing backslashes, that any directory or file starting with `public` or `assets` will be publicly available. This will also overwrite any protected path restriction. If you have a private path `/private` and you define the public path `/private/public/` everything in `/private/public/` will be publicly accessible, while everything else in private will still need authentication. All paths must start with a "/" and will be prepended by `site_prefix` if defined. -| `oidc_issuer_url` | | The base URL of the [OpenID Connect provider](#openid-connect-oidc-authentication). Required for enabling Single Sign-On. | -| `oidc_client_id` | sqlpage | The ID that identifies your SQLPage application to the OIDC provider. You get this when registering your app with the provider. | -| `oidc_client_secret` | | The secret key for your SQLPage application. Keep this confidential as it allows your app to authenticate with the OIDC provider. | -| `oidc_scopes` | openid email profile | Space-separated list of [scopes](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) your app requests from the OIDC provider. | -| `oidc_additional_trusted_audiences` | unset | A list of additional audiences that are allowed in JWT tokens, beyond the client ID. When empty or unset, any additional audience is accepted. For increased security, set to an empty list `[]` to only allow the client ID as audience. | -| `max_pending_rows` | 256 | Maximum number of rendered rows that can be queued up in memory when a client is slow to receive them. | -| `compress_responses` | false | When the client supports it, compress the http response body. This can save bandwidth and speed up page loading on slow connections, but can also increase CPU usage and cause rendering delays on pages that take time to render (because streaming responses are buffered for longer than necessary). | -| `https_domain` | | Domain name to request a certificate for. Setting this parameter will automatically make SQLPage listen on port 443 and request an SSL certificate. The server will take a little bit longer to start the first time it has to request a certificate. | -| `https_certificate_email` | contact@ | The email address to use when requesting a certificate. | -| `https_certificate_cache_dir` | ./sqlpage/https | A writeable directory where to cache the certificates, so that SQLPage can serve https traffic immediately when it restarts. | -| `https_acme_directory_url` | https://acme-v02.api.letsencrypt.org/directory | The URL of the ACME directory to use when requesting a certificate. | -| `environment` | development | The environment in which SQLPage is running. Can be either `development` or `production`. In `production` mode, SQLPage will hide error messages and stack traces from the user, and will cache sql files in memory to avoid reloading them from disk. | -| `cache_stale_duration_ms` | 1000 (prod), 0 (dev) | The duration in milliseconds that a file can be cached before its freshness is checked against the filesystem. Defaults to 1000ms (1 second) in production and 0ms in development. | -| `content_security_policy` | `script-src 'self' 'nonce-{NONCE}'` | The [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to set in the HTTP headers. If you get CSP errors in the browser console, you can set this to the empty string to disable CSP. If you want a custom CSP that contains a nonce, include the `'nonce-{NONCE}'` directive in your configuration string and it will be populated with a random value per request. | -| `system_root_ca_certificates` | false | Whether to use the system root CA certificates to validate SSL certificates when making http requests with `sqlpage.fetch`. If set to false, SQLPage will use its own set of root CA certificates. If the `SSL_CERT_FILE` or `SSL_CERT_DIR` environment variables are set, they will be used instead of the system root CA certificates. | -| `max_recursion_depth` | 10 | Maximum depth of recursion allowed in the `run_sql` function. Maximum value is 255. | -| `markdown_allow_dangerous_html` | false | Whether to allow raw HTML in markdown content. Only enable this if the markdown content is fully trusted (not user generated). | -| `markdown_allow_dangerous_protocol` | false | Whether to allow dangerous protocols (like javascript:) in markdown links. Only enable this if the markdown content is fully trusted (not user generated). | - -Multiple configuration file formats are supported: -you can use a [`.json5`](https://json5.org/) file, a [`.toml`](https://toml.io/) file, or a [`.yaml`](https://en.wikipedia.org/wiki/YAML#Syntax) file. +Start from the repository's [example configuration](./sqlpage/sqlpage.json). Keep its `$schema` property to get validation and completion in compatible editors. ## Environment variables -All the parameters above can be set through environment variables. - -The name of the environment variable is the same as the name of the configuration variable, -but in uppercase. - -The environment variable name can optionally be prefixed with `SQLPAGE_`. - -Additionnally, when troubleshooting, you can set the -[`LOG_LEVEL`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html) -environment variable to `sqlpage=debug` to get more detailed logs and see exactly what SQLPage is doing. - -SQLPage also supports [OpenTelemetry](https://opentelemetry.io/) tracing via the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. See the [SQLPage monitoring example](https://github.com/sqlpage/sqlpage/tree/main/examples/telemetry). - -If you have a `.env` file in the current directory or in any of its parent directories, SQLPage will automatically load environment variables from it. - -### Database connection strings - -The `database_url` parameter sets all the connection parameters for the database, including - - - the database engine type (`sqlite`, `postgres`, `mysql`, `mssql`, or ODBC connection strings) - - the username and password - - the host (or ip adress) and port - - the database name - - any additional parameters, including - - `mode=rwc` for SQLite to allow read-write connections - - `sslmode=require` (or `disable`, `allow`, `verify-ca`, `verify-full`) - for PostgreSQL to enable or disable SSL - - `sslrootcert=/path/to/ca.pem` for PostgreSQL to specify the path to the CA certificate file - - `sslcert=/path/to/cert.pem` to specify the path to the TLS client certificate file and `sslkey=/path/to/key.pem` to specify the path to the TLS client key file for PostgreSQL and MySQL. - - `application_name=my_application` for PostgreSQL to set the application name, which can be useful for monitoring and logging on the database server side. - - `collation=utf8mb4_unicode_ci` for MySQL to set the collation of the connection - -All the parameters need to be properly [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) if they contain special characters like `@` (`%40`), `:` (`%3A`), `/` (`%2F`), `?` (`%3F`), `#` (`%23`). - -A full connection string for a PostgreSQL database might look like this: - -``` -postgres://my_user:p%40ss@localhost:5432/my_database?sslmode=verify-ca&sslrootcert=/path/to/ca.pem&sslcert=/path/to/cert.pem&sslkey=/path/to/key.pem&application_name=my_application -``` - -#### ODBC Connection Strings - -For ODBC-compatible databases (Oracle, Snowflake, BigQuery, IBM DB2, etc.), you can use ODBC connection strings directly: - -```bash -# Using a Data Source Name (DSN) -DATABASE_URL="DSN=MyDatabase" - -# Using inline connection parameters -DATABASE_URL="Driver={PostgreSQL};Server=localhost;Port=5432;Database=mydb;UID=myuser;PWD=mypassword" - -# Oracle example -DATABASE_URL="Driver={Oracle ODBC Driver};Server=localhost:1521/XE;UID=hr;PWD=password" - -# Snowflake example -DATABASE_URL="Driver={SnowflakeDSIIDriver};Server=account.snowflakecomputing.com;Database=mydb;UID=user;PWD=password" -``` - -ODBC drivers must be installed and configured on your system. On Linux, the `unixODBC` driver manager is statically linked into the SQLPage binary, so you usually only need to install and configure the database-specific ODBC driver for your target database (for example Snowflake, Oracle, DuckDB...). - -If the `database_password` configuration parameter is set, it will override any password specified in the `database_url`. -It does not need to be percent-encoded. -This allows you to keep the password separate from the connection string, which can be useful for security purposes, especially when storing configurations in version control systems. - -### OpenID Connect (OIDC) Authentication - -OpenID Connect (OIDC) is a secure way to let users log in to your SQLPage application using their existing accounts from popular services. When OIDC is configured, you can control which parts of your application require authentication using the `oidc_protected_paths` option. By default, all pages are protected. You can specify a list of URL prefixes to protect specific areas, allowing you to have a mix of public and private pages. - -To set up OIDC, you'll need to: -1. Register your application with an OIDC provider -2. Configure the provider's details in SQLPage - -#### Setting Your Application's Address - -When users log in through an OIDC provider, they need to be sent back to your application afterward. For this to work correctly, you need to tell SQLPage where your application is located online: - -- Use the `host` setting to specify your application's web address (for example, "myapp.example.com") -- If you already have the `https_domain` setting set (to fetch https certificates for your site), then you don't need to duplicate it into `host`. - -Example configuration: -```json -{ - "oidc_issuer_url": "https://accounts.google.com", - "oidc_client_id": "your-client-id", - "oidc_client_secret": "your-client-secret", - "host": "myapp.example.com" -} -``` - -#### Cloud Identity Providers - -- **Google** - - Documentation: https://developers.google.com/identity/openid-connect/openid-connect - - Set *oidc_issuer_url* to `https://accounts.google.com` - -- **Microsoft Entra ID** (formerly Azure AD) - - Documentation: https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app - - Set *oidc_issuer_url* to `https://login.microsoftonline.com/{tenant}/v2.0` - - ([Find your tenant name](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#find-your-apps-openid-configuration-document-uri)) - -- **GitHub** - - Issuer URL: `https://github.com` - - Documentation: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps - -#### Self-Hosted Solutions - -- **Keycloak** - - Issuer URL: `https://your-keycloak-server/auth/realms/your-realm` - - [Setup Guide](https://www.keycloak.org/getting-started/getting-started-docker) - -- **Authentik** - - Issuer URL: `https://your-authentik-server/application/o/your-application` - - [Setup Guide](https://goauthentik.io/docs/providers/oauth2) - -After registering your application with the provider, you'll receive a client ID and client secret. These are used to configure SQLPage to work with your chosen provider. - -Note: OIDC is optional. If you don't configure it, your SQLPage application will be accessible without authentication. - -### Example `.env` file - -```bash -DATABASE_URL="postgres://my_user@localhost:5432/my_database?sslmode=verify-ca&sslrootcert=/path/to/ca.pem" -DATABASE_PASSWORD="my_secure_password" -SQLITE_EXTENSIONS="mod_spatialite crypto define regexp" -``` - -## Custom components - -SQLPage allows you to create custom components in addition to or instead of the default ones. -To create a custom component, create a [`.handlebars`](https://handlebarsjs.com/guide/expressions.html) -file in the `sqlpage/templates` directory of your SQLPage installation. - -For instance, if you want to create a custom `my_component` component, that displays the value of the `my_column` column, create a `sqlpage/templates/my_component.handlebars` file with the following content: - -```handlebars - -``` - -[See the full custom component documentation](https://sql-page.com/custom_components.sql). - -## Directories - -SQLPage needs two important directories to work: the configuration directory, and the web root. - -### Configuration directory - -The configuration directory is the `./sqlpage/` folder by default. -In the [official docker image](https://hub.docker.com/r/lovasoa/sqlpage), it is located in `/etc/sqlpage/`. -It can be configured using the `--config-dir` command-line argument, or the `SQLPAGE_CONFIGURATION_DIRECTORY` environment variable. - -It can contain - - - the [`sqlpage.json`](#configuring-sqlpage) configuration file, - - the [`templates`](#custom-components) directory, - - the [`migrations`](#migrations) directory, - - the [connection management](#connection-management) sql files. - -### Web Root - -The web root is where you place your sql files. -By default, it is set to the current working directory, from which the sqlpage binary is launched. -In the [official docker image](https://hub.docker.com/r/lovasoa/sqlpage), the web root is set to `/var/www`. -It can be configured using the `--web-root` command-line argument, or the `SQLPAGE_WEB_ROOT` environment variable. - -## Connection management - -### Connection initialization scripts - -SQLPage allows you to run a SQL script when a new database connection is opened, -by simply creating a `sqlpage/on_connect.sql` file. - -This can be useful to set up the database connection for your application. -For instance, on postgres, you can use this to [set the `search path` and various other connection options](https://www.postgresql.org/docs/current/sql-set.html). - -```sql -SET TIME ZONE 'UTC'; -SET search_path = my_schema; -``` - -On SQLite, you can use this to [`ATTACH`](https://www.sqlite.org/lang_attach.html) additional databases. - -```sql -ATTACH DATABASE '/path/to/my_other_database.db' AS my_other_database; -``` - -(and then, you can use `my_other_database.my_table` in your queries) - -You can also use this to create *temporary tables* to store intermediate results that are useful in your SQLPage application, but that you don't want to store permanently in the database. - -```sql -CREATE TEMPORARY TABLE my_temporary_table( - my_temp_column TEXT -); -``` - -### Connection cleanup scripts: `on_reset.sql` - -SQLPage allows you to run a SQL script after a request has been processed, -by simply creating a `sqlpage/on_reset.sql` file. - -This can be useful to clean up temporary tables, -rollback transactions that were left open, -or other resources that were created during the request. - -You can also use this script to close database connections that are -in an undesirable state, such as being in a transaction that was left open. -To close a connection, write a select statement that returns a single row -with a single boolean column named `is_healthy`, and set it to false. - -#### Rollback transactions - -You can automatically rollback any open transactions -when a connection is returned to the pool, -so that a new request is never executed in the context of an open transaction from a previous request. - -For this to work, you need to create a `sqlpage/on_reset.sql` containing the following line: - -```sql -ROLLBACK; -``` - -#### Cleaning up all connection state - -Some databases allow you to clean up all the state associatPed with a connection. - -##### PostgreSQL - -By creating a `sqlpage/on_reset.sql` file containing a [`DISCARD ALL`](https://www.postgresql.org/docs/current/sql-discard.html) statement. - -```sql -DISCARD ALL; -``` +Every configuration option can also be supplied as an uppercase environment variable. For example, `database_url` becomes `DATABASE_URL`. Variables prefixed with `SQLPAGE_`, such as `SQLPAGE_DATABASE_URL`, are also accepted. -##### SQL Server +Values are applied in this order, from lowest to highest priority: -By creating a `sqlpage/on_reset.sql` file containing a call to the [`sp_reset_connection`](https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/system-stored-procedures-transact-sql?view=sql-server-ver16#api-system-stored-procedures) stored procedure. +1. built-in defaults; +2. `sqlpage/sqlpage.json`; +3. unprefixed environment variables; +4. `SQLPAGE_`-prefixed environment variables; +5. command-line arguments, where available. -```sql -EXEC sp_reset_connection; -``` +A local `.env` file can be used to define environment variables during development. Do not commit secrets such as database passwords or OIDC client secrets. -## Migrations +## Database connection strings -SQLPage allows you to run SQL scripts when the database schema changes, by creating a `sqlpage/migrations` directory. -We have a guide on [how to create migrations](https://sql-page.com/your-first-sql-website/migrations.sql). +Set `database_url` (or `DATABASE_URL`) to a URL supported by your database driver, for example: -## Custom URL routes +- SQLite: `sqlite://sqlpage.db?mode=rwc` +- PostgreSQL: `postgres://user:password@localhost/database` +- MySQL: `mysql://user:password@localhost/database` +- Microsoft SQL Server: `mssql://user:password@localhost/database` +- ODBC: an ODBC connection string such as `DSN=DuckDB` -By default, SQLPage encourages a simple mapping between the URL and the SQL file that is executed. -You can also create custom URL routes by creating [`404.sql` files](https://sql-page.com/your-first-sql-website/custom_urls.sql). -If you need advanced routing, you can also [add a reverse proxy in front of SQLPage](https://sql-page.com/your-first-sql-website/nginx.sql). +Percent-encode special characters in URL usernames and passwords. You can keep the password separate from the URL with `database_password` or `DATABASE_PASSWORD`. diff --git a/examples/CRUD - Authentication/sqlpage/sqlpage.json b/examples/CRUD - Authentication/sqlpage/sqlpage.json index c335b8b12..1a4c616d7 100644 --- a/examples/CRUD - Authentication/sqlpage/sqlpage.json +++ b/examples/CRUD - Authentication/sqlpage/sqlpage.json @@ -1,4 +1,5 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "listen_on": "0.0.0.0:8080", "database_url": "sqlite://./db/Components.sqlite?mode=rwc", "allow_exec": false, diff --git a/examples/PostGIS - using sqlpage with geographic data/sqlpage/sqlpage.json b/examples/PostGIS - using sqlpage with geographic data/sqlpage/sqlpage.json index 5216cfac5..7e94f0d6d 100644 --- a/examples/PostGIS - using sqlpage with geographic data/sqlpage/sqlpage.json +++ b/examples/PostGIS - using sqlpage with geographic data/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "database_url": "postgres://my_username:my_password@localhost:5432/my_geographic_database" } diff --git a/examples/SQLPage developer user interface/sqlpage/sqlpage.json b/examples/SQLPage developer user interface/sqlpage/sqlpage.json index 6759cb60e..a05cfa172 100644 --- a/examples/SQLPage developer user interface/sqlpage/sqlpage.json +++ b/examples/SQLPage developer user interface/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "content_security_policy": "script-src blob: 'self' https://cdn.jsdelivr.net;" } diff --git a/examples/image gallery with user uploads/sqlpage/sqlpage.json b/examples/image gallery with user uploads/sqlpage/sqlpage.json index d7dc65b38..7ca4f3d4c 100644 --- a/examples/image gallery with user uploads/sqlpage/sqlpage.json +++ b/examples/image gallery with user uploads/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "max_uploaded_file_size": 5000000 } diff --git a/examples/light-dark-toggle/sqlpage/sqlpage.json b/examples/light-dark-toggle/sqlpage/sqlpage.json new file mode 100644 index 000000000..77298ba38 --- /dev/null +++ b/examples/light-dark-toggle/sqlpage/sqlpage.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", + "database_url": "sqlite://:memory:", + "port": 5005 +} diff --git a/examples/light-dark-toggle/sqlpage/sqlpage.yaml b/examples/light-dark-toggle/sqlpage/sqlpage.yaml deleted file mode 100644 index 69c042e67..000000000 --- a/examples/light-dark-toggle/sqlpage/sqlpage.yaml +++ /dev/null @@ -1,2 +0,0 @@ -database_url: "sqlite://:memory:" -port: 5005 diff --git a/examples/make a geographic data application using sqlite extensions/sqlpage/sqlpage.json b/examples/make a geographic data application using sqlite extensions/sqlpage/sqlpage.json index 7835c9be5..3ff8c1355 100644 --- a/examples/make a geographic data application using sqlite extensions/sqlpage/sqlpage.json +++ b/examples/make a geographic data application using sqlite extensions/sqlpage/sqlpage.json @@ -1,3 +1,6 @@ { - "sqlite_extensions": ["mod_spatialite"] + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", + "sqlite_extensions": [ + "mod_spatialite" + ] } diff --git a/examples/nginx/sqlpage_config/sqlpage.json b/examples/nginx/sqlpage_config/sqlpage.json index 1c5649627..36775e364 100644 --- a/examples/nginx/sqlpage_config/sqlpage.json +++ b/examples/nginx/sqlpage_config/sqlpage.json @@ -1,4 +1,5 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "max_database_pool_connections": 10, "database_connection_idle_timeout_seconds": 1800, "max_uploaded_file_size": 10485760, diff --git a/examples/official-site/configuration.sql b/examples/official-site/configuration.sql new file mode 100644 index 000000000..dfb1fd32b --- /dev/null +++ b/examples/official-site/configuration.sql @@ -0,0 +1,51 @@ +-- Generated by scripts/generate_configuration_reference.py from sqlpage/sqlpage.schema.json. +-- Do not edit this file directly. +select 'dynamic' as component, json_patch(json_extract(properties, '$[0]'), json_object( + 'title', 'SQLPage configuration reference' +)) as properties from example where component = 'shell' limit 1; + +select 'text' as component; +select '# SQLPage configuration reference + +SQLPage reads `sqlpage/sqlpage.json`. Add the [`$schema`](https://json-schema.org/understanding-json-schema/reference/schema) property to get validation and editor completion. Every option can also be supplied as an uppercase environment variable; environment variables override file values. + +The [JSON Schema](https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json) is the source of truth for this reference and SQLPage''s in-memory configuration structure.' as contents_md; + +select 'table' as component, true as sort, true as search; +select 'database_url' as option, 'string' as type, '—' as default_value, 'Database connection URL or ODBC connection string.' as description union all +select 'database_password' as option, 'string' as type, '—' as default_value, 'Database password. When set, overrides a password embedded in database_url.' as description union all +select 'max_database_pool_connections' as option, 'integer' as type, '—' as default_value, 'Maximum number of simultaneous database connections.' as description union all +select 'database_connection_idle_timeout_seconds' as option, 'number' as type, '—' as default_value, 'Close idle database connections after this many seconds. Use 0 to disable.' as description union all +select 'database_connection_max_lifetime_seconds' as option, 'number' as type, '—' as default_value, 'Close database connections after this many seconds regardless of activity. Use 0 to disable.' as description union all +select 'sqlite_extensions' as option, 'array' as type, '[]' as default_value, 'SQLite extensions to load when connecting.' as description union all +select 'listen_on' as option, 'string' as type, '—' as default_value, 'Socket address on which the HTTP server listens. Defaults to 0.0.0.0:8080, or port 443 with https_domain.' as description union all +select 'port' as option, 'integer or string' as type, '—' as default_value, 'TCP port on which to listen. A Kubernetes service URI is ignored for compatibility.' as description union all +select 'unix_socket' as option, 'string' as type, '—' as default_value, 'UNIX socket path to listen on instead of TCP.' as description union all +select 'database_connection_retries' as option, 'integer' as type, '6' as default_value, 'Number of database connection retries at startup, five seconds apart.' as description union all +select 'database_connection_acquire_timeout_seconds' as option, 'number' as type, '10' as default_value, 'Maximum seconds to wait for a connection from the database pool.' as description union all +select 'web_root' as option, 'string' as type, '"."' as default_value, 'Directory containing the SQL files served by SQLPage.' as description union all +select 'configuration_directory' as option, 'string' as type, '"./sqlpage"' as default_value, 'Directory containing SQLPage configuration, templates, and migrations.' as description union all +select 'allow_exec' as option, 'boolean' as type, 'false' as default_value, 'Allow SQL queries to call sqlpage.exec. Enable only when all SQL authors are trusted.' as description union all +select 'max_uploaded_file_size' as option, 'integer' as type, '5242880' as default_value, 'Maximum uploaded file size in bytes.' as description union all +select 'oidc_issuer_url' as option, 'string' as type, '—' as default_value, 'Base URL of the OpenID Connect provider.' as description union all +select 'oidc_client_id' as option, 'string' as type, '"sqlpage"' as default_value, 'OIDC client ID assigned to SQLPage.' as description union all +select 'oidc_client_secret' as option, 'string' as type, '—' as default_value, 'OIDC client secret assigned to SQLPage.' as description union all +select 'oidc_scopes' as option, 'string' as type, '"openid email profile"' as default_value, 'Space-separated OIDC scopes requested during authentication.' as description union all +select 'oidc_protected_paths' as option, 'array' as type, '["/"]' as default_value, 'Path prefixes that require OIDC authentication. Public paths take precedence.' as description union all +select 'oidc_public_paths' as option, 'array' as type, '[]' as default_value, 'Path prefixes excluded from OIDC authentication.' as description union all +select 'oidc_additional_trusted_audiences' as option, 'array' as type, '—' as default_value, 'Additional trusted OIDC JWT audiences. Null trusts all additional audiences; an empty array trusts only the client ID.' as description union all +select 'https_domain' as option, 'string' as type, '—' as default_value, 'Domain name for automatic HTTPS with a Let’s Encrypt certificate.' as description union all +select 'host' as option, 'string' as type, '—' as default_value, 'Public hostname used for OIDC redirect URLs. Defaults to https_domain.' as description union all +select 'https_certificate_email' as option, 'string' as type, '—' as default_value, 'Email address used when requesting the automatic HTTPS certificate.' as description union all +select 'https_certificate_cache_dir' as option, 'string' as type, '"./sqlpage/https"' as default_value, 'Directory used to store automatic HTTPS certificates.' as description union all +select 'https_acme_directory_url' as option, 'string' as type, '"https://acme-v02.api.letsencrypt.org/directory"' as default_value, 'ACME directory URL used for automatic HTTPS certificates.' as description union all +select 'environment' as option, 'string' as type, '"development"' as default_value, 'Runtime environment, controlling whether detailed errors are shown.' as description union all +select 'site_prefix' as option, 'string' as type, '"/"' as default_value, 'URL path prefix from which the site is served. SQLPage normalizes leading and trailing slashes.' as description union all +select 'max_pending_rows' as option, 'integer' as type, '256' as default_value, 'Maximum number of result rows buffered before sending them to the client.' as description union all +select 'compress_responses' as option, 'boolean' as type, 'false' as default_value, 'Compress HTTP response bodies when supported by the client.' as description union all +select 'content_security_policy' as option, 'string' as type, '—' as default_value, 'Content-Security-Policy header template. Use {{@csp_nonce}} for SQLPage’s generated nonce.' as description union all +select 'system_root_ca_certificates' as option, 'boolean' as type, 'false' as default_value, 'Load trusted certificates from the operating system certificate store for sqlpage.fetch.' as description union all +select 'max_recursion_depth' as option, 'integer' as type, '10' as default_value, 'Maximum recursion depth for run_sql.' as description union all +select 'markdown_allow_dangerous_html' as option, 'boolean' as type, 'false' as default_value, 'Allow raw HTML in Markdown.' as description union all +select 'markdown_allow_dangerous_protocol' as option, 'boolean' as type, 'false' as default_value, 'Allow dangerous URL protocols in Markdown links.' as description union all +select 'cache_stale_duration_ms' as option, 'integer' as type, '—' as default_value, 'Milliseconds for which cached SQL files remain fresh. Defaults to 0 in development and 1000 in production.' as description; diff --git a/examples/official-site/llms.txt.sql b/examples/official-site/llms.txt.sql index a1f5cda56..766f20caf 100644 --- a/examples/official-site/llms.txt.sql +++ b/examples/official-site/llms.txt.sql @@ -31,7 +31,7 @@ Key features: component ) || ' built-in UI components with parameters and examples - [Functions reference](/functions.sql): SQLPage built-in functions for handling requests, encoding data, and more -- [Configuration guide](https://github.com/sqlpage/SQLPage/blob/main/configuration.md): Complete list of configuration options in sqlpage.json +- [Configuration guide](/configuration.sql): Complete list of configuration options in sqlpage.json ## Components diff --git a/examples/official-site/safety.sql b/examples/official-site/safety.sql index a9af1f140..127f6ad67 100644 --- a/examples/official-site/safety.sql +++ b/examples/official-site/safety.sql @@ -147,7 +147,7 @@ For more information, see the [this discussion](https://github.com/sqlpage/SQLPa ## Database connections SQLPage uses a fixed pool of database connections, and will never open more connections than the ones you -[configured](https://github.com/sqlpage/SQLPage/blob/main/configuration.md). So even under heavy load, your database +[configured](/configuration.sql). So even under heavy load, your database connection limit will never be saturated by SQLPage. And SQLPage will accept any restriction you put on the database user you use to connect to your database, so you can diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index 6294ebed6..611b243fc 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -1317,7 +1317,7 @@ and to include custom visual styles (with CSS) or interactive behavior (with Jav '); INSERT INTO parameter(component, name, description_md, type, top_level, optional) SELECT 'shell', * FROM (VALUES - ('favicon', 'The URL of the icon the web browser should display in bookmarks and tabs. This property is particularly useful if multiple sites are hosted on the same domain with different [``site_prefix``](https://github.com/sqlpage/SQLPage/blob/main/configuration.md#configuring-sqlpage).', 'URL', TRUE, TRUE), + ('favicon', 'The URL of the icon the web browser should display in bookmarks and tabs. This property is particularly useful if multiple sites are hosted on the same domain with different [``site_prefix``](/configuration.sql).', 'URL', TRUE, TRUE), ('manifest', 'The location of the [manifest.json](https://developer.mozilla.org/en-US/docs/Web/Manifest) if the site is a [PWA](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps). Among other features, serving a manifest enables your site to be "installed" as an app on most mobile devices.', 'URL', TRUE, TRUE) ) x; INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'shell', * FROM (VALUES @@ -1399,7 +1399,7 @@ You see the [page layouts demo](./examples/layouts.sql) for a live example of th {"link": "/functions.sql", "title": "SQLPage Functions", "icon": "math-function"}, {"link": "/extensions-to-sql", "title": "Extensions to SQL", "icon": "cube-plus"}, {"link": "/custom_components.sql", "title": "Custom Components", "icon": "puzzle"}, - {"link": "//github.com/sqlpage/SQLPage/blob/main/configuration.md#configuring-sqlpage", "title": "Configuration", "icon": "settings"} + {"link": "/configuration.sql", "title": "Configuration", "icon": "settings"} ]}, {"title": "Search", "link": "/search"} ], diff --git a/examples/official-site/sqlpage/migrations/08_functions.sql b/examples/official-site/sqlpage/migrations/08_functions.sql index 3a5fd4a4d..5872094a7 100644 --- a/examples/official-site/sqlpage/migrations/08_functions.sql +++ b/examples/official-site/sqlpage/migrations/08_functions.sql @@ -259,7 +259,7 @@ Currently running from `/home/user/my_sqlpage_website` The current working directory is the directory from which the SQLPage server process was started. By default, this is also the directory from which `.sql` files are loaded and served. -However, this can be changed by setting the `web_root` [configuration option](https://github.com/sqlpage/SQLPage/blob/main/configuration.md). +However, this can be changed by setting the `web_root` [configuration option](/configuration.sql). ' ); INSERT INTO sqlpage_functions ( @@ -291,7 +291,7 @@ SQL files are served from `/home/user/my_sqlpage_website` The web root is the directory from which `.sql` files are loaded and served. By default, it is the current working directory, but it can be changed using: - the `--web-root` command line argument - - the `web_root` [configuration option](https://github.com/sqlpage/SQLPage/blob/main/configuration.md) in `sqlpage.json` + - the `web_root` [configuration option](/configuration.sql) in `sqlpage.json` - the `WEB_ROOT` environment variable This is more reliable than `sqlpage.current_working_directory()` when you need to reference the location of your SQL files. diff --git a/examples/official-site/sqlpage/migrations/24_read_file_as_data_url.sql b/examples/official-site/sqlpage/migrations/24_read_file_as_data_url.sql index eba68adc3..d99552533 100644 --- a/examples/official-site/sqlpage/migrations/24_read_file_as_data_url.sql +++ b/examples/official-site/sqlpage/migrations/24_read_file_as_data_url.sql @@ -14,7 +14,7 @@ containing the contents of the given file. The file path is relative to the `web root` directory, which is the directory from which your website is served. By default, this is the directory SQLPage is launched from, but you can change it -with the `web_root` [configuration option](https://github.com/sqlpage/SQLPage/blob/main/configuration.md). +with the `web_root` [configuration option](/configuration.sql). If the given argument is null, the function will return null. diff --git a/examples/official-site/sqlpage/migrations/26_v0.17_release.sql b/examples/official-site/sqlpage/migrations/26_v0.17_release.sql index 60bff5990..2a4998efe 100644 --- a/examples/official-site/sqlpage/migrations/26_v0.17_release.sql +++ b/examples/official-site/sqlpage/migrations/26_v0.17_release.sql @@ -48,7 +48,7 @@ insert into files (url) values (sqlpage.read_file_as_data_url(sqlpage.uploaded_f returning ''text'' as component, ''Uploaded new file with id: '' || id as contents; ``` -The maximum size of uploaded files is configurable with the [`max_uploaded_file_size`](https://github.com/sqlpage/SQLPage/blob/main/configuration.md) +The maximum size of uploaded files is configurable with the [`max_uploaded_file_size`](/configuration.sql) configuration parameter. By default, it is set to 5 MiB. ### Parsing CSV files @@ -120,7 +120,7 @@ And while we''re at it, SQLPage also supports HTTP/2, for even faster page loads To enable HTTPS, you need to buy a [domain name](https://en.wikipedia.org/wiki/Domain_name) and make it point to the server where SQLPage is running. -Then set the `https_domain` configuration parameter to `yourdomain.com` in your [`sqlpage.json` configuration file](./configuration.md). +Then set the `https_domain` configuration parameter to `yourdomain.com` in your [`sqlpage.json` configuration file](/configuration.sql). ```json { @@ -163,7 +163,7 @@ and to create JSON APIs. - The `cookie` component now supports setting an explicit expiration date for cookies. - The `cookie` component now supports setting the `SameSite` attribute of cookies, and defaults to `SameSite=Strict` for all cookies. What this means in practice is that cookies set by SQLPage will not be sent to your website if the user is coming from another website. This prevents someone from tricking your users into executing SQLPage queries on your website by sending them a malicious link. - Bugfix: setting `min` or `max` to `0` in a number field in the `form` component now works as expected. - - Added support for `.env` files to set SQLPage''s [environment variables](./configuration.md#environment-variables). + - Added support for `.env` files to set SQLPage''s [environment variables](/configuration.sql). - Better responsive design in the card component. Up to 5 cards per line on large screens. The number of cards per line is still customizable with the `columns` attribute. - [New icons](https://tabler-icons.io/changelog): - ![new icons in tabler 42](https://github.com/tabler/tabler-icons/assets/1282324/00856af9-841d-4aa9-995d-121c7ddcc005) diff --git a/examples/official-site/sqlpage/migrations/69_blog_performance_guide.sql b/examples/official-site/sqlpage/migrations/69_blog_performance_guide.sql index 6dcdf1119..ed4bca42c 100644 --- a/examples/official-site/sqlpage/migrations/69_blog_performance_guide.sql +++ b/examples/official-site/sqlpage/migrations/69_blog_performance_guide.sql @@ -269,13 +269,13 @@ SQLPage uses connection pooling: it keeps multiple database connections opened, and reuses them for consecutive requests. When it does not receive requests for a long time, it closes idle connection. When it receives many requests, it opens new connection, but never more than the value specified by `max_database_pool_connections` in its -[configuration](https://github.com/sqlpage/SQLPage/blob/main/configuration.md). +[configuration](/configuration.sql). You can increase the value of that parameter if your website has many concurrent users and your database is configured to allow opening many simultaneous connections. ### SQLPage performance debugging -When `environment` is set to `development` in its [configuration](https://github.com/sqlpage/SQLPage/blob/main/configuration.md), +When `environment` is set to `development` in its [configuration](/configuration.sql), SQLPage will include precise measurement of the time it spends in each of the steps it has to go through before starting to send data back to the user''s browser. You can visualize that performance data in your browser''s network inspector. diff --git a/examples/official-site/sqlpage/sqlpage.json b/examples/official-site/sqlpage/sqlpage.json new file mode 100644 index 000000000..4dafcc04c --- /dev/null +++ b/examples/official-site/sqlpage/sqlpage.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", + "database_url": "sqlite::memory:?cache=shared", + "max_uploaded_file_size": 256000, + "database_connection_acquire_timeout_seconds": 30 +} diff --git a/examples/official-site/sqlpage/sqlpage.yaml b/examples/official-site/sqlpage/sqlpage.yaml deleted file mode 100644 index 8d0cfbeb4..000000000 --- a/examples/official-site/sqlpage/sqlpage.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# The documentation site is fully static, so we don't need to persist any data. -database_url: "sqlite::memory:?cache=shared" - -# We have a file upload example, and would like to limit the size of the uploaded files -max_uploaded_file_size: 256000 - -database_connection_acquire_timeout_seconds: 30 diff --git a/examples/official-site/your-first-sql-website/nginx.md b/examples/official-site/your-first-sql-website/nginx.md index 9e156135f..c76f80557 100644 --- a/examples/official-site/your-first-sql-website/nginx.md +++ b/examples/official-site/your-first-sql-website/nginx.md @@ -80,7 +80,7 @@ but will increase the load on your SQLPage server, and reduce the amount of user Refer to the official documentation for [proxy buffering](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering), [gzip](https://nginx.org/en/docs/http/ngx_http_gzip_module.html), and [chunked transfer](https://nginx.org/en/docs/http/ngx_http_core_module.html#chunked_transfer_encoding) when tuning these values. -When SQLPage sits behind a reverse proxy, set `compress_responses` to `false` [in `sqlpage.json`](https://github.com/sqlpage/SQLPage/blob/main/configuration.md) so that NGINX compresses once at the edge. +When SQLPage sits behind a reverse proxy, set `compress_responses` to `false` [in `sqlpage.json`](/configuration.sql) so that NGINX compresses once at the edge. ### URL Rewriting diff --git a/examples/official-site/your-first-sql-website/tutorial.md b/examples/official-site/your-first-sql-website/tutorial.md index 582a9463c..2037cdfab 100644 --- a/examples/official-site/your-first-sql-website/tutorial.md +++ b/examples/official-site/your-first-sql-website/tutorial.md @@ -98,7 +98,7 @@ Later, when you want to deploy your website online, you can switch back to a per > For instance, a SQL Server database named `db` running on `localhost` port `1433` with the username `funny:user` and the password `p@ssw0rd` would be represented as > `mssql://funny%3Auser:p%40ssw0rd@localhost:1433/db`. -For more information about the properties that can be set in sqlpage.json, see [SQLPage's configuration documentation](https://github.com/sqlpage/SQLPage/blob/main/configuration.md#configuring-sqlpage) +For more information about the properties that can be set in sqlpage.json, see [SQLPage's configuration documentation](/configuration.sql) ![screenshot for the full sql website folder organisation](full-website.png) @@ -188,7 +188,7 @@ If you prefer to host your website yourself, you can use a cloud provider or a V - [Setup docker](https://github.com/sqlpage/SQLPage?tab=readme-ov-file#with-docker) or another process manager such as [systemd](https://github.com/sqlpage/SQLPage/blob/main/sqlpage.service) to start SQLPage automatically when your server boots and to keep it running - Optionally, [setup a reverse proxy](nginx.sql) to avoid exposing SQLPage directly to the internet - Optionally, setup a TLS certificate to enable HTTPS -- Configure connection to a cloud database or a database running on your server in [`sqlpage.json`](https://github.com/sqlpage/SQLPage/blob/main/configuration.md#configuring-sqlpage) +- Configure connection to a cloud database or a database running on your server in [`sqlpage.json`](/configuration.sql) # Go further diff --git a/examples/read-and-set-http-cookies/sqlpage/sqlpage.json b/examples/read-and-set-http-cookies/sqlpage/sqlpage.json index 268bd02be..62174ecb3 100644 --- a/examples/read-and-set-http-cookies/sqlpage/sqlpage.json +++ b/examples/read-and-set-http-cookies/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "database_url": "sqlite://:memory:" } diff --git a/examples/simple-website-example/sqlpage/sqlpage.json b/examples/simple-website-example/sqlpage/sqlpage.json index 268bd02be..62174ecb3 100644 --- a/examples/simple-website-example/sqlpage/sqlpage.json +++ b/examples/simple-website-example/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "database_url": "sqlite://:memory:" } diff --git a/examples/single sign on/sqlpage/sqlpage.json b/examples/single sign on/sqlpage/sqlpage.json new file mode 100644 index 000000000..547bbd437 --- /dev/null +++ b/examples/single sign on/sqlpage/sqlpage.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", + "oidc_issuer_url": "http://localhost:8181/realms/sqlpage_demo", + "oidc_client_id": "sqlpage", + "oidc_client_secret": "qiawfnYrYzsmoaOZT28rRjPPRamfvrYr", + "oidc_protected_paths": [ + "/protected" + ], + "oidc_public_paths": [ + "/protected/public" + ], + "oidc_additional_trusted_audiences": [] +} diff --git a/examples/single sign on/sqlpage/sqlpage.yaml b/examples/single sign on/sqlpage/sqlpage.yaml deleted file mode 100644 index 7eeace520..000000000 --- a/examples/single sign on/sqlpage/sqlpage.yaml +++ /dev/null @@ -1,6 +0,0 @@ -oidc_issuer_url: http://localhost:8181/realms/sqlpage_demo # Given by keycloak as the "OpenID Endpoint Configuration" url. -oidc_client_id: sqlpage # configured in keycloak (http://localhost:8181/admin/master/console/#/sqlpage_demo/clients/a2bec2b8-f850-405e-9f26-59063ffa6f08/settings) -oidc_client_secret: qiawfnYrYzsmoaOZT28rRjPPRamfvrYr # For a safer setup, use environment variables to store this -oidc_protected_paths: ["/protected"] # Makes the website root is publicly accessible, requiring authentication only for the /protected path -oidc_public_paths: ["/protected/public"] # Adds an exception for the /protected/public path, which is publicly accessible too -oidc_additional_trusted_audiences: [] # For increased security, reject any token that has more than just the client ID in the "aud" claim diff --git a/examples/telemetry/sqlpage/sqlpage.json b/examples/telemetry/sqlpage/sqlpage.json index 5322302db..e5ed3bd2e 100644 --- a/examples/telemetry/sqlpage/sqlpage.json +++ b/examples/telemetry/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "listen_on": "0.0.0.0:8080" } diff --git a/examples/todo application/sqlpage/sqlpage.json b/examples/todo application/sqlpage/sqlpage.json index 086aa292d..bbd4630c4 100644 --- a/examples/todo application/sqlpage/sqlpage.json +++ b/examples/todo application/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "database_url": "sqlite://./sqlpage/sqlpage.db?mode=rwc" } diff --git a/examples/web servers - apache/sqlpage_config/sqlpage.json b/examples/web servers - apache/sqlpage_config/sqlpage.json index 76f13c266..7764cd7c1 100644 --- a/examples/web servers - apache/sqlpage_config/sqlpage.json +++ b/examples/web servers - apache/sqlpage_config/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "site_prefix": "/my_website" } diff --git a/scripts/check_configuration_examples.py b/scripts/check_configuration_examples.py new file mode 100755 index 000000000..a7511d40a --- /dev/null +++ b/scripts/check_configuration_examples.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Check that every checked-in example JSON configuration references the live schema.""" +import json +import os +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA_PATH = ROOT / "sqlpage/sqlpage.schema.json" +EXPECTED_SCHEMA = json.loads(SCHEMA_PATH.read_text())["$id"] +configs = [] +for directory, subdirectories, files in os.walk(ROOT): + subdirectories[:] = [name for name in subdirectories if name not in {".git", "node_modules", "target"}] + if "sqlpage.json" in files: + configs.append(Path(directory) / "sqlpage.json") + +missing = [] +for path in configs: + configuration = json.loads(path.read_text()) + if configuration.get("$schema") != EXPECTED_SCHEMA: + missing.append(path.relative_to(ROOT)) +if missing: + raise SystemExit("Configurations missing the live $schema reference:\n" + "\n".join(map(str, missing))) +print(f"Checked {len(configs)} example configurations") diff --git a/scripts/generate_configuration_reference.py b/scripts/generate_configuration_reference.py new file mode 100755 index 000000000..1bcc3ce53 --- /dev/null +++ b/scripts/generate_configuration_reference.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Generate the official-site configuration reference from the JSON Schema.""" +import argparse +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA_PATH = ROOT / "sqlpage/sqlpage.schema.json" +OUTPUT_PATH = ROOT / "examples/official-site/configuration.sql" + +def sql(value: str) -> str: + return value.replace("'", "''") + +def display_type(spec: dict) -> str: + value = spec.get("type", "") + if isinstance(value, list): + value = [item for item in value if item != "null"] + return " or ".join(value) + return value + +def display_default(spec: dict) -> str: + if "default" not in spec: + return "—" + return json.dumps(spec["default"], ensure_ascii=False) + +parser = argparse.ArgumentParser() +parser.add_argument("--check", action="store_true", help="fail when the checked-in reference is stale") +args = parser.parse_args() + +schema = json.loads(SCHEMA_PATH.read_text()) +properties = [(name, spec) for name, spec in schema["properties"].items() if name != "$schema"] +rows = "\n".join( + f"select '{sql(name)}' as option, '{sql(display_type(spec))}' as type, " + f"'{sql(display_default(spec))}' as default_value, '{sql(spec['description'])}' as description" + + (" union all" if index < len(properties) - 1 else ";") + for index, (name, spec) in enumerate(properties) +) +content = f"""-- Generated by scripts/generate_configuration_reference.py from sqlpage/sqlpage.schema.json. +-- Do not edit this file directly. +select 'dynamic' as component, json_patch(json_extract(properties, '$[0]'), json_object( + 'title', 'SQLPage configuration reference' +)) as properties from example where component = 'shell' limit 1; + +select 'text' as component; +select '# SQLPage configuration reference\n\nSQLPage reads `sqlpage/sqlpage.json`. Add the [`$schema`](https://json-schema.org/understanding-json-schema/reference/schema) property to get validation and editor completion. Every option can also be supplied as an uppercase environment variable; environment variables override file values.\n\nThe [JSON Schema]({schema['$id']}) is the source of truth for this reference and SQLPage''s in-memory configuration structure.' as contents_md; + +select 'table' as component, true as sort, true as search; +{rows} +""" +if args.check: + if OUTPUT_PATH.read_text() != content: + raise SystemExit(f"{OUTPUT_PATH.relative_to(ROOT)} is stale; run {Path(__file__).relative_to(ROOT)}") +else: + OUTPUT_PATH.write_text(content) diff --git a/sqlpage/sqlpage.json b/sqlpage/sqlpage.json index 086aa292d..bbd4630c4 100644 --- a/sqlpage/sqlpage.json +++ b/sqlpage/sqlpage.json @@ -1,3 +1,4 @@ { + "$schema": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", "database_url": "sqlite://./sqlpage/sqlpage.db?mode=rwc" } diff --git a/sqlpage/sqlpage.schema.json b/sqlpage/sqlpage.schema.json new file mode 100644 index 000000000..ae598168e --- /dev/null +++ b/sqlpage/sqlpage.schema.json @@ -0,0 +1,336 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", + "title": "SQLPage configuration", + "description": "Configuration options accepted by SQLPage in sqlpage.json. Every option can also be supplied as an uppercase environment variable.", + "type": "object", + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string", + "format": "uri", + "description": "JSON Schema used to validate this configuration file.", + "default": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json", + "x-rust-exclude": true, + "const": "https://github.com/sqlpage/SQLPage/raw/refs/heads/main/sqlpage/sqlpage.schema.json" + }, + "database_url": { + "description": "Database connection URL or ODBC connection string.", + "x-rust-type": "String", + "type": "string", + "x-rust-serde": "default = \"default_database_url\"" + }, + "database_password": { + "description": "Database password. When set, overrides a password embedded in database_url.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ], + "x-rust-serde": "default" + }, + "max_database_pool_connections": { + "description": "Maximum number of simultaneous database connections.", + "x-rust-type": "Option", + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "database_connection_idle_timeout_seconds": { + "description": "Close idle database connections after this many seconds. Use 0 to disable.", + "x-rust-type": "Option", + "type": [ + "number", + "null" + ], + "x-rust-serde": "default, deserialize_with = \"deserialize_duration_seconds\", rename = \"database_connection_idle_timeout_seconds\"", + "x-rust-field-name": "database_connection_idle_timeout", + "minimum": 0 + }, + "database_connection_max_lifetime_seconds": { + "description": "Close database connections after this many seconds regardless of activity. Use 0 to disable.", + "x-rust-type": "Option", + "type": [ + "number", + "null" + ], + "x-rust-serde": "default, deserialize_with = \"deserialize_duration_seconds\", rename = \"database_connection_max_lifetime_seconds\"", + "x-rust-field-name": "database_connection_max_lifetime", + "minimum": 0 + }, + "sqlite_extensions": { + "description": "SQLite extensions to load when connecting.", + "x-rust-type": "Vec", + "type": "array", + "default": [], + "x-rust-serde": "default", + "items": { + "type": "string" + } + }, + "listen_on": { + "description": "Socket address on which the HTTP server listens. Defaults to 0.0.0.0:8080, or port 443 with https_domain.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ], + "x-rust-serde": "default, deserialize_with = \"deserialize_socket_addr\"" + }, + "port": { + "description": "TCP port on which to listen. A Kubernetes service URI is ignored for compatibility.", + "x-rust-type": "Option", + "type": [ + "integer", + "string", + "null" + ], + "x-rust-serde": "default, deserialize_with = \"deserialize_port\"", + "minimum": 0, + "maximum": 65535 + }, + "unix_socket": { + "description": "UNIX socket path to listen on instead of TCP.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ] + }, + "database_connection_retries": { + "description": "Number of database connection retries at startup, five seconds apart.", + "x-rust-type": "u32", + "type": "integer", + "default": 6, + "x-rust-serde": "default = \"default_database_connection_retries\"", + "minimum": 0 + }, + "database_connection_acquire_timeout_seconds": { + "description": "Maximum seconds to wait for a connection from the database pool.", + "x-rust-type": "f64", + "type": "number", + "default": 10, + "x-rust-serde": "default = \"default_database_connection_acquire_timeout_seconds\"", + "exclusiveMinimum": 0 + }, + "web_root": { + "description": "Directory containing the SQL files served by SQLPage.", + "x-rust-type": "PathBuf", + "type": "string", + "default": ".", + "x-rust-serde": "default = \"default_web_root\"" + }, + "configuration_directory": { + "description": "Directory containing SQLPage configuration, templates, and migrations.", + "x-rust-type": "PathBuf", + "type": "string", + "default": "./sqlpage", + "x-rust-serde": "default = \"configuration_directory\"" + }, + "allow_exec": { + "description": "Allow SQL queries to call sqlpage.exec. Enable only when all SQL authors are trusted.", + "x-rust-type": "bool", + "type": "boolean", + "default": false, + "x-rust-serde": "default" + }, + "max_uploaded_file_size": { + "description": "Maximum uploaded file size in bytes.", + "x-rust-type": "usize", + "type": "integer", + "default": 5242880, + "x-rust-serde": "default = \"default_max_file_size\"", + "minimum": 0 + }, + "oidc_issuer_url": { + "description": "Base URL of the OpenID Connect provider.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "oidc_client_id": { + "description": "OIDC client ID assigned to SQLPage.", + "x-rust-type": "String", + "type": "string", + "default": "sqlpage", + "x-rust-serde": "default = \"default_oidc_client_id\"" + }, + "oidc_client_secret": { + "description": "OIDC client secret assigned to SQLPage.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ] + }, + "oidc_scopes": { + "description": "Space-separated OIDC scopes requested during authentication.", + "x-rust-type": "String", + "type": "string", + "default": "openid email profile", + "x-rust-serde": "default = \"default_oidc_scopes\"" + }, + "oidc_protected_paths": { + "description": "Path prefixes that require OIDC authentication. Public paths take precedence.", + "x-rust-type": "Vec", + "type": "array", + "default": [ + "/" + ], + "x-rust-serde": "default = \"default_oidc_protected_paths\"", + "items": { + "type": "string", + "pattern": "^/" + } + }, + "oidc_public_paths": { + "description": "Path prefixes excluded from OIDC authentication.", + "x-rust-type": "Vec", + "type": "array", + "default": [], + "x-rust-serde": "default", + "items": { + "type": "string", + "pattern": "^/" + } + }, + "oidc_additional_trusted_audiences": { + "description": "Additional trusted OIDC JWT audiences. Null trusts all additional audiences; an empty array trusts only the client ID.", + "x-rust-type": "Option>", + "type": [ + "array", + "null" + ], + "x-rust-serde": "default", + "items": { + "type": "string" + } + }, + "https_domain": { + "description": "Domain name for automatic HTTPS with a Let’s Encrypt certificate.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ] + }, + "host": { + "description": "Public hostname used for OIDC redirect URLs. Defaults to https_domain.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ] + }, + "https_certificate_email": { + "description": "Email address used when requesting the automatic HTTPS certificate.", + "x-rust-type": "Option", + "type": [ + "string", + "null" + ], + "format": "email" + }, + "https_certificate_cache_dir": { + "description": "Directory used to store automatic HTTPS certificates.", + "x-rust-type": "PathBuf", + "type": "string", + "default": "./sqlpage/https", + "x-rust-serde": "default = \"default_https_certificate_cache_dir\"" + }, + "https_acme_directory_url": { + "description": "ACME directory URL used for automatic HTTPS certificates.", + "x-rust-type": "String", + "type": "string", + "default": "https://acme-v02.api.letsencrypt.org/directory", + "x-rust-serde": "default = \"default_https_acme_directory_url\"", + "format": "uri" + }, + "environment": { + "description": "Runtime environment, controlling whether detailed errors are shown.", + "x-rust-type": "DevOrProd", + "type": "string", + "default": "development", + "x-rust-serde": "default", + "enum": [ + "development", + "production" + ] + }, + "site_prefix": { + "description": "URL path prefix from which the site is served. SQLPage normalizes leading and trailing slashes.", + "x-rust-type": "String", + "type": "string", + "default": "/", + "x-rust-serde": "deserialize_with = \"deserialize_site_prefix\", default = \"default_site_prefix\"" + }, + "max_pending_rows": { + "description": "Maximum number of result rows buffered before sending them to the client.", + "x-rust-type": "usize", + "type": "integer", + "default": 256, + "x-rust-serde": "default = \"default_max_pending_rows\"", + "minimum": 1 + }, + "compress_responses": { + "description": "Compress HTTP response bodies when supported by the client.", + "x-rust-type": "bool", + "type": "boolean", + "default": false, + "x-rust-serde": "default = \"default_compress_responses\"" + }, + "content_security_policy": { + "description": "Content-Security-Policy header template. Use {{@csp_nonce}} for SQLPage’s generated nonce.", + "x-rust-type": "ContentSecurityPolicyTemplate", + "type": [ + "string", + "null" + ], + "x-rust-serde": "default" + }, + "system_root_ca_certificates": { + "description": "Load trusted certificates from the operating system certificate store for sqlpage.fetch.", + "x-rust-type": "bool", + "type": "boolean", + "default": false, + "x-rust-serde": "default = \"default_system_root_ca_certificates\"" + }, + "max_recursion_depth": { + "description": "Maximum recursion depth for run_sql.", + "x-rust-type": "u8", + "type": "integer", + "default": 10, + "x-rust-serde": "default = \"default_max_recursion_depth\"", + "minimum": 0, + "maximum": 255 + }, + "markdown_allow_dangerous_html": { + "description": "Allow raw HTML in Markdown.", + "x-rust-type": "bool", + "type": "boolean", + "default": false, + "x-rust-serde": "default = \"default_markdown_allow_dangerous_html\"" + }, + "markdown_allow_dangerous_protocol": { + "description": "Allow dangerous URL protocols in Markdown links.", + "x-rust-type": "bool", + "type": "boolean", + "default": false, + "x-rust-serde": "default = \"default_markdown_allow_dangerous_protocol\"" + }, + "cache_stale_duration_ms": { + "description": "Milliseconds for which cached SQL files remain fresh. Defaults to 0 in development and 1000 in production.", + "x-rust-type": "Option", + "type": [ + "integer", + "null" + ], + "minimum": 0 + } + } +} diff --git a/src/app_config.rs b/src/app_config.rs index 42760e780..f3195675b 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -165,188 +165,7 @@ pub fn load_from_env() -> anyhow::Result { .with_context(|| format!("Unable to load configuration from {}", config_dir.display())) } -#[derive(Debug, Deserialize, PartialEq, Clone)] -#[allow(clippy::struct_excessive_bools)] -pub struct AppConfig { - #[serde(default = "default_database_url")] - pub database_url: String, - /// A separate field for the database password. If set, this will override any password specified in the `database_url`. - #[serde(default)] - pub database_password: Option, - pub max_database_pool_connections: Option, - #[serde( - default, - deserialize_with = "deserialize_duration_seconds", - rename = "database_connection_idle_timeout_seconds" - )] - pub database_connection_idle_timeout: Option, - #[serde( - default, - deserialize_with = "deserialize_duration_seconds", - rename = "database_connection_max_lifetime_seconds" - )] - pub database_connection_max_lifetime: Option, - - #[serde(default)] - pub sqlite_extensions: Vec, - - #[serde(default, deserialize_with = "deserialize_socket_addr")] - pub listen_on: Option, - #[serde(default, deserialize_with = "deserialize_port")] - pub port: Option, - pub unix_socket: Option, - - /// Number of times to retry connecting to the database after a failure when the server starts - /// up. Retries will happen every 5 seconds. The default is 6 retries, which means the server - /// will wait up to 30 seconds for the database to become available. - #[serde(default = "default_database_connection_retries")] - pub database_connection_retries: u32, - - /// Maximum number of seconds to wait before giving up when acquiring a database connection from the - /// pool. The default is 10 seconds. - #[serde(default = "default_database_connection_acquire_timeout_seconds")] - pub database_connection_acquire_timeout_seconds: f64, - - /// The directory where the .sql files are located. Defaults to the current directory. - #[serde(default = "default_web_root")] - pub web_root: PathBuf, - - /// The directory where the sqlpage configuration file is located. Defaults to `./sqlpage`. - #[serde(default = "configuration_directory")] - pub configuration_directory: PathBuf, - - /// Set to true to allow the `sqlpage.exec` function to be used in SQL queries. - /// This should be enabled only if you trust the users writing SQL queries, since it gives - /// them the ability to execute arbitrary shell commands on the server. - #[serde(default)] - pub allow_exec: bool, - - /// Maximum size of uploaded files in bytes. The default is 10MiB (10 * 1024 * 1024 bytes) - #[serde(default = "default_max_file_size")] - pub max_uploaded_file_size: usize, - - /// The base URL of the `OpenID` Connect provider. - /// Required when enabling Single Sign-On through an OIDC provider. - pub oidc_issuer_url: Option, - /// The client ID assigned to `SQLPage` when registering with the OIDC provider. - /// Defaults to `sqlpage`. - #[serde(default = "default_oidc_client_id")] - pub oidc_client_id: String, - /// The client secret for authenticating `SQLPage` to the OIDC provider. - /// Required when enabling Single Sign-On through an OIDC provider. - pub oidc_client_secret: Option, - /// Space-separated list of [scopes](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) to request during OIDC authentication. - /// Defaults to "openid email profile" - #[serde(default = "default_oidc_scopes")] - pub oidc_scopes: String, - - /// Defines a list of path prefixes that should be protected by OIDC authentication. - /// By default, all paths are protected. - /// If you specify a list of prefixes, only requests whose path starts with one of the prefixes will require authentication. - /// For example, if you set this to `["/private"]`, then requests to `/private/some_page.sql` will require authentication, - /// but requests to `/index.sql` will not. - /// NOTE: `OIDC_PUBLIC_PATHS` takes precedence over `OIDC_PROTECTED_PATHS`. - /// For example, if you have `["/private"]` on the `protected_paths` like before, but also `["/private/public"]` on the `public_paths`, then `/private` requires authentication, but `/private/public` requires not authentication. - /// You cannot make a path inside a public path private again. So expanding the previous example, if you now add `/private/public/private_again`, then this path will still be accessible. - #[serde(default = "default_oidc_protected_paths")] - pub oidc_protected_paths: Vec, - - /// Defines path prefixes to exclude from OIDC authentication. - /// By default, no paths are excluded. - /// Paths matching these prefixes will not require authentication. - /// For example, if set to `["/public"]`, requests to `/public/some_page.sql` will not require authentication, - /// but requests to `/index.sql` will still require it. - /// To make `/protected/public.sql` public while protecting its containing directory, - /// set `oidc_public_paths` to `["/protected/public.sql"]` and `oidc_protected_paths` to `["/protected"]`. - /// Be aware that any path starting with `/protected/public.sql` (e.g., `/protected/public.sql.backup`) will also become public. - #[serde(default)] - pub oidc_public_paths: Vec, - - /// Additional trusted audiences for OIDC JWT tokens, beyond the client ID. - /// By default (when None), all additional audiences are trusted for compatibility - /// with providers that include multiple audience values (like ZITADEL, Azure AD, etc.). - /// Set to an empty list to only allow the client ID as audience. - /// Set to a specific list to only allow those specific additional audiences. - #[serde(default)] - pub oidc_additional_trusted_audiences: Option>, - - /// A domain name to use for the HTTPS server. If this is set, the server will perform all the necessary - /// steps to set up an HTTPS server automatically. All you need to do is point your domain name to the - /// server's IP address. - /// - /// It will listen on port 443 for HTTPS connections, - /// and will automatically request a certificate from Let's Encrypt - /// using the ACME protocol (requesting a TLS-ALPN-01 challenge). - pub https_domain: Option, - - /// The hostname where your application is publicly accessible (e.g., "myapp.example.com"). - /// This is used for OIDC redirect URLs. If not set, `https_domain` will be used instead. - pub host: Option, - - /// The email address to use when requesting a certificate from Let's Encrypt. - /// Defaults to `contact@`. - pub https_certificate_email: Option, - - /// The directory to store the Let's Encrypt certificate in. Defaults to `./sqlpage/https`. - #[serde(default = "default_https_certificate_cache_dir")] - pub https_certificate_cache_dir: PathBuf, - - /// URL to the ACME directory. Defaults to the Let's Encrypt production directory. - #[serde(default = "default_https_acme_directory_url")] - pub https_acme_directory_url: String, - - /// Whether we should run in development or production mode. Used to determine - /// whether to show error messages to the user. - #[serde(default)] - pub environment: DevOrProd, - - /// Serve the website from a sub path. For example, if you set this to `/sqlpage/`, the website will be - /// served from `https://yourdomain.com/sqlpage/`. Defaults to `/`. - /// This is useful if you want to serve the website on the same domain as other content, and - /// you are using a reverse proxy to route requests to the correct server. - #[serde( - deserialize_with = "deserialize_site_prefix", - default = "default_site_prefix" - )] - pub site_prefix: String, - - /// Maximum number of messages that can be stored in memory before sending them to the client. - /// This prevents a single request from using up all available memory. - #[serde(default = "default_max_pending_rows")] - pub max_pending_rows: usize, - - /// Whether to compress the http response body when the client supports it. - /// Enabling response compression hinders the ability of `SQLPage` to stream every single byte - /// of data as soon as it is produced. - /// As a rule of thub, enable response compression when your app is fast. - #[serde(default = "default_compress_responses")] - pub compress_responses: bool, - - /// Content-Security-Policy header to send to the client. - /// If not set, a default policy allowing - /// - scripts from the same origin, - /// - script elements with the `nonce="{{@csp_nonce}}"` attribute, - #[serde(default)] - pub content_security_policy: ContentSecurityPolicyTemplate, - - /// Whether `sqlpage.fetch` should load trusted certificates from the operating system's certificate store - /// By default, it loads Mozilla's root certificates that are embedded in the `SQLPage` binary, or the ones pointed to by the - /// `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables. - #[serde(default = "default_system_root_ca_certificates")] - pub system_root_ca_certificates: bool, - - /// Maximum depth of recursion allowed in the `run_sql` function. - #[serde(default = "default_max_recursion_depth")] - pub max_recursion_depth: u8, - - #[serde(default = "default_markdown_allow_dangerous_html")] - pub markdown_allow_dangerous_html: bool, - - #[serde(default = "default_markdown_allow_dangerous_protocol")] - pub markdown_allow_dangerous_protocol: bool, - - pub cache_stale_duration_ms: Option, -} +include!(concat!(env!("OUT_DIR"), "/app_config.rs")); impl AppConfig { #[must_use] @@ -926,6 +745,11 @@ mod test { std::fs::remove_dir_all(&temp_dir).unwrap(); } + #[test] + fn example_json_configuration_deserializes() { + serde_json::from_str::(include_str!("../sqlpage/sqlpage.json")).unwrap(); + } + #[test] fn test_default_values() { let _lock = ENV_LOCK