From a121f2b837562af861c7f22d5170940a4a086037 Mon Sep 17 00:00:00 2001 From: eraden Date: Wed, 26 Jun 2024 11:12:03 +0200 Subject: [PATCH 1/2] Rename to idp --- Cargo.lock | 417 ++--------------- Cargo.toml | 2 +- crates/account_manager/Cargo.toml | 35 -- .../migrations/202204131841_init.sql | 23 - .../migrations/202204131842_addresses.sql | 13 - crates/account_manager/src/actions.rs | 124 ----- .../account_manager/src/bin/account-client.rs | 39 -- crates/account_manager/src/db/accounts.rs | 441 ------------------ crates/account_manager/src/db/addresses.rs | 311 ------------ crates/account_manager/src/db/mod.rs | 31 -- crates/account_manager/src/idp.rs | 126 ----- crates/account_manager/src/main.rs | 61 --- crates/account_manager/src/mqtt.rs | 30 -- crates/account_manager/src/rpc.rs | 86 ---- crates/channels/src/accounts.rs | 4 +- crates/channels/src/carts.rs | 5 +- crates/config/src/lib.rs | 25 +- myco.toml | 2 +- 18 files changed, 51 insertions(+), 1724 deletions(-) delete mode 100644 crates/account_manager/Cargo.toml delete mode 100644 crates/account_manager/migrations/202204131841_init.sql delete mode 100644 crates/account_manager/migrations/202204131842_addresses.sql delete mode 100644 crates/account_manager/src/actions.rs delete mode 100644 crates/account_manager/src/bin/account-client.rs delete mode 100644 crates/account_manager/src/db/accounts.rs delete mode 100644 crates/account_manager/src/db/addresses.rs delete mode 100644 crates/account_manager/src/db/mod.rs delete mode 100644 crates/account_manager/src/idp.rs delete mode 100644 crates/account_manager/src/main.rs delete mode 100644 crates/account_manager/src/mqtt.rs delete mode 100644 crates/account_manager/src/rpc.rs diff --git a/Cargo.lock b/Cargo.lock index 8a069ac..163882a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,35 +8,6 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -[[package]] -name = "account_manager" -version = "0.1.0" -dependencies = [ - "bincode", - "bytes 1.6.0", - "channels", - "config", - "dotenv", - "fake", - "futures 0.3.30", - "gumdrop", - "json", - "kanidm_client", - "kanidm_proto", - "model", - "rauthy-client", - "rumqttc", - "serde", - "sqlx", - "sqlx-core 0.7.4", - "tarpc", - "testx", - "thiserror", - "tokio", - "tracing", - "uuid 1.9.0", -] - [[package]] name = "actix-codec" version = "0.5.2" @@ -401,7 +372,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ "brotli", - "flate2", "futures-core", "memchr", "pin-project-lite", @@ -756,12 +726,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base64" version = "0.13.1" @@ -786,17 +750,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "base64urlsafedata" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a56894edf5cd1efa7068d7454adeb7ce0b3da4ffa5ab08cfc06165bbc62f0c7" -dependencies = [ - "base64 0.21.7", - "paste", - "serde", -] - [[package]] name = "bigdecimal" version = "0.3.1" @@ -912,7 +865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", - "proc-macro-crate 3.1.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.68", @@ -1262,22 +1215,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6d59c71e7dc3af60f0af9db32364d96a16e9310f3f5db2b55ed642162dd35" -[[package]] -name = "compact_jwt" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1aca09e6a9e9011c2a2fb13f26a0d2440a709ac0e68ccf02d168d54f4801b27" -dependencies = [ - "base64 0.21.7", - "base64urlsafedata", - "hex", - "serde", - "serde_json", - "tracing", - "url", - "uuid 1.9.0", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1344,17 +1281,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - [[package]] name = "cookie" version = "0.18.1" @@ -1370,23 +1296,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "cookie_store" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" -dependencies = [ - "cookie 0.17.0", - "idna 0.3.0", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -2703,16 +2612,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.5.0" @@ -2723,6 +2622,33 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idp" +version = "0.1.0" +dependencies = [ + "bincode", + "bytes 1.6.0", + "channels", + "config", + "dotenv", + "fake", + "futures 0.1.31", + "gumdrop", + "json", + "model", + "rauthy-client", + "rumqttc", + "serde", + "sqlx", + "sqlx-core 0.6.3", + "tarpc", + "testx", + "thiserror", + "tokio", + "tracing", + "uuid 1.9.0", +] + [[package]] name = "image" version = "0.25.1" @@ -2742,7 +2668,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", ] [[package]] @@ -2753,7 +2678,6 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.5", - "serde", ] [[package]] @@ -2927,69 +2851,6 @@ dependencies = [ "signature", ] -[[package]] -name = "kanidm_client" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "096cddae6b5b1891c58ecf3056f45205be68f995e21278e006d32fed71910e7d" -dependencies = [ - "compact_jwt", - "hyper 0.14.29", - "kanidm_lib_file_permissions", - "kanidm_proto", - "reqwest 0.11.27", - "serde", - "serde_json", - "time", - "tokio", - "toml 0.5.11", - "tracing", - "url", - "uuid 1.9.0", - "webauthn-rs-proto", -] - -[[package]] -name = "kanidm_lib_file_permissions" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bb7525ce9007b0798a8eaf010708ef49da7f1b2516eebd3058f253df6db843" -dependencies = [ - "kanidm_utils_users", - "whoami", -] - -[[package]] -name = "kanidm_proto" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad03a5e96bf8a4fa981b864c3317950dce7d7ea6b0e8accd61329ec72ca1cd6" -dependencies = [ - "base32", - "base64urlsafedata", - "num_enum", - "scim_proto", - "serde", - "serde_json", - "serde_with", - "time", - "tracing", - "url", - "urlencoding", - "utoipa", - "uuid 1.9.0", - "webauthn-rs-proto", -] - -[[package]] -name = "kanidm_utils_users" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89aa036a35fe4b2953c7c8ab8ad456db3ab8547aec1f1a762ab524d7480c243b" -dependencies = [ - "libc", -] - [[package]] name = "kv-log-macro" version = "1.0.7" @@ -3211,16 +3072,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minidom" version = "0.15.2" @@ -3401,36 +3252,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "object" version = "0.36.0" @@ -3834,33 +3655,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "peg" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a625d12ad770914cbf7eff6f9314c3ef803bfe364a1b20bc36ddf56673e71e5" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f241d42067ed3ab6a4fece1db720838e1418f36d868585a27931f95d6bc03582" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -4028,16 +3822,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -4103,12 +3887,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - [[package]] name = "ptr_meta" version = "0.1.4" @@ -4129,16 +3907,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "publicsuffix" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" -dependencies = [ - "idna 0.3.0", - "psl-types", -] - [[package]] name = "qrcode" version = "0.14.0" @@ -4513,11 +4281,8 @@ version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "async-compression", "base64 0.21.7", "bytes 1.6.0", - "cookie 0.17.0", - "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -4530,7 +4295,6 @@ dependencies = [ "js-sys", "log", "mime", - "mime_guess", "native-tls", "once_cell", "percent-encoding", @@ -4956,23 +4720,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "scim_proto" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55fbcfbcbc11ff46228a2b7b6018e1f6f37499fff47851e20583862ba1d9ef3f" -dependencies = [ - "base64 0.22.1", - "peg", - "serde", - "serde_json", - "time", - "tracing", - "tracing-subscriber", - "url", - "uuid 1.9.0", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -5192,17 +4939,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_derive" version = "1.0.203" @@ -5278,36 +5014,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.2.6", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.68", -] - [[package]] name = "sha1" version = "0.10.6" @@ -5879,9 +5585,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", @@ -6054,15 +5758,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.7.8" @@ -6376,15 +6071,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -6453,7 +6139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", "serde", ] @@ -6473,30 +6159,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utoipa" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_json", - "utoipa-gen", -] - -[[package]] -name = "utoipa-gen" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bf0e16c02bc4bf5322ab65f10ab1149bdbcaa782cba66dc7057370a3f8190be" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.68", -] - [[package]] name = "uuid" version = "0.8.2" @@ -6525,7 +6187,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" dependencies = [ - "idna 0.5.0", + "idna", "once_cell", "regex", "serde", @@ -6613,8 +6275,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", - "serde", - "serde_json", "wasm-bindgen-macro", ] @@ -6697,23 +6357,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webauthn-rs-proto" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1c6dc254607f48eec3bdb35b86b377202436859ca1e4c9290afafd7349dcc3" -dependencies = [ - "base64 0.21.7", - "base64urlsafedata", - "js-sys", - "serde", - "serde-wasm-bindgen", - "serde_json", - "url", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "webpki" version = "0.22.4" diff --git a/Cargo.toml b/Cargo.toml index e0cc4a3..06b0d28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ "crates/testx", "crates/db-utils", # actors - "crates/account_manager", + "crates/idp", # "crates/cart_manager", # "crates/database_manager", # "crates/email_manager", diff --git a/crates/account_manager/Cargo.toml b/crates/account_manager/Cargo.toml deleted file mode 100644 index dbd48ef..0000000 --- a/crates/account_manager/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "account_manager" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "account-manager" -path = "src/main.rs" - -[dependencies] -bincode = { version = "1" } -bytes = { version = "1" } -channels = { path = "../channels" } -config = { path = "../config" } -dotenv = { version = "0" } -futures = { version = "0" } -gumdrop = { version = "0" } -json = { version = "0" } -kanidm_client = "1.2.2" -kanidm_proto = "1.2.2" -model = { path = "../model", features = ['db'] } -rauthy-client = { version = "0.4.0", features = ["actix-web", "qrcode"] } -rumqttc = { version = "*" } -serde = { version = "1", features = ["derive"] } -sqlx = { version = "0", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } -sqlx-core = { version = "0", features = [] } -tarpc = { version = "0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } -thiserror = { version = "1" } -tokio = { version = "1", features = ['full'] } -tracing = { version = "0" } -uuid = { workspace = true, features = ["v4"] } - -[dev-dependencies] -fake = { version = "2" } -testx = { path = "../testx" } diff --git a/crates/account_manager/migrations/202204131841_init.sql b/crates/account_manager/migrations/202204131841_init.sql deleted file mode 100644 index 8e54c83..0000000 --- a/crates/account_manager/migrations/202204131841_init.sql +++ /dev/null @@ -1,23 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; - -CREATE TYPE "AccountState" AS ENUM ( - 'active', - 'suspended', - 'banned' - ); - -CREATE TYPE "Role" AS ENUM ( - 'admin', - 'user' - ); - -CREATE TABLE public.accounts -( - id serial NOT NULL, - email character varying NOT NULL, - login character varying NOT NULL, - pass_hash character varying NOT NULL, - role "Role" DEFAULT 'user'::"Role" NOT NULL, - customer_id uuid DEFAULT gen_random_uuid() NOT NULL, - state "AccountState" DEFAULT 'active'::"AccountState" NOT NULL -); diff --git a/crates/account_manager/migrations/202204131842_addresses.sql b/crates/account_manager/migrations/202204131842_addresses.sql deleted file mode 100644 index ea595c9..0000000 --- a/crates/account_manager/migrations/202204131842_addresses.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE public.account_addresses -( - id serial NOT NULL, - name text NOT NULL, - email text NOT NULL, - street text NOT NULL, - city text NOT NULL, - country text NOT NULL, - zip text NOT NULL, - account_id integer, - is_default boolean DEFAULT false NOT NULL, - phone text DEFAULT ''::text NOT NULL -); diff --git a/crates/account_manager/src/actions.rs b/crates/account_manager/src/actions.rs deleted file mode 100644 index 61026e0..0000000 --- a/crates/account_manager/src/actions.rs +++ /dev/null @@ -1,124 +0,0 @@ -use channels::accounts::{all, find_by_identity, me, register}; -use config::SharedAppConfig; -use model::{Encrypt, FullAccount, Ranged}; - -use crate::db::{AccountAddresses, Database, FindAccount}; -use crate::{Error, Result}; - -macro_rules! ok_or_rollback { - ($res: expr, $t: expr, $err: expr) => { - match $res { - Ok(v) => v, - Err(e) => { - tracing::error!("{}", e); - $t.rollback().await.ok(); - - return Err($err); - } - } - }; -} - -macro_rules! begin_t { - ($db: expr, $err: expr) => { - match $db.pool.begin().await { - Ok(t) => t, - Err(e) => { - tracing::error!("{}", e); - return Err($err); - } - } - }; -} - -pub async fn all(input: all::Input, db: Database) -> all::Output { - use channels::accounts::Error; - - let mut t = begin_t!(db, Error::DbCritical); - - let dbm = crate::db::AllAccounts { - limit: input.limit.into_raw(), - offset: input.offset.into_raw(), - }; - - let res = dbm.run(&mut t).await; - - let accounts = ok_or_rollback!(res, t, Error::All); - - t.commit().await.map_err(|e| { - tracing::error!("{}", e); - Error::DbCritical - })?; - - Ok(all::Details { accounts }) -} - -pub async fn me(account_id: model::AccountId, db: Database) -> me::Output { - use channels::accounts::Error; - - let mut t = begin_t!(db, Error::Account); - - let res = FindAccount { account_id }.run(&mut t).await; - let account: FullAccount = ok_or_rollback!(res, t, Error::Account); - - let res = AccountAddresses { account_id }.run(&mut t).await; - let addresses = ok_or_rollback!(res, t, Error::Addresses); - - t.commit().await.map_err(|e| { - tracing::error!("{}", e); - Error::DbCritical - })?; - - Ok(me::Details { account, addresses }) -} - -pub async fn create_account( - msg: register::Input, - db: &Database, - config: SharedAppConfig, -) -> Result { - let salt = config.lock().web().pass_salt(); - let hash = msg.password.encrypt(&salt).map_err(|e| { - tracing::error!("{e:?}"); - Error::Hashing - })?; - - let mut t = begin_t!(db, Error::DbCritical); - - let res = crate::db::CreateAccount { - email: msg.email, - login: msg.login, - pass_hash: model::PassHash::new(hash), - role: msg.role, - } - .run(&mut t) - .await; - - let account: FullAccount = ok_or_rollback!(res, t, Error::Saving); - - t.commit().await.map_err(|e| { - tracing::error!("{}", e); - Error::DbCritical - })?; - - Ok(account) -} - -pub async fn find_by_identity( - input: find_by_identity::Input, - db: Database, -) -> find_by_identity::Output { - use channels::accounts::Error; - - let mut t = begin_t!(db, Error::DbCritical); - let dbm = crate::db::AccountByIdentity { - login: input.login, - email: input.email, - }; - - let res = dbm.run(&mut t).await; - - let account: FullAccount = ok_or_rollback!(res, t, Error::InvalidIdentity); - - Ok(find_by_identity::Details { account }) -} diff --git a/crates/account_manager/src/bin/account-client.rs b/crates/account_manager/src/bin/account-client.rs deleted file mode 100644 index 264421f..0000000 --- a/crates/account_manager/src/bin/account-client.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::time::Duration; - -use config::UpdateConfig; -use tarpc::context; -use tokio::time::sleep; - -#[derive(gumdrop::Options)] -struct Flags { - help: bool, - /// Sets the name to say hello to. - name: String, -} - -impl UpdateConfig for Flags {} - -#[tokio::main] -async fn main() -> std::io::Result<()> { - use channels::accounts::me::Input; - - let opts: Flags = gumdrop::Options::parse_args_default_or_exit(); - - let config = config::default_load(&opts); - let client = channels::accounts::rpc::create_client(config).await; - - let r = client - .me( - context::current(), - Input { - account_id: 1.into(), - }, - ) - .await; - println!("{:?}", r); - - // Let the background span processor finish. - sleep(Duration::from_micros(1)).await; - - Ok(()) -} diff --git a/crates/account_manager/src/db/accounts.rs b/crates/account_manager/src/db/accounts.rs deleted file mode 100644 index 6b098b6..0000000 --- a/crates/account_manager/src/db/accounts.rs +++ /dev/null @@ -1,441 +0,0 @@ -use model::{AccountId, AccountState, Email, FullAccount, Login, PassHash, Role}; - -pub type Result = std::result::Result; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] -pub enum Error { - #[error("Can't create account")] - CantCreate, - #[error("Can't find account does to lack of identity")] - NoIdentity, - #[error("Account does not exists")] - NotExists, - #[error("Failed to load all accounts")] - All, - #[error("Can't update account")] - CantUpdate, -} - -#[derive(Debug)] -pub struct AllAccounts { - pub limit: i32, - pub offset: i32, -} - -impl AllAccounts { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result> { - sqlx::query_as( - r#" -SELECT id, email, login, pass_hash, role, customer_id, state -FROM accounts -LIMIT $1 OFFSET $2 - "#, - ) - .bind(self.limit) - .bind(self.offset) - .fetch_all(pool) - .await - .map_err(|e| { - tracing::error!("{e:?}"); - Error::All - }) - } -} - -#[derive(Debug)] -pub struct CreateAccount { - pub email: Email, - pub login: Login, - pub pass_hash: PassHash, - pub role: Role, -} - -impl CreateAccount { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - sqlx::query_as( - r#" -INSERT INTO accounts (login, email, role, pass_hash) -VALUES ($1, $2, $3, $4) -RETURNING id, email, login, pass_hash, role, customer_id, state - "#, - ) - .bind(self.login) - .bind(self.email) - .bind(self.role) - .bind(self.pass_hash) - .fetch_one(pool) - .await - .map_err(|e| { - tracing::error!("{e:?}"); - Error::CantCreate - }) - } -} - -#[derive(Debug)] -pub struct UpdateAccount { - pub id: AccountId, - pub email: Email, - pub login: Login, - pub pass_hash: Option, - pub role: Role, - pub state: AccountState, -} - -impl UpdateAccount { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - match self.pass_hash { - Some(hash) => sqlx::query_as( - r#" -UPDATE accounts -SET login = $2, email = $3, role = $4, pass_hash = $5, state = $6 -WHERE id = $1 -RETURNING id, email, login, pass_hash, role, customer_id, state - "#, - ) - .bind(self.id) - .bind(self.login) - .bind(self.email) - .bind(self.role) - .bind(hash) - .bind(self.state), - None => sqlx::query_as( - r#" -UPDATE accounts -SET login = $2, email = $3, role = $4, state = $5 -WHERE id = $1 -RETURNING id, email, login, pass_hash, role, customer_id, state - "#, - ) - .bind(self.id) - .bind(self.login) - .bind(self.email) - .bind(self.role) - .bind(self.state), - } - .fetch_one(pool) - .await - .map_err(|e| { - tracing::error!("{e:?}"); - Error::CantUpdate - }) - } -} - -#[derive(Debug)] -pub struct FindAccount { - pub account_id: AccountId, -} - -impl FindAccount { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - sqlx::query_as( - r#" -SELECT id, email, login, pass_hash, role, customer_id, state -FROM accounts -WHERE id = $1 - "#, - ) - .bind(self.account_id) - .fetch_one(pool) - .await - .map_err(|e| { - tracing::error!("{e:?}"); - Error::NotExists - }) - } -} - -#[derive(Debug)] -pub struct AccountByIdentity { - pub login: Option, - pub email: Option, -} - -impl AccountByIdentity { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - match (self.login, self.email) { - (Some(login), None) => sqlx::query_as( - r#" -SELECT id, email, login, pass_hash, role, customer_id, state -FROM accounts -WHERE login = $1 - "#, - ) - .bind(login), - (None, Some(email)) => sqlx::query_as( - r#" -SELECT id, email, login, pass_hash, role, customer_id, state -FROM accounts -WHERE email = $1 - "#, - ) - .bind(email), - (Some(login), Some(email)) => sqlx::query_as( - r#" -SELECT id, email, login, pass_hash, role, customer_id, state -FROM accounts -WHERE login = $1 AND email = $2 - "#, - ) - .bind(login) - .bind(email), - _ => return Err(Error::NoIdentity), - } - .fetch_one(pool) - .await - .map_err(|e| { - tracing::error!("{e:?}"); - Error::CantCreate - }) - } -} - -#[cfg(test)] -mod tests { - use config::UpdateConfig; - use fake::Fake; - use model::*; - - use super::*; - use crate::db::Database; - - pub struct NoOpts; - - impl UpdateConfig for NoOpts {} - - async fn test_create_account( - t: &mut sqlx::Transaction<'_, sqlx::Postgres>, - login: Option, - email: Option, - hash: Option, - ) -> FullAccount { - use fake::faker::internet::en; - let login: String = login.unwrap_or_else(|| en::Username().fake()); - let email: String = email.unwrap_or_else(|| en::FreeEmail().fake()); - let hash: String = hash.unwrap_or_else(|| en::Password(10..20).fake()); - - CreateAccount { - email: Email::new(email), - login: Login::new(login), - pass_hash: PassHash::new(hash), - role: Role::Admin, - } - .run(t) - .await - .unwrap() - } - - #[tokio::test] - async fn create_account() { - testx::db_t_ref!(t); - - let login: String = fake::faker::internet::en::Username().fake(); - let email: String = fake::faker::internet::en::FreeEmail().fake(); - let hash: String = fake::faker::internet::en::Password(10..20).fake(); - - let account: FullAccount = CreateAccount { - email: Email::new(&email), - login: Login::new(&login), - pass_hash: PassHash::new(&hash), - role: Role::Admin, - } - .run(&mut t) - .await - .unwrap(); - - let expected = FullAccount { - login: Login::new(login), - email: Email::new(email), - pass_hash: PassHash::new(&hash), - role: Role::Admin, - customer_id: account.customer_id, - id: account.id, - state: AccountState::Active, - }; - - t.rollback().await.unwrap(); - assert_eq!(account, expected); - } - - #[tokio::test] - async fn all_accounts() { - testx::db_t_ref!(t); - - test_create_account(&mut t, None, None, None).await; - test_create_account(&mut t, None, None, None).await; - test_create_account(&mut t, None, None, None).await; - - let v: Vec = AllAccounts { - limit: 200, - offset: 0, - } - .run(&mut t) - .await - .unwrap(); - - testx::db_rollback!(t); - assert!(v.len() >= 3); - } - - #[tokio::test] - async fn update_account_without_pass() { - testx::db_t_ref!(t); - - let original_login: String = fake::faker::internet::en::Username().fake(); - let original_email: String = fake::faker::internet::en::FreeEmail().fake(); - let original_hash: String = fake::faker::internet::en::Password(10..20).fake(); - - let original_account = test_create_account( - &mut t, - Some(original_login.clone()), - Some(original_email.clone()), - Some(original_hash.clone()), - ) - .await; - - let updated_login: String = fake::faker::internet::en::Username().fake(); - let updated_email: String = fake::faker::internet::en::FreeEmail().fake(); - - let updated_account: FullAccount = UpdateAccount { - id: original_account.id, - email: Email::new(updated_email.clone()), - login: Login::new(updated_login.clone()), - pass_hash: None, - role: Role::Admin, - state: AccountState::Active, - } - .run(&mut t) - .await - .unwrap(); - - let expected = FullAccount { - id: original_account.id, - email: Email::new(updated_email), - login: Login::new(updated_login), - pass_hash: PassHash::new(original_hash), - role: Role::Admin, - customer_id: original_account.customer_id, - state: AccountState::Active, - }; - - testx::db_rollback!(t); - assert_ne!(original_account, expected); - assert_eq!(updated_account, expected); - } - - #[tokio::test] - async fn update_account_with_pass() { - testx::db_t_ref!(t); - - let original_login: String = fake::faker::internet::en::Username().fake(); - let original_email: String = fake::faker::internet::en::FreeEmail().fake(); - let original_hash: String = fake::faker::internet::en::Password(10..20).fake(); - - let original_account = test_create_account( - &mut t, - Some(original_login.clone()), - Some(original_email.clone()), - Some(original_hash.clone()), - ) - .await; - - let updated_login: String = fake::faker::internet::en::Username().fake(); - let updated_email: String = fake::faker::internet::en::FreeEmail().fake(); - let updated_hash: String = fake::faker::internet::en::Password(10..20).fake(); - - let updated_account: FullAccount = UpdateAccount { - id: original_account.id, - email: Email::new(updated_email.clone()), - login: Login::new(updated_login.clone()), - pass_hash: Some(PassHash::new(updated_hash.clone())), - role: Role::Admin, - state: AccountState::Active, - } - .run(&mut t) - .await - .unwrap(); - - let expected = FullAccount { - id: original_account.id, - email: Email::new(updated_email), - login: Login::new(updated_login), - pass_hash: PassHash::new(updated_hash), - role: Role::Admin, - customer_id: original_account.customer_id, - state: AccountState::Active, - }; - - testx::db_rollback!(t); - assert_ne!(original_account, expected); - assert_eq!(updated_account, expected); - } - - #[tokio::test] - async fn find() { - testx::db_t_ref!(t); - - let account = test_create_account(&mut t, None, None, None).await; - - let res: FullAccount = FindAccount { - account_id: account.id, - } - .run(&mut t) - .await - .unwrap(); - - testx::db_rollback!(t); - assert_eq!(account, res); - } - - #[tokio::test] - async fn find_identity_email() { - testx::db_t_ref!(t); - - let account = test_create_account(&mut t, None, None, None).await; - - let res: FullAccount = AccountByIdentity { - email: Some(account.email.clone()), - login: None, - } - .run(&mut t) - .await - .unwrap(); - - testx::db_rollback!(t); - assert_eq!(account, res); - } - - #[tokio::test] - async fn find_identity_login() { - testx::db_t_ref!(t); - - let account = test_create_account(&mut t, None, None, None).await; - - let res: FullAccount = AccountByIdentity { - login: Some(account.login.clone()), - email: None, - } - .run(&mut t) - .await - .unwrap(); - - testx::db_rollback!(t); - assert_eq!(account, res); - } -} diff --git a/crates/account_manager/src/db/addresses.rs b/crates/account_manager/src/db/addresses.rs deleted file mode 100644 index 7d8aab8..0000000 --- a/crates/account_manager/src/db/addresses.rs +++ /dev/null @@ -1,311 +0,0 @@ -pub type Result = std::result::Result; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] -pub enum Error { - #[error("Can't load account addresses")] - AccountAddresses, - #[error("Failed to save account address")] - CreateAccountAddress, -} - -#[derive(Debug)] -pub struct AccountAddresses { - pub account_id: model::AccountId, -} - -impl AccountAddresses { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result> { - sqlx::query_as( - r#" -SELECT id, name, email, phone, street, city, country, zip, account_id, is_default -FROM account_addresses -WHERE account_id = $1 - "#, - ) - .bind(self.account_id) - .fetch_all(pool) - .await - .map_err(|_| Error::AccountAddresses.into()) - } -} - -#[derive(Debug)] -pub struct FindAccountAddress { - pub account_id: model::AccountId, - pub address_id: model::AddressId, -} - -impl FindAccountAddress { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - sqlx::query_as( - r#" -SELECT id, name, email, phone, street, city, country, zip, account_id, is_default -FROM account_addresses -WHERE account_id = $1 AND id = $2 - "#, - ) - .bind(self.account_id) - .bind(self.address_id) - .fetch_one(pool) - .await - .map_err(|_| Error::AccountAddresses.into()) - } -} - -#[derive(Debug)] -pub struct DefaultAccountAddress { - pub account_id: model::AccountId, -} - -impl DefaultAccountAddress { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - sqlx::query_as( - r#" -SELECT id, name, email, phone, street, city, country, zip, account_id, is_default -FROM account_addresses -WHERE account_id = $1 AND is_default - "#, - ) - .bind(self.account_id) - .fetch_one(pool) - .await - .map_err(|_| Error::AccountAddresses.into()) - } -} - -#[derive(Debug)] -pub struct CreateAccountAddress { - pub name: model::Name, - pub email: model::Email, - pub phone: model::Phone, - pub street: model::Street, - pub city: model::City, - pub country: model::Country, - pub zip: model::Zip, - pub account_id: Option, - pub is_default: bool, -} - -impl CreateAccountAddress { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - if self.is_default && self.account_id.is_some() { - if let Err(e) = sqlx::query( - r#" -UPDATE account_addresses -SET is_default = FALSE -WHERE account_id = $1 - "#, - ) - .bind(self.account_id) - .fetch_all(&mut *pool) - .await - { - tracing::error!("{e}"); - dbg!(e); - } - } - - sqlx::query_as( - r#" -INSERT INTO account_addresses ( name, email, phone, street, city, country, zip, account_id, is_default) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) -RETURNING id, name, email, phone, street, city, country, zip, account_id, is_default - "#, - ) - .bind(self.name) - .bind(self.email) - .bind(self.phone) - .bind(self.street) - .bind(self.city) - .bind(self.country) - .bind(self.zip) - .bind(self.account_id) - .bind(self.is_default) - .fetch_one(pool) - .await - .map_err(|e| { - tracing::error!("{e}"); - dbg!(e); - Error::CreateAccountAddress.into() - }) - } -} - -#[derive(Debug)] -pub struct UpdateAccountAddress { - pub id: model::AddressId, - pub name: model::Name, - pub email: model::Email, - pub phone: model::Phone, - pub street: model::Street, - pub city: model::City, - pub country: model::Country, - pub zip: model::Zip, - pub account_id: model::AccountId, - pub is_default: bool, -} - -impl UpdateAccountAddress { - pub async fn run( - self, - pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, - ) -> Result { - sqlx::query_as( - r#" -UPDATE account_addresses -SET name = $2, email = $3, street = $4, city = $5, country = $6, zip = $7, account_id = $8, is_default = $9, phone = $10 -WHERE id = $1 -RETURNING id, name, email, phone, street, city, country, zip, account_id, is_default - "#, - ) - .bind(self.id) - .bind(self.name) - .bind(self.email) - .bind(self.street) - .bind(self.city) - .bind(self.country) - .bind(self.zip) - .bind(self.account_id) - .bind(self.is_default) - .bind(self.phone) - .fetch_one(pool) - .await - .map_err(|_| Error::CreateAccountAddress.into()) - } -} - -#[cfg(test)] -mod test { - use config::*; - use fake::Fake; - use model::*; - - use super::super::accounts::CreateAccount; - use super::*; - use crate::db::Database; - - pub struct NoOpts; - - impl UpdateConfig for NoOpts {} - - async fn test_create_account(pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> FullAccount { - let login: String = fake::faker::internet::en::Username().fake(); - let email: String = fake::faker::internet::en::FreeEmail().fake(); - let hash: String = fake::faker::internet::en::Password(10..20).fake(); - - CreateAccount { - email: Email::new(email), - login: Login::new(login), - pass_hash: PassHash::new(hash), - role: Role::Admin, - } - .run(pool) - .await - .unwrap() - } - - #[tokio::test] - async fn full_check() { - testx::db_t_ref!(t); - - // account - let account = test_create_account(&mut t).await; - - // address - let mut address: AccountAddress = { - let name: String = fake::faker::name::en::Name().fake(); - let email: String = fake::faker::internet::en::FreeEmail().fake(); - let phone: String = fake::faker::phone_number::en::PhoneNumber().fake(); - let street: String = fake::faker::address::en::StreetName().fake(); - let city: String = fake::faker::address::en::CityName().fake(); - let country: String = fake::faker::address::en::CountryName().fake(); - let zip: String = fake::faker::address::en::ZipCode().fake(); - let account_id = Some(account.id); - let is_default: bool = true; - - let address = CreateAccountAddress { - name: Name::new(name.clone()), - email: Email::new(email.clone()), - phone: Phone::new(phone.clone()), - street: Street::new(street.clone()), - city: City::new(city.clone()), - country: Country::new(country.clone()), - zip: Zip::new(zip.clone()), - account_id, - is_default, - } - .run(&mut t) - .await - .unwrap(); - - assert_eq!( - address, - AccountAddress { - id: address.id, - name: Name::new(name.clone()), - email: Email::new(email.clone()), - phone: Phone::new(phone.clone()), - street: Street::new(street.clone()), - city: City::new(city.clone()), - country: Country::new(country.clone()), - zip: Zip::new(zip.clone()), - account_id: account.id, - is_default, - } - ); - address - }; - - let found = FindAccountAddress { - account_id: account.id, - address_id: address.id, - } - .run(&mut t) - .await - .unwrap(); - - assert_eq!(found, address); - - let changed = UpdateAccountAddress { - id: address.id, - name: address.name.clone(), - email: address.email.clone(), - phone: address.phone.clone(), - street: address.street.clone(), - city: address.city.clone(), - country: address.country.clone(), - zip: address.zip.clone(), - account_id: address.account_id, - is_default: true, - } - .run(&mut t) - .await - .unwrap(); - - address.is_default = true; - - assert_eq!(changed, address); - - let default_address = DefaultAccountAddress { - account_id: account.id, - } - .run(&mut t) - .await - .unwrap(); - - testx::db_rollback!(t); - assert_eq!(default_address, address); - } -} diff --git a/crates/account_manager/src/db/mod.rs b/crates/account_manager/src/db/mod.rs deleted file mode 100644 index d00f7b5..0000000 --- a/crates/account_manager/src/db/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -pub mod accounts; -pub mod addresses; - -pub use accounts::*; -pub use addresses::*; -use config::SharedAppConfig; -use sqlx::{Pool, Postgres}; - -#[derive(Clone)] -pub struct Database { - pub pool: sqlx::PgPool, - _config: SharedAppConfig, -} - -impl Database { - pub async fn build(config: SharedAppConfig) -> Self { - let url = config.lock().account_manager().database_url.clone(); - let pool = sqlx::PgPool::connect(&url).await.unwrap_or_else(|e| { - tracing::error!("Failed to connect to database. {e:?}"); - std::process::exit(1); - }); - Self { - pool, - _config: config, - } - } - - pub fn pool(&self) -> Pool { - self.pool.clone() - } -} diff --git a/crates/account_manager/src/idp.rs b/crates/account_manager/src/idp.rs deleted file mode 100644 index e3b6c55..0000000 --- a/crates/account_manager/src/idp.rs +++ /dev/null @@ -1,126 +0,0 @@ -use kanidm_client::{ClientError, KanidmClient}; -use kanidm_proto::internal::CUStatus; -use kanidm_proto::v1::Entry; - -pub async fn refresh_token(kanidm: &KanidmClient) -> Result<(), ClientError> { - kanidm - .auth_simple_password( - "idm_admin", - &std::env::var("KANIDM_IDM_ADMIN_PASS") - .expect("idm_admin password is requied, please set KANIDM_IDM_ADMIN_PASS"), - ) - .await?; - Ok(()) -} - -pub async fn create_account_with_password( - kanidm: &KanidmClient, - login: &str, - display_name: &str, - email: &str, - password: &str, -) -> Result<(), ClientError> { - refresh_token(kanidm).await?; - let _person_created = kanidm - .idm_person_account_create(login, display_name) - .await - .ok(); - let accounts = accounts(kanidm).await?; - let uid = find_account(&accounts, FindBy::Name(login)).await?; - let id = uid.to_string(); - - kanidm - .idm_person_account_update(&id, None, None, None, Some(&[email.to_string()])) - .await?; - let (session_token, status) = kanidm.idm_account_credential_update_begin(&id).await?; - tracing::debug!( - "Begin update credentials ({can_commit}): {status:?}", - can_commit = status.can_commit - ); - - kanidm - .idm_account_credential_update_set_password(&session_token, password) - .await?; - - let status = kanidm - .idm_account_credential_update_status(&session_token) - .await?; - tracing::debug!( - "Set password ({can_commit}): {status:?}", - can_commit = status.can_commit - ); - - let status = kanidm - .idm_account_credential_update_init_totp(&session_token) - .await?; - tracing::debug!( - "Init TOTP ({can_commit}): {status:?}", - can_commit = status.can_commit - ); - - // let status = kanidm - // .idm_account_credential_update_check_totp(&session_token, totp_chal, - // label) .await?; - - tracing::debug!( - "TOTP check ({can_commit}): {status:?}", - can_commit = status.can_commit - ); - - kanidm - .idm_account_credential_update_commit(&session_token) - .await?; - let status = kanidm - .idm_account_credential_update_status(&session_token) - .await?; - tracing::debug!( - "Commit ({can_commit}): {status:?}", - can_commit = status.can_commit - ); - Ok(()) -} - -pub async fn accounts(kanidm: &KanidmClient) -> Result, ClientError> { - refresh_token(kanidm).await?; - - kanidm.idm_person_account_list().await -} - -#[derive(Debug)] -pub enum FindBy<'s> { - Email(&'s str), - Name(&'s str), -} - -impl<'s> FindBy<'s> { - fn key(&self) -> &'static str { - match self { - Self::Email(..) => "mail", - Self::Name(..) => "name", - } - } - fn as_str(&self) -> &'s str { - match self { - Self::Email(s) => s, - Self::Name(s) => s, - } - } -} - -pub async fn find_account(list: &[Entry], find_by: FindBy<'_>) -> Result { - list.iter() - .find_map(|entra| { - tracing::debug!("compare {find_by:?} with {entra:?}"); - let attrs = &entra.attrs; - attrs.get(find_by.key()).filter(|v| { - tracing::debug!("compare value {v:?} with {s}", s = find_by.as_str()); - v.iter().any(|s| s == find_by.as_str()) - })?; - let id = attrs.get("uuid").and_then(|v| v.first())?; - id.parse::().ok() - }) - .ok_or_else(|| { - tracing::info!("User not found"); - ClientError::Unauthorized - }) -} diff --git a/crates/account_manager/src/main.rs b/crates/account_manager/src/main.rs deleted file mode 100644 index 905af98..0000000 --- a/crates/account_manager/src/main.rs +++ /dev/null @@ -1,61 +0,0 @@ -#![feature(structural_match)] - -use config::UpdateConfig; - -pub mod actions; -pub mod db; -pub mod idp; -pub mod mqtt; -pub mod rpc; - -pub type Result = std::result::Result; - -#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)] -pub enum Error { - #[error("Unable to send or receive msg from database")] - DbCritical, - #[error("Failed to load account data")] - Account, - #[error("Failed to load account addresses")] - Addresses, - #[error("Unable to save record")] - Saving, - #[error("Unable to hash password")] - Hashing, -} - -pub struct Opts {} - -impl UpdateConfig for Opts {} - -#[tokio::main] -async fn main() { - dotenv::dotenv().ok(); - config::init_tracing("account-manager"); - - let opts = Opts {}; - - let config = config::default_load(&opts); - - let db = db::Database::build(config.clone()).await; - - let kanidm = kanidm_client::KanidmClientBuilder::new() - .address(config.lock().account_manager().idm_url().to_owned()) - .danger_accept_invalid_certs(cfg!(debug_assertions)) - .connect_timeout(2) - .build() - .unwrap(); - idp::accounts(&kanidm).await.unwrap(); - idp::create_account_with_password( - &kanidm, - "eraden", - "Adrian Woźniak", - "adrian.wozniak@ita-prog.pl", - "n59GmOOdcpVUJqJ1", - ) - .await - .unwrap(); - - let mqtt_client = mqtt::start(config.clone(), db.clone()).await; - rpc::start(config.clone(), db.clone(), mqtt_client.clone()).await; -} diff --git a/crates/account_manager/src/mqtt.rs b/crates/account_manager/src/mqtt.rs deleted file mode 100644 index 25541dc..0000000 --- a/crates/account_manager/src/mqtt.rs +++ /dev/null @@ -1,30 +0,0 @@ -use config::SharedAppConfig; -use rumqttc::{Event, Incoming}; - -use crate::db::Database; - -pub async fn start(config: SharedAppConfig, _db: Database) -> channels::AsyncClient { - let (client, mut event_loop) = channels::accounts::mqtt::create_client(config); - - let spawn_client = client.clone(); - tokio::spawn(async move { - let _client = spawn_client.clone(); - loop { - let notification = event_loop.poll().await; - - match notification { - Ok(Event::Incoming(Incoming::Publish(publish))) => match publish.topic.as_str() { - _ => {} - }, - Ok(Event::Incoming(_incoming)) => {} - Ok(Event::Outgoing(_outgoing)) => {} - Err(e) => { - tracing::error!("{}", e); - } - } - } - // tracing::info!("Mqtt channel closed"); - }); - - client -} diff --git a/crates/account_manager/src/rpc.rs b/crates/account_manager/src/rpc.rs deleted file mode 100644 index c7bb512..0000000 --- a/crates/account_manager/src/rpc.rs +++ /dev/null @@ -1,86 +0,0 @@ -use channels::accounts::rpc::Accounts; -use channels::accounts::{all, find_by_identity, me, register}; -use channels::AsyncClient; -use config::SharedAppConfig; -use tarpc::context; - -use crate::actions; -use crate::db::Database; - -#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)] -#[serde(rename_all = "kebab-case", tag = "account")] -pub enum Error { - #[error("Unable to send or receive msg from database")] - DbCritical, - #[error("Failed to load account data")] - Account, - #[error("Failed to load account addresses")] - Addresses, - #[error("Unable to save record")] - Saving, - #[error("Unable to hash password")] - Hashing, -} - -#[derive(Clone)] -struct AccountsServer { - db: Database, - config: SharedAppConfig, - mqtt_client: AsyncClient, -} - -impl Accounts for AccountsServer { - async fn me(self, _: context::Context, input: me::Input) -> me::Output { - let res = actions::me(input.account_id, self.db).await; - tracing::info!("ME result: {:?}", res); - res - } - - async fn register_account( - self, - _: context::Context, - input: register::Input, - ) -> register::Output { - use channels::accounts::Error; - - let res = actions::create_account(input, &self.db, self.config).await; - tracing::info!("REGISTER result: {:?}", res); - match res { - Ok(account) => { - self.mqtt_client.emit_account_created(&account).await; - Ok(register::Details { account }) - } - Err(_e) => Err(Error::Account), - } - } - - async fn all(self, _: context::Context, input: all::Input) -> all::Output { - let res = actions::all(input, self.db).await; - tracing::info!("ME result: {:?}", res); - res - } - - async fn find_by_identity( - self, - _: context::Context, - input: find_by_identity::Input, - ) -> find_by_identity::Output { - let res = actions::find_by_identity(input, self.db).await; - tracing::info!("ME result: {:?}", res); - res - } -} - -pub async fn start(config: SharedAppConfig, db: Database, mqtt_client: AsyncClient) { - let port = { config.lock().account_manager().rpc_port }; - - channels::rpc::start("accounts", port, || { - AccountsServer { - db: db.clone(), - config: config.clone(), - mqtt_client: mqtt_client.clone(), - } - .serve() - }) - .await; -} diff --git a/crates/channels/src/accounts.rs b/crates/channels/src/accounts.rs index 84ddfe0..d096673 100644 --- a/crates/channels/src/accounts.rs +++ b/crates/channels/src/accounts.rs @@ -182,7 +182,7 @@ pub mod rpc { use tarpc::tokio_serde::formats::Bincode; let l = config.lock(); - let addr = l.account_manager().rpc_addr(); + let addr = l.idp().rpc_addr(); let transport = tarpc::serde_transport::tcp::connect(addr, Bincode::default); @@ -204,6 +204,6 @@ pub mod mqtt { use crate::AsyncClient; pub fn create_client(config: SharedAppConfig) -> (AsyncClient, EventLoop) { - crate::mqtt::create_client(CLIENT_NAME, config.lock().account_manager().mqtt_addr()) + crate::mqtt::create_client(CLIENT_NAME, config.lock().idp().mqtt_addr()) } } diff --git a/crates/channels/src/carts.rs b/crates/channels/src/carts.rs index f3425a6..4d34019 100644 --- a/crates/channels/src/carts.rs +++ b/crates/channels/src/carts.rs @@ -132,10 +132,7 @@ pub mod rpc { let addr = { let l = config.lock(); - ( - l.account_manager().rpc_bind.clone(), - l.account_manager().rpc_port, - ) + (l.idp().rpc_bind.clone(), l.idp().rpc_port) }; let transport = tarpc::serde_transport::tcp::connect(addr, Bincode::default); diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 0b9d585..7f42f38 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -348,16 +348,18 @@ impl FilesConfig { } #[derive(Debug, Serialize, Deserialize)] -pub struct AccountManagerConfig { +pub struct IdpConfig { pub rpc_port: u16, pub rpc_bind: String, pub mqtt_port: u16, pub mqtt_bind: String, pub database_url: String, pub idm_url: String, + #[serde(default)] + pub secret: Option, } -impl Default for AccountManagerConfig { +impl Default for IdpConfig { fn default() -> Self { Self { rpc_port: 19329, @@ -366,13 +368,14 @@ impl Default for AccountManagerConfig { mqtt_bind: "0.0.0.0".into(), database_url: "postgres://postgres@localhost/myco_accounts".into(), idm_url: "https://localhost:8443".into(), + secret: Some("CHANGE ME".into()), } } } -impl Example for AccountManagerConfig {} +impl Example for IdpConfig {} -impl AccountManagerConfig { +impl IdpConfig { pub fn rpc_addr(&self) -> (&str, u16) { (&self.rpc_bind, self.rpc_port) } @@ -384,6 +387,10 @@ impl AccountManagerConfig { pub fn idm_url(&self) -> &str { &self.idm_url } + + pub fn secret(&self) -> Option<&String> { + self.secret.as_ref() + } } #[derive(Debug, Serialize, Deserialize)] @@ -668,7 +675,7 @@ impl TokensConfig { #[derive(Serialize, Deserialize)] pub struct AppConfig { #[serde(default)] - account_manager: AccountManagerConfig, + idp: IdpConfig, #[serde(default)] cart_manager: CartManagerConfig, #[serde(skip)] @@ -705,7 +712,7 @@ impl Example for AppConfig { database: DatabaseConfig::example(), search: SearchConfig::example(), files: FilesConfig::example(), - account_manager: AccountManagerConfig::example(), + idp: IdpConfig::example(), cart_manager: CartManagerConfig::example(), email_sender: EmailSenderConfig::example(), stocks: StocksConfig::example(), @@ -718,8 +725,8 @@ impl Example for AppConfig { } impl AppConfig { - pub fn account_manager(&self) -> &AccountManagerConfig { - &self.account_manager + pub fn idp(&self) -> &IdpConfig { + &self.idp } pub fn cart_manager(&self) -> &CartManagerConfig { @@ -800,7 +807,7 @@ impl Default for AppConfig { database: DatabaseConfig::default(), search: SearchConfig::default(), files: FilesConfig::default(), - account_manager: AccountManagerConfig::default(), + idp: IdpConfig::default(), cart_manager: CartManagerConfig::default(), email_sender: EmailSenderConfig::default(), stocks: StocksConfig::default(), diff --git a/myco.toml b/myco.toml index b362304..9ffaf81 100644 --- a/myco.toml +++ b/myco.toml @@ -1,4 +1,4 @@ -[account_manager] +[idp] rpc_port = 19329 rpc_bind = "0.0.0.0" mqtt_port = 1883 From 7f5f38dd5af2af37e82f2591b944497b618a0c64 Mon Sep 17 00:00:00 2001 From: eraden Date: Wed, 26 Jun 2024 11:12:06 +0200 Subject: [PATCH 2/2] Rename to idp --- crates/idp/Cargo.toml | 33 ++ crates/idp/migrations/202204131841_init.sql | 23 + .../idp/migrations/202204131842_addresses.sql | 13 + crates/idp/src/actions.rs | 124 +++++ crates/idp/src/bin/account-client.rs | 39 ++ crates/idp/src/db/accounts.rs | 441 ++++++++++++++++++ crates/idp/src/db/addresses.rs | 311 ++++++++++++ crates/idp/src/db/mod.rs | 31 ++ crates/idp/src/idp.rs | 163 +++++++ crates/idp/src/main.rs | 61 +++ crates/idp/src/mqtt.rs | 30 ++ crates/idp/src/rpc.rs | 86 ++++ 12 files changed, 1355 insertions(+) create mode 100644 crates/idp/Cargo.toml create mode 100644 crates/idp/migrations/202204131841_init.sql create mode 100644 crates/idp/migrations/202204131842_addresses.sql create mode 100644 crates/idp/src/actions.rs create mode 100644 crates/idp/src/bin/account-client.rs create mode 100644 crates/idp/src/db/accounts.rs create mode 100644 crates/idp/src/db/addresses.rs create mode 100644 crates/idp/src/db/mod.rs create mode 100644 crates/idp/src/idp.rs create mode 100644 crates/idp/src/main.rs create mode 100644 crates/idp/src/mqtt.rs create mode 100644 crates/idp/src/rpc.rs diff --git a/crates/idp/Cargo.toml b/crates/idp/Cargo.toml new file mode 100644 index 0000000..1822532 --- /dev/null +++ b/crates/idp/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "idp" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "idp" +path = "src/main.rs" + +[dependencies] +bincode = { version = "1" } +bytes = { version = "1" } +channels = { path = "../channels" } +config = { path = "../config" } +dotenv = { version = "0" } +futures = { version = "0" } +gumdrop = { version = "0" } +json = { version = "0" } +model = { path = "../model", features = ['db'] } +rauthy-client = { version = "0.4.0", features = ["actix-web", "qrcode"] } +rumqttc = { version = "*" } +serde = { version = "1", features = ["derive"] } +sqlx = { version = "0", features = ["migrate", "runtime-actix-rustls", "all-types", "postgres"] } +sqlx-core = { version = "0", features = [] } +tarpc = { version = "0", features = ["tokio1", "serde-transport-bincode", "serde-transport", "serde", "serde-transport-json", "tcp"] } +thiserror = { version = "1" } +tokio = { version = "1", features = ['full'] } +tracing = { version = "0" } +uuid = { workspace = true, features = ["v4"] } + +[dev-dependencies] +fake = { version = "2" } +testx = { path = "../testx" } diff --git a/crates/idp/migrations/202204131841_init.sql b/crates/idp/migrations/202204131841_init.sql new file mode 100644 index 0000000..8e54c83 --- /dev/null +++ b/crates/idp/migrations/202204131841_init.sql @@ -0,0 +1,23 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; + +CREATE TYPE "AccountState" AS ENUM ( + 'active', + 'suspended', + 'banned' + ); + +CREATE TYPE "Role" AS ENUM ( + 'admin', + 'user' + ); + +CREATE TABLE public.accounts +( + id serial NOT NULL, + email character varying NOT NULL, + login character varying NOT NULL, + pass_hash character varying NOT NULL, + role "Role" DEFAULT 'user'::"Role" NOT NULL, + customer_id uuid DEFAULT gen_random_uuid() NOT NULL, + state "AccountState" DEFAULT 'active'::"AccountState" NOT NULL +); diff --git a/crates/idp/migrations/202204131842_addresses.sql b/crates/idp/migrations/202204131842_addresses.sql new file mode 100644 index 0000000..ea595c9 --- /dev/null +++ b/crates/idp/migrations/202204131842_addresses.sql @@ -0,0 +1,13 @@ +CREATE TABLE public.account_addresses +( + id serial NOT NULL, + name text NOT NULL, + email text NOT NULL, + street text NOT NULL, + city text NOT NULL, + country text NOT NULL, + zip text NOT NULL, + account_id integer, + is_default boolean DEFAULT false NOT NULL, + phone text DEFAULT ''::text NOT NULL +); diff --git a/crates/idp/src/actions.rs b/crates/idp/src/actions.rs new file mode 100644 index 0000000..61026e0 --- /dev/null +++ b/crates/idp/src/actions.rs @@ -0,0 +1,124 @@ +use channels::accounts::{all, find_by_identity, me, register}; +use config::SharedAppConfig; +use model::{Encrypt, FullAccount, Ranged}; + +use crate::db::{AccountAddresses, Database, FindAccount}; +use crate::{Error, Result}; + +macro_rules! ok_or_rollback { + ($res: expr, $t: expr, $err: expr) => { + match $res { + Ok(v) => v, + Err(e) => { + tracing::error!("{}", e); + $t.rollback().await.ok(); + + return Err($err); + } + } + }; +} + +macro_rules! begin_t { + ($db: expr, $err: expr) => { + match $db.pool.begin().await { + Ok(t) => t, + Err(e) => { + tracing::error!("{}", e); + return Err($err); + } + } + }; +} + +pub async fn all(input: all::Input, db: Database) -> all::Output { + use channels::accounts::Error; + + let mut t = begin_t!(db, Error::DbCritical); + + let dbm = crate::db::AllAccounts { + limit: input.limit.into_raw(), + offset: input.offset.into_raw(), + }; + + let res = dbm.run(&mut t).await; + + let accounts = ok_or_rollback!(res, t, Error::All); + + t.commit().await.map_err(|e| { + tracing::error!("{}", e); + Error::DbCritical + })?; + + Ok(all::Details { accounts }) +} + +pub async fn me(account_id: model::AccountId, db: Database) -> me::Output { + use channels::accounts::Error; + + let mut t = begin_t!(db, Error::Account); + + let res = FindAccount { account_id }.run(&mut t).await; + let account: FullAccount = ok_or_rollback!(res, t, Error::Account); + + let res = AccountAddresses { account_id }.run(&mut t).await; + let addresses = ok_or_rollback!(res, t, Error::Addresses); + + t.commit().await.map_err(|e| { + tracing::error!("{}", e); + Error::DbCritical + })?; + + Ok(me::Details { account, addresses }) +} + +pub async fn create_account( + msg: register::Input, + db: &Database, + config: SharedAppConfig, +) -> Result { + let salt = config.lock().web().pass_salt(); + let hash = msg.password.encrypt(&salt).map_err(|e| { + tracing::error!("{e:?}"); + Error::Hashing + })?; + + let mut t = begin_t!(db, Error::DbCritical); + + let res = crate::db::CreateAccount { + email: msg.email, + login: msg.login, + pass_hash: model::PassHash::new(hash), + role: msg.role, + } + .run(&mut t) + .await; + + let account: FullAccount = ok_or_rollback!(res, t, Error::Saving); + + t.commit().await.map_err(|e| { + tracing::error!("{}", e); + Error::DbCritical + })?; + + Ok(account) +} + +pub async fn find_by_identity( + input: find_by_identity::Input, + db: Database, +) -> find_by_identity::Output { + use channels::accounts::Error; + + let mut t = begin_t!(db, Error::DbCritical); + let dbm = crate::db::AccountByIdentity { + login: input.login, + email: input.email, + }; + + let res = dbm.run(&mut t).await; + + let account: FullAccount = ok_or_rollback!(res, t, Error::InvalidIdentity); + + Ok(find_by_identity::Details { account }) +} diff --git a/crates/idp/src/bin/account-client.rs b/crates/idp/src/bin/account-client.rs new file mode 100644 index 0000000..264421f --- /dev/null +++ b/crates/idp/src/bin/account-client.rs @@ -0,0 +1,39 @@ +use std::time::Duration; + +use config::UpdateConfig; +use tarpc::context; +use tokio::time::sleep; + +#[derive(gumdrop::Options)] +struct Flags { + help: bool, + /// Sets the name to say hello to. + name: String, +} + +impl UpdateConfig for Flags {} + +#[tokio::main] +async fn main() -> std::io::Result<()> { + use channels::accounts::me::Input; + + let opts: Flags = gumdrop::Options::parse_args_default_or_exit(); + + let config = config::default_load(&opts); + let client = channels::accounts::rpc::create_client(config).await; + + let r = client + .me( + context::current(), + Input { + account_id: 1.into(), + }, + ) + .await; + println!("{:?}", r); + + // Let the background span processor finish. + sleep(Duration::from_micros(1)).await; + + Ok(()) +} diff --git a/crates/idp/src/db/accounts.rs b/crates/idp/src/db/accounts.rs new file mode 100644 index 0000000..6b098b6 --- /dev/null +++ b/crates/idp/src/db/accounts.rs @@ -0,0 +1,441 @@ +use model::{AccountId, AccountState, Email, FullAccount, Login, PassHash, Role}; + +pub type Result = std::result::Result; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] +pub enum Error { + #[error("Can't create account")] + CantCreate, + #[error("Can't find account does to lack of identity")] + NoIdentity, + #[error("Account does not exists")] + NotExists, + #[error("Failed to load all accounts")] + All, + #[error("Can't update account")] + CantUpdate, +} + +#[derive(Debug)] +pub struct AllAccounts { + pub limit: i32, + pub offset: i32, +} + +impl AllAccounts { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result> { + sqlx::query_as( + r#" +SELECT id, email, login, pass_hash, role, customer_id, state +FROM accounts +LIMIT $1 OFFSET $2 + "#, + ) + .bind(self.limit) + .bind(self.offset) + .fetch_all(pool) + .await + .map_err(|e| { + tracing::error!("{e:?}"); + Error::All + }) + } +} + +#[derive(Debug)] +pub struct CreateAccount { + pub email: Email, + pub login: Login, + pub pass_hash: PassHash, + pub role: Role, +} + +impl CreateAccount { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + sqlx::query_as( + r#" +INSERT INTO accounts (login, email, role, pass_hash) +VALUES ($1, $2, $3, $4) +RETURNING id, email, login, pass_hash, role, customer_id, state + "#, + ) + .bind(self.login) + .bind(self.email) + .bind(self.role) + .bind(self.pass_hash) + .fetch_one(pool) + .await + .map_err(|e| { + tracing::error!("{e:?}"); + Error::CantCreate + }) + } +} + +#[derive(Debug)] +pub struct UpdateAccount { + pub id: AccountId, + pub email: Email, + pub login: Login, + pub pass_hash: Option, + pub role: Role, + pub state: AccountState, +} + +impl UpdateAccount { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + match self.pass_hash { + Some(hash) => sqlx::query_as( + r#" +UPDATE accounts +SET login = $2, email = $3, role = $4, pass_hash = $5, state = $6 +WHERE id = $1 +RETURNING id, email, login, pass_hash, role, customer_id, state + "#, + ) + .bind(self.id) + .bind(self.login) + .bind(self.email) + .bind(self.role) + .bind(hash) + .bind(self.state), + None => sqlx::query_as( + r#" +UPDATE accounts +SET login = $2, email = $3, role = $4, state = $5 +WHERE id = $1 +RETURNING id, email, login, pass_hash, role, customer_id, state + "#, + ) + .bind(self.id) + .bind(self.login) + .bind(self.email) + .bind(self.role) + .bind(self.state), + } + .fetch_one(pool) + .await + .map_err(|e| { + tracing::error!("{e:?}"); + Error::CantUpdate + }) + } +} + +#[derive(Debug)] +pub struct FindAccount { + pub account_id: AccountId, +} + +impl FindAccount { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + sqlx::query_as( + r#" +SELECT id, email, login, pass_hash, role, customer_id, state +FROM accounts +WHERE id = $1 + "#, + ) + .bind(self.account_id) + .fetch_one(pool) + .await + .map_err(|e| { + tracing::error!("{e:?}"); + Error::NotExists + }) + } +} + +#[derive(Debug)] +pub struct AccountByIdentity { + pub login: Option, + pub email: Option, +} + +impl AccountByIdentity { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + match (self.login, self.email) { + (Some(login), None) => sqlx::query_as( + r#" +SELECT id, email, login, pass_hash, role, customer_id, state +FROM accounts +WHERE login = $1 + "#, + ) + .bind(login), + (None, Some(email)) => sqlx::query_as( + r#" +SELECT id, email, login, pass_hash, role, customer_id, state +FROM accounts +WHERE email = $1 + "#, + ) + .bind(email), + (Some(login), Some(email)) => sqlx::query_as( + r#" +SELECT id, email, login, pass_hash, role, customer_id, state +FROM accounts +WHERE login = $1 AND email = $2 + "#, + ) + .bind(login) + .bind(email), + _ => return Err(Error::NoIdentity), + } + .fetch_one(pool) + .await + .map_err(|e| { + tracing::error!("{e:?}"); + Error::CantCreate + }) + } +} + +#[cfg(test)] +mod tests { + use config::UpdateConfig; + use fake::Fake; + use model::*; + + use super::*; + use crate::db::Database; + + pub struct NoOpts; + + impl UpdateConfig for NoOpts {} + + async fn test_create_account( + t: &mut sqlx::Transaction<'_, sqlx::Postgres>, + login: Option, + email: Option, + hash: Option, + ) -> FullAccount { + use fake::faker::internet::en; + let login: String = login.unwrap_or_else(|| en::Username().fake()); + let email: String = email.unwrap_or_else(|| en::FreeEmail().fake()); + let hash: String = hash.unwrap_or_else(|| en::Password(10..20).fake()); + + CreateAccount { + email: Email::new(email), + login: Login::new(login), + pass_hash: PassHash::new(hash), + role: Role::Admin, + } + .run(t) + .await + .unwrap() + } + + #[tokio::test] + async fn create_account() { + testx::db_t_ref!(t); + + let login: String = fake::faker::internet::en::Username().fake(); + let email: String = fake::faker::internet::en::FreeEmail().fake(); + let hash: String = fake::faker::internet::en::Password(10..20).fake(); + + let account: FullAccount = CreateAccount { + email: Email::new(&email), + login: Login::new(&login), + pass_hash: PassHash::new(&hash), + role: Role::Admin, + } + .run(&mut t) + .await + .unwrap(); + + let expected = FullAccount { + login: Login::new(login), + email: Email::new(email), + pass_hash: PassHash::new(&hash), + role: Role::Admin, + customer_id: account.customer_id, + id: account.id, + state: AccountState::Active, + }; + + t.rollback().await.unwrap(); + assert_eq!(account, expected); + } + + #[tokio::test] + async fn all_accounts() { + testx::db_t_ref!(t); + + test_create_account(&mut t, None, None, None).await; + test_create_account(&mut t, None, None, None).await; + test_create_account(&mut t, None, None, None).await; + + let v: Vec = AllAccounts { + limit: 200, + offset: 0, + } + .run(&mut t) + .await + .unwrap(); + + testx::db_rollback!(t); + assert!(v.len() >= 3); + } + + #[tokio::test] + async fn update_account_without_pass() { + testx::db_t_ref!(t); + + let original_login: String = fake::faker::internet::en::Username().fake(); + let original_email: String = fake::faker::internet::en::FreeEmail().fake(); + let original_hash: String = fake::faker::internet::en::Password(10..20).fake(); + + let original_account = test_create_account( + &mut t, + Some(original_login.clone()), + Some(original_email.clone()), + Some(original_hash.clone()), + ) + .await; + + let updated_login: String = fake::faker::internet::en::Username().fake(); + let updated_email: String = fake::faker::internet::en::FreeEmail().fake(); + + let updated_account: FullAccount = UpdateAccount { + id: original_account.id, + email: Email::new(updated_email.clone()), + login: Login::new(updated_login.clone()), + pass_hash: None, + role: Role::Admin, + state: AccountState::Active, + } + .run(&mut t) + .await + .unwrap(); + + let expected = FullAccount { + id: original_account.id, + email: Email::new(updated_email), + login: Login::new(updated_login), + pass_hash: PassHash::new(original_hash), + role: Role::Admin, + customer_id: original_account.customer_id, + state: AccountState::Active, + }; + + testx::db_rollback!(t); + assert_ne!(original_account, expected); + assert_eq!(updated_account, expected); + } + + #[tokio::test] + async fn update_account_with_pass() { + testx::db_t_ref!(t); + + let original_login: String = fake::faker::internet::en::Username().fake(); + let original_email: String = fake::faker::internet::en::FreeEmail().fake(); + let original_hash: String = fake::faker::internet::en::Password(10..20).fake(); + + let original_account = test_create_account( + &mut t, + Some(original_login.clone()), + Some(original_email.clone()), + Some(original_hash.clone()), + ) + .await; + + let updated_login: String = fake::faker::internet::en::Username().fake(); + let updated_email: String = fake::faker::internet::en::FreeEmail().fake(); + let updated_hash: String = fake::faker::internet::en::Password(10..20).fake(); + + let updated_account: FullAccount = UpdateAccount { + id: original_account.id, + email: Email::new(updated_email.clone()), + login: Login::new(updated_login.clone()), + pass_hash: Some(PassHash::new(updated_hash.clone())), + role: Role::Admin, + state: AccountState::Active, + } + .run(&mut t) + .await + .unwrap(); + + let expected = FullAccount { + id: original_account.id, + email: Email::new(updated_email), + login: Login::new(updated_login), + pass_hash: PassHash::new(updated_hash), + role: Role::Admin, + customer_id: original_account.customer_id, + state: AccountState::Active, + }; + + testx::db_rollback!(t); + assert_ne!(original_account, expected); + assert_eq!(updated_account, expected); + } + + #[tokio::test] + async fn find() { + testx::db_t_ref!(t); + + let account = test_create_account(&mut t, None, None, None).await; + + let res: FullAccount = FindAccount { + account_id: account.id, + } + .run(&mut t) + .await + .unwrap(); + + testx::db_rollback!(t); + assert_eq!(account, res); + } + + #[tokio::test] + async fn find_identity_email() { + testx::db_t_ref!(t); + + let account = test_create_account(&mut t, None, None, None).await; + + let res: FullAccount = AccountByIdentity { + email: Some(account.email.clone()), + login: None, + } + .run(&mut t) + .await + .unwrap(); + + testx::db_rollback!(t); + assert_eq!(account, res); + } + + #[tokio::test] + async fn find_identity_login() { + testx::db_t_ref!(t); + + let account = test_create_account(&mut t, None, None, None).await; + + let res: FullAccount = AccountByIdentity { + login: Some(account.login.clone()), + email: None, + } + .run(&mut t) + .await + .unwrap(); + + testx::db_rollback!(t); + assert_eq!(account, res); + } +} diff --git a/crates/idp/src/db/addresses.rs b/crates/idp/src/db/addresses.rs new file mode 100644 index 0000000..7d8aab8 --- /dev/null +++ b/crates/idp/src/db/addresses.rs @@ -0,0 +1,311 @@ +pub type Result = std::result::Result; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)] +pub enum Error { + #[error("Can't load account addresses")] + AccountAddresses, + #[error("Failed to save account address")] + CreateAccountAddress, +} + +#[derive(Debug)] +pub struct AccountAddresses { + pub account_id: model::AccountId, +} + +impl AccountAddresses { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result> { + sqlx::query_as( + r#" +SELECT id, name, email, phone, street, city, country, zip, account_id, is_default +FROM account_addresses +WHERE account_id = $1 + "#, + ) + .bind(self.account_id) + .fetch_all(pool) + .await + .map_err(|_| Error::AccountAddresses.into()) + } +} + +#[derive(Debug)] +pub struct FindAccountAddress { + pub account_id: model::AccountId, + pub address_id: model::AddressId, +} + +impl FindAccountAddress { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + sqlx::query_as( + r#" +SELECT id, name, email, phone, street, city, country, zip, account_id, is_default +FROM account_addresses +WHERE account_id = $1 AND id = $2 + "#, + ) + .bind(self.account_id) + .bind(self.address_id) + .fetch_one(pool) + .await + .map_err(|_| Error::AccountAddresses.into()) + } +} + +#[derive(Debug)] +pub struct DefaultAccountAddress { + pub account_id: model::AccountId, +} + +impl DefaultAccountAddress { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + sqlx::query_as( + r#" +SELECT id, name, email, phone, street, city, country, zip, account_id, is_default +FROM account_addresses +WHERE account_id = $1 AND is_default + "#, + ) + .bind(self.account_id) + .fetch_one(pool) + .await + .map_err(|_| Error::AccountAddresses.into()) + } +} + +#[derive(Debug)] +pub struct CreateAccountAddress { + pub name: model::Name, + pub email: model::Email, + pub phone: model::Phone, + pub street: model::Street, + pub city: model::City, + pub country: model::Country, + pub zip: model::Zip, + pub account_id: Option, + pub is_default: bool, +} + +impl CreateAccountAddress { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + if self.is_default && self.account_id.is_some() { + if let Err(e) = sqlx::query( + r#" +UPDATE account_addresses +SET is_default = FALSE +WHERE account_id = $1 + "#, + ) + .bind(self.account_id) + .fetch_all(&mut *pool) + .await + { + tracing::error!("{e}"); + dbg!(e); + } + } + + sqlx::query_as( + r#" +INSERT INTO account_addresses ( name, email, phone, street, city, country, zip, account_id, is_default) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +RETURNING id, name, email, phone, street, city, country, zip, account_id, is_default + "#, + ) + .bind(self.name) + .bind(self.email) + .bind(self.phone) + .bind(self.street) + .bind(self.city) + .bind(self.country) + .bind(self.zip) + .bind(self.account_id) + .bind(self.is_default) + .fetch_one(pool) + .await + .map_err(|e| { + tracing::error!("{e}"); + dbg!(e); + Error::CreateAccountAddress.into() + }) + } +} + +#[derive(Debug)] +pub struct UpdateAccountAddress { + pub id: model::AddressId, + pub name: model::Name, + pub email: model::Email, + pub phone: model::Phone, + pub street: model::Street, + pub city: model::City, + pub country: model::Country, + pub zip: model::Zip, + pub account_id: model::AccountId, + pub is_default: bool, +} + +impl UpdateAccountAddress { + pub async fn run( + self, + pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result { + sqlx::query_as( + r#" +UPDATE account_addresses +SET name = $2, email = $3, street = $4, city = $5, country = $6, zip = $7, account_id = $8, is_default = $9, phone = $10 +WHERE id = $1 +RETURNING id, name, email, phone, street, city, country, zip, account_id, is_default + "#, + ) + .bind(self.id) + .bind(self.name) + .bind(self.email) + .bind(self.street) + .bind(self.city) + .bind(self.country) + .bind(self.zip) + .bind(self.account_id) + .bind(self.is_default) + .bind(self.phone) + .fetch_one(pool) + .await + .map_err(|_| Error::CreateAccountAddress.into()) + } +} + +#[cfg(test)] +mod test { + use config::*; + use fake::Fake; + use model::*; + + use super::super::accounts::CreateAccount; + use super::*; + use crate::db::Database; + + pub struct NoOpts; + + impl UpdateConfig for NoOpts {} + + async fn test_create_account(pool: &mut sqlx::Transaction<'_, sqlx::Postgres>) -> FullAccount { + let login: String = fake::faker::internet::en::Username().fake(); + let email: String = fake::faker::internet::en::FreeEmail().fake(); + let hash: String = fake::faker::internet::en::Password(10..20).fake(); + + CreateAccount { + email: Email::new(email), + login: Login::new(login), + pass_hash: PassHash::new(hash), + role: Role::Admin, + } + .run(pool) + .await + .unwrap() + } + + #[tokio::test] + async fn full_check() { + testx::db_t_ref!(t); + + // account + let account = test_create_account(&mut t).await; + + // address + let mut address: AccountAddress = { + let name: String = fake::faker::name::en::Name().fake(); + let email: String = fake::faker::internet::en::FreeEmail().fake(); + let phone: String = fake::faker::phone_number::en::PhoneNumber().fake(); + let street: String = fake::faker::address::en::StreetName().fake(); + let city: String = fake::faker::address::en::CityName().fake(); + let country: String = fake::faker::address::en::CountryName().fake(); + let zip: String = fake::faker::address::en::ZipCode().fake(); + let account_id = Some(account.id); + let is_default: bool = true; + + let address = CreateAccountAddress { + name: Name::new(name.clone()), + email: Email::new(email.clone()), + phone: Phone::new(phone.clone()), + street: Street::new(street.clone()), + city: City::new(city.clone()), + country: Country::new(country.clone()), + zip: Zip::new(zip.clone()), + account_id, + is_default, + } + .run(&mut t) + .await + .unwrap(); + + assert_eq!( + address, + AccountAddress { + id: address.id, + name: Name::new(name.clone()), + email: Email::new(email.clone()), + phone: Phone::new(phone.clone()), + street: Street::new(street.clone()), + city: City::new(city.clone()), + country: Country::new(country.clone()), + zip: Zip::new(zip.clone()), + account_id: account.id, + is_default, + } + ); + address + }; + + let found = FindAccountAddress { + account_id: account.id, + address_id: address.id, + } + .run(&mut t) + .await + .unwrap(); + + assert_eq!(found, address); + + let changed = UpdateAccountAddress { + id: address.id, + name: address.name.clone(), + email: address.email.clone(), + phone: address.phone.clone(), + street: address.street.clone(), + city: address.city.clone(), + country: address.country.clone(), + zip: address.zip.clone(), + account_id: address.account_id, + is_default: true, + } + .run(&mut t) + .await + .unwrap(); + + address.is_default = true; + + assert_eq!(changed, address); + + let default_address = DefaultAccountAddress { + account_id: account.id, + } + .run(&mut t) + .await + .unwrap(); + + testx::db_rollback!(t); + assert_eq!(default_address, address); + } +} diff --git a/crates/idp/src/db/mod.rs b/crates/idp/src/db/mod.rs new file mode 100644 index 0000000..8c8fed4 --- /dev/null +++ b/crates/idp/src/db/mod.rs @@ -0,0 +1,31 @@ +pub mod accounts; +pub mod addresses; + +pub use accounts::*; +pub use addresses::*; +use config::SharedAppConfig; +use sqlx::{Pool, Postgres}; + +#[derive(Clone)] +pub struct Database { + pub pool: sqlx::PgPool, + _config: SharedAppConfig, +} + +impl Database { + pub async fn build(config: SharedAppConfig) -> Self { + let url = config.lock().idp().database_url.clone(); + let pool = sqlx::PgPool::connect(&url).await.unwrap_or_else(|e| { + tracing::error!("Failed to connect to database. {e:?}"); + std::process::exit(1); + }); + Self { + pool, + _config: config, + } + } + + pub fn pool(&self) -> Pool { + self.pool.clone() + } +} diff --git a/crates/idp/src/idp.rs b/crates/idp/src/idp.rs new file mode 100644 index 0000000..6c9441b --- /dev/null +++ b/crates/idp/src/idp.rs @@ -0,0 +1,163 @@ +use config::SharedAppConfig; + +pub async fn init(config: SharedAppConfig) { + let (secret, web) = { + let c = config.lock(); + (c.idp().secret(), c.web().host()) + }; + rauthy_client::init_with(None, RauthyHttpsOnly::No, DangerAcceptInvalidCerts::Yes).await?; + + let config = RauthyConfig { + // Sets the .is_admin field for the principal based on the `ClaimMapping`. + admin_claim: ClaimMapping::Or(vec![JwtClaim { + typ: JwtClaimTyp::Roles, + value: "admin".to_string(), + }]), + // Sets the .is_user field for the principal based on the `ClaimMapping`. + // Without this claim, a user would not have access to this app. This is + // used, because usually you never want to just have all your OIDC users to + // have access to a certain application. + user_claim: ClaimMapping::Or(vec![JwtClaim { + typ: JwtClaimTyp::Groups, + value: "user".to_string(), + }]), + // In almost all cases, this should just match the `client_id` + allowed_audiences: HashSet::from(["idp".to_string()]), + client_id: "idp".to_string(), + // If set to 'false', tokens with a non-verified email address will be rejected. + email_verified: !cfg!(debug_assertions), + // The issuer URL from your Rauthy deployment + iss: format!("{host}/auth/v1"), + // The scopes you want to request. The only mandatory which always needs to exist is + // `openid`, the rest is optional and depending on your needs. + scope: vec![ + "openid".to_string(), + "email".to_string(), + "profile".to_string(), + "groups".to_string(), + ], + // If set to None, the client will be treated as a public client and not provide any + // secret to the /token endpoint after the callback. Set a secret for confidential clients. + secret: secret.map(String::from), + // secret: Some("secretCopiedFromTheRauthyUiIfIsConfidentialClient".to_string(),), + }; + // The redirect_uri here must match the URI of this application, where we accept + // and handle the callback after a successful login. + OidcProvider::setup_from_config(config, format!("{host}/callback")).await?; +} + +pub async fn refresh_token(kanidm: &KanidmClient) -> Result<(), ClientError> { + Ok(()) +} + +pub async fn create_account_with_password( + kanidm: &KanidmClient, + login: &str, + display_name: &str, + email: &str, + password: &str, +) -> Result<(), ClientError> { + refresh_token(kanidm).await?; + let _person_created = kanidm + .idm_person_account_create(login, display_name) + .await + .ok(); + let accounts = accounts(kanidm).await?; + let uid = find_account(&accounts, FindBy::Name(login)).await?; + let id = uid.to_string(); + + kanidm + .idm_person_account_update(&id, None, None, None, Some(&[email.to_string()])) + .await?; + let (session_token, status) = kanidm.idm_account_credential_update_begin(&id).await?; + tracing::debug!( + "Begin update credentials ({can_commit}): {status:?}", + can_commit = status.can_commit + ); + + kanidm + .idm_account_credential_update_set_password(&session_token, password) + .await?; + + let status = kanidm + .idm_account_credential_update_status(&session_token) + .await?; + tracing::debug!( + "Set password ({can_commit}): {status:?}", + can_commit = status.can_commit + ); + + let status = kanidm + .idm_account_credential_update_init_totp(&session_token) + .await?; + tracing::debug!( + "Init TOTP ({can_commit}): {status:?}", + can_commit = status.can_commit + ); + + // let status = kanidm + // .idm_account_credential_update_check_totp(&session_token, totp_chal, + // label) .await?; + + tracing::debug!( + "TOTP check ({can_commit}): {status:?}", + can_commit = status.can_commit + ); + + kanidm + .idm_account_credential_update_commit(&session_token) + .await?; + let status = kanidm + .idm_account_credential_update_status(&session_token) + .await?; + tracing::debug!( + "Commit ({can_commit}): {status:?}", + can_commit = status.can_commit + ); + Ok(()) +} + +pub async fn accounts(kanidm: &KanidmClient) -> Result, ClientError> { + refresh_token(kanidm).await?; + + kanidm.idm_person_account_list().await +} + +#[derive(Debug)] +pub enum FindBy<'s> { + Email(&'s str), + Name(&'s str), +} + +impl<'s> FindBy<'s> { + fn key(&self) -> &'static str { + match self { + Self::Email(..) => "mail", + Self::Name(..) => "name", + } + } + fn as_str(&self) -> &'s str { + match self { + Self::Email(s) => s, + Self::Name(s) => s, + } + } +} + +pub async fn find_account(list: &[Entry], find_by: FindBy<'_>) -> Result { + list.iter() + .find_map(|entra| { + tracing::debug!("compare {find_by:?} with {entra:?}"); + let attrs = &entra.attrs; + attrs.get(find_by.key()).filter(|v| { + tracing::debug!("compare value {v:?} with {s}", s = find_by.as_str()); + v.iter().any(|s| s == find_by.as_str()) + })?; + let id = attrs.get("uuid").and_then(|v| v.first())?; + id.parse::().ok() + }) + .ok_or_else(|| { + tracing::info!("User not found"); + ClientError::Unauthorized + }) +} diff --git a/crates/idp/src/main.rs b/crates/idp/src/main.rs new file mode 100644 index 0000000..68454f8 --- /dev/null +++ b/crates/idp/src/main.rs @@ -0,0 +1,61 @@ +#![feature(structural_match)] + +use config::UpdateConfig; + +pub mod actions; +pub mod db; +pub mod idp; +pub mod mqtt; +pub mod rpc; + +pub type Result = std::result::Result; + +#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)] +pub enum Error { + #[error("Unable to send or receive msg from database")] + DbCritical, + #[error("Failed to load account data")] + Account, + #[error("Failed to load account addresses")] + Addresses, + #[error("Unable to save record")] + Saving, + #[error("Unable to hash password")] + Hashing, +} + +pub struct Opts {} + +impl UpdateConfig for Opts {} + +#[tokio::main] +async fn main() { + dotenv::dotenv().ok(); + config::init_tracing("account-manager"); + + let opts = Opts {}; + + let config = config::default_load(&opts); + + let db = db::Database::build(config.clone()).await; + + let kanidm = kanidm_client::KanidmClientBuilder::new() + .address(config.lock().idp().idm_url().to_owned()) + .danger_accept_invalid_certs(cfg!(debug_assertions)) + .connect_timeout(2) + .build() + .unwrap(); + idp::accounts(&kanidm).await.unwrap(); + idp::create_account_with_password( + &kanidm, + "eraden", + "Adrian Woźniak", + "adrian.wozniak@ita-prog.pl", + "n59GmOOdcpVUJqJ1", + ) + .await + .unwrap(); + + let mqtt_client = mqtt::start(config.clone(), db.clone()).await; + rpc::start(config.clone(), db.clone(), mqtt_client.clone()).await; +} diff --git a/crates/idp/src/mqtt.rs b/crates/idp/src/mqtt.rs new file mode 100644 index 0000000..25541dc --- /dev/null +++ b/crates/idp/src/mqtt.rs @@ -0,0 +1,30 @@ +use config::SharedAppConfig; +use rumqttc::{Event, Incoming}; + +use crate::db::Database; + +pub async fn start(config: SharedAppConfig, _db: Database) -> channels::AsyncClient { + let (client, mut event_loop) = channels::accounts::mqtt::create_client(config); + + let spawn_client = client.clone(); + tokio::spawn(async move { + let _client = spawn_client.clone(); + loop { + let notification = event_loop.poll().await; + + match notification { + Ok(Event::Incoming(Incoming::Publish(publish))) => match publish.topic.as_str() { + _ => {} + }, + Ok(Event::Incoming(_incoming)) => {} + Ok(Event::Outgoing(_outgoing)) => {} + Err(e) => { + tracing::error!("{}", e); + } + } + } + // tracing::info!("Mqtt channel closed"); + }); + + client +} diff --git a/crates/idp/src/rpc.rs b/crates/idp/src/rpc.rs new file mode 100644 index 0000000..1fb946d --- /dev/null +++ b/crates/idp/src/rpc.rs @@ -0,0 +1,86 @@ +use channels::accounts::rpc::Accounts; +use channels::accounts::{all, find_by_identity, me, register}; +use channels::AsyncClient; +use config::SharedAppConfig; +use tarpc::context; + +use crate::actions; +use crate::db::Database; + +#[derive(Debug, Copy, Clone, serde::Serialize, thiserror::Error)] +#[serde(rename_all = "kebab-case", tag = "account")] +pub enum Error { + #[error("Unable to send or receive msg from database")] + DbCritical, + #[error("Failed to load account data")] + Account, + #[error("Failed to load account addresses")] + Addresses, + #[error("Unable to save record")] + Saving, + #[error("Unable to hash password")] + Hashing, +} + +#[derive(Clone)] +struct AccountsServer { + db: Database, + config: SharedAppConfig, + mqtt_client: AsyncClient, +} + +impl Accounts for AccountsServer { + async fn me(self, _: context::Context, input: me::Input) -> me::Output { + let res = actions::me(input.account_id, self.db).await; + tracing::info!("ME result: {:?}", res); + res + } + + async fn register_account( + self, + _: context::Context, + input: register::Input, + ) -> register::Output { + use channels::accounts::Error; + + let res = actions::create_account(input, &self.db, self.config).await; + tracing::info!("REGISTER result: {:?}", res); + match res { + Ok(account) => { + self.mqtt_client.emit_account_created(&account).await; + Ok(register::Details { account }) + } + Err(_e) => Err(Error::Account), + } + } + + async fn all(self, _: context::Context, input: all::Input) -> all::Output { + let res = actions::all(input, self.db).await; + tracing::info!("ME result: {:?}", res); + res + } + + async fn find_by_identity( + self, + _: context::Context, + input: find_by_identity::Input, + ) -> find_by_identity::Output { + let res = actions::find_by_identity(input, self.db).await; + tracing::info!("ME result: {:?}", res); + res + } +} + +pub async fn start(config: SharedAppConfig, db: Database, mqtt_client: AsyncClient) { + let port = { config.lock().idp().rpc_port }; + + channels::rpc::start("accounts", port, || { + AccountsServer { + db: db.clone(), + config: config.clone(), + mqtt_client: mqtt_client.clone(), + } + .serve() + }) + .await; +}