Skip to content

Commit

Permalink
add an authentication example
Browse files Browse the repository at this point in the history
update documentation
  • Loading branch information
lovasoa committed Jun 21, 2024
1 parent 64a8f09 commit a128ad0
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 62 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
- datagrids are now slightly more compact, with less padding and less space taken by each item.
- fix a bug in the [card](https://sql.ophir.dev/documentation.sql?component=card#component) component where the icon would sometimes overflow the card's text content.
- new `image` property in the [button](https://sql.ophir.dev/documentation.sql?component=button#component) component to display a small image inside a button.
- In the `shell` component
- allow easily creating complex menus even in SQLite:
```sql
select 'shell' as component, 'My Website' as title, '{"title":"About","submenu":[{"link":"/x.sql","title":"X"},{"link":"/y.sql","title":"Y"}]}') as menu_item;
```
- allow easily creating optional menu items that are only displayed in some conditions:
```sql
select 'shell' as component, 'My Website' as title, CASE WHEN $role = 'admin' THEN 'Admin' END as menu_item;
```


## 0.23.0 (2024-06-09)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- delete expired sessions
delete from user_sessions where created_at < datetime('now', '-1 day');

-- check that the
SELECT 'authentication' AS component,
'login.sql?failed' AS link, -- redirect to the login page on error
(SELECT password_hash FROM users WHERE username = :Username) AS password_hash, -- this is a hash of the password 'admin'
:Password AS password; -- this is the password that the user sent through our form in 'index.sql'

-- if we haven't been redirected, then the password is correct
-- create a new session
insert into user_sessions (session_token, username) values (sqlpage.random_string(32), :Username)
returning 'cookie' as component, 'session_token' as name, session_token as value;

-- redirect to the authentication example home page
select 'redirect' as component, '/examples/authentication' as link;
18 changes: 18 additions & 0 deletions examples/official-site/examples/authentication/index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- redirect the user to the login page if they are not logged in
-- this query should be present at the top of every page that requires authentication
set $role = (select role from users natural join user_sessions where session_token = sqlpage.cookie('session_token'));
select 'redirect' as component, 'login.sql' as link where $role is null;

select 'dynamic' as component,
json_insert(properties, '$[0].menu_item[#]', 'logout') as properties
FROM example WHERE component = 'shell' LIMIT 1;

select 'alert' as component, 'info' as color, CONCAT('You are logged in as ', $role) as title;

select 'text' as component, '
# Authentication
Read the [source code](//github.com/lovasoa/SQLpage/blob/main/examples/official-site/examples/authentication/) for this demo.
[Log out](logout.sql)
' as contents_md;
24 changes: 24 additions & 0 deletions examples/official-site/examples/authentication/login.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
select 'dynamic' as component, properties FROM example WHERE component = 'shell' LIMIT 1;

select 'form' as component, 'Authentication' as title, 'Log in' as validate, 'create_session_token.sql' as action;
select 'Username' as name, 'user' as prefix_icon, 'admin' as placeholder;
select 'Password' as name, 'lock' as prefix_icon, 'admin' as placeholder, 'password' as type;

select 'alert' as component, 'danger' as color, 'Invalid username or password' as title where $failed is not null;

select 'text' as component, '
# Authentication
This is a simple example of an authentication form.
It uses
- the [`form`](/documentation.sql?component=form#component) component to create a login form
- the [`authentication`](/documentation.sql?component=authentication#component) component to check the user password
- the [`cookie`](/documentation.sql?component=cookie#component) component to store a unique session token in the user browser
- the [`redirect`](/documentation.sql?component=redirect#component) component to redirect the user to the login page if they are not logged in
## Example credentials
- Username: `admin`, Password: `admin`
- Username: `user`, Password: `user`
' as contents_md;
4 changes: 4 additions & 0 deletions examples/official-site/examples/authentication/logout.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
delete from user_sessions
where session_token = sqlpage.cookie('session_token');

select 'redirect' as component, 'login.sql' as link;
29 changes: 0 additions & 29 deletions examples/official-site/examples/dynamic_menu.sql

This file was deleted.

56 changes: 23 additions & 33 deletions examples/official-site/sqlpage/migrations/01_documentation.sql
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ You see the [page layouts demo](./examples/layouts.sql) for a live example of th
{"link": "/examples/layouts.sql", "title": "Layouts"},
{"link": "/examples/multistep-form", "title": "Forms"},
{"link": "/examples/handle_picture_upload.sql", "title": "File uploads"},
{"link": "/examples/hash_password.sql", "title": "Password protection"},
{"link": "/examples/authentication/", "title": "Password protection"},
{"link": "//github.com/lovasoa/SQLpage/blob/main/examples/", "title": "All examples & demos"}
]},
{"title": "Community", "submenu": [
Expand Down Expand Up @@ -899,46 +899,36 @@ FROM my_menu_items
(check your database documentation for the exact syntax of the `json_group_array` function).
Another "dynamic" aspect is when menu items are adjusted based on the environment. For example,
Logout/UserProfile buttons may be presented to an authenticated user and Login/SignUp, otherwise
(such an example will presented in a separate demo). The following simple example demonstrates
the concept by hiding one arbitrary menu when the page is loaded. Then you can select from a
dropdown menu, which menu to hide. To hide a menu item, return NULL or empty JSON object ''{}''
as demonstrated below.
Another case when dynamic menus are useful is when you want to show some
menu items only in certain conditions.
For instance, you could show an "Admin panel" menu item only to users with the "admin" role,
a "Profile" menu item only to authenticated users,
and a "Login" menu item only to unauthenticated users:
```sql
SET $dummy = ifnull(:menu, abs(random()) % 5);
SET $role = (
SELECT role FROM users
INNER JOIN sessions ON users.id = sessions.user_id
WHERE sessions.session_id = sqlpage.cookie(''session_id'')
); -- Read more about how to handle user sessions in the "authentication" component documentation
SELECT
''shell'' AS component,
''SQLPage'' AS title,
''database'' AS icon,
''/'' AS link,
iif($dummy = 1, NULL, ''{"title":"About","submenu":[{"link":"/safety.sql","title":"Security"},{"link":"/performance.sql","title":"Performance"}]}'') AS menu_item,
iif($dummy = 2, ''{}'', ''{"title":"Examples","submenu":[{"link":"/examples/tabs.sql","title":"Tabs"},{"link":"/examples/layouts.sql","title":"Layouts"}]}'') AS menu_item,
iif($dummy = 3, NULL, ''{"title":"Community","submenu":[{"link":"blog.sql","title":"Blog"},{"link":"//github.com/lovasoa/sqlpage/issues","title":"Report a bug"}]}'') AS menu_item,
iif($dummy = 4, ''{}'', ''{"title":"Documentation","submenu":[{"link":"/your-first-sql-website","title":"Getting started"},{"link":"/components.sql","title":"All Components"}]}'') AS menu_item,
''Official [SQLPage](https://sql.ophir.dev) documentation'' as footer;
''shell'' AS component,
''My authenticated website'' AS title,
-- Add an admin panel link if the user is an admin
CASE WHEN $role = ''admin'' THEN ''{"link": "admin.sql", "title": "Admin panel"}'' END AS menu_item,
SELECT
''form'' AS component,
''Hide Menu'' AS validate,
sqlpage.path() AS action;
SELECT
''select'' AS type,
''menu'' AS name,
''Hide Menu'' AS label,
2 AS width,
CAST($dummy AS INT) AS value,
''[{"label": "None", "value": 0},
{"label": "About", "value": 1},
{"label": "Examples", "value": 2},
{"label": "Community", "value": 3},
{"label": "Documentation", "value": 4}]'' AS options;
-- Add a profile page if the user is authenticated
CASE WHEN $role IS NOT NULL THEN ''{"link": "profile.sql", "title": "My profile"}'' END AS menu_item,
-- Add a login link if the user is not authenticated
CASE WHEN $role IS NULL THEN ''login'' END AS menu_item
;
```
Follow [this link](examples/dynamic_menu.sql) to try this code.
More about how to handle user sessions in the [authentication component documentation](?component=authentication#component).
', NULL),
('shell', '
### A page without a shell
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
create table users (
username text primary key,
password_hash text not null,
role text not null
);

-- Create example users with trivial passwords for the website's demo
insert into users (username, password_hash, role)
values
('admin', '$argon2i$v=19$m=8,t=1,p=1$YWFhYWFhYWE$ROyXNhK0utkzTA', 'admin'), -- password: admin
('user', '$argon2i$v=19$m=8,t=1,p=1$YWFhYWFhYWE$qsrWdjgl96ooYw', 'user'); -- password: user
-- (the password hashes can be generated using the `sqlpage.hash_password` function)

create table user_sessions (
session_token text primary key,
username text not null references users(username),
created_at timestamp not null default current_timestamp
);

0 comments on commit a128ad0

Please sign in to comment.