diff --git a/CHANGELOG.md b/CHANGELOG.md index 60cfb564..b60cab0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## 0.14.0 (unreleased) - Better error messages for Microsoft SQL Server. SQLPage now displays the line number of the error, which is especially useful for debugging long migration scripts. + - Many improvements in the official website and the documentation. + Most notably, the documentation now has syntax highlighting on code blocks. ## 0.13.0 (2023-10-16) - New [timeline](https://sql.ophir.dev/documentation.sql?component=timeline#component) component to display a timeline of events. diff --git a/examples/official-site/documentation.sql b/examples/official-site/documentation.sql index 3d15c3f7..3957ec88 100644 --- a/examples/official-site/documentation.sql +++ b/examples/official-site/documentation.sql @@ -1,6 +1,13 @@ -- This line, at the top of the page, tells web browsers to keep the page locally in cache once they have it. select 'http_header' as component, 'public, max-age=600, stale-while-revalidate=3600, stale-if-error=86400' as "Cache-Control"; -select 'dynamic' as component, properties FROM example WHERE component = 'shell' LIMIT 1; +select + 'dynamic' as component, + json_set( + properties, + '$[0].title', + 'SQLPage components' || COALESCE(': ' || $component, ' documentation') + ) as properties +FROM example WHERE component = 'shell' LIMIT 1; select 'text' as component, format('SQLPage v%s documentation', sqlpage.version()) as title; select ' @@ -86,22 +93,31 @@ select { "title": "Example ' || (row_number() OVER ()) || '", "description_md": ' || json_quote(description) || ', + "language": "sql", "contents": ' || json_quote(( select group_concat( - 'SELECT ' || char(10) || + 'select ' || char(10) || ( + with t as (select * from json_tree(top.value)) select group_concat( ' ' || - CASE typeof(value) - WHEN 'integer' THEN value::text - WHEN 'real' THEN value::text - ELSE quote(value::text) + CASE t.type + WHEN 'integer' THEN t.atom + WHEN 'real' THEN t.atom + WHEN 'true' THEN 'TRUE' + WHEN 'false' THEN 'FALSE' + WHEN 'null' THEN 'NULL' + ELSE quote(t.value) END || ' as ' || - key + CASE parent.fullkey + WHEN '$' THEN t.key + ELSE parent.key + END , ',' || char(10) - ) from json_each(top.value) + ) from t inner join t parent on parent.id = t.parent + where t.atom is not null ) || ';', char(10) ) diff --git a/examples/official-site/functions.sql b/examples/official-site/functions.sql index cd29d58f..936f0643 100644 --- a/examples/official-site/functions.sql +++ b/examples/official-site/functions.sql @@ -1,4 +1,10 @@ -select 'dynamic' as component, properties FROM example WHERE component = 'shell' LIMIT 1; +select 'dynamic' as component, + json_set( + properties, + '$[0].title', + 'SQLPage functions' || COALESCE(': ' || $function, ' documentation') + ) as properties +FROM example WHERE component = 'shell' LIMIT 1; select 'text' as component, 'SQLPage built-in functions' as title; select ' diff --git a/examples/official-site/index.sql b/examples/official-site/index.sql index 85e6e2f7..7273b43e 100644 --- a/examples/official-site/index.sql +++ b/examples/official-site/index.sql @@ -60,29 +60,36 @@ As an example, the list of features on this page is generated using a simple SQL ```sql SELECT ''card'' as component, ''What is SQLPage ?'' as title; -SELECT header AS title, contents AS description FROM homepage_features; +SELECT header AS title, contents AS description_md FROM homepage_features; ``` -Additionnally, SQLPage itself is written in a fast and secure programming language: Rust. -We made all the optimizations so that you can think about your data, and nothing else.' as description_md, +However, you can also create your own components, or edit the existing ones to customize your website to your liking. +Creating a new component is as simple as creating an HTML template file. +' as description_md, 'rocket' as icon, 'green' as color; SELECT 'Technically, it''s just a good old web server' as title, ' The principles behind SQLPage are not too far from those that powered the early days of the internet. -Like [PHP](https://en.wikipedia.org/wiki/PHP), SQLPage just receives a request, finds the file to execute, runs it, and returns a response. +Like [PHP](https://en.wikipedia.org/wiki/PHP), SQLPage just receives a request, finds the file to execute, runs it, +and returns a web page for the browser to display. + +SQLPage is a *web server* written in a fast and secure programming language: +[**Rust**](https://en.wikipedia.org/wiki/Rust_(programming_language)). +It is extremely easy to use: +you [download a single executable file](https://github.com/lovasoa/SQLpage/releases), +write an `.sql` file, and you''re done. +We made all the optimizations, wrote all of the HTTP request handling code and rendering logic, +implemented all of the security features, so that you can think about your data, and nothing else. -SQLPage is a *web server* written in -[rust](https://en.wikipedia.org/wiki/Rust_(programming_language)) -and [distributed as a single executable file](https://github.com/lovasoa/SQLpage/releases). -When it receives a request with a URL ending in `.sql`, it finds the corresponding +When SQLPage receives a request with a URL ending in `.sql`, it finds the corresponding SQL file, runs it on the database, passing it information from the web request as SQL statement parameters [in a safe manner](safety.sql). When the database starts returning rows for the query, SQLPage maps each piece of information in the row to a parameter in the template of a pre-defined component, and streams the result back to the user''s browser. ' as description_md, - 'flask' as icon, + 'server' as icon, 'purple' as color; SELECT 'Start Simple, Scale to Advanced' as title, 'SQLPage is a great starting point for building websites, especially if you''re new to coding, or want to test out a new idea quickly. diff --git a/examples/official-site/safety.sql b/examples/official-site/safety.sql index bd633e2c..6d2d18c1 100644 --- a/examples/official-site/safety.sql +++ b/examples/official-site/safety.sql @@ -17,9 +17,16 @@ Most programmers, hearing this, will immediately think of the security implicati This page is here to provide a list of the security guarantees that SQLPage provides. SQLPage was designed from the ground up to be usable by non-technical *data analysts* and other non-web-developers, -so it provides safe defaults everywhere, so that you don''t have to worry about inadvertently -exposing more data than you intended. +so it provides safe defaults everywhere, so that you don''t have to think about basic security issues +you would have to worry about in a traditional web development stack. +## SQLPage does not expose your database to the internet + +SQLPage websites are *server-side rendered*, which means that the SQL queries stay on the server +where SQLPage is installed. + +The results of these queries are then rendered to HTML, and sent to the user''s browser. +A malicious user cannot run arbitrary SQL queries on your database, because SQLPage does not expose your database to the internet. ## Protection against SQL injections @@ -76,6 +83,22 @@ that disallows the execution of any inline JavaScript code, and only allows load If you have some legitimate JavaScript code that you want to execute on your website, you can use the `javascript` parameter of the [`shell`](documentation.sql?component=shell#component) component to do so. +## Authentication + +SQLPage provides an [authentication](/documentation.sql?component=authentication#component) component that allows you to +restrict access to some pages of your website to authenticated users. + +It also provides useful built-in functions such as +[`sqlpage.basic_auth_username()`](/functions.sql?function=basic_auth_username#function), +[`sqlpage.basic_auth_password()`](/functions.sql?function=basic_auth_password#function) and +[`sqlpage.hash_password()`](/functions.sql?function=hash_password#function) +to help you implement your authentication system entirely in SQL. + +The components and functions provided by SQLPage are designed to be used by non-technical users, +and to respect [security best practices](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) by default. +Passwords are [hashed with a salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) using the +[argon2](https://en.wikipedia.org/wiki/Argon2) algorithm. + ## Database connections SQLPage uses a fixed pool of database connections, and will never open more connections than the ones you diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index 131fe96f..90d5db4b 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -286,8 +286,8 @@ When loading the page, the value for `:username` will be `NULL` if no value has In this select input, the various options are hardcoded, but they could also be loaded from a database table, using a function to convert the rows into a json array like - `json_group_array()` in SQLite, - - `json_agg()` in Postgres, or - - `JSON_ARRAYAGG()` in MySQL. + - `json_agg()` in Postgres, + - `JSON_ARRAYAGG()` in MySQL, or - `FOR JSON PATH` in Microsoft SQL Server. @@ -295,7 +295,10 @@ In SQLite, the query would look like ```sql SELECT ''select'' as type, - json_group_array(json_object("label", name, "value", id)) as options + json_group_array(json_object( + "label", name, + "value", id + )) as options FROM fruits ``` ', json('[{"component":"form"}, '|| @@ -480,5 +483,8 @@ INSERT INTO example(component, description, properties) VALUES "description": "Documentation for the SQLPage low-code web application framework.", "font": "Poppins", "icon": "book", + "javascript": ["https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js", + "https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js"], + "css": "https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism-okaidia.min.css", "footer": "Official [SQLPage](https://sql.ophir.dev) documentation" }]')); diff --git a/examples/official-site/sqlpage/migrations/07_authentication.sql b/examples/official-site/sqlpage/migrations/07_authentication.sql index d1422d29..016b4395 100644 --- a/examples/official-site/sqlpage/migrations/07_authentication.sql +++ b/examples/official-site/sqlpage/migrations/07_authentication.sql @@ -62,7 +62,7 @@ The username and password entered by the user will be accessible in your SQL cod ```sql SELECT ''authentication'' AS component, - ''$argon2id$v=19$m=16,t=2,p=1$TERTd0lIcUpraWFTcmRQYw$+bjtag7Xjb6p1dsuYOkngw'' AS password_hash, -- generated using https://argon2.online/ + ''$argon2id$v=19$m=16,t=2,p=1$TERTd0lIcUpraWFTcmRQYw$+bjtag7Xjb6p1dsuYOkngw'' AS password_hash, -- generated using sqlpage.hash_password sqlpage.basic_auth_password() AS password; -- this is the password that the user entered in the browser popup ``` @@ -73,7 +73,7 @@ The most basic usage of the authentication component is to simply check if the u ```sql SELECT ''authentication'' AS component, ''login.sql'' AS link, - ''$argon2id$v=19$m=16,t=2,p=1$TERTd0lIcUpraWFTcmRQYw$+bjtag7Xjb6p1dsuYOkngw'' AS password_hash, -- generated using https://argon2.online/ + ''$argon2id$v=19$m=16,t=2,p=1$TERTd0lIcUpraWFTcmRQYw$+bjtag7Xjb6p1dsuYOkngw'' AS password_hash, -- generated using sqlpage.hash_password :password AS password; -- this is the password that the user sent through our form ``` diff --git a/examples/official-site/sqlpage/migrations/17_blog_sqlpage_v_0_13.sql b/examples/official-site/sqlpage/migrations/17_blog_sqlpage_v_0_13.sql index a0faed31..948d770b 100644 --- a/examples/official-site/sqlpage/migrations/17_blog_sqlpage_v_0_13.sql +++ b/examples/official-site/sqlpage/migrations/17_blog_sqlpage_v_0_13.sql @@ -15,7 +15,7 @@ VALUES ( - Updated dependencies, for bug fixes and performance improvements. - New icons (see https://tabler-icons.io/changelog) - When `NULL` is passed as an icon name, display no icon instead of raising an error. - - Official docker image folder structure changed. The docker image now expects + - The folder structure changed in the [official docker image](https://hub.docker.com/r/lovasoa/sqlpage). The docker image now expects - the SQLPage website (`.sql` files) to be in `/var/www/`, and - the SQLPage configuration folder to be in `/etc/sqlpage/` - the configuration file should be in `/etc/sqlpage/sqlpage.json` diff --git a/examples/official-site/your-first-sql-website/index.sql b/examples/official-site/your-first-sql-website/index.sql index 41a0a85b..b57e11fc 100644 --- a/examples/official-site/your-first-sql-website/index.sql +++ b/examples/official-site/your-first-sql-website/index.sql @@ -36,7 +36,7 @@ and render the database responses as nice web pages. [Download the latest SQLPage](https://github.com/lovasoa/SQLpage/releases) for your operating system. -> **Note**: Advanced user can alternatively install SQLPage using +> **Note**: Advanced users can alternatively install SQLPage using > [docker](https://hub.docker.com/repository/docker/lovasoa/sqlpage/general), > [brew](https://formulae.brew.sh/formula/sqlpage), > or [nix](https://search.nixos.org/packages?channel=unstable&show=sqlpage) @@ -84,7 +84,7 @@ Your database schema ==================== > If you already have a database populated with data, -> or if you intend to use other tools to manage your database schema, +> or if you intend to use other tools to manage your database structure, > you can skip this section. The [database schema](https://en.wikipedia.org/wiki/Database_schema) for your SQLPage website @@ -109,6 +109,9 @@ CREATE TABLE users ( ); ``` +> **Note**: The migration system is not supported on Microsoft SQL Server databases. +> If you are using a SQL Server database, you should create your tables using a different tool, such as *SQL Server Management Studio*. + Connect to a custom database ============================ diff --git a/sqlpage/templates/code.handlebars b/sqlpage/templates/code.handlebars index 6fde70b4..6578a369 100644 --- a/sqlpage/templates/code.handlebars +++ b/sqlpage/templates/code.handlebars @@ -5,6 +5,8 @@ {{#if description_md}} {{{markdown description_md}}} {{/if}} -
{{contents}}
+
{{contents}}
{{/each_row}}