From 3713cc2443240a9dfbcfdb9b3ec0faa71295b24f Mon Sep 17 00:00:00 2001 From: Matthieu Bessat Date: Thu, 28 Nov 2024 12:47:00 +0100 Subject: [PATCH 1/2] refactor: structure of an hexagonal architecture Created a kernel crate to store models and future action implementations. Will be useful to create admin cli. --- Cargo.lock | 525 +++++++++--------- Cargo.toml | 38 +- docs/draft.md | 4 + justfile | 5 +- lib/admin_cli/Cargo.toml | 11 + lib/admin_cli/src/main.rs | 6 + lib/http_server/Cargo.toml | 51 ++ build.rs => lib/http_server/build.rs | 0 lib/http_server/src/controllers/api/index.rs | 14 + .../http_server/src}/controllers/api/mod.rs | 1 + .../controllers/api/oauth2/access_token.rs | 7 +- .../src}/controllers/api/oauth2/mod.rs | 0 .../src}/controllers/api/openid/mod.rs | 0 .../src}/controllers/api/openid/well_known.rs | 3 +- .../src}/controllers/api/read_user.rs | 7 +- .../http_server/src}/controllers/mod.rs | 0 .../src}/controllers/ui/admin/apps.rs | 0 .../controllers/ui/admin/authorizations.rs | 0 .../src/controllers/ui/admin/mod.rs | 0 .../src/controllers/ui/admin/users.rs | 0 .../http_server/src}/controllers/ui/apps.rs | 4 +- .../src}/controllers/ui/authorize.rs | 17 +- .../http_server/src}/controllers/ui/home.rs | 0 .../http_server/src}/controllers/ui/login.rs | 12 +- .../http_server/src}/controllers/ui/logout.rs | 2 +- .../http_server/src}/controllers/ui/me.rs | 13 +- .../http_server/src}/controllers/ui/mod.rs | 0 .../src}/controllers/ui/register.rs | 9 +- .../ui/user_panel/authorizations.rs | 7 +- .../src}/controllers/ui/user_panel/mod.rs | 0 src/server.rs => lib/http_server/src/lib.rs | 34 +- src/cli.rs => lib/http_server/src/main.rs | 13 +- .../http_server/src}/middlewares/app_auth.rs | 5 +- .../http_server/src}/middlewares/mod.rs | 0 .../http_server/src}/middlewares/renderer.rs | 3 +- .../http_server/src}/middlewares/user_auth.rs | 4 +- {src => lib/http_server/src}/renderer.rs | 15 +- {src => lib/http_server/src}/router.rs | 5 +- .../http_server/src}/services/app_session.rs | 0 {src => lib/http_server/src}/services/mod.rs | 1 - .../http_server/src}/services/oauth2.rs | 2 +- .../http_server/src}/services/session.rs | 3 +- .../src}/templates/components/footer.html | 0 .../src}/templates/components/header.html | 0 .../src}/templates/layouts/base.html | 0 .../src}/templates/pages/apps.html | 0 .../src}/templates/pages/authorize.html | 0 .../src}/templates/pages/home.html | 0 .../src}/templates/pages/login.html | 0 .../src}/templates/pages/me/details-form.html | 0 .../src}/templates/pages/me/index.html | 0 .../src}/templates/pages/register.html | 0 .../pages/user_panel/authorizations.html | 0 .../http_server/src}/token_claims.rs | 3 +- lib/kernel/Cargo.toml | 22 + lib/kernel/src/actions/mod.rs | 0 lib/kernel/src/actions/user.rs | 0 lib/kernel/src/consts.rs | 4 + lib/kernel/src/context.rs | 51 ++ {src => lib/kernel/src}/database.rs | 8 +- lib/kernel/src/lib.rs | 7 + .../kernel/src}/models/authorization.rs | 0 {src => lib/kernel/src}/models/config.rs | 1 - {src => lib/kernel/src}/models/mod.rs | 1 - {src => lib/kernel/src}/models/user.rs | 0 lib/kernel/src/repositories/mod.rs | 2 + lib/kernel/src/repositories/storage.rs | 7 + lib/kernel/src/repositories/users.rs | 14 + lib/utils/Cargo.toml | 10 + src/utils.rs => lib/utils/src/lib.rs | 4 + src/consts.rs | 1 - src/main.rs | 62 --- src/services/password.rs | 35 -- tests/hurl_integration/run_scenario.sh | 39 ++ tests/hurl_integration/scenario_1/config.toml | 56 ++ tests/hurl_integration/scenario_1/init_db.sh | 9 + .../scenario_1/john_doe_profile_pic.jpg | Bin 0 -> 30658 bytes tests/hurl_integration/scenario_1/main.hurl | 88 +++ tests/manual/.gitignore | 1 + tests/manual/access_token_request.sh | 7 + tests/manual/all.sh | 8 + tests/manual/authorize.sh | 15 + tests/manual/create_test_user.sql | 5 + tests/manual/get_user_info.sh | 5 + tests/manual/login.sh | 6 + tests/manual/oauth2c.sh | 10 + tests/manual/register.sh | 6 + 87 files changed, 834 insertions(+), 474 deletions(-) create mode 100644 lib/admin_cli/Cargo.toml create mode 100644 lib/admin_cli/src/main.rs create mode 100644 lib/http_server/Cargo.toml rename build.rs => lib/http_server/build.rs (100%) create mode 100644 lib/http_server/src/controllers/api/index.rs rename {src => lib/http_server/src}/controllers/api/mod.rs (77%) rename {src => lib/http_server/src}/controllers/api/oauth2/access_token.rs (95%) rename {src => lib/http_server/src}/controllers/api/oauth2/mod.rs (100%) rename {src => lib/http_server/src}/controllers/api/openid/mod.rs (100%) rename {src => lib/http_server/src}/controllers/api/openid/well_known.rs (93%) rename {src => lib/http_server/src}/controllers/api/read_user.rs (83%) rename {src => lib/http_server/src}/controllers/mod.rs (100%) rename {src => lib/http_server/src}/controllers/ui/admin/apps.rs (100%) rename {src => lib/http_server/src}/controllers/ui/admin/authorizations.rs (100%) rename src/controllers/ui/admin/users.rs => lib/http_server/src/controllers/ui/admin/mod.rs (100%) create mode 100644 lib/http_server/src/controllers/ui/admin/users.rs rename {src => lib/http_server/src}/controllers/ui/apps.rs (89%) rename {src => lib/http_server/src}/controllers/ui/authorize.rs (95%) rename {src => lib/http_server/src}/controllers/ui/home.rs (100%) rename {src => lib/http_server/src}/controllers/ui/login.rs (88%) rename {src => lib/http_server/src}/controllers/ui/logout.rs (85%) rename {src => lib/http_server/src}/controllers/ui/me.rs (93%) rename {src => lib/http_server/src}/controllers/ui/mod.rs (100%) rename {src => lib/http_server/src}/controllers/ui/register.rs (93%) rename {src => lib/http_server/src}/controllers/ui/user_panel/authorizations.rs (89%) rename {src => lib/http_server/src}/controllers/ui/user_panel/mod.rs (100%) rename src/server.rs => lib/http_server/src/lib.rs (65%) rename src/cli.rs => lib/http_server/src/main.rs (74%) rename {src => lib/http_server/src}/middlewares/app_auth.rs (95%) rename {src => lib/http_server/src}/middlewares/mod.rs (100%) rename {src => lib/http_server/src}/middlewares/renderer.rs (84%) rename {src => lib/http_server/src}/middlewares/user_auth.rs (93%) rename {src => lib/http_server/src}/renderer.rs (75%) rename {src => lib/http_server/src}/router.rs (95%) rename {src => lib/http_server/src}/services/app_session.rs (100%) rename {src => lib/http_server/src}/services/mod.rs (75%) rename {src => lib/http_server/src}/services/oauth2.rs (87%) rename {src => lib/http_server/src}/services/session.rs (94%) rename {src => lib/http_server/src}/templates/components/footer.html (100%) rename {src => lib/http_server/src}/templates/components/header.html (100%) rename {src => lib/http_server/src}/templates/layouts/base.html (100%) rename {src => lib/http_server/src}/templates/pages/apps.html (100%) rename {src => lib/http_server/src}/templates/pages/authorize.html (100%) rename {src => lib/http_server/src}/templates/pages/home.html (100%) rename {src => lib/http_server/src}/templates/pages/login.html (100%) rename {src => lib/http_server/src}/templates/pages/me/details-form.html (100%) rename {src => lib/http_server/src}/templates/pages/me/index.html (100%) rename {src => lib/http_server/src}/templates/pages/register.html (100%) rename {src => lib/http_server/src}/templates/pages/user_panel/authorizations.html (100%) rename {src/models => lib/http_server/src}/token_claims.rs (95%) create mode 100644 lib/kernel/Cargo.toml create mode 100644 lib/kernel/src/actions/mod.rs create mode 100644 lib/kernel/src/actions/user.rs create mode 100644 lib/kernel/src/consts.rs create mode 100644 lib/kernel/src/context.rs rename {src => lib/kernel/src}/database.rs (64%) create mode 100644 lib/kernel/src/lib.rs rename {src => lib/kernel/src}/models/authorization.rs (100%) rename {src => lib/kernel/src}/models/config.rs (99%) rename {src => lib/kernel/src}/models/mod.rs (70%) rename {src => lib/kernel/src}/models/user.rs (100%) create mode 100644 lib/kernel/src/repositories/mod.rs create mode 100644 lib/kernel/src/repositories/storage.rs create mode 100644 lib/kernel/src/repositories/users.rs create mode 100644 lib/utils/Cargo.toml rename src/utils.rs => lib/utils/src/lib.rs (92%) delete mode 100644 src/consts.rs delete mode 100644 src/main.rs delete mode 100644 src/services/password.rs create mode 100755 tests/hurl_integration/run_scenario.sh create mode 100644 tests/hurl_integration/scenario_1/config.toml create mode 100755 tests/hurl_integration/scenario_1/init_db.sh create mode 100644 tests/hurl_integration/scenario_1/john_doe_profile_pic.jpg create mode 100644 tests/hurl_integration/scenario_1/main.hurl create mode 100644 tests/manual/.gitignore create mode 100755 tests/manual/access_token_request.sh create mode 100755 tests/manual/all.sh create mode 100755 tests/manual/authorize.sh create mode 100644 tests/manual/create_test_user.sql create mode 100755 tests/manual/get_user_info.sh create mode 100755 tests/manual/login.sh create mode 100755 tests/manual/oauth2c.sh create mode 100755 tests/manual/register.sh diff --git a/Cargo.lock b/Cargo.lock index f19d1ee..b4f9f1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,14 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "admin_cli" +version = "0.0.0" +dependencies = [ + "anyhow", + "fully_pub", +] + [[package]] name = "ahash" version = "0.8.11" @@ -41,9 +49,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -62,9 +70,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -77,49 +85,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "argh" @@ -140,7 +142,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -172,7 +174,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -192,9 +194,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", @@ -217,7 +219,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tower", "tower-layer", @@ -240,7 +242,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -248,25 +250,26 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9" +checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" dependencies = [ "axum", "axum-core", "bytes", "cookie", + "fastrand", "futures-util", "http", "http-body", "http-body-util", "mime", + "multer", "pin-project-lite", "serde", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -277,7 +280,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -289,14 +292,14 @@ dependencies = [ "axum", "minijinja", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "axum_typed_multipart" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0412547e063ce471a3f5ccf8a5129ae5ff64c63e40ee1bf1079dec3fcede4e7" +checksum = "41044b23e250b46af4b73286707fe517f73b3b1518cc34347ec4ae643e3e9a27" dependencies = [ "anyhow", "axum", @@ -306,22 +309,22 @@ dependencies = [ "futures-core", "futures-util", "tempfile", - "thiserror", + "thiserror 2.0.3", "tokio", "uuid", ] [[package]] name = "axum_typed_multipart_macros" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbb13e6a88be66ca8a226e4cee4d60eea0245bbdd4f22a95dfb90cbcf6be4b3" +checksum = "aa0a06af331eb00c4f372a38664b3e4fc599bb308b2342ffdb3770b2cad3d432" dependencies = [ "darling", "heck 0.5.0", "proc-macro-error2", "quote", - "syn 2.0.79", + "syn 2.0.90", "ubyte", ] @@ -340,12 +343,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base64" version = "0.21.7" @@ -405,15 +402,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.1.30" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "shlex", ] @@ -441,19 +438,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "const-oid" @@ -461,12 +448,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - [[package]] name = "cookie" version = "0.18.1" @@ -486,9 +467,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -554,7 +535,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -565,7 +546,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -608,7 +589,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -628,9 +609,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -666,12 +647,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -693,15 +674,15 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -730,7 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fd8cb48eceb4e8b471af6a8e4e223cbe1286552791b9ab274512ba9cfd754df" dependencies = [ "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -785,7 +766,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -858,9 +839,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hashlink" @@ -886,12 +867,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hex" version = "0.4.3" @@ -961,9 +936,42 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "http_server" +version = "0.0.0" +dependencies = [ + "anyhow", + "argh", + "axum", + "axum-extra", + "axum-macros", + "axum-template", + "axum_typed_multipart", + "chrono", + "env_logger", + "fully_pub", + "jsonwebtoken", + "kernel", + "log", + "minijinja", + "minijinja-embed", + "serde", + "serde_json", + "serde_urlencoded", + "sqlx", + "strum", + "strum_macros", + "time", + "tokio", + "tower-http", + "url", + "utils", + "uuid", +] [[package]] name = "httparse" @@ -985,9 +993,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -1004,9 +1012,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-util", @@ -1156,7 +1164,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -1193,7 +1201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -1204,15 +1212,15 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "fb15147158e79fd8b8afd0252522769c4f48725460b37338544d8379d94fc8f9" dependencies = [ "wasm-bindgen", ] @@ -1232,6 +1240,27 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "kernel" +version = "0.0.0" +dependencies = [ + "anyhow", + "chrono", + "dotenvy", + "env_logger", + "fully_pub", + "log", + "serde", + "serde_json", + "sqlx", + "strum", + "strum_macros", + "toml", + "url", + "utils", + "uuid", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1243,15 +1272,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" @@ -1272,9 +1301,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -1330,50 +1359,11 @@ dependencies = [ "unicase", ] -[[package]] -name = "minauthator" -version = "0.1.0" -dependencies = [ - "anyhow", - "argh", - "argon2", - "axum", - "axum-extra", - "axum-macros", - "axum-template", - "axum_typed_multipart", - "base64 0.22.1", - "chrono", - "dotenvy", - "env_logger", - "fully_pub", - "jsonwebtoken", - "log", - "minijinja", - "minijinja-embed", - "rand", - "rand_core", - "redis", - "serde", - "serde_json", - "serde_urlencoded", - "sqlx", - "strum", - "strum_macros", - "time", - "tokio", - "toml", - "totp-rs", - "tower-http", - "url", - "uuid", -] - [[package]] name = "minijinja" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1028b628753a7e1a88fc59c9ba4b02ecc3bc0bd3c7af23df667bc28df9b3310e" +checksum = "2c37e1b517d1dcd0e51dc36c4567b9d5a29262b3ec8da6cb5d35e27a8fb529b5" dependencies = [ "serde", ] @@ -1401,11 +1391,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", @@ -1583,9 +1572,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1654,14 +1643,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1705,21 +1694,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redis" -version = "0.27.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6baebe319ef5e4b470f248335620098d1c2e9261e995be05f56f719ca4bdb2" -dependencies = [ - "arc-swap", - "combine", - "itoa", - "num-bigint", - "percent-encoding", - "ryu", - "url", -] - [[package]] name = "redox_syscall" version = "0.5.7" @@ -1731,9 +1705,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1743,9 +1717,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1775,9 +1749,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest", @@ -1801,9 +1775,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags", "errno", @@ -1832,29 +1806,29 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -1939,7 +1913,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -1960,9 +1934,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2043,7 +2017,7 @@ dependencies = [ "sha2", "smallvec", "sqlformat", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -2128,7 +2102,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "tracing", "uuid", "whoami", @@ -2168,7 +2142,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "tracing", "uuid", "whoami", @@ -2238,7 +2212,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2260,9 +2234,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -2277,9 +2251,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "synstructure" @@ -2289,14 +2263,14 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -2307,22 +2281,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -2383,9 +2377,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -2405,7 +2399,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] @@ -2466,19 +2460,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "totp-rs" -version = "5.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b2f27dad992486c26b4e7455f38aa487e838d6d61b57e72906ee2b8c287a90" -dependencies = [ - "base32", - "constant_time_eq", - "hmac", - "sha1", - "sha2", -] - [[package]] name = "tower" version = "0.5.1" @@ -2497,9 +2478,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ "bitflags", "bytes", @@ -2534,9 +2515,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -2546,20 +2527,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -2578,12 +2559,9 @@ checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" @@ -2593,9 +2571,9 @@ checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" @@ -2632,9 +2610,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2665,11 +2643,22 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utils" +version = "0.0.0" +dependencies = [ + "anyhow", + "argon2", + "base64 0.22.1", + "rand", + "rand_core", +] + [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", "serde", @@ -2701,9 +2690,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "21d3b25c3ea1126a2ad5f4f9068483c2af1e64168f847abe863a526b8dbfe00b" dependencies = [ "cfg-if", "once_cell", @@ -2712,24 +2701,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "52857d4c32e496dc6537646b5b117081e71fd2ff06de792e3577a150627db283" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "920b0ffe069571ebbfc9ddc0b36ba305ef65577c94b06262ed793716a1afd981" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2737,22 +2726,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "bf59002391099644be3524e23b781fa43d2be0c5aa0719a18c0731b9d195cab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "e5047c5392700766601942795a436d7d2599af60dcc3cc1248c9120bfb0827b0" [[package]] name = "whoami" @@ -2944,9 +2933,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -2956,13 +2945,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", "synstructure", ] @@ -2984,27 +2973,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", "synstructure", ] @@ -3033,5 +3022,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index 41357f6..c6d0713 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,26 +3,26 @@ cargo-features = ["codegen-backend"] [profile.dev] codegen-backend = "cranelift" -[package] -name = "minauthator" -description = "Identity provider and OAuth2 server for an small-scale organization." -version = "0.1.0" -edition = "2021" +[workspace] +members = [ + "lib/kernel", + "lib/utils", + "lib/http_server", + "lib/admin_cli" +] -[dependencies] +[workspace.dependencies] # commons utils anyhow = "1.0" fully_pub = "0.1" -argon2 = "0.5" strum = "0.26.3" strum_macros = "0.26" uuid = { version = "1.8", features = ["serde", "v4"] } dotenvy = "0.15.7" -base64 = "0.22.1" -rand = "0.8.5" -rand_core = { version = "0.6.4", features = ["std"] } url = "2.5.3" -argh = "0.1" # for CLI + +# CLI +argh = "0.1" # Async tokio = { version = "1.40.0", features = ["rt-multi-thread"] } @@ -34,7 +34,6 @@ env_logger = "0.11" # Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_urlencoded = "0.7.1" toml = "0.8" chrono = { version = "0.4", features = ["serde"] } @@ -43,21 +42,6 @@ chrono = { version = "0.4", features = ["serde"] } sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "chrono", "uuid"] } redis = { version = "0.27.3", default-features = false, features = ["acl"] } -# Web -axum = { version = "0.7.7", features = ["json", "multipart"] } -axum-extra = { version = "0.9.4", features = ["cookie"] } -axum-template = { version = "2.4.0", features = ["minijinja"] } -axum_typed_multipart = "0.13.1" -minijinja = { version = "2.1", features = ["builtins"] } -# to make work the static assets server -tower-http = { version = "0.6.1", features = ["fs"] } - # Auth utils totp-rs = "5.6" -minijinja-embed = "2.3.1" -axum-macros = "0.4.2" -jsonwebtoken = "9.3.0" -time = "0.3.36" -[build-dependencies] -minijinja-embed = "2.3.1" diff --git a/docs/draft.md b/docs/draft.md index aa65361..5a962c7 100644 --- a/docs/draft.md +++ b/docs/draft.md @@ -3,3 +3,7 @@ https://datatracker.ietf.org/doc/html/rfc6749 https://stackoverflow.com/questions/79118231/how-to-access-the-axum-request-path-in-a-minijinja-template + +## Oauth2 test + +-> authorize diff --git a/justfile b/justfile index 4f04243..8bc1c40 100644 --- a/justfile +++ b/justfile @@ -1,11 +1,12 @@ export RUST_BACKTRACE := "1" export RUST_LOG := "trace" +export RUN_ARGS := "run --bin minauthator-server -- --config ./config.toml --database ./tmp/dbs/minauthator.db --static-assets ./assets" watch-run: - cargo-watch -x 'run -- --config ./config.toml --database ./tmp/dbs/minauthator.db --static-assets ./assets' + cargo-watch -x "$RUN_ARGS" run: - cargo run -- --database ./tmp/dbs/minauthator.db --config ./config.toml --static-assets ./assets + cargo $RUN_ARGS docker-run: docker run -p 3085:8080 -v ./tmp/docker/config:/etc/minauthator -v ./tmp/docker/db:/var/lib/minauthator minauthator diff --git a/lib/admin_cli/Cargo.toml b/lib/admin_cli/Cargo.toml new file mode 100644 index 0000000..97e0107 --- /dev/null +++ b/lib/admin_cli/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "admin_cli" +edition = "2021" + +[dependencies] +anyhow = { workspace = true } +fully_pub = { workspace = true } + +[[bin]] +name = "minauthator-admin" +path = "src/main.rs" diff --git a/lib/admin_cli/src/main.rs b/lib/admin_cli/src/main.rs new file mode 100644 index 0000000..9cf2d88 --- /dev/null +++ b/lib/admin_cli/src/main.rs @@ -0,0 +1,6 @@ +use anyhow::Result; + +fn main() -> Result<()> { + println!("Starting minauthator admin CLI"); + Ok(()) +} diff --git a/lib/http_server/Cargo.toml b/lib/http_server/Cargo.toml new file mode 100644 index 0000000..8695cf6 --- /dev/null +++ b/lib/http_server/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "http_server" +edition = "2021" + +[dependencies] +kernel = { path = "../kernel" } +utils = { path = "../utils" } + +# common +log = { workspace = true } +env_logger = { workspace = true } + +strum = { workspace = true } +strum_macros = { workspace = true } + +anyhow = { workspace = true } +fully_pub = { workspace = true } + +tokio = { workspace = true } + +# Web +axum = { version = "0.7.7", features = ["json", "multipart"] } +axum-extra = { version = "0.9.4", features = ["cookie"] } +axum-template = { version = "2.4.0", features = ["minijinja"] } +axum_typed_multipart = "0.13.1" +minijinja = { version = "2.1", features = ["builtins"] } +# to make work the static assets server +tower-http = { version = "0.6.1", features = ["fs"] } + +minijinja-embed = "2.3.1" +axum-macros = "0.4.2" +jsonwebtoken = "9.3.0" +time = "0.3.36" + +serde = { workspace = true } +serde_json = { workspace = true } +serde_urlencoded = "0.7.1" +chrono = { workspace = true } + +argh = { workspace = true } + +sqlx = { workspace = true } +uuid = { workspace = true } +url = { workspace = true } + +[build-dependencies] +minijinja-embed = "2.3.1" + +[[bin]] +name = "minauthator-server" +path = "src/main.rs" diff --git a/build.rs b/lib/http_server/build.rs similarity index 100% rename from build.rs rename to lib/http_server/build.rs diff --git a/lib/http_server/src/controllers/api/index.rs b/lib/http_server/src/controllers/api/index.rs new file mode 100644 index 0000000..9253ece --- /dev/null +++ b/lib/http_server/src/controllers/api/index.rs @@ -0,0 +1,14 @@ +use axum::{extract::State, response::IntoResponse, Json}; +use serde_json::json; + +use crate::AppState; + +pub async fn get_index( + State(app_state): State, +) -> impl IntoResponse { + Json(json!({ + "software": "Minauthator", + "name": app_state.config.instance.name, + "base_uri": app_state.config.instance.base_uri + })) +} diff --git a/src/controllers/api/mod.rs b/lib/http_server/src/controllers/api/mod.rs similarity index 77% rename from src/controllers/api/mod.rs rename to lib/http_server/src/controllers/api/mod.rs index 36f3a8e..eef80ff 100644 --- a/src/controllers/api/mod.rs +++ b/lib/http_server/src/controllers/api/mod.rs @@ -1,3 +1,4 @@ +pub mod index; pub mod oauth2; pub mod read_user; pub mod openid; diff --git a/src/controllers/api/oauth2/access_token.rs b/lib/http_server/src/controllers/api/oauth2/access_token.rs similarity index 95% rename from src/controllers/api/oauth2/access_token.rs rename to lib/http_server/src/controllers/api/oauth2/access_token.rs index b0d9c54..1da6993 100644 --- a/src/controllers/api/oauth2/access_token.rs +++ b/lib/http_server/src/controllers/api/oauth2/access_token.rs @@ -4,10 +4,9 @@ use fully_pub::fully_pub; use log::error; use serde::{Deserialize, Serialize}; +use kernel::models::authorization::Authorization; use crate::{ - models::{authorization::Authorization, token_claims::AppUserTokenClaims}, - server::AppState, - services::{app_session::AppClientSession, session::create_token} + services::{app_session::AppClientSession, session::create_token}, token_claims::AppUserTokenClaims, AppState }; const AUTHORIZATION_CODE_TTL_SECONDS: i64 = 120; @@ -43,7 +42,7 @@ pub async fn get_access_token( ) .bind(&form.code) .bind(&app_client_session.client_id) - .fetch_one(&app_state.db) + .fetch_one(&app_state.db.0) .await; let authorization = match authorizations_res { Ok(val) => val, diff --git a/src/controllers/api/oauth2/mod.rs b/lib/http_server/src/controllers/api/oauth2/mod.rs similarity index 100% rename from src/controllers/api/oauth2/mod.rs rename to lib/http_server/src/controllers/api/oauth2/mod.rs diff --git a/src/controllers/api/openid/mod.rs b/lib/http_server/src/controllers/api/openid/mod.rs similarity index 100% rename from src/controllers/api/openid/mod.rs rename to lib/http_server/src/controllers/api/openid/mod.rs diff --git a/src/controllers/api/openid/well_known.rs b/lib/http_server/src/controllers/api/openid/well_known.rs similarity index 93% rename from src/controllers/api/openid/well_known.rs rename to lib/http_server/src/controllers/api/openid/well_known.rs index 64ac5a3..54daf3e 100644 --- a/src/controllers/api/openid/well_known.rs +++ b/lib/http_server/src/controllers/api/openid/well_known.rs @@ -1,9 +1,10 @@ use axum::{extract::State, response::IntoResponse, Json}; use fully_pub::fully_pub; +use kernel::models::authorization::AuthorizationScope; use serde::Serialize; use strum::IntoEnumIterator; -use crate::{models::authorization::AuthorizationScope, server::AppState}; +use crate::AppState; #[derive(Serialize)] #[fully_pub] diff --git a/src/controllers/api/read_user.rs b/lib/http_server/src/controllers/api/read_user.rs similarity index 83% rename from src/controllers/api/read_user.rs rename to lib/http_server/src/controllers/api/read_user.rs index a6b34dd..4b9e7c1 100644 --- a/src/controllers/api/read_user.rs +++ b/lib/http_server/src/controllers/api/read_user.rs @@ -2,7 +2,8 @@ use axum::{extract::State, response::IntoResponse, Extension, Json}; use fully_pub::fully_pub; use serde::Serialize; -use crate::{models::{token_claims::AppUserTokenClaims, user::User}, server::AppState}; +use crate::{token_claims::AppUserTokenClaims, AppState}; +use kernel::models::user::User; #[derive(Serialize)] #[fully_pub] @@ -19,10 +20,10 @@ pub async fn read_user_basic( State(app_state): State, Extension(token_claims): Extension, ) -> impl IntoResponse { - // 1. This handler require client user authentification (JWT) + // 1. This handler require app user authentification (JWT) let user_res = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1") .bind(&token_claims.user_id) - .fetch_one(&app_state.db) + .fetch_one(&app_state.db.0) .await .expect("To get user from claim"); let output = ReadUserBasicExtract { diff --git a/src/controllers/mod.rs b/lib/http_server/src/controllers/mod.rs similarity index 100% rename from src/controllers/mod.rs rename to lib/http_server/src/controllers/mod.rs diff --git a/src/controllers/ui/admin/apps.rs b/lib/http_server/src/controllers/ui/admin/apps.rs similarity index 100% rename from src/controllers/ui/admin/apps.rs rename to lib/http_server/src/controllers/ui/admin/apps.rs diff --git a/src/controllers/ui/admin/authorizations.rs b/lib/http_server/src/controllers/ui/admin/authorizations.rs similarity index 100% rename from src/controllers/ui/admin/authorizations.rs rename to lib/http_server/src/controllers/ui/admin/authorizations.rs diff --git a/src/controllers/ui/admin/users.rs b/lib/http_server/src/controllers/ui/admin/mod.rs similarity index 100% rename from src/controllers/ui/admin/users.rs rename to lib/http_server/src/controllers/ui/admin/mod.rs diff --git a/lib/http_server/src/controllers/ui/admin/users.rs b/lib/http_server/src/controllers/ui/admin/users.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/ui/apps.rs b/lib/http_server/src/controllers/ui/apps.rs similarity index 89% rename from src/controllers/ui/apps.rs rename to lib/http_server/src/controllers/ui/apps.rs index d048cb2..05992fb 100644 --- a/src/controllers/ui/apps.rs +++ b/lib/http_server/src/controllers/ui/apps.rs @@ -1,10 +1,10 @@ use axum::{extract::State, response::IntoResponse, Extension}; use minijinja::context; +use kernel::models::{config::AppVisibility, config::Application}; use crate::{ - models::{config::AppVisibility, config::Application}, renderer::TemplateRenderer, - server::AppState + AppState }; pub async fn list_apps( diff --git a/src/controllers/ui/authorize.rs b/lib/http_server/src/controllers/ui/authorize.rs similarity index 95% rename from src/controllers/ui/authorize.rs rename to lib/http_server/src/controllers/ui/authorize.rs index 0bcb1e5..f6016c9 100644 --- a/src/controllers/ui/authorize.rs +++ b/lib/http_server/src/controllers/ui/authorize.rs @@ -7,14 +7,15 @@ use serde::{Deserialize, Serialize}; use url::Url; use uuid::Uuid; +use kernel::{ + models::{authorization::Authorization, config::AppAuthorizeFlow} +}; +use utils::get_random_alphanumerical; use crate::{ - models::{authorization::Authorization, config::AppAuthorizeFlow, token_claims::UserTokenClaims}, - renderer::TemplateRenderer, server::AppState, - services::oauth2::{parse_scope, verify_redirect_uri}, - utils::get_random_alphanumerical + renderer::TemplateRenderer, services::oauth2::{parse_scope, verify_redirect_uri}, token_claims::UserTokenClaims, AppState }; -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[fully_pub] /// query params described in [RFC6749 section 4.1.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1) struct AuthorizationParams { @@ -105,7 +106,7 @@ pub async fn authorize_form( .bind(&token_claims.sub) .bind(&authorization_params.client_id) .bind(sqlx::types::Json(&scopes)) - .fetch_one(&app_state.db) + .fetch_one(&app_state.db.0) .await; match authorizations_res { @@ -119,7 +120,7 @@ pub async fn authorize_form( .bind(existing_authorization.id) .bind(authorization_code.clone()) .bind(Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true)) - .execute(&app_state.db) + .execute(&app_state.db.0) .await.unwrap(); // Authorization already given, just redirect to the app @@ -219,7 +220,7 @@ pub async fn perform_authorize( .bind(authorization.code) .bind(authorization.last_used_at.map(|x| x.to_rfc3339_opts(SecondsFormat::Millis, true))) .bind(authorization.created_at.to_rfc3339_opts(SecondsFormat::Millis, true)) - .execute(&app_state.db) + .execute(&app_state.db.0) .await; if let Err(err) = res { error!("Failed to save authorization in DB. {}", err); diff --git a/src/controllers/ui/home.rs b/lib/http_server/src/controllers/ui/home.rs similarity index 100% rename from src/controllers/ui/home.rs rename to lib/http_server/src/controllers/ui/home.rs diff --git a/src/controllers/ui/login.rs b/lib/http_server/src/controllers/ui/login.rs similarity index 88% rename from src/controllers/ui/login.rs rename to lib/http_server/src/controllers/ui/login.rs index 6bdca40..59cce05 100644 --- a/src/controllers/ui/login.rs +++ b/lib/http_server/src/controllers/ui/login.rs @@ -1,15 +1,15 @@ use axum_extra::extract::{cookie::{Cookie, SameSite}, CookieJar}; use chrono::{SecondsFormat, Utc}; +use kernel::models::user::{User, UserStatus}; use log::info; use serde::Deserialize; -use axum::{extract::{Query, State}, http::{HeaderMap, HeaderValue, StatusCode}, response::{Html, IntoResponse, Redirect}, Extension, Form}; +use axum::{extract::{Query, State}, http::StatusCode, response::{IntoResponse, Redirect}, Extension, Form}; use fully_pub::fully_pub; use minijinja::context; use time::Duration; +use utils::verify_password_hash; -use crate::{ - consts::WEB_GUI_JWT_COOKIE_NAME, models::{token_claims::UserTokenClaims, user::{User, UserStatus}}, renderer::TemplateRenderer, server::AppState, services::{password::verify_password_hash, session::create_token} -}; +use crate::{renderer::TemplateRenderer, services::session::create_token, token_claims::UserTokenClaims, AppState, WEB_GUI_JWT_COOKIE_NAME}; pub async fn login_form( Extension(renderer): Extension @@ -47,7 +47,7 @@ pub async fn perform_login( let user_res = sqlx::query_as::<_, User>("SELECT * FROM users WHERE handle = $1 OR email = $2") .bind(&login.login) .bind(&login.login) - .fetch_one(&app_state.db) + .fetch_one(&app_state.db.0) .await; let password_hash = match &user_res { @@ -87,7 +87,7 @@ pub async fn perform_login( let _result = sqlx::query("UPDATE users SET last_login_at = $2 WHERE id = $1") .bind(user.id.clone()) .bind(Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true)) - .execute(&app_state.db) + .execute(&app_state.db.0) .await.unwrap(); let jwt_max_age = Duration::days(15); diff --git a/src/controllers/ui/logout.rs b/lib/http_server/src/controllers/ui/logout.rs similarity index 85% rename from src/controllers/ui/logout.rs rename to lib/http_server/src/controllers/ui/logout.rs index fd55422..256b7a6 100644 --- a/src/controllers/ui/logout.rs +++ b/lib/http_server/src/controllers/ui/logout.rs @@ -1,7 +1,7 @@ use axum::response::{IntoResponse, Redirect}; use axum_extra::extract::CookieJar; -use crate::consts::WEB_GUI_JWT_COOKIE_NAME; +use crate::WEB_GUI_JWT_COOKIE_NAME; pub async fn perform_logout( cookies: CookieJar diff --git a/src/controllers/ui/me.rs b/lib/http_server/src/controllers/ui/me.rs similarity index 93% rename from src/controllers/ui/me.rs rename to lib/http_server/src/controllers/ui/me.rs index 2a6b8c0..6d3113d 100644 --- a/src/controllers/ui/me.rs +++ b/lib/http_server/src/controllers/ui/me.rs @@ -5,10 +5,11 @@ use log::error; use minijinja::context; use crate::{ - models::{token_claims::UserTokenClaims, user::User}, + token_claims::UserTokenClaims, renderer::TemplateRenderer, - server::AppState + AppState }; +use kernel::models::user::User; pub async fn me_page( State(app_state): State, @@ -17,7 +18,7 @@ pub async fn me_page( ) -> impl IntoResponse { let user_res = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1") .bind(&token_claims.sub) - .fetch_one(&app_state.db) + .fetch_one(&app_state.db.0) .await .expect("To get user from claim"); @@ -38,7 +39,7 @@ pub async fn me_update_details_form( let user_res = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1") .bind(&token_claims.sub) - .fetch_one(&app_state.db) + .fetch_one(&app_state.db.0) .await .expect("To get user from claim"); @@ -79,12 +80,12 @@ pub async fn me_perform_update_details( .bind(details_update.full_name) .bind(details_update.website) .bind(details_update.picture.contents.to_vec()) - .execute(&app_state.db) + .execute(&app_state.db.0) .await; let user_res = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1") .bind(&token_claims.sub) - .fetch_one(&app_state.db) + .fetch_one(&app_state.db.0) .await .expect("To get user from claim"); diff --git a/src/controllers/ui/mod.rs b/lib/http_server/src/controllers/ui/mod.rs similarity index 100% rename from src/controllers/ui/mod.rs rename to lib/http_server/src/controllers/ui/mod.rs diff --git a/src/controllers/ui/register.rs b/lib/http_server/src/controllers/ui/register.rs similarity index 93% rename from src/controllers/ui/register.rs rename to lib/http_server/src/controllers/ui/register.rs index 28ba1db..7a902fc 100644 --- a/src/controllers/ui/register.rs +++ b/lib/http_server/src/controllers/ui/register.rs @@ -7,7 +7,10 @@ use fully_pub::fully_pub; use sqlx::types::Json; use uuid::Uuid; -use crate::{models::user::{User, UserStatus}, renderer::TemplateRenderer, server::AppState, services::password::get_password_hash}; +use crate::{renderer::TemplateRenderer, AppState}; + +use kernel::models::user::{User, UserStatus}; +use utils::get_password_hash; pub async fn register_form( State(app_state): State @@ -66,7 +69,7 @@ pub async fn perform_register( .bind(user.roles) .bind(user.password_hash) .bind(user.created_at.to_rfc3339_opts(SecondsFormat::Millis, true)) - .execute(&app_state.db) + .execute(&app_state.db.0) .await; match res { Err(err) => { @@ -93,7 +96,7 @@ pub async fn perform_register( StatusCode::OK, "pages/register", context!( - success => true + success => true ) ) } diff --git a/src/controllers/ui/user_panel/authorizations.rs b/lib/http_server/src/controllers/ui/user_panel/authorizations.rs similarity index 89% rename from src/controllers/ui/user_panel/authorizations.rs rename to lib/http_server/src/controllers/ui/user_panel/authorizations.rs index 551b769..7f691be 100644 --- a/src/controllers/ui/user_panel/authorizations.rs +++ b/lib/http_server/src/controllers/ui/user_panel/authorizations.rs @@ -4,7 +4,8 @@ use log::error; use minijinja::context; use serde::Deserialize; -use crate::{models::{authorization::Authorization, token_claims::UserTokenClaims}, renderer::TemplateRenderer, server::AppState}; +use kernel::models::authorization::Authorization; +use crate::{renderer::TemplateRenderer, token_claims::UserTokenClaims, AppState}; pub async fn get_authorizations( State(app_state): State, @@ -13,7 +14,7 @@ pub async fn get_authorizations( ) -> impl IntoResponse { let user_authorizations = sqlx::query_as::<_, Authorization>("SELECT * FROM authorizations WHERE user_id = $1") .bind(&token_claims.sub) - .fetch_all(&app_state.db) + .fetch_all(&app_state.db.0) .await .expect("To get user authorization with user_id from claim"); renderer.render( @@ -37,7 +38,7 @@ pub async fn revoke_authorization( ) -> impl IntoResponse { let delete_res = sqlx::query("DELETE FROM authorizations WHERE id = $1") .bind(&form.authorization_id) - .execute(&app_state.db) + .execute(&app_state.db.0) .await; match delete_res { Ok(_) => {}, diff --git a/src/controllers/ui/user_panel/mod.rs b/lib/http_server/src/controllers/ui/user_panel/mod.rs similarity index 100% rename from src/controllers/ui/user_panel/mod.rs rename to lib/http_server/src/controllers/ui/user_panel/mod.rs diff --git a/src/server.rs b/lib/http_server/src/lib.rs similarity index 65% rename from src/server.rs rename to lib/http_server/src/lib.rs index 6833988..cc32177 100644 --- a/src/server.rs +++ b/lib/http_server/src/lib.rs @@ -1,24 +1,22 @@ -use base64::{prelude::BASE64_STANDARD, Engine}; +pub mod controllers; +pub mod router; +pub mod services; +pub mod middlewares; +pub mod renderer; +pub mod token_claims; + use fully_pub::fully_pub; use anyhow::{Result, Context}; +use kernel::{context::AppSecrets, models::config::Config, repositories::storage::Storage}; use log::info; -use minijinja::{context, Environment}; -use sqlx::{Pool, Sqlite}; -use crate::{models::config::{AppSecrets, Config}, router::build_router}; +use minijinja::Environment; -fn build_templating_env(config: &Config) -> Environment<'static> { - let mut env = Environment::new(); +use crate::{ + router::build_router, + renderer::build_templating_env +}; - minijinja_embed::load_templates!(&mut env); - - env.add_global("gl", context! { - instance => config.instance - }); - env.add_function("encode_b64str", |bin_val: Vec| { - BASE64_STANDARD.encode(bin_val) - }); - env -} +pub const WEB_GUI_JWT_COOKIE_NAME: &str = "minauthator_jwt"; #[derive(Debug)] #[fully_pub] @@ -34,7 +32,7 @@ pub struct ServerConfig { pub struct AppState { secrets: AppSecrets, config: Config, - db: Pool, + db: Storage, templating_env: Environment<'static> } @@ -42,7 +40,7 @@ pub async fn start_http_server( server_config: ServerConfig, config: Config, secrets: AppSecrets, - db_pool: Pool + db_pool: Storage ) -> Result<()> { // build state let state = AppState { diff --git a/src/cli.rs b/lib/http_server/src/main.rs similarity index 74% rename from src/cli.rs rename to lib/http_server/src/main.rs index 9317f31..6b93ae9 100644 --- a/src/cli.rs +++ b/lib/http_server/src/main.rs @@ -1,9 +1,9 @@ use argh::FromArgs; use anyhow::{Context, Result}; +use http_server::{start_http_server, ServerConfig}; +use kernel::{consts::DEFAULT_ASSETS_PATH, context::{get_kernel_context, StartKernelConfig}}; use log::info; -use crate::{get_app_context, server::{start_http_server, ServerConfig}, DEFAULT_ASSETS_PATH}; - #[derive(Debug, FromArgs)] /// Minauthator daemon struct ServerCliFlags { @@ -27,14 +27,15 @@ struct ServerCliFlags { listen_port: u32 } -/// handle CLI arguments to start process daemon -pub async fn start_server_cli() -> Result<()> { +/// handle CLI arguments to start HTTP server daemon +#[tokio::main] +pub async fn main() -> Result<()> { info!("Starting minauth"); let flags: ServerCliFlags = argh::from_env(); - let (config, secrets, db_pool) = get_app_context(crate::StartAppConfig { + let (config, secrets, db_pool) = get_kernel_context(StartKernelConfig { config_path: flags.config, database_path: flags.database - }).await.context("Getting app context")?; + }).await.context("Getting kernel context")?; start_http_server( ServerConfig { assets_path: flags.static_assets.unwrap_or(DEFAULT_ASSETS_PATH.to_string()), diff --git a/src/middlewares/app_auth.rs b/lib/http_server/src/middlewares/app_auth.rs similarity index 95% rename from src/middlewares/app_auth.rs rename to lib/http_server/src/middlewares/app_auth.rs index defe63b..3709f8b 100644 --- a/src/middlewares/app_auth.rs +++ b/lib/http_server/src/middlewares/app_auth.rs @@ -5,9 +5,12 @@ use axum::{ response::{Html, IntoResponse, Response}, Extension }; +use utils::parse_basic_auth; use crate::{ - models::token_claims::AppUserTokenClaims, server::AppState, services::{app_session::AppClientSession, session::verify_token}, utils::parse_basic_auth + services::{app_session::AppClientSession, session::verify_token}, + token_claims::AppUserTokenClaims, + AppState }; diff --git a/src/middlewares/mod.rs b/lib/http_server/src/middlewares/mod.rs similarity index 100% rename from src/middlewares/mod.rs rename to lib/http_server/src/middlewares/mod.rs diff --git a/src/middlewares/renderer.rs b/lib/http_server/src/middlewares/renderer.rs similarity index 84% rename from src/middlewares/renderer.rs rename to lib/http_server/src/middlewares/renderer.rs index 0e01238..2886afa 100644 --- a/src/middlewares/renderer.rs +++ b/lib/http_server/src/middlewares/renderer.rs @@ -1,6 +1,5 @@ use axum::{extract::{Request, State}, http::StatusCode, middleware::Next, response::Response, Extension}; - -use crate::{models::token_claims::UserTokenClaims, renderer::TemplateRenderer, server::AppState}; +use crate::{renderer::TemplateRenderer, token_claims::UserTokenClaims, AppState}; pub async fn renderer_middleware( State(app_state): State, diff --git a/src/middlewares/user_auth.rs b/lib/http_server/src/middlewares/user_auth.rs similarity index 93% rename from src/middlewares/user_auth.rs rename to lib/http_server/src/middlewares/user_auth.rs index 095eecf..7582eb7 100644 --- a/src/middlewares/user_auth.rs +++ b/lib/http_server/src/middlewares/user_auth.rs @@ -7,7 +7,9 @@ use axum::{ use axum_extra::extract::CookieJar; use crate::{ - consts::WEB_GUI_JWT_COOKIE_NAME, models::token_claims::UserTokenClaims, server::AppState, services::session::verify_token + services::session::verify_token, + token_claims::UserTokenClaims, + AppState, WEB_GUI_JWT_COOKIE_NAME }; diff --git a/src/renderer.rs b/lib/http_server/src/renderer.rs similarity index 75% rename from src/renderer.rs rename to lib/http_server/src/renderer.rs index 9b163a0..d7b0c50 100644 --- a/src/renderer.rs +++ b/lib/http_server/src/renderer.rs @@ -1,9 +1,11 @@ use axum::{http::StatusCode, response::{Html, IntoResponse}}; use fully_pub::fully_pub; +use kernel::models::config::Config; use log::error; use minijinja::{context, Environment, Value}; +use utils::encode_base64_picture; -use crate::models::token_claims::UserTokenClaims; +use crate::token_claims::UserTokenClaims; #[derive(Debug, Clone)] @@ -43,3 +45,14 @@ impl TemplateRenderer { } } +pub fn build_templating_env(config: &Config) -> Environment<'static> { + let mut env = Environment::new(); + + minijinja_embed::load_templates!(&mut env); + + env.add_global("gl", context! { + instance => config.instance + }); + env.add_function("inline_picture", encode_base64_picture); + env +} diff --git a/src/router.rs b/lib/http_server/src/router.rs similarity index 95% rename from src/router.rs rename to lib/http_server/src/router.rs index 3836d54..d3e95b6 100644 --- a/src/router.rs +++ b/lib/http_server/src/router.rs @@ -9,7 +9,7 @@ use crate::{ app_auth, renderer::renderer_middleware }, - server::{AppState, ServerConfig} + AppState, ServerConfig }; pub fn build_router(server_config: &ServerConfig, app_state: AppState) -> Router { @@ -43,7 +43,8 @@ pub fn build_router(server_config: &ServerConfig, app_state: AppState) -> Router let api_user_routes = Router::new() .route("/api/user", get(api::read_user::read_user_basic)) - .layer(middleware::from_fn_with_state(app_state.clone(), app_auth::enforce_jwt_auth_middleware)); + .layer(middleware::from_fn_with_state(app_state.clone(), app_auth::enforce_jwt_auth_middleware)) + .route("/api", get(api::index::get_index)); let well_known_routes = Router::new() .route("/.well-known/openid-configuration", get(api::openid::well_known::get_well_known_openid_configuration)); diff --git a/src/services/app_session.rs b/lib/http_server/src/services/app_session.rs similarity index 100% rename from src/services/app_session.rs rename to lib/http_server/src/services/app_session.rs diff --git a/src/services/mod.rs b/lib/http_server/src/services/mod.rs similarity index 75% rename from src/services/mod.rs rename to lib/http_server/src/services/mod.rs index 7193e1b..ed4dda0 100644 --- a/src/services/mod.rs +++ b/lib/http_server/src/services/mod.rs @@ -1,4 +1,3 @@ -pub mod password; pub mod session; pub mod oauth2; pub mod app_session; diff --git a/src/services/oauth2.rs b/lib/http_server/src/services/oauth2.rs similarity index 87% rename from src/services/oauth2.rs rename to lib/http_server/src/services/oauth2.rs index 55b640e..ed60954 100644 --- a/src/services/oauth2.rs +++ b/lib/http_server/src/services/oauth2.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use anyhow::{Result, Context}; -use crate::models::{authorization::AuthorizationScope, config::Application}; +use kernel::models::{authorization::AuthorizationScope, config::Application}; pub fn verify_redirect_uri(app: &Application, input_redirect_uri: &str) -> bool { app.allowed_redirect_uris diff --git a/src/services/session.rs b/lib/http_server/src/services/session.rs similarity index 94% rename from src/services/session.rs rename to lib/http_server/src/services/session.rs index cf55087..018e094 100644 --- a/src/services/session.rs +++ b/lib/http_server/src/services/session.rs @@ -1,8 +1,7 @@ use anyhow::Result; use serde::{de::DeserializeOwned, Serialize}; use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey}; - -use crate::models::config::AppSecrets; +use kernel::context::AppSecrets; pub fn create_token(secrets: &AppSecrets, claims: T) -> String { diff --git a/src/templates/components/footer.html b/lib/http_server/src/templates/components/footer.html similarity index 100% rename from src/templates/components/footer.html rename to lib/http_server/src/templates/components/footer.html diff --git a/src/templates/components/header.html b/lib/http_server/src/templates/components/header.html similarity index 100% rename from src/templates/components/header.html rename to lib/http_server/src/templates/components/header.html diff --git a/src/templates/layouts/base.html b/lib/http_server/src/templates/layouts/base.html similarity index 100% rename from src/templates/layouts/base.html rename to lib/http_server/src/templates/layouts/base.html diff --git a/src/templates/pages/apps.html b/lib/http_server/src/templates/pages/apps.html similarity index 100% rename from src/templates/pages/apps.html rename to lib/http_server/src/templates/pages/apps.html diff --git a/src/templates/pages/authorize.html b/lib/http_server/src/templates/pages/authorize.html similarity index 100% rename from src/templates/pages/authorize.html rename to lib/http_server/src/templates/pages/authorize.html diff --git a/src/templates/pages/home.html b/lib/http_server/src/templates/pages/home.html similarity index 100% rename from src/templates/pages/home.html rename to lib/http_server/src/templates/pages/home.html diff --git a/src/templates/pages/login.html b/lib/http_server/src/templates/pages/login.html similarity index 100% rename from src/templates/pages/login.html rename to lib/http_server/src/templates/pages/login.html diff --git a/src/templates/pages/me/details-form.html b/lib/http_server/src/templates/pages/me/details-form.html similarity index 100% rename from src/templates/pages/me/details-form.html rename to lib/http_server/src/templates/pages/me/details-form.html diff --git a/src/templates/pages/me/index.html b/lib/http_server/src/templates/pages/me/index.html similarity index 100% rename from src/templates/pages/me/index.html rename to lib/http_server/src/templates/pages/me/index.html diff --git a/src/templates/pages/register.html b/lib/http_server/src/templates/pages/register.html similarity index 100% rename from src/templates/pages/register.html rename to lib/http_server/src/templates/pages/register.html diff --git a/src/templates/pages/user_panel/authorizations.html b/lib/http_server/src/templates/pages/user_panel/authorizations.html similarity index 100% rename from src/templates/pages/user_panel/authorizations.html rename to lib/http_server/src/templates/pages/user_panel/authorizations.html diff --git a/src/models/token_claims.rs b/lib/http_server/src/token_claims.rs similarity index 95% rename from src/models/token_claims.rs rename to lib/http_server/src/token_claims.rs index 66e20d2..932b4ce 100644 --- a/src/models/token_claims.rs +++ b/lib/http_server/src/token_claims.rs @@ -1,10 +1,9 @@ use fully_pub::fully_pub; use jsonwebtoken::get_current_timestamp; +use kernel::models::authorization::AuthorizationScope; use serde::{Deserialize, Serialize}; use time::Duration; -use super::authorization::AuthorizationScope; - #[derive(Debug, Serialize, Deserialize, Clone)] #[fully_pub] struct UserTokenClaims { diff --git a/lib/kernel/Cargo.toml b/lib/kernel/Cargo.toml new file mode 100644 index 0000000..a345f83 --- /dev/null +++ b/lib/kernel/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "kernel" +edition = "2021" + +[dependencies] +utils = { path = "../utils" } + +log = { workspace = true } +env_logger = { workspace = true } +anyhow = { workspace = true } +fully_pub = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +chrono = { workspace = true } +toml = { workspace = true } +sqlx = { workspace = true } +dotenvy = { workspace = true } + +uuid = { workspace = true } +url = { workspace = true } diff --git a/lib/kernel/src/actions/mod.rs b/lib/kernel/src/actions/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/lib/kernel/src/actions/user.rs b/lib/kernel/src/actions/user.rs new file mode 100644 index 0000000..e69de29 diff --git a/lib/kernel/src/consts.rs b/lib/kernel/src/consts.rs new file mode 100644 index 0000000..bd3bef1 --- /dev/null +++ b/lib/kernel/src/consts.rs @@ -0,0 +1,4 @@ +pub const DEFAULT_DB_PATH: &str = "/var/lib/minauthator/minauthator.db"; +pub const DEFAULT_ASSETS_PATH: &str = "/usr/local/lib/minauthator/assets"; +pub const DEFAULT_CONFIG_PATH: &str = "/etc/minauthator/config.yaml"; + diff --git a/lib/kernel/src/context.rs b/lib/kernel/src/context.rs new file mode 100644 index 0000000..af5fab3 --- /dev/null +++ b/lib/kernel/src/context.rs @@ -0,0 +1,51 @@ +use std::{env, fs}; +use anyhow::{Result, Context, anyhow}; +use fully_pub::fully_pub; + +use log::info; +use sqlx::{Pool, Sqlite}; +use crate::{ + consts::{DEFAULT_CONFIG_PATH, DEFAULT_DB_PATH}, database::prepare_database, models::config::Config, repositories::storage::Storage +}; + +/// get server config +fn get_config(path: String) -> Result { + let inp_def_yaml = fs::read_to_string(path) + .expect("Should have been able to read the the config file"); + + toml::from_str(&inp_def_yaml) + .map_err(|e| anyhow!("Failed to parse config, {:?}", e)) +} + +#[fully_pub] +struct StartKernelConfig { + config_path: Option, + database_path: Option, +} + +#[derive(Debug, Clone)] +#[fully_pub] +struct AppSecrets { + jwt_secret: String +} + +pub async fn get_kernel_context(start_config: StartKernelConfig) -> Result<(Config, AppSecrets, Storage)> { + env_logger::init(); + let _ = dotenvy::dotenv(); + + let database_path = &start_config.database_path.unwrap_or(DEFAULT_DB_PATH.to_string()); + info!("Using database file at {}", database_path); + let storage = prepare_database(database_path).await.context("Could not prepare db.")?; + + let config_path = start_config.config_path.unwrap_or(DEFAULT_CONFIG_PATH.to_string()); + info!("Using config file at {}", &config_path); + let config: Config = get_config(config_path) + .expect("Cannot get config."); + + dotenvy::dotenv().context("loading .env")?; + let secrets = AppSecrets { + jwt_secret: env::var("APP_JWT_SECRET").context("Expecting APP_JWT_SECRET env var.")? + }; + + Ok((config, secrets, storage)) +} diff --git a/src/database.rs b/lib/kernel/src/database.rs similarity index 64% rename from src/database.rs rename to lib/kernel/src/database.rs index 66077de..7e0b334 100644 --- a/src/database.rs +++ b/lib/kernel/src/database.rs @@ -1,8 +1,10 @@ use anyhow::{Context, Result}; -use sqlx::{sqlite::{SqliteConnectOptions, SqlitePoolOptions}, Pool, Sqlite, ConnectOptions}; +use sqlx::{sqlite::{SqliteConnectOptions, SqlitePoolOptions}, ConnectOptions}; use std::str::FromStr; -pub async fn prepare_database(sqlite_db_path: &str) -> Result> { +use crate::repositories::storage::Storage; + +pub async fn prepare_database(sqlite_db_path: &str) -> Result { let conn_str = format!("sqlite:{}", sqlite_db_path); let pool = SqlitePoolOptions::new() @@ -14,6 +16,6 @@ pub async fn prepare_database(sqlite_db_path: &str) -> Result> { .await .context("could not connect to database_url")?; - Ok(pool) + Ok(Storage(pool)) } diff --git a/lib/kernel/src/lib.rs b/lib/kernel/src/lib.rs new file mode 100644 index 0000000..534248a --- /dev/null +++ b/lib/kernel/src/lib.rs @@ -0,0 +1,7 @@ +pub mod models; +pub mod database; +pub mod consts; +pub mod context; +pub mod actions; +pub mod repositories; + diff --git a/src/models/authorization.rs b/lib/kernel/src/models/authorization.rs similarity index 100% rename from src/models/authorization.rs rename to lib/kernel/src/models/authorization.rs diff --git a/src/models/config.rs b/lib/kernel/src/models/config.rs similarity index 99% rename from src/models/config.rs rename to lib/kernel/src/models/config.rs index a9512b0..57a275a 100644 --- a/src/models/config.rs +++ b/lib/kernel/src/models/config.rs @@ -69,7 +69,6 @@ struct Config { roles: Vec } - #[derive(Debug, Clone)] #[fully_pub] struct AppSecrets { diff --git a/src/models/mod.rs b/lib/kernel/src/models/mod.rs similarity index 70% rename from src/models/mod.rs rename to lib/kernel/src/models/mod.rs index da715f7..37a7310 100644 --- a/src/models/mod.rs +++ b/lib/kernel/src/models/mod.rs @@ -1,4 +1,3 @@ pub mod config; pub mod user; pub mod authorization; -pub mod token_claims; diff --git a/src/models/user.rs b/lib/kernel/src/models/user.rs similarity index 100% rename from src/models/user.rs rename to lib/kernel/src/models/user.rs diff --git a/lib/kernel/src/repositories/mod.rs b/lib/kernel/src/repositories/mod.rs new file mode 100644 index 0000000..d090739 --- /dev/null +++ b/lib/kernel/src/repositories/mod.rs @@ -0,0 +1,2 @@ +pub mod storage; +pub mod users; diff --git a/lib/kernel/src/repositories/storage.rs b/lib/kernel/src/repositories/storage.rs new file mode 100644 index 0000000..bbad80c --- /dev/null +++ b/lib/kernel/src/repositories/storage.rs @@ -0,0 +1,7 @@ +use fully_pub::fully_pub; +use sqlx::{Pool, Sqlite}; + +/// storage interface +#[fully_pub] +#[derive(Clone, Debug)] +struct Storage(Pool); diff --git a/lib/kernel/src/repositories/users.rs b/lib/kernel/src/repositories/users.rs new file mode 100644 index 0000000..6ef31f4 --- /dev/null +++ b/lib/kernel/src/repositories/users.rs @@ -0,0 +1,14 @@ +// user repositories + +use crate::models::user::User; + +use super::storage::Storage; +use anyhow::{Result, Context}; + +async fn get_user_by_id(storage: &Storage, user_id: &str) -> Result { + sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1") + .bind(user_id) + .fetch_one(&storage.0) + .await + .context("To get user from claim") +} diff --git a/lib/utils/Cargo.toml b/lib/utils/Cargo.toml new file mode 100644 index 0000000..18fc24c --- /dev/null +++ b/lib/utils/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "utils" +edition = "2021" + +[dependencies] +anyhow = { workspace = true } +argon2 = "0.5" +base64 = "0.22" +rand = "0.8.5" +rand_core = { version = "0.6.4", features = ["std"] } diff --git a/src/utils.rs b/lib/utils/src/lib.rs similarity index 92% rename from src/utils.rs rename to lib/utils/src/lib.rs index 4afce48..bb524dc 100644 --- a/src/utils.rs +++ b/lib/utils/src/lib.rs @@ -61,3 +61,7 @@ pub fn parse_basic_auth(header_value: &str) -> Result<(String, String)> { )) } +pub fn encode_base64_picture(picture_bytes: Vec) -> String { + let encoded = BASE64_STANDARD.encode(picture_bytes); + return format!("data:image/*;base64,{}", encoded); +} diff --git a/src/consts.rs b/src/consts.rs deleted file mode 100644 index 1d9bd00..0000000 --- a/src/consts.rs +++ /dev/null @@ -1 +0,0 @@ -pub const WEB_GUI_JWT_COOKIE_NAME: &str = "minauthator_jwt"; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 016a7bd..0000000 --- a/src/main.rs +++ /dev/null @@ -1,62 +0,0 @@ -pub mod models; -pub mod controllers; -pub mod router; -pub mod server; -pub mod database; -pub mod cli; -pub mod utils; -pub mod services; -pub mod middlewares; -pub mod renderer; -pub mod consts; - -use std::{env, fs}; -use anyhow::{Result, Context, anyhow}; - -use database::prepare_database; -use log::info; -use sqlx::{Pool, Sqlite}; -use models::config::{AppSecrets, Config}; - -pub const DEFAULT_DB_PATH: &str = "/var/lib/minauthator/minauthator.db"; -pub const DEFAULT_ASSETS_PATH: &str = "/usr/local/lib/minauthator/assets"; -pub const DEFAULT_CONFIG_PATH: &str = "/etc/minauthator/config.yaml"; - -fn get_config(path: String) -> Result { - let inp_def_yaml = fs::read_to_string(path) - .expect("Should have been able to read the the config file"); - - toml::from_str(&inp_def_yaml) - .map_err(|e| anyhow!("Failed to parse config, {:?}", e)) -} - -struct StartAppConfig { - config_path: Option, - database_path: Option, -} - -#[tokio::main] -async fn main() -> Result<()> { - cli::start_server_cli().await -} - -async fn get_app_context(start_app_config: StartAppConfig) -> Result<(Config, AppSecrets, Pool)> { - env_logger::init(); - let _ = dotenvy::dotenv(); - - let database_path = &start_app_config.database_path.unwrap_or(DEFAULT_DB_PATH.to_string()); - info!("Using database file at {}", database_path); - let pool = prepare_database(database_path).await.context("Could not prepare db.")?; - - let config_path = start_app_config.config_path.unwrap_or(DEFAULT_CONFIG_PATH.to_string()); - info!("Using config file at {}", &config_path); - let config: Config = get_config(config_path) - .expect("Cannot get config."); - - dotenvy::dotenv().context("loading .env")?; - let secrets = AppSecrets { - jwt_secret: env::var("APP_JWT_SECRET").context("Expecting APP_JWT_SECRET env var.")? - }; - - Ok((config, secrets, pool)) -} diff --git a/src/services/password.rs b/src/services/password.rs deleted file mode 100644 index c9e246b..0000000 --- a/src/services/password.rs +++ /dev/null @@ -1,35 +0,0 @@ -use anyhow::{anyhow, Result}; -use argon2::{ - password_hash::{ - rand_core::OsRng, - PasswordHash, PasswordHasher, PasswordVerifier, SaltString - }, - Argon2 -}; - -pub fn get_password_hash(password: String) -> Result<(String, String)> { - let salt = SaltString::generate(&mut OsRng); - - // Argon2 with default params (Argon2id v19) - let argon2 = Argon2::default(); - - // Hash password to PHC string ($argon2id$v=19$...) - match argon2.hash_password(password.as_bytes(), &salt) { - Ok(val) => Ok((salt.to_string(), val.to_string())), - Err(_) => Err(anyhow!("Failed to process password.")) - } -} - -pub fn verify_password_hash(password_hash: String, password: String) -> Result<()> { - let parsed_hash = match PasswordHash::new(&password_hash) { - Ok(val) => val, - Err(_) => { - return Err(anyhow!("Failed to parse password hash")); - } - }; - match Argon2::default().verify_password(password.as_bytes(), &parsed_hash) { - Ok(()) => Ok(()), - Err(_) => Err(anyhow!("Failed to verify password.")) - } -} - diff --git a/tests/hurl_integration/run_scenario.sh b/tests/hurl_integration/run_scenario.sh new file mode 100755 index 0000000..c723ee3 --- /dev/null +++ b/tests/hurl_integration/run_scenario.sh @@ -0,0 +1,39 @@ +#!/usr/bin/sh + +set -eou pipefail + +scenario_name="$1" +project_root="$(dirname $(cargo locate-project | jq -r .root))" +scenario_dir="$project_root/tests/hurl_integration/$1" +scenario_tmp_dir_path="$project_root/tmp/tests/$scenario_name" +database_path="$project_root/tmp/tests/$scenario_name/minauthator.db" + +echo "Starting scenario $scenario_name." +mkdir -p $scenario_tmp_dir_path +if [ -f $database_path ]; then + rm $database_path +fi +sqlite3 $database_path < $project_root/migrations/all.sql + +export DB_PATH=$database_path +if [ -f $scenario_dir/init_db.sh ]; then + $scenario_dir/init_db.sh +fi + +pkill -f $project_root/target/debug/minauthator-server & +sleep 0.1 +$project_root/target/debug/minauthator-server \ + --config "$scenario_dir/config.toml" \ + --database $database_path \ + --listen-host "127.0.0.1" \ + --listen-port "8086" \ + --static-assets "$project_root/assets" & + +server_pid=$! +sleep 0.2 +hurl \ + --variable base_url="http://localhost:8086" \ + --test --error-format long \ + $scenario_dir/main.hurl +kill $server_pid +echo "End of scenario." diff --git a/tests/hurl_integration/scenario_1/config.toml b/tests/hurl_integration/scenario_1/config.toml new file mode 100644 index 0000000..ad3f58e --- /dev/null +++ b/tests/hurl_integration/scenario_1/config.toml @@ -0,0 +1,56 @@ +[instance] +base_uri = "http://localhost:8086" +name = "Example org" +logo_uri = "https://example.org/logo.png" + +[[applications]] +slug = "demo_app" +name = "Demo app" +description = "A super application where you can do everything you want." +client_id = "00000001-0000-0000-0000-000000000001" +client_secret = "dummy_client_secret" +login_uri = "https://localhost:9876" +allowed_redirect_uris = [ + "http://localhost:9090/callback", + "http://localhost:9876/callback" +] +visibility = "Internal" +authorize_flow = "Implicit" + +[[applications]] +slug = "wiki" +name = "Wiki app" +description = "The knowledge base of the exemple org." +client_id = "f9de1885-448d-44bb-8c48-7e985486a8c6" +client_secret = "49c6c16a-0a8a-4981-a60d-5cb96582cc1a" +login_uri = "https://wiki.example.org/login" +allowed_redirect_uris = [ + "https://wiki.example.org/oauth2/callback" +] +visibility = "Public" +authorize_flow = "Implicit" + +[[applications]] +slug = "private_app" +name = "Demo app" +description = "Private app you should never discover" +client_id = "c8a08783-2342-4ce3-a3cb-9dc89b6bdf" +client_secret = "this_is_the_secret" +login_uri = "https://private-app.org" +allowed_redirect_uris = [ + "http://localhost:9091/authorize", +] +visibility = "Private" +authorize_flow = "Implicit" + +[[roles]] +slug = "basic" +name = "Basic" +description = "Basic user" +default = true + +[[roles]] +slug = "admin" +name = "Administrator" +description = "Full power on organization instance" + diff --git a/tests/hurl_integration/scenario_1/init_db.sh b/tests/hurl_integration/scenario_1/init_db.sh new file mode 100755 index 0000000..11dc8ce --- /dev/null +++ b/tests/hurl_integration/scenario_1/init_db.sh @@ -0,0 +1,9 @@ +password_hash="$(echo -n "root" | argon2 salt_06cGGWYDJCZ -e)" +echo $password_hash +SQL=$(cat <i1^=$gouQUgoKQO zfr|1^u+cHlFmSQ)@NltlaqtOAiSP+X2yk$TDTztQ$S5c%@QA2rsK{wZ$tlSH0|6o- zBcmXrV4lgylmD;fwI6_wh5$eSAOh(D2>3umeBkRafC>OWKm`6Lfd4OuNC?O% zs6YT3`aiWO9sr00L_|bHKt@19K|nzK_e=A892Z7)xW#Rf&YyC59ohB`G@e&1OIfa_yB}|c>j$60FnQ%bsz#hA{`RJ z|HG9=CSs8B2z%WCVEr5MPlFGT06g~}Hy8_RgAEex*@wSvl&)diw~!EX*fpRdNJA2h z2`3#`-Q(D5vp(1`3l!wt*o5Qx^Vf1KP~Iv35LhylNvoJ-6r~tUaX9`<5)m4+vd(f* zHpz&c*cjqHf)M<3d<>Z?p*d|xdI{6>JBid(S^t*Tn7FVcN%+X?Z>y~a;`d*ec+g-Q+WSdQh{R&Vtj%p6mp7@(<`r!!ky@=@B z(63OQ7HyneFJ0%(-}wXIk>yG}la9g4iI`9)Ohv@#!F&b!K9aGntCLhy<{J&bEW6L0 z)S4vf2(vkQlYaX15Mmn)DMGh_ik{apXY+!j#1nNpaGjh=Hq0Ts2pxu?;I}t5r`Utx zhx7%6Isor=cmG=z|DcuoWJpfQNfI&?@@O!D-vy6N8goUs*9NPBizK=-yrgJWpdnq= z7pswH3a1LgVtjL~CX&gpL^3 zT7i_?3lLGx^_CCg(ghxC9XVlRXySs>Ik7XsjtqMceu^?t5BzkGvuZ7|4c(|-HSt$} zw^p;=lc`pZ`$&@96&Rd0{$0CUfg*2Eq^7Gz`=Fs*41Wv1_6rMX00`dK^lm5_lc=wl3bGT7C>xA{awKM(dl;5RN@ zeWmqN+q<&FQ4GDv8;bcN@LmQvD%HvbN4Jd}6lV=Am1|B_NF7D}R~ntCetSd{!N_yU-#keh^DZlHB^rY&yDmA8-RQG9?Go*_ zlwfN~-pQ?AC+Xs+gGEtx4u5loYHCgJyZN3&PztBT`^!uuE^6v7%2a$OMufE;~C zM)F$wS|gbufF*L6n3j*b8aM@z`&&~bvdbcOX+KN-^kiqG{q0ZT(DuoQiqyXjbob@Yf+xi{%Yt8e)Zq8c?71y6qU%hI!pvldx4asLu;V13goOohp@Tw5Gh%;Rr0Qp!QDzh)O zO64Y9?<(7gKWXVZyEScgG(Mf(_dO-qe2FCcy%ZS07ryxZJDrG%gN3K+AMk9v7>YBa_Q(|4%-r7r7C zap1Y8h8<3Pd@kEm6Q;RJ046?6XxhxQehAjwMn9rYqo`pY36sN00#_%XX5day-NGi8 z(?Orn{&^5{^P(>e5w@X;q}>wgf`^sFiT$HHlQRi1lVC~CQmVrpFL_n*NvSMYb7wO} zd$a`Xujyy=0nL??S>6HL(;yO10c32T6h>cSMhffr0wAgIte{X~Muj?n@uFsNSvUPX zskDHKmAD6(w2Nl9yotB!2chJ6o>uLKD%KS^mY1!^d}5O(m^p5PcQjoqyfVzR%Hn*i zpIg67`#A>rDY&}qR(2AqIrXz6-N>aDP7&FBBOpZ=XVWawsr~e&Jx2^ zTwLiOLMz(}$+$^uzG+bt`JUk>r6?3{m8sb>;jpm@D|0}!M4FN2Hxl&zNea6SYk40` z*srOIL0lX*c=F;yQCI~Nr|hDHv_EsOm`1z;F#KS}D7hRpf9Mw7wjoIxgX_F@!cXLI zGdRYucTA5E>jJ!XxPH-RySZ>CyM6L>0%wsa&dkceel7yPo^O2RYAuh+uJY%MI7!y{ zqKulmKKRQ!B`>}m%wC}bAKb3dkCN!7qlLRZ&ZZ{9zdaxCy{R;=P$)mE8cOvSA##tTmQ1&@Le-HB6BK0cXWD;v_3Gobm<$D5|H0(O15?1a;r&mV(^bT6Ql%f0&1|LUBrv5%TW7ns zLN0ZK6mctQHKFwI#>j``1`zA5O{vxl^+rsjtfLM)$p% zH6|@GnHWyWwy!M1hJ?@Y4*TQ{Uu*Fj?94dPgy2#&o}O0#t8?-nY-Z#60sAA)L`n0F zE$!4|zw27+ekr9kZt4Izo-}Gi-^IuWa#N_%=QTtHae{y0t|&XW*lp9dyk$EQSu`n; z?VNOYomu)@NKuV0so1Eb=aP7r!lF93V5qNx~t5?qA(pDxqqxEiE%?V_a zSVaM8GaX@7WZ8kMQ7a;bk^j0UNN5Q_e4Md?NNToKZ6N(n#^sd5rQoiKRhLk#vSqON z?)rg|-s%8UI199(mcy(PsemfY+W3+|iLZIaoNDRBQo>xAEOx>k>-}sDH~t_;ufE$_p{Gw-HpFNTxO1KI104%^E`N2;kM*VVIxYlNYfrR z$@v#BWD-2h8z+dzbKcBH6BJ)w0Zqs#>14>GX{y|}T?rZ`ZvE2Z16x=fWqHC+>;$Wa z*6GQOtILDJY>w8!{07KPyFVxr>s|p%A@I6rmPLQByhW+wtY3;#1Is)SR!;exUUC-A z0C!%6v4?OFvuX!yJLZn(pqH4k!&<0N$jcin!o`)JPYG`F?63FjxYm9w_}z_vz^!wp zb5m_R3a9-cClX32uHRtl>}lC~1?sKvGv_b4nw+yE)JZItSV}~WOwO$12}hlrxPh;Y zz3X|(TA1EixreH-e_yLWE+mvuZW!Of7KAvTb|`7!(uQXGze9|Twr3?!m~3{oUP9

=3fx41NB1EieZTzN&r;r)h&CWrHH!803Uy(8GZYvtLNZy`%>~ zkMPTlDh=lLz4_B;BGwJc-yaRiGTs3`KcJ^q-PFu5VElBINhWz&<^ZeM&f;}#b~ zjgqx_L6-cDcG9k2NCusGdGKlN-b)?rau5?rMmoT&a4=Xtny8$d^b9owQp*%!NWQY5 zGQ0bQ>YvH_ab9rPgB;q(Of+_%j>`r)<~=J#KJ~)(N@XUyRoJie6jby)&ret%>tdWf zWeE8K}Eah`47CmV0h#8D#HbD)xl#Lq4K_VtRYg4Id zVGWuUz&8dkzXBk#5LG_kY_I|h!Z*6l4q@GrQR!hiW?FS3Ef7Ch0yAi&o`FqF>8wyf zHBzGXF$+FSFj-Ld>A$b_jHTr8|BlAPyQOpyXH(PoaoHUBg>NSTwn(7o&pmQ)xGtYz?a`-{!)V0=cdBD01%(Hcdx^v9oiA-;ysS^@ai zU?VB*-sC4#)J8I38=gD4WSf~k+%$=MUhAoI%Au zBpygWNSIv@x_~lLc8&S8p4FFn=cMf)zaMu4HC@CnC#}htiX>;UR(QMO z*yCUX@QiA4CV7p{CNCAObR;Qx?g~1_R$p|6D1A(Pds9h2VJ4j9=!6Ky-}X1!+Ydsa zjB`i~hwzzvi!^Jw!+HMPS<Qyc1N>VGGpM;Ik2R+hsfEO#9cB2N5>6&4Z~bNPA?u5xglN&y&zhf+LFF+^ax?C zZgSAOtS%zbAhD#DQinye(0*lBEA^WE-FzEkeyNHmBa<&9kC$ zq>GkiZ4*Y&QA^QX_IQ-s(3MVxnniCS6IrkFhN`-&nZa9X0FhqA0+}f()g&rtQ(Uh= z71$q0RN)@8qX;jwY{q7Clc|t+VY!vD5dAw|p5^-iWmUqK*_bunSg>tUU>@~4+}N+N z905REi7AG#mF`C<#!V@6kM0O06e&_3m1$D(`0-W%$0Z0T!U~}WWR|?J%niDQH>uTa z>|eq9Kp}PSd>!-r0kA&eoywFge_Ps}1&il;iDl!0hv0Hgx9=^)+aqJUOY!0wqj7;V z(ne5@#ABfi=a(P)w~8D>VIkZ84@D+T!yp`}|CD#AJKzw*{DwqNVQyAkyRkXNN<^*r zR#6F7wJT6-^2@6yEdyWQ5Hfwst73zUfiXFJXOGq{R_U| zz!3`_td^(nJNCjYOTLowJO}=B3F1Ytae$2f&fa{^n1z$tb?C9q1WKpmaDl))T5Yu3 zkDa=}@jIQ^i^O-A-&;8>?Yg}RP$BrFyPaFtIsJ)!(qCk`U%d=l5wgvpS_jn-SkwcGh-=_rwWEa)#wuV6v# z0WP=?YUqN9l-otVT`wbHa3sCquU0y~w>a7hlqSGmc9#;#l?S$NH%!iq{`8mDApdQJ z=$7}YbQH;-(8xX&rRS;Gx>W@m6vG=P02ghr$RtVx4sOR}MzOk#K;Oy;cjY?)@|F6mJF_Aj^%KiXwq>dZlYJ>x!^-;H^bGiyIkoB@) zXCK>NRh)9m&&Yw}+K&xwn|3>y_B(Cu4n$REd1K#vy8=#<19ML+;1oKgzE;VjM9suU zcM4;9A*il{Ose!J{NXWYiVp9JmiNK?;jYT(!QoOGv??-&4c=IdcjRyRWJ&`sR42rv z7hGUDa`&Xq6T{)}`c^io$k^R=bNlW{DcL!JKrm~S{b23c-<+~s^==Tv<6{ z3Dw6&d&V102P;vfns4{xRg5F4(bWt&)}gU8I8HM4&`(Td7igEkVE#19|GsHm~<0^t)VPxtfwOX8U95_j9XT<|mb^^b~<)B6U^u~k-+@O}h(NQaJN6xQL*HoZK z3u-Anygz0Dx!Heui;D^oYXk8den8WXL;080$jw1Jdriru|0>Vbne?Dh@Rw1xNNtp* zb5|-@mOR!%(W^(jrSrt>V zckY?730`fJgTfccuYl%x#}~2KJh8MKs%t4UEUlBXH15Wh=qo7gJ$rNk774QL+%)}vi%IJ70Y z!`GcYiv}uvz>i!P;O_-?H6P2SWt?z9g?=m_&VDcP_?gsn=NAsmlV0v41vDiq?f97% zNLra>M|dt82IXX_Joo5gL0k*adhS_JJVsg4_z16h$WK%&EGnP-6G)#xEtmy{q{Cxo z(j(5nD$(e)C#Z)0hvl6;n?b{#1oQgQ?j`FdzDrJeiP<1;cT^qod4x$zb#t@PO(NwG zRqW>_vcBGaF6X8e?}IeeGk?E0a&uP%VYZpgPPe6lTcfS%yF(kRv-{%DvtkOkn35}> zESr*Kkd5KZqq)Bhvd_VLCEcC!5LHCT*Bxh!6a#BLB-m z9_NC{@Qhj{7rEqQ@XRu`mj4_|HuD9cIQzpOnzcw+VrnAoA5&9wsBunA_EvDk)czxS z1=|EcJ5+2DV~w)27(!w1u(e9nKm)Y);k0d>%2=C`hFZL=>?>K@VI>tH66@4?;FQd~ z0_rAg6#nKEyRk3rTm5PaDCp_sH?TcaO3$&$+zexLjA0CeBG+@(i2BQ>p!BxSGB|a=mz1UqWlPMki>P~*58nDsd3*)Jj(?}lx`K^w+wSj88b9UoZ8oV|jU#@J*Ka;; z9>i`bhSEO^Hjlf|tlQH1;4pe|u1z}?-zh5<7utwkNZ2?`Y|3U8Hwzy5B~f)$sUon@ zU3dREkj}=`^&Qy|LkurlaMhveG;eNq&#G8q`{#0xJRYru6J$5f1kr9d;q?q}(QW-FOs8xM%4s|e|RNhg^ z6K=ifQT*Kh(5G8(KW!<_m5wh;sPPfcD|Zd@)BI=ang?oXBGSbko&2DgoT_NixeLf& z5*_3()Zby?+4|lt#r73IYN-2Ol*_y9uk`6Be&h<*BWceHp94td<=N0g^4YznpPKCN z@$wT$Pc$j$NiHa3wrHY*;o~uE_SayZ@YLf+!eN)Y0RBo`)gNJGf2S&?s^qazl0>UZ zmLTx?=tJvzUVGuuGcRB3HwUN{qwpZ{SAgw&$$*RJ!b@hBI0c0+0xkoY$0ARocdcBD z(T72vGuHmwUAO%8N3Q_f#93%b zLsBwD?Il`mEqgLb@{ExgI^$dZAp>{>i=tSjssPyiyGl`El-rlk>uMH7Ya#we>A|lv z5fryHQcW=0lrMKg!_DF(*hE+aLL_@Wg?4(lyL!70M=3j~P42fHftlyO)1tO$a{VX*Vj<&DOfXj_f2t>r@|HWwv^z!esXeZa$sg3Zr9X--ta}beVdT^fb+zjuX-*zZwZ9y`#~Zm{ z*3$E$yL~iRqkAyC8b7AG5Ume{pVyubh;O|0zVI8P>G-|FyZrG9*B}m+=rk2TLV_KxDAsLPt-0*l}HeYHDu!6j` z@x$=5W(*vVW#h-EyO-voPbK~7NhCOMtm&kjD*$b8{mhs3?jJ*7XPHc~Sa?$=7Dgmy z!cg>cA~E{8L|$;5!U>Bk%e99{DMxL@?>fPd(LD%qnMJMD)GPRx8i7(^H@T(95p0sv}X-d&=6321!K!aCBQ^%a<@ zhv$tYc1Oc9BZ1q7)~!`3)(k2Yjrzqp2si$3T5CZq`l%VIvY0z9(X0=Y6JIs_ zD$B+&zK^^GvBX45Uuz)`xaheVffKcVKubWJ3?if-ul>EwM`R8|xD~+E z#~WT6*VSeJ#DXR2d&Etq*u;;d0EB*Ivh}{`e$%HYkYDM>D7%I(Y$YhmHi1mEG)Vc& zz00t^W-&h%aoQQzj6m`9T1fZ(C~F$J0QP5DUifqF$_6k_VX9Fe*}XGkt+CH-IrDhV z_K~pmDgtXd!xgDsrm*sW>_fYg*}tXJ&4labvJ^RLe)I7=7pfn->W8^^O~WYxBv8Eq zrTyO7jtT8JjS1zUq@Uj=op*a2vPc%?7g>Z8sy*L^C&6%;ClyH8SB5fv z0LTl1VA+`AjKlMZ_4B;d9B7lyu`Db#eCY&0?H7eHn~wCy#-1cmZD~9yh8xZgvf!YS z%->>PJ=+eD*u-$c@H^alo!AEOQ)D}d5MTk^ zY4Q#v;d`)?-ONKn)^{8SOHESb&zYN4FbOFSV~=*9(zkGSy1)MRVwuL{oxA1F{7#kU zl3!W9WR|AZR)j&^KvCGKqar1sEe+5L(|HMu?g$Ww8y_lfpbN4gmSZ@wfjM6-q@sDi zvZ*erPe@b|87-EAgEJaVh~N4h&AB9QRqLyY z9OCEzl#jxpGIvfOZppq2k#23gj}WfsS<|!$gVk9&IUQN7`5Fv6qUXZT1NJ z4($9MRVEfkh-IR!{T+BM{)>+}^F3ECn1q!Fz3EB`YQt)f));3<0%BC0`CBgjIlox; zP^tb8Jj;sWns@88Y|XsNdrF0Ec-T$6gE zYKx#l%cEM}o-BcZRh7IAvip^NQDF!Ka^rPU6yP(u(GpG3A@`T6&aKecM%Z9#MmVtb z4tFeO&2sX}Zc?Oie^59APFH&JB`jm3KWzZ=XEgjWbS~7P7K^da3lb2el@> z0y-8CBw_&myIRkloJOh(?bZZOCNwRW9;%_^C}>K=u4-1UT;JA__?A503g{RP{ctZ; zQRLD8*rL@ZziYe59|b@dIK^|NHAv|aC`1_z7bl^X|3pe}(l2+b?_NY4%qQKL*g6N9 zgWu>!8TrOe6^_uk-s>cvOO|1++_(4&7M%j9#ut)3jVZyDT1Q6xQqZq$Ag>91!)X%V zM2C?CNqpJsSc)$<5rn9sRaTK^kr_Oen9j_3E6L~%#<^e77rEKgIs+F?jsh4kV!I@V zJSH5bQ??n)99y=W1>*Q*8uLUmp6Ms0K@=K;gBadw$qIwE^+%i(*pW+QbF4AM|CW2h z1bQylgW&qi^TK-iCBVdfcBR^{Jv+u35;!8OJR}0rizrJ+?l{%y3*a#7sskTraN^HE z%~NKd#pjtH+V4fH;~Oar7fW^4?z_{CnG9pM7JqXJ! zO^;+QUZ_nc?q&)_Kd9Sv(y&UY&%uh{uDhtIRXjcDz0}ALx$LtRxJm|BiWHgL;QQE7 zB!h}XKyyDX)|(Db&RkL)d2ISKX~2c9Hc-}gf5?A z2>j|2??D?_meWss^xkFzQ|OKWPf6|Y9hb?VA}(Wqe$41o7**Uu9SEpOI>g{6uJ0N~ zgM1YI$KIpOWlmkG*C=-az4Nwl`si?>Q;VHtRlh3w$T+%(% zJ8}U();<*TtNw<aKYNSEfoa!`~|8mc#0CCZQvCj7R5#s+5aD9$#{u%Q6|+_+(z zT9&PN*2_T^3873%byi;YD?5`GX#HW9)lHo5FF7)bFN^G@G#=WA${we_{!>~^b6Glh z&?UK4KlP8S)%G^Kz&9*W5`d=ScW(#7b0}{=Dmx9RhZ=@Ua$VQ-r^9Ur9+L4XqU)_p zbds_aOHgL8fDPgG7%r^HX2HyjW*dXG!f;?h-~SR>cZ{7vcdw3T3$0PJj!0%9uGKOL zWkI=CDE>$yp0!>(!K8mm9*hvhSNFM|_ExsvbP8TZ-zQpSyr_7DqT(FuzNgvq`|efI07QHF8}XPS=%U2|5T$Z%SGEm?5nn!ArY~VcQZsb-=FJ4di;*v~a~l zD~$G`d#`*Eky?mF`^*30Tlyb)anZ{9^tnk*KFu@E?_DxXZNe&6=W7qnI5yeO<>Gb^ z$MX$k_g3%6utYR+XDt0tCZB^=b-HP^a{I)jC0q?)R3Gqc_9w_5O-T-EMqP`^T`TTd zdxQ`F75?=su1Tt|fa6*Hb)0=v+6RSc-tT06DbVV!8iL+LKdQKIaY6Sb>-8f(t>0#P z@tvlzY@)YJ+q(!8Iyl5e!W%vK#{r_D22zU@r)LYTeGZu?UQ}|_+2}@gLiM^~^z%ch zh}kJ%)HcPZ&)x0Y%Kb7d9<0bf!A@z57;q*+u$C0yT9PQ%Phay0hHy7-s1-BU`Dw_L z80~NQN9V^vB?U~G~7Y8$3gdY zu);bp<+=b;cxg8h!_or!JFX2NcjRl|k~X$%k3>CrIsP1s-d~`uGxGtG%0Mg>UlC50QT|R$W1l$pJhyA_zC?wXr zqH~@*h|}}zG6=WxqWMLueB!I17ddA6@tH4tTk+(GYi(Bbemv!UHk<9fQ zF;O`}e|TrK95h|jwdTLcy7cD;5(a9T8rggjq9tvEXh;20g;fk7hT^heOADJ3<_)55 zOHoHGvi>Qwz4Xe*FZ=eE_fXOFF4GH?Vl(k#PiQ}3(D6vb)8kh5LpI4SpZ9)W{)(&- zoZ$&Y?VNjaC*+u;PyNX7{I&acM}tIp+?mu9v*NZfDE^DO$d@GM)PHf(skF69@YTx% zarYH)g=JF=)SDh1qr3h7k)MP)aRt?I`~d)t+d30dUYBg=!lj(s31UHsziu%$4{W?_ zC!v60$hYu&D!w41fNkwtcCYKWZMOoTo@Okc0UMN$5}K<3)>gNgCGH7RIQm)v3j{&kpE&d zzG;nvJr*WI^+^l&`^%u>h6MfeU=iln#Q0WbSwTW6?eid)1JD-lYI`K+i6 zbOrt`t7T?et@2NNwMWaOvN%j*&0hi^kUr&#KtAtV44*^|9~Xm&j|DYybF~$rAD9O} z&L_&6}G?p2cHkJm0*xlChD(11jjg(DAW{-M9Wd6XO+r zLz)H}zc0LrW;hu;oi&|1W93fPw)j1gWKeD*8t}F3PB}(`DN#Zt?vyjJNq)4^9bv#x zoyT6rdc)D}5YZ($-ds=wdg-2(3;%UpMG26GY|*??fb|S5mGA2MYNlC?a0B3YyrdW? z;y7ZH@XmlW)(ovMv9TaOS;jReTD~VAa*Yh45R4IkX0*bhjYRHOM7@FlQz z7|Hf7qhirD1U;8jE+x$dT+!T2ML^&A#vRWzcp!)ooQPydS5p$3!+X{7VbWrp91Y!( z;qD4b&N{XAO;e_d;&kmJ-by!zL2d&1)?gOl#m!hO_+N8vop#9taBmF5|JJyZucG!D4~<3!ve_`4c~k?l)U^0Dl8c`<5hv zYS5v8kP>ux!7M(M`bpX8H#RToyZUxvhuwA%fn4xZ(H|LJ&qFZO@1jtN!0F!pXeON7 zW;`HE+uvoE&9o4#HO9$3%COxJWr?pil*vAyg1HOIatf3)3Ts*Su}WsKAZUa)Uv}^rh`3I*MhfCE;La8UKEe;E7G+rYf z6^C=q2XNvgrJqR|XJj)S=_Qvjf4w@oUo>g#R#&XCERU!&Tj~YvsedF`?tW}A*2!fc@4nI&IoV!?{JeY7*jYX>^ zbcC+rOvU=x>rFET3$QYFZ)l8^S}IS@R-gOxuQQE}z8H;|c{aIBE&EWWG92RZK9vu| zeMsBiV;uK5EYgd7VawocGD=T96Qt`83a`~mn-Y()$E6@Z<@tTiEFD~eYuh|uadtPk za%3kivOfX!D*ASoFk7(9V^kfo%qyee4r((Z7Z+&}h_)uZ7C^vtVs92QGdg(%?6`hD z!#Yc#jCoOG(RE~L@6^E5n>xE#yYi9$G~YAtF96a}dlvcO-ePKIpgikMDBNcF3i!wJ z^~N&q%shHj(3W}(U9!Wj57xEoD$F^os{KV;#j)jn9M+hGcBDHO-OdB4Yv_G5H&m!P zmGCTV&Im|W+qLZ(A9Xq#C|l0xMt>7a#jDN0QJMR!)lU}0l()#yT9JIwW+)`jygTP( zv_kr~4^l{M$bPlv%uFzee4xb5Seahyy+Ir{IRfs za`Fw`0_RA9yuG*W_vTvX5Lg6lX^78TQ+LtQ>3uZ<2r`Y(8WgEx0mB@R#%j_gA#nD1 zpPg z%=y++yBRLWvI9iT$Vg~U(w(M?I$y5Y>OtIK!}E+mlgO$tmBGf%w7S)7iA)fE-*LBMMTh3!{=|_JHe&f1KfpB#@dK;oI0woM(`J&sQa@>t^`z2HL(e3O0NgSra zY4}rxPTVS@rOm{nHBC9sY71Ra5wk#ohx%_z=T`vz(hXQQKe}n)$7Yw0K=g}4f45wo zAndu@6xB;9AxL<1Sj(AZ#@ECe)5I4YwM$=KlU^VyQi2`J68Rg)Gd+VBZ)^ZuKZH+u zBu(06p#G;vDp8s{0LXmFR*;R!=F(gzS4TP>1ZX-;2J2-}XV8ul@M|v~Sb(~dc8PL) z35{J-(url63u&8zK_}JKf2dKnIIvW2j*;9!->OeIb3t(Fp*mUCL2hZB-%4w%{)|~cPMCW1K zW(d^}6MjC5&-_t=Z>nWfK%z0VW7+Q&56I}6v*4RYjvf{1+Ue9{x&lHabVF{LSV72# z$!ep~;Vl~>%2*|syLxLv#%D?Px*p;n{?S(T+75={{>Uf*`H~7l>Qb5$pYSN>wI52S z89QMWyx6icds} z7Ab6TSYwwITM%By!{CQ-CQr_k`o*7{p-Ky?=ad?K_8{{L`1$6heN%Sh^Gd&6afWMe z5VqpTPX|~MT5~R5J*#)7PdLN-g0BFTRi*QNvxei}LpGv;*ZOY)HQyO-Aqz-K zMA-V8$P&syau$fX$ipfx>LW6N5pK167DTnC7PYn4MJ{YZ;Ohu4^H;rg>n;Zpg>IFs}0;)?Hfhyc_s)Os@4;q906t?U_?YkLU zl+Ba+XbJMDZ*UXqIca<@B)#P1pflxr#6(&bbPi7b59Z1%>37n98X}c|ejUD**`>jR zqnn9Xze6@U#lK~F^6ehFnwfQ+uweM-1pQg}boro>Ps6MLdLPlrzA^MU_MJy*kc<;I z)!&as7p@OcTkEG{FKE}Ok}(>{ncLilwuny$HpIdF9~DearP|3wp8m@MIm^-~ z+mu6;SbeDPURIv)LQ&rkieX6{1{zW?vkaOY5z(NPvBOe665*|GQ>7Zp-=vaG!dkIX zK(YvbE{O-lFr`y#aLTw&1(CX&F7S}qvDQO*f zy`%r;gNA5w^xw(}j2f?6_pM9*RvIaRbmvJoj1M@6Zt6uS;?x|eEo8KyB)C?WMRRAh z5EtdkJAqq_&soUQTB7fCJ0A^^F40dFClivyW6+Q^D}7?C-ZOMi;~SyJZg=Qws>rzR zfE10!?MaC19scnN@m)k2o3`z82$2qVPwVx>WfXiUMaK-UNnKuqGb{K$s=Ymf^~Ja#km?!U;Sjx75|kQOs@d+ZBy6f z-G3brY_9+xi%SVdr!tDX=#* z;iRdEAC^GW7}X&?#D$z5-4TjD7P&@*U7`$d)Rer(;BrRtEp|MfXQ;VI#zU?e{4|f2 zjY0YOUsDUbN=8zK11S<~n5jWG_^iXt&lS(!!8wc%Ga3jYhN5?frC!an#ax%Ohl)lj zN{U9N!5M&|m}q?A$C|3f@%NV6tdL=_j9VA0MD^z6!wh3GYa*y0`CNO#ysbmmHX+ed=zB!-nsan#W8$2{wuLUN(=Pb%ht2g9Dbf79`8jFuWCQPD9J ze=7lN)soo0)yb|_M}Ex4D;zdz zvkpmIeo{5JY?-^LJbKN*?Zvcpt)w!$CaH%ebG-{j8rkJ-yojR4_)z9}s7+cr*Nlvs-;2g6N1Ve(io2h-4OI-B# z*F}-ZqZ+u{d4na{)R#Uo77yQ`e7KaWGdRQZob>eS-$<|3>{EU#|}N zfs>)*!+WVl?L1ur=#7h4K#M*=xP^81`{t2c+XvZdCi&$b_)3mz0vT{@lIzR@xTL8= zNxWFayh7s2V$@vi8Wcn?kSRNi`U+?zi?t+*c?D3?s-M$R-!}7!r*?r z9A^u{CA~(u4?+ArMO1@O@<{MI&S?QgpI5-wpWFVX-ffN4ZjiA;I^UAf?zEu~=e=9O z)Z!uP?Tgch8Zq6yG*>{c$ssFt*<&fw&rMKz@G>6|JFVU*BHP%t^4IE}1 zm~bKUl4=b7i~C`+G|gb_tV4Xgy=g59g_~GWW{dD~sDKsrMP5RiK{MZr$`W4CZsfd6 z9v#ld1R@MZtrJmsDuw3BkH!!Y3jw{A_N?(W_!5jA?QVkEV#}GBPjh(Wn$UsNJ)N(e zo#b1R$l?=zsM6dVTMBDqe?(wnV>`JheJG}21cmACr3tohX=Me@#KgkC{&BrJoPs%j z-IRR5;)26SLkgqC2tVnj|tJ~?(+8>VUZA{Etj%(ml;r&*` zR9r9L4%fdtkSpj1mv8EqF+L?H8#+^m$MM{{ZxdRV>hT>PXDU8uuwFX!Y3JxP%cle* zG$+>i(doHR&S)yi;Ym9dW{~zHq{|UQ#3?tBlCb%;iX7nS1#Y5w%Fmd(kpB}6H}c4` z90)YIkK#jt#}T;ld8N{_eXKmLNBEtTBAJeRs0LT^m2J@`=%|BE$Ij{5GHc%hcqA@I zs^z~YCGM2&+wE`cy1R7&r}Pd}p4v0mX;PSJ% z95~Qgyu5KEO&cpMn9~%rN0rlpREXqOdlX^J+;~HL1?161f!o_%k(kN`)ul{GX!aHn z7{3X~jkbzWl7{f&nu)-k3WJGs^fLruue)AK_O`!u=zB+Uk;{}C0HBZ?(Y`VU`Ld%U z!EbI*+S2!n?2ju0T5FQH%CQEMyx){~!Q4RXq35?K(T+;r1R(Lj^UAN!aSv%84=TyP zp8o(T9ZEWl#=m4@%n`spAAi{?0Vj3xLN*k>rWV881USGCBkq}n2AaJNM=RV%ZXgBp zz!vQaIC1Thl9l)}Ef_@++XyjeUgra#zTFSnjC?Du#oCDj~mI1i?c~Vj^kqd zrlM_F`z&MEaUZx^h9rhLcSpm%Q4Q=j!dDgbJGUMa!}ahF!di}@jjq8y8(s!>J4>qj zu0@v=km^7=n`Cq7kL4-VI=)Vo)iTe6BbfU;h&{q<7dCF8bXf(o_PALey81jZeN#Lv z^0`shx>1#-W0B3@`<1GIe5~(JMq<;TZB-Bam8FClw6pZ=#i$H0Rij5kzjQWPJfa<= zk0>FN3Lj^^t1Kf&l}yv_;acK3H^ZSbK?8*fN8RBgkAEocbr4ecXtT*&8cFz6+NOAl zHy+B*DnKJUxL7@VInu~uVdBzRb>Ul^E3+jeIPJ6(hy^}{glO6#XD5`j`jkv1go{k0 z^hYF%9C+C)ee@{}(&J;i+rU(lWx0%Vy-lF~)ogmSQM~%ZKK|&?pOGC011deuF+2HN zO)hr4w0!qmrw37i?PkXYm9@~dBNN;GLc(u=^yyga8%6e3x@(&p*21zZ)*^cYL%@H< zqhso>jpKWRTk~k4FR{_iZo_56YOSA9h0=Ro(tgEry>DL3&S&Mcw;jaMD$k}mYf+9d zHlZmlJk_k(M3n;kLaeBvAX#C)7GFoy9TdT(#0IVYzo&Y*?!IYq`I>PvNN^+Exny)f zrnYshBT(MPmYcd->GX8?UK7KD9+O{Nj$EXSY zmP_dgY+tQ%1}8*mP+b`fk@j55b`VJKOG(w5zx+;Id|-b@1^^aq`qEX@TTQ{4Md+KcJL7Y05Tc&P!>rA zMo70TmHHFsWVgbn&uDSoK&P@X+y%8)PaKXEZFECKq{XA!8*LP($vGj)FveT$cXUZw z#<%XRR)xI_$o3P-rQS4XFsxpW<$UfElJHk3MiXGcDPVQ`hg`4zWculK$;+jXU^6 zj2q=|f`vSPB`c^?n0^9f%}Zvs;Bid=3E;=HJcHlD`?QDbRJ75!I1;{=J+l$_OcY7? zR=$nYe)fnNy2A1(=$r3pUx7n$JlArF@+f@T5ER}N_7c@G&=&Id8z_*v+(8TGvull_ zMgS+}DCg3&7I0}*JiF8_91oRj8@xN7$rB&EpM=TK8nkfEzKD%&?q~l1ysOW`g514D z_;T_Mkd63_qL|uqN6M}ICUcwBu{pm<;IF8|B=A}DSrb|4@3=5FD4M}^(n7Uh_)A96 z960oi-ExhMsY?cT>k=9{pqHsn&D8xf)7st}vO}61xpe#; zMk&KUeV-!Ny%|{~P9V6@U296}<}(gA`n5QsMX9)*G0WK90Y zCnhPJ`eTQc`KR%BJiDnG7@`?}!u=tYZ;{u)ANyaGzY}v~pR96YX|o)zVg2nS-&l(k z*_r+BoV2)d4<$+aP517G$8BT3b>q#j72?WWot}b?M3%q>f`-uG>m_*7X?n*KXeVgN zXe(~f%eX?1p6XkGu(pjy&0ul9$ex(K2|{a)kf-Wx9&7*Wl634}BS~%r4*aBh}G?nYy zPNg|Y*kefoF%72BSkTg#lE~VETePH#X0uIUuA${j0m>Uj=h`qFl_nCtV{RlVMtQXv z#@CRh!N6H~BM5l~21b69w;Kt7I4Co=<4m+-zYw-L$osEyUAHHm)`ts7$o^EdA+|=5 z6iRf)rgsx814iL-SCzCB7v^ghSBb>>mk%R(rOv+nnPrt{jHzip)zXn zy#D}5rSzEb^ub5Z&VBRQ-QeVqK2Q6n2OX7k79?;#C0Ii4c~buXjUJpvo4`_|T$C}% zN3Uwh8uWb#_l@0^jx$B~NW|V#_|4i;Xk^P6nBmy)t9AMR04;?h?x-txkGHbVb%P1f zvJYcQEn6tvM$QA(n(MN1WN2}qepVAD@?^Y<-O9Yo#~C`5tZ_Zc)o7h76CrHUGjVsw zU6E=i^5q$3W;adge2msN*?W)Qg|dV2?ZPO=_}t^c7I^;vMfR!ISlSLpc9DnqyVEj7N8;~{zI1%z! z=&YWHmK_Xd<4j*B&|Kc?vlm0w8;0Db6ezq*8b+#ujuT& z9oG?m@K;R6*5t?D*(^DymXNQpb=+*$R#Yqmd<4DO+YT&R(fQvaLdu3$IK=P|Y8{nS z(M%k7wZYBrW4ir7_UTd@k_Lc&%9kgj^jz88>T5%jEQB&~EZ~^(7;+-Bwz&&aiK$9p zGd0A#8sT-^f5SZjSCZ$nf;cEyABGss0lIXIa|5{+gO#FD#V8oPFQ|^1ON-ps7J|0j zIo7V26D!<7HFmLmVbe2VWk(z_8Xy8d`z)M!`8sw>v6?Bj$yzl11e;9#C4-#Y*$uk> z73L!w>TPc&an6?NCkDnOJg{^3T~nrW*{DeBH%pdlXI?qlHdvm@0NS!+$>CRH4WU05 zYbvoqx3HYFj(A>fz2Ca|&K;1+0>5+#OixmjMFj7^p`?}g$EY&foz?oprNsXL)=5|Y z0NH-J>m0V=^!BMUjZ{XL;w&kjm76^8KIvt1kMdV8XR0ZQ8iM*X1=03^8Du#PdX+FQ~Nd8CvZ zAle7+rU5*On0*aH=0fD%Fx(X6H)QNNVlDIwu}_*-Y? zP7$@-D=m!tE?@)@rt(YBB6ERhC3xwGdelw2Cn<0|Avl}m6ucIap!W^Q(iY7ud^jSG z9DWdE1RE%utgcbmwo5J6r5NJZvyW*j$erv604)ur0mE|$Ob`gafPzo49RUH3xHw?x%wg6R7c^X?o{oNxVWJ-i{~9tis>27xPR`@RolrQw##1|Cle zKoU&_7PZ0Koo=DwS7-GOo2N1E&6|tXXFg)bFj6MU6 zE`P25C)3L`=7C3oRvTXGtle0*^A6kr?7bX32}^9?&CZI_G(AVxc{9xT@LnTj{Xq9w zZ9Z8ua@~_N_ekDJB~!KU$&=G9_kv_>sqZ~-r` zVGP|mMs2%k?3u=Ba8?D4bX#01(lj4cAD6N^UGZx2ZeyEo@;okqr{H>c?1VRf+6I=R zRM25Lu9Cc^+m0d?w}6sUpv5R0XRSIZ@=qpSI}fXOx|6*r)?f1Wp4c@;IlCR*!iR;^ zv+&uI8Ee`(`z(fkO5)7@u8cT+#|p7_i#$qd&dJkVVS%c2^lyVA7qQ<^54F(rQwh=u zjjViUxV{4Xmj0faHu^ZZZ-yW~n9u%|){~L0;p6^a7gCr4YMk3ZbKTgl} zT_i1OC%aWV{{V)J*yrKOT1Pyru7T8@QK!P_@?6IeQAyA{}z9rJQoyOprjKM=ahSzwL~R>2VZ zom{SO)Zd00hg|DWX7vtJm1G4EoC-_r=rG~ONM$y^T-<4_AT`vxwrcERxu%$Nou;hleaO^4Fg6FUkLb+YX z@WI}s0!I=-rN<%Dq|M9`M$QAdUEiRzd~LoHvB!Lb^`Z@I`Zydf$WWc6Jvk6@;@6oBqsm)1 zQ$oQ*mFN~wfgq_wTwb}D?2bV05Th2}5%LEqc#ndCn9X}f-3M(4gva!XLp-uYfFbJb zw$dI^1|W$ec|0M=E;kP5m1D7d1&Zr)l&6w7Y4ba42b8o9+aPJ|d#o2;YlcaVG!AeK z741KAuZJ7SDkYmD#TzX;gYgjIhhyo&ycZS8^*vW3GIz}+rO$Em*#|En&V3S1cU7{< zy21Vzzl)CuEuTM|JhFJgzaU6RBcCKFGkov#;<8Dk1~j~ZAJz)jX&O)EVl%uA?;dn#?G-xLe5Nm0_CN)9fA(lsvckQIX&7i8tzL4R?UDNuiTy&CJct0DLZ) z_#+@1tkM4fbZQHPJhFg=4qs)u?v(!5!&m;g|zm=~`ms0xHn4ewF!=?vd6NT&(1Z@~<;$$?&SA6=g}H(O2nR zELJI3ONw_mID1G}4h6@B@rWJ|DWd47h|9%|`DsarS-x#qKr!+h1KF|v03kOv7V9Ir z6EF?&!BtGIJgsQA#>ZpmNcTCTz$?t{J<}NdH_X_{{{S&xUty- z>2k*md=bf7&9pYRut3XY!diJ!Z045EAJhFR{9KbO>D!3Evg*1{J7SL705q193`Z@r z0cYV_VYh1n2w#PabU-JPh|&-m@Xk?NOg2rDpW!v4O?M=H)hAMGaOfI7Zlf`#Lc$O3 zt(-}5B+~r#{{YC2t!$cW^j$sY)ZKDya(E(hl8(-6m94*9&dkh_#l}~X+wd)Bj^^jQ zj>Y^cUZ2*0k&Kt6dwC8kgDx(uv!f1A$)iO9-4k!RXj58}fEKZvDB5&` z+lA%2>~~&|MHZFgWA>UvV+C@&4h$!b5oY*#6dRi2zAO%CXhn>d4{2UKrIboGA@nQ) z!tumMc8LWzAC>gY0+G-_7w)#IyrTHsOdZvvZMSll$&TKQQN1c;jz@CflilgO?pLlA zV~SPtVYWy23Xu-dBZnZCU)fxPuvq0*!H4M|Z*r`e6SmPsy$i6|=G|-Tz-&@93=y`9 zj#GeAvo=~Vm)ZDDMA|lz#*nzUQf;J%06P^Ck7gDLDQ18zH9c)9MmQS*2!Lh0v{Lf8 zlGn|qwRq*V)d{{RyjQMPy-ysgTSOPGD7-b$_@ngf>}$=v4< z+Pmco2siUFNg&~_UX+?V)SuGZOVflh#VowI_XTRtB-d)-DmH287q*-L?2U&p#MLopY1&&L*WEtX zfl8YiVCM2{s|HFbYZ*Nc#VFiuSYyfpq+u>~_Dwcjot?@FjL~n9;P}d~9{UK4n{z1g zg$u3j9l2Tf7`i<4xil0S(`7SY6MU#Wp}dbN2fG!O`vsLrk*&4~KnJnqSZWyojClA9 z51SeH%JNSDuHTpmfdx{z}zg>llumj9~|N!r?D|Q=kq6+kAkwT_R=3$bdfg@ySZq>QZNP z#iuuf<2mBeI(x>z-yl+Nka$Yi$uN#J!Wn0%(hBq+gig&Wb{f%3lMlb!@}(0<0XXIY z&Ud=|1Ons5e5lJSBN92}HLY#A#Q02@mtK^eThx&B?vplN zM4%AI77O!B@s6dg&#r6PdD(=!*E9j}x1WpOh~ua=W7C0SzL_igZ>|@J{ON;-C~xr7h{Va-{}gMTa3#LSrLzWPbC{q)Ee0KJl^1rOloM9 zGD{qt8*EI7%xtHEZCOP2-!24|&c8J-BmV$R=V?4Aj=7PJ8tv9RnqEx!PtWVy4{e?n|2dIjX30O3&Uy3AT$JNCQZ z`>AU^Nsp(%TE~aWbxW3Oy1Vl-w>a1?`fYlgd=5_8=khbfCrlQ0Nz0Yl6CKVcgCE8O* zV#`IuvU-@YOb;y-Q<{E7gBj-gD!f;2(`0Gnx3ZoL`($Aww{-si4HjwHwaqv1qojK^ z0~@$&&nQsHN&6FP*`MfSHfDsS3fbCY$&sXXai#3+9-ALrgJCiv)9EtLA^c)$`(XIMiE@5fcJ2`c;}F$v~<4;XzmA*;aEW~-bDw-^Y zl3Ek7O4xKM`& zM*}~p{Qk>VkvQM8fn3_DKdqExu+YTOv{A)0Wv0_u%+^E&2GM`rJAr`g+{cjGiZ064 zxQ)u0&S%wr@7Y~pVKt6?a8pLnWmPswV@UT+&}E+jP6geqKTK%`85zIY_LXC#V~0l^ zY&%@h?jPn_j7$iGo}e2^@Rf~87dVY;7(Qt_#$BH3zA^-M-uqVf7@;k)Er`3n7c2wE9wT;GBwhp6**tX|d0BIpBoc|f+P(T|sYnpc798{pcW(P34`s5WBA%>v^?SMZ6=z_h!T|`h?-B6!ey=|8Xf6#eJZJ*HJ&RQ#j<(O@niYaoW-4 z6H(N$@Yr3EAZ;#l)E#c;xzOQVcCaZ|4<9C7{2@Hh#J_J%wr0PDrJ9mZ5FB9>B(fcJC-IcIg3epgXTZF^p9sf!o_BlH)eklxC3}U=GQ6oF$n50F-%BCDIbs z_OwMjk>-tZsWTWugR9EYdO@*J7UfZy0W3Un3Mdc3JU3I`!`Os2IJCRP6&M}1VY`dr zN^$ATTl}Z^A$yB4FtFb$#qITACixty3<(J(r14xUtZ6NzAK0ZRgXIs-=~2o*#$or5 zvRe_tW<9^s2ML@h1bI_H@1|zZ9|>3)`D}?HJT<4k%0CTaPPpd?&N(JQIk!}Yoo9)mCceib>f{P^;R^&?FZ!^LAWeGO7#dpVA(`& zQykmm4&`C;H64@0Sm5A(#alzEnc20~w0ERBA=X+(g9|;a$#Wv(e-^a3FA!Txt;J#S zG9nKyXrR$|_gA`hX&CRCLubM?WtiV!YC{_?H}BzV;nd=DTJZi|;@1&6**P@B?e3p% zR5~cY-go#2IE?ZmxJ9Z&N3?D>~qThxv8qb!q|J5rm0 zMUhd~{{T9|EI1qPf$OxFbKA1Q;%la!$3K=1ZFtV6jnDMiF6Un&iYg>!EgLCv#>(?* zvm-BWZs{SEUK{u9iyJMz@kMH(RIL@d?r_7?GG>sA46*yK=jUB&CsgX(?2EgQkM%3| zkK(qnFf?q2Z`w!$kKKM0)TQ+`8Qt8Ml2!07#}6gcXmbt5NW~snR?|(8wXBdH)C$1k zyvHEeY%Q*c?d>gb?kp~ucydAhrs_rvtN`Zn@GW!xvWARPv{i88w;eq@WQIuPce>~b zRDD~5U4=Y*ea&$oeUXUmS}@RLw;DgY)rOy%=_Vu-xN&PT=lYG>FLMo2wZV|I0rZ-( ziy)mF*d6(ZDAwiMNp@qC0h`5k< z2~2tYUgv9@^(_Nf438Xh;H=(eJFv1`=8hbB34|@N=cj#K1pLmAIxU5uxGET?YuFs# zK2vZ?=$;(IV5}49@?^rBWTSH}TE#I3)QIu$g7z~a2R+nD>{2)q7D)K+#r7yrDf~20 zWC^Ur?Rj5An=BGJoBhYitcpiAwhG#N+G8pm@EKa~cv5(yFH1)zNcd`Sb@R2c&Mu7t zw0N0J$^=GyE@gq4>OffW?`|C8OS<>MV^oc{nO$Oy@G%t<~GJ9woLAZR4=LS9LqAfdv!8)uTv z8cp(-e1>B~fQZ;PJM{|n?rTjoyoB1I3}e(RH_f6zN2$-AQtfrJ+^34*=ilCt2*yj^ z)9gu3^5z}Gy0~*1=GT>9lLp}9*aW6Cl8v%h@KG|x=CVQkNO?nS)QktIib4K_@f40M zg|jqTyvC4;w%>J}jl@&F3bu-0q4&znD7T!J5j#7ygnY`9Tzt64w}AA!9z7=qbHX;6 zQ{zd3?(Q}U-we-k8U;z*6T(U3Y~Z)CPr%<^0~^1h$M#9g-RvZHX;YHW@n)ZdOq@Hq?PZUco~g_Y|L z;M%YbHNxkrI{ic*-;k2Z#S*alZCD?0H{D0ax0EfL%J z2BC^%2X{Zh=Uqvp66s4C;by@Bbin1DN?dJ70_Mr?bCmIAl1X0K9rbVr*-)}qV9f7O zSO>XHVrl|O_m4a)){bt?GU?J(%>@4dhG|%1+M#&ssz!a)H;hXj+L$uINg@g?;Sg$N2g~c-l^3FHY}Xi2g-+HsZu!C<2JwQ$1Gv?EPuNEwK3f^j`B+DABkGY z%E^_Wp6ikux8IVx@G3`_$d$Srzya?7t#*pyBy&DHEWU{!sVltDS`8*`xdq-WQe@%& zO{S3VLph`RO0((=Z&9yxaQ1)gRKySIy}j*d@}vCDp4Sd)xj{1Ney0VV69#P2*V6RU zUA5bRpq%PlZ_Dd1;)BYqceT4o?5K`OX^x!NGMdxfUFj1!vYqFO75&y$A9Kk)g1W}a zEe+e;C1^2bpC<@p#w3jGdq-fZGGRG!J%oGO6`mGjTF~haC&Gl_M<|t}hn~nG%#J(E za=H`ava^QB{ggF~U&~I}+s5L)#*n;UghjH?9y?+8a1~_ibUDH8GznJFP!v3cSF5qn z5hG6kg{r}_!Ln|3jx9fnAbqj@sZzx-c;~{hlC1zEdE!5-N+$eLulxP(R?mRBBF65Hgb zdZNKi!s8@XDR}X894sXY?U%r=p<DjJ@%`9=@ zbPW?&+~$Wnnp|>6qX}r=2XpQ1YCzH9BZ-n8(rq>!efy*;N$hL6@~X4&pF3RG$8~DY ztDec2gGr|f4pLSD2 zU})QXtHUA5EN|h!rhKrq(md0UqsVb#d3TFlA5i={%*viXzynI8&ippbf(rOP7ffcT z=9W2`ANaI_n|6&3p)ISS@TKIn&-$D_%WI>47U0AFG-3DOg*T98jkAs6>AIey2y7VcEq8NE zqx3i7G?Hz~)I~X@9i%S5fzz0Hr)DHAV4w#I{{R;pW3V)FUFk{WI%A5EhLxh@U_-Jt z04l1LjB)H{*70bh2DFpi1@kljR2;0Yq-U5x6S?fUM_+4o1e9-r7UN#j1W>+@aJVO4 zYQ%@MJIc_FvF7^3;!a(pU3Ae6$ay`8 zrDquQa|~_pZBywZ(*fhbSJE}VnTl4Qt!a2x2H#HtXd3?j8);~Cw))tQ$G@O(vsxh~ zfrR#=w0cPNvfSJH)XT39XV;i}CS-(mcPkY4daf;RW!5_T2Xq+@ucMH;ULyf*ySlV| zIc9=F!V5xnH!=q~#N7@O%^|ja5Lg|PDQ)nusJOzCSzYQh@Cw#pZJCX9)M;h$95GlJ zDO|){;LWb$`AEtky;lL5xUbkNZy{wc%Yl4uJh0N1RE4W#p*B&Jx_2J*OWlf zV-xL`dxZ^F4_6p`d0s?ArFPGxgyX@V3P9hfsH0g)h+61cJleEt_RAZ`U@24JyJU`I zySCNFIo%bz=Y@vmjGU4!;JyT$i?hGNDDnHc1qwl+IU*GV{lx;v-*rQ z`vdn(jQjQ zorX>aHAz5+^z4I+nNIp0!@lalgVLT@o_K#ORQYYayAN}AtmT*U7;$B{nIiVM1O^&( zV8UY@kdnvtID^{hSi<&fVFVrB&@wT$%1HgxL7i4=qnB|_HNe%XYrM5UJSjs3SyP-i^3L@i!`>!2_*&0Kd2|RMXWR=}A$A|EWZ3x(m zI!qChQMdaiAi-o#Bs6lEhy!=s9wzQ7`!P?kAPjjeXbRgIfsQl>DQH9IhACkqli*U8C#wf2mA}OsjF;OBz#H@rXQffsW8U{!b~m{{T_q`z9mc++%OK z<8VihHN@;;pneird47{UHVeKHFjyjCd(EEc4Q7*0kd`~ia8nJfDgj5$9CZMu7dMA3jzmLRk+mkm3BN+b-yuO>&dUV0b84GaGbr zcDsgc1k{7#`s6TrJ3Me$M=&I0pN^5FPrqm;lYV`%T(v z2Kp4?+^EG6j>%Dbi=V-uIatFu@}JB($CYLy4MPl@wm5ysJ6T&9o$kNL-0R%b1uz)WZscDW2A-u z0O7b$`;}D3?IFxQ?!_JFW7{K7wWj|7lqeCfm~7}G7Gr1QI1alm1PTB-bq5h(A!)|MG|ZYW=I9ngwufD zN)3i6hE&zbDIAjI(xIM_it24dxVT@+<$A21q>tyy*K7Hg zBY{6F4VNXo#dhL}=VoOdQMVMh>_^=fCHtk}2#P14E4$&qYjlQOF%Oyv{H@XEHYaxQ zl)!k44kvt3R=T#?8;v;O&I)X-OWhWokF@u`7P2wwLIEF0Sni*Ae0xYcfm-ZoOL64m zl4vP2q9f3^*1?sRXTszCZ>Rcstdw|Y2m6)Q^v4sB_>W*MV*1xlGC?sS$aVh!3Xx(- z9NIX=Q0$2$?Se7~aw$R5HH@HnONN)Yx!lroncalP9G6OO;?r;mCXf&4K!yV`=EBin zVzIG%>8%`>9|&Cq3}Sj%QNCsnz`^cwlOsfX^c;U0`3;Ksu{ERFw z#)h4d@aB)Yzv?|yYVXC;v4^FlVVaGD!BKYERJ2#<6Fe|HfEop$=S=L3!blzXQq`wA zRufwgqT}6HvSNKX5BF5901r?>w-J)Jk~^T{yqs2T=XZ5wsYGScBz&x5!$-;4JF(_Jr6(V; z@SBD2z|M3>u{?gtkb=pYT4?wF%J(V9GXa@#57Znwyeu+h=KlbDBxnBs>U$~?EKQN4 z{{XV0)3QBma4(q0A9beF!(_!V7Xs&nCW=LZ(c~=2^qg+{Eq)YaI5pkYAVlnG%z*pj zaO31!+UYrs&3HYEnk3D|63;71U>0a6@%rOTGV){$^I#+$JUU-P%PvtLnaBm89^rDxLEBFR(r&hd za-+9m^`@2i_Pwuo2kPvZ*RnA6c&0c~HSIC*Jl`vY#7J?j7K5g9WyjQGbgp-yyWQ}L zia_sWbeR5*Ow5>3X-3D4y`ju4ExtlGQBNLDv5@>m++|tmnseER$z2!>aO9!jpkv2t zq-_cV{Znl9^*osHG{~JRlPR~I|;bhP7-QzH0_`K zj#85-kZhJk(^_PDHk9!ExrNU>e^9^a{{TdUcJZ-4gRC%}Jd@oN2jN?%G#G(!uzV{R+&FKNs&u&zrtRIDtzu!ey1bzvN5lWw D`fT8^ literal 0 HcmV?d00001 diff --git a/tests/hurl_integration/scenario_1/main.hurl b/tests/hurl_integration/scenario_1/main.hurl new file mode 100644 index 0000000..1e68473 --- /dev/null +++ b/tests/hurl_integration/scenario_1/main.hurl @@ -0,0 +1,88 @@ +GET {{ base_url }}/api +HTTP 200 +[Asserts] +jsonpath "$.software" == "Minauthator" + +POST {{ base_url }}/login +[FormParams] +login: root +password: root +HTTP 303 +[Captures] +user_jwt: cookie "minauthator_jwt" +[Asserts] +cookie "minauthator_jwt" exists +cookie "minauthator_jwt[Value]" contains "eyJ0" +cookie "minauthator_jwt[SameSite]" == "Lax" + +GET {{ base_url }}/me +HTTP 200 +Content-Type: text/html; charset=utf-8 +[Asserts] +xpath "string(///h1)" == "Welcome root!" + +POST {{ base_url }}/me/details-form +[MultipartFormData] +handle: root +email: root@johndoe.net +full_name: John Doe +website: https://johndoe.net +picture: file,john_doe_profile_pic.jpg; image/jpeg +HTTP 200 + +GET {{ base_url }}/me/authorizations +HTTP 200 +[Asserts] +xpath "string(///h1)" == "Your authorizations" +xpath "string(///i)" == "You didn't authorized or accessed any applications for now." + +# OAuth2 implicit flow (pre-granted app) +GET {{ base_url }}/authorize +[QueryStringParams] +client_id: 00000001-0000-0000-0000-000000000001 +response_type: code +redirect_uri: http://localhost:9090/callback +state: Afk4kf6pbZkms78jM +scope: user_read_basic +HTTP 302 +[Asserts] +header "Location" contains "http://localhost:9090/callback?code=" +[Captures] +authorization_code: header "Location" regex "\\?code=(.*)&" + +# OAuth2 get access token +POST {{ base_url }}/api/token +[BasicAuth] +00000001-0000-0000-0000-000000000001: dummy_client_secret +[FormParams] +code: {{ authorization_code }} +scope: user_read_basic +redirect_uri: http://localhost:9090/callback +grant_type: authorization_code +HTTP 200 +Content-Type: application/json +[Asserts] +jsonpath "$.access_token" exists +jsonpath "$.access_token" matches "eyJ[[:alpha:]0-9].[[:alpha:]0-9].[[:alpha:]0-9]" +[Captures] +access_token: jsonpath "$.access_token" + +# Get basic user info +GET {{ base_url }}/api/user +Authorization: JWT {{ access_token }} +HTTP 200 +Content-Type: application/json +[Asserts] +jsonpath "$.handle" == "root" +jsonpath "$.email" == "root@johndoe.net" + +GET {{ base_url }}/me/authorizations +HTTP 200 +[Asserts] +xpath "string(///h1)" == "Your authorizations" +xpath "string(///main/ul/li)" contains "UserReadBasic" + +GET {{ base_url }}/logout +HTTP 303 +[Asserts] +cookie "minauthator_jwt" == "" diff --git a/tests/manual/.gitignore b/tests/manual/.gitignore new file mode 100644 index 0000000..a9a5aec --- /dev/null +++ b/tests/manual/.gitignore @@ -0,0 +1 @@ +tmp diff --git a/tests/manual/access_token_request.sh b/tests/manual/access_token_request.sh new file mode 100755 index 0000000..5d7543f --- /dev/null +++ b/tests/manual/access_token_request.sh @@ -0,0 +1,7 @@ +#!/usr/bin/sh + +curl -v http://localhost:8085/api/token \ + -u "a1785786-8be1-443c-9a6f-35feed703609":"49c6c16a-0a8a-4981-a60d-5cb96582cc1a" \ + -d grant_type="authorization_code" \ + -d code="$(cat tmp/authorize_code.txt)" \ + -d redirect_uri="http://localhost:9090/authorize" > tmp/access_token.json diff --git a/tests/manual/all.sh b/tests/manual/all.sh new file mode 100755 index 0000000..abca835 --- /dev/null +++ b/tests/manual/all.sh @@ -0,0 +1,8 @@ +#!/usr/bin/sh + +set -xeuo pipefail + +./login.sh +./authorize.sh +./access_token_request.sh +./get_user_info.sh diff --git a/tests/manual/authorize.sh b/tests/manual/authorize.sh new file mode 100755 index 0000000..fa9ff4c --- /dev/null +++ b/tests/manual/authorize.sh @@ -0,0 +1,15 @@ +#!/usr/bin/sh + +curl -v http://localhost:8085/authorize \ + -G \ + -D "tmp/headers.txt" \ + --cookie "tmp/.curl-cookies" \ + -d client_id="a1785786-8be1-443c-9a6f-35feed703609" \ + -d response_type="code" \ + -d redirect_uri="http://localhost:9090/callback" \ + -d scope="user_read_basic" \ + -d state="qxYAfk4kf6pbZkms78jM" + +code="$(cat tmp/headers.txt | grep -i "location" | awk -F ": " '{print $2}' | trurl -f - -g "{query:code}")" + +echo "$code" > tmp/authorize_code.txt diff --git a/tests/manual/create_test_user.sql b/tests/manual/create_test_user.sql new file mode 100644 index 0000000..f5b98b8 --- /dev/null +++ b/tests/manual/create_test_user.sql @@ -0,0 +1,5 @@ +INSERT INTO users + (id, handle, email, roles, status, password_hash, created_at) + VALUES + ('30c134a7-d541-4ec7-9310-9c8e298077db', 'test', 'test@example.org', '[]', 'Active', '$argon2i$v=19$m=4096,t=3,p=1$V2laYjAwTlFHOUpiekRlVzRQUU0$33h8XwAWM3pKQM7Ksler0l7rMJfseTuWPJKrdX/cGyc', '2024-11-30T00:00:00Z'); + diff --git a/tests/manual/get_user_info.sh b/tests/manual/get_user_info.sh new file mode 100755 index 0000000..437449f --- /dev/null +++ b/tests/manual/get_user_info.sh @@ -0,0 +1,5 @@ +#!/usr/bin/sh + +curl -v http://localhost:8085/api/user \ + -u "a1785786-8be1-443c-9a6f-35feed703609":"49c6c16a-0a8a-4981-a60d-5cb96582cc1a" \ + -H "Authorization: JWT $(jq -r .access_token tmp/access_token.json)" diff --git a/tests/manual/login.sh b/tests/manual/login.sh new file mode 100755 index 0000000..25916ac --- /dev/null +++ b/tests/manual/login.sh @@ -0,0 +1,6 @@ +#!/usr/bin/sh + +curl -v http://localhost:8085/login \ + --cookie-jar "tmp/.curl-cookies" \ + -d login="test" \ + -d password="test" diff --git a/tests/manual/oauth2c.sh b/tests/manual/oauth2c.sh new file mode 100755 index 0000000..12069ce --- /dev/null +++ b/tests/manual/oauth2c.sh @@ -0,0 +1,10 @@ +#!/usr/bin/bash + +oauth2c http://localhost:8085 \ + --client-id "a1785786-8be1-443c-9a6f-35feed703609" \ + --client-secret "49c6c16a-0a8a-4981-a60d-5cb96582cc1a" \ + --response-types code \ + --response-mode query \ + --grant-type authorization_code \ + --auth-method client_secret_basic \ + --scopes "user_read_basic" diff --git a/tests/manual/register.sh b/tests/manual/register.sh new file mode 100755 index 0000000..c2a5f36 --- /dev/null +++ b/tests/manual/register.sh @@ -0,0 +1,6 @@ +#!/usr/bin/sh + +curl -v http://localhost:8085/register \ + -d email="test@example.org" \ + -d handle="test" \ + -d password="test" From b956bdbf05c40eb82aa6e0751c9537864bf56394 Mon Sep 17 00:00:00 2001 From: Matthieu Bessat Date: Sun, 1 Dec 2024 21:49:26 +0100 Subject: [PATCH 2/2] test(integration/http): add hurl scenario --- http_integration_tests/.gitignore | 1 - http_integration_tests/access_token_request.sh | 7 ------- http_integration_tests/authorize.sh | 15 --------------- http_integration_tests/get_user_info.sh | 5 ----- http_integration_tests/login.sh | 6 ------ http_integration_tests/oauth2c.sh | 10 ---------- http_integration_tests/register.sh | 6 ------ 7 files changed, 50 deletions(-) delete mode 100644 http_integration_tests/.gitignore delete mode 100755 http_integration_tests/access_token_request.sh delete mode 100755 http_integration_tests/authorize.sh delete mode 100755 http_integration_tests/get_user_info.sh delete mode 100755 http_integration_tests/login.sh delete mode 100755 http_integration_tests/oauth2c.sh delete mode 100755 http_integration_tests/register.sh diff --git a/http_integration_tests/.gitignore b/http_integration_tests/.gitignore deleted file mode 100644 index a9a5aec..0000000 --- a/http_integration_tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -tmp diff --git a/http_integration_tests/access_token_request.sh b/http_integration_tests/access_token_request.sh deleted file mode 100755 index 5d7543f..0000000 --- a/http_integration_tests/access_token_request.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/sh - -curl -v http://localhost:8085/api/token \ - -u "a1785786-8be1-443c-9a6f-35feed703609":"49c6c16a-0a8a-4981-a60d-5cb96582cc1a" \ - -d grant_type="authorization_code" \ - -d code="$(cat tmp/authorize_code.txt)" \ - -d redirect_uri="http://localhost:9090/authorize" > tmp/access_token.json diff --git a/http_integration_tests/authorize.sh b/http_integration_tests/authorize.sh deleted file mode 100755 index 615f570..0000000 --- a/http_integration_tests/authorize.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/sh - -curl -v http://localhost:8085/authorize \ - -G \ - -D "tmp/headers.txt" \ - --cookie "tmp/.curl-cookies" \ - -d client_id="a1785786-8be1-443c-9a6f-35feed703609" \ - -d response_type="code" \ - -d redirect_uri="http://localhost:9090/authorize" \ - -d scope="user_read_basic" \ - -d state="qxYAfk4kf6pbZkms78jM" - -code="$(cat tmp/headers.txt | grep -i "location" | awk -F ": " '{print $2}' | trurl -f - -g "{query:code}")" - -echo "$code" > tmp/authorize_code.txt diff --git a/http_integration_tests/get_user_info.sh b/http_integration_tests/get_user_info.sh deleted file mode 100755 index 437449f..0000000 --- a/http_integration_tests/get_user_info.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/sh - -curl -v http://localhost:8085/api/user \ - -u "a1785786-8be1-443c-9a6f-35feed703609":"49c6c16a-0a8a-4981-a60d-5cb96582cc1a" \ - -H "Authorization: JWT $(jq -r .access_token tmp/access_token.json)" diff --git a/http_integration_tests/login.sh b/http_integration_tests/login.sh deleted file mode 100755 index 25916ac..0000000 --- a/http_integration_tests/login.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/sh - -curl -v http://localhost:8085/login \ - --cookie-jar "tmp/.curl-cookies" \ - -d login="test" \ - -d password="test" diff --git a/http_integration_tests/oauth2c.sh b/http_integration_tests/oauth2c.sh deleted file mode 100755 index 12069ce..0000000 --- a/http_integration_tests/oauth2c.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/bash - -oauth2c http://localhost:8085 \ - --client-id "a1785786-8be1-443c-9a6f-35feed703609" \ - --client-secret "49c6c16a-0a8a-4981-a60d-5cb96582cc1a" \ - --response-types code \ - --response-mode query \ - --grant-type authorization_code \ - --auth-method client_secret_basic \ - --scopes "user_read_basic" diff --git a/http_integration_tests/register.sh b/http_integration_tests/register.sh deleted file mode 100755 index c2a5f36..0000000 --- a/http_integration_tests/register.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/sh - -curl -v http://localhost:8085/register \ - -d email="test@example.org" \ - -d handle="test" \ - -d password="test"