From 667fcca4e941290452be31383068178a726fcccc Mon Sep 17 00:00:00 2001 From: Lyssieth Date: Sun, 8 Oct 2023 22:53:42 +0300 Subject: [PATCH] feat: we can now log in. --- .gitignore | 2 + Cargo.lock | 786 +++++++++++++++++++++++++------- Cargo.toml | 9 +- Dockerfile | 4 +- build.rs | 5 + migrations/0001_create-user.sql | 1 + src/authentication.rs | 86 +++- src/main.rs | 114 ++++- src/rest.rs | 7 + src/rest/ping.rs | 8 + src/subsonic.rs | 155 +++++++ src/user.rs | 11 + 12 files changed, 979 insertions(+), 209 deletions(-) create mode 100644 build.rs create mode 100644 migrations/0001_create-user.sql create mode 100644 src/rest.rs create mode 100644 src/rest/ping.rs create mode 100644 src/subsonic.rs create mode 100644 src/user.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..ef9c187 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +users.db* +.en* diff --git a/Cargo.lock b/Cargo.lock index b9aaa90..8902839 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.1" @@ -76,6 +88,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -116,6 +134,15 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -143,6 +170,12 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -154,6 +187,9 @@ name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -191,6 +227,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -262,10 +304,10 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" +name = "const-oid" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "cookie" @@ -300,6 +342,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crc32fast" version = "1.3.2" @@ -309,6 +366,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -330,38 +406,14 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.20.3" +name = "der" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.38", -] - -[[package]] -name = "darling_macro" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.38", + "const-oid", + "pem-rfc7468", + "zeroize", ] [[package]] @@ -369,18 +421,8 @@ name = "deranged" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", + "serde", ] [[package]] @@ -390,17 +432,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] [[package]] -name = "encoding_rs" -version = "0.8.33" +name = "dotenvy" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" dependencies = [ - "cfg-if", + "serde", ] [[package]] @@ -419,6 +468,23 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -435,6 +501,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "flate2" version = "1.0.27" @@ -445,6 +517,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -467,6 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -475,6 +559,34 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + [[package]] name = "futures-macro" version = "0.3.28" @@ -505,9 +617,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", + "futures-io", "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -580,6 +694,19 @@ name = "hashbrown" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.1", +] [[package]] name = "headers" @@ -605,12 +732,27 @@ dependencies = [ "http", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.3" @@ -629,6 +771,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -710,12 +861,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.4.0" @@ -761,6 +906,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -781,6 +935,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" @@ -788,6 +945,23 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.9" @@ -819,6 +993,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.6.4" @@ -841,6 +1025,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -862,22 +1052,13 @@ dependencies = [ ] [[package]] -name = "multer" -version = "2.1.0" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", "memchr", - "mime", - "spin", - "tokio", - "version_check", + "minimal-lexical", ] [[package]] @@ -890,6 +1071,44 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -897,6 +1116,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -965,6 +1185,21 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -983,6 +1218,33 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "poem" version = "1.3.58" @@ -1002,7 +1264,6 @@ dependencies = [ "hyper", "mime", "mime_guess", - "multer", "parking_lot", "percent-encoding", "pin-project-lite", @@ -1015,13 +1276,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "serde_yaml", "smallvec", - "tempfile", "thiserror", "time", "tokio", - "tokio-stream", "tokio-util", "tracing", ] @@ -1038,51 +1296,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "poem-openapi" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62659dcc7ca09a525881300646f3b28e319889072e83cd16a2865ba024a185e" -dependencies = [ - "base64", - "bytes", - "derive_more", - "futures-util", - "indexmap 2.0.2", - "mime", - "num-traits", - "poem", - "poem-openapi-derive", - "quick-xml", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "serde_yaml", - "thiserror", - "time", - "tokio", - "url", -] - -[[package]] -name = "poem-openapi-derive" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90bf699e87e95b8303f9b59684cf3b9a8fff840872b13cbe0680aa4330d5226" -dependencies = [ - "darling", - "http", - "indexmap 2.0.2", - "mime", - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "syn 2.0.38", - "thiserror", -] - [[package]] name = "polyval" version = "0.6.1" @@ -1183,12 +1396,14 @@ dependencies = [ name = "rave" version = "0.1.0" dependencies = [ + "cfg-if", "color-eyre", "poem", - "poem-openapi", "quick-xml", "serde", "serde_json", + "sqlx", + "time", "tokio", "tracing", "tracing-subscriber", @@ -1258,21 +1473,34 @@ dependencies = [ "uncased", ] +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.17" @@ -1298,12 +1526,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" - [[package]] name = "serde" version = "1.0.188" @@ -1347,19 +1569,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" -dependencies = [ - "indexmap 2.0.2", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha1" version = "0.10.6" @@ -1400,6 +1609,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -1435,17 +1654,251 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] -name = "strsim" -version = "0.10.0" +name = "spki" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.0.2", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "time", + "tracing", + "url", +] + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] [[package]] name = "subtle" @@ -1646,6 +2099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1764,6 +2218,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.5.1" @@ -1774,12 +2240,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsafe-libyaml" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" - [[package]] name = "url" version = "2.4.1" @@ -1807,6 +2267,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1882,6 +2348,12 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + [[package]] name = "winapi" version = "0.3.9" @@ -1987,3 +2459,9 @@ checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907" dependencies = [ "memchr", ] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 3f36e68..01af75d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ publish = ["crates-io"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cfg-if = "1.0.0" color-eyre = "0.6.2" poem = { version = "1.3.58", features = [ "compression", @@ -15,15 +16,11 @@ poem = { version = "1.3.58", features = [ "static-files", "xml", ] } -poem-openapi = { version = "3.0.5", features = [ - "time", - "openapi-explorer", - "url", - "static-files", -] } quick-xml = { version = "0.30.0", features = ["serialize"] } serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" +sqlx = { version = "0.7.2", features = ["time", "sqlite", "runtime-tokio"] } +time = { version = "0.3.29", features = ["serde-human-readable", "macros", "parsing"] } tokio = { version = "1.32.0", features = ["full"] } tracing = { version = "0.1.37", features = ["async-await"] } tracing-subscriber = { version = "0.3.17", features = [ diff --git a/Dockerfile b/Dockerfile index 5504b2d..9dc3863 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,8 @@ RUN rm -rfv /volume/src/*.rs COPY . . RUN rm -rfv /volume/target/*/release/rave + +ENV DATABASE_URL='sqlite:/config/users.db' RUN cargo build --release --features docker RUN mv target/*-unknown-linux-musl/release/rave /tmp/rave @@ -22,7 +24,7 @@ FROM gcr.io/distroless/static AS runtime COPY --from=build /tmp/rave /rave -VOLUME [ "/storage", "/config", "/cache" ] +VOLUME [ "/storage", "/config" ] ENV RUST_LOG=info diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7609593 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +// generated by `sqlx migrate build-script` +fn main() { + // trigger recompilation when a new migration is added + println!("cargo:rerun-if-changed=migrations"); +} \ No newline at end of file diff --git a/migrations/0001_create-user.sql b/migrations/0001_create-user.sql new file mode 100644 index 0000000..8ddc1d3 --- /dev/null +++ b/migrations/0001_create-user.sql @@ -0,0 +1 @@ +-- Add migration script here diff --git a/src/authentication.rs b/src/authentication.rs index b5f1379..b1650d2 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -1,7 +1,10 @@ -use std::{collections::HashMap, str::FromStr, string::ToString}; +use std::{collections::HashMap, fmt::Display, str::FromStr, string::ToString}; use color_eyre::Report; -use poem::{http::StatusCode, Error, FromRequest, Request, RequestBody, Result}; +use poem::{Error, FromRequest, IntoResponse, Request, RequestBody, Result}; +use tracing::debug; + +use crate::subsonic::{self, SubsonicResponse}; mod de; @@ -10,7 +13,12 @@ impl<'a> FromRequest<'a> for Authentication { async fn from_request(req: &'a Request, _: &mut RequestBody) -> Result { let query = req.uri().query().unwrap_or_default(); if query.is_empty() { - return Err(Error::from_string("Empty query", StatusCode::BAD_REQUEST)); + return Err(Error::from_response( + SubsonicResponse::new_error(subsonic::Error::RequiredParameterMissing(Some( + "please provide a `u` parameter".to_string(), + ))) + .into_response(), + )); } let query = url_escape::decode(query); @@ -20,75 +28,97 @@ impl<'a> FromRequest<'a> for Authentication { .filter_map(|q| q.split_once('=')) .collect::>(); + debug!("Query: {query:?}"); + let user = { let user = query.get("u").map(ToString::to_string); if user.is_none() { - return Err(Error::from_string( - "Missing username", - StatusCode::BAD_REQUEST, + return Err(Error::from_response( + SubsonicResponse::new_error(subsonic::Error::RequiredParameterMissing(Some( + "please provide a `u` parameter".to_string(), + ))) + .into_response(), )); } user.expect("Missing username") }; + debug!("User: {user}"); + let password = query.get("p").map(ToString::to_string); if password.is_some() { - return Err(Error::from_string( - "Password authentication is not supported", - StatusCode::BAD_REQUEST, + return Err(Error::from_response( + SubsonicResponse::new_error(subsonic::Error::Generic(Some( + "password authentication is not supported".to_string(), + ))) + .into_response(), )); } + debug!("Password: {password:?}"); + let token = query.get("t").map(ToString::to_string); let salt = query.get("s").map(ToString::to_string); if token.is_none() || salt.is_none() { - return Err(Error::from_string( - "Missing token or salt", - StatusCode::BAD_REQUEST, + return Err(Error::from_response( + SubsonicResponse::new_error(subsonic::Error::RequiredParameterMissing(Some( + "please provide both `t` and `s` parameters".to_string(), + ))) + .into_response(), )); } let token = token.expect("Missing token"); + debug!("Token: {token}"); let salt = salt.expect("Missing salt"); + debug!("Salt: {salt}"); let version = { let version = query.get("v").map(ToString::to_string); if version.is_none() { - return Err(Error::from_string( - "Missing version", - StatusCode::BAD_REQUEST, + return Err(Error::from_response( + SubsonicResponse::new_error(subsonic::Error::RequiredParameterMissing(Some( + "please provide a `v` parameter".to_string(), + ))) + .into_response(), )); } version .expect("Missing version") .parse::() .map_err(|e| { - Error::from_string(format!("Invalid version: {e}"), StatusCode::BAD_REQUEST) + Error::from_response( + SubsonicResponse::new_error(subsonic::Error::Generic(Some(format!( + "invalid version parameter: {e}" + )))) + .into_response(), + ) }) }?; - if version < VersionTriple(1, 13, 0) { - return Err(Error::from_string( - "Unsupported version. We only support 1.13.0 and above", - StatusCode::BAD_REQUEST, - )); - } + debug!("Version: {version}"); let client = { let client = query.get("c").map(ToString::to_string); if client.is_none() { - return Err(Error::from_string( - "Missing client", - StatusCode::BAD_REQUEST, + return Err(Error::from_response( + SubsonicResponse::new_error(subsonic::Error::RequiredParameterMissing(Some( + "please provide a `c` parameter".to_string(), + ))) + .into_response(), )); } client.expect("Missing client") }; + debug!("Client: {client}"); + let format = query .get("f") .map_or_else(|| "xml".to_string(), ToString::to_string); + debug!("Format: {format}"); + Ok(Self { username: user, token, @@ -113,6 +143,12 @@ pub struct Authentication { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct VersionTriple(pub u32, pub u32, pub u32); +impl Display for VersionTriple { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.0, self.1, self.2) + } +} + impl PartialOrd for VersionTriple { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) diff --git a/src/main.rs b/src/main.rs index 1086c58..18f1d1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,26 @@ #![warn(clippy::pedantic, clippy::nursery)] #![deny(clippy::unwrap_used, clippy::panic)] +#![allow(clippy::module_name_repetitions, clippy::too_many_lines)] use std::time::Duration; -use authentication::Authentication; use color_eyre::Result; use poem::{ get, listener::TcpListener, middleware, web::{CompressionAlgo, CompressionLevel}, - EndpointExt, Route, + Endpoint, EndpointExt, Route, }; +use rest::build; +use sqlx::SqlitePool; use tracing::info; use tracing_subscriber::{fmt, EnvFilter}; mod authentication; +mod rest; +mod subsonic; +mod user; const LISTEN: &str = "0.0.0.0:1234"; @@ -24,11 +29,39 @@ async fn main() -> Result<()> { color_eyre::install()?; install_tracing()?; - let listener = TcpListener::bind(LISTEN); + let route = create_route(); - let route = Route::new() + let pool = create_pool().await; + + sqlx::migrate!().run(&pool).await?; + + let route = route.with(middleware::AddData::new(pool)); + + let server = create_server(); + + let signal_waiter = || async { + let _ = tokio::signal::ctrl_c().await; + }; + + let server = + server.run_with_graceful_shutdown(route, signal_waiter(), Some(Duration::from_secs(5))); + + server.await?; + + Ok(()) +} +async fn create_pool() -> SqlitePool { + let url = std::env::var("DATABASE_URL").expect("DATABASE_URL not set"); + + SqlitePool::connect(&url) + .await + .expect("Failed to connect to database") +} + +fn create_route() -> Box> { + Route::new() .at("/", get(hello_world)) - .at("/auth", get(auth_test)) + .nest("/rest", build()) .with(middleware::CatchPanic::new()) .with( middleware::Compression::new() @@ -39,29 +72,34 @@ async fn main() -> Result<()> { .with(middleware::CookieJarManager::new()) .with(middleware::NormalizePath::new( middleware::TrailingSlash::Trim, - )); + )) + .boxed() +} + +fn create_server() -> poem::Server, std::convert::Infallible> { + let listener = TcpListener::bind(LISTEN); info!("Listening on http://{LISTEN}"); - let signal_waiter = || async { - let _ = tokio::signal::ctrl_c().await; - }; - - let server = poem::Server::new(listener) - .name("rave") - .run_with_graceful_shutdown(route, signal_waiter(), Some(Duration::from_secs(5))); - - server.await?; - - Ok(()) + poem::Server::new(listener).name("rave") } fn install_tracing() -> Result<()> { - let filter = std::env::var("RUST_LOG").unwrap_or_else(|_| "warn,rave=debug".to_string()); + let filter = { + cfg_if::cfg_if! { + if #[cfg(debug_assertions)] { + std::env::var("RUST_LOG").unwrap_or_else(|_| "poem=trace,rave=debug".to_string()) + } else { + std::env::var("RUST_LOG").unwrap_or_else(|_| "poem=warn,rave=debug".to_string()) + } + } + }; + + let filter = EnvFilter::from(filter); fmt() .pretty() - .with_env_filter(EnvFilter::from(filter)) + .with_env_filter(filter) .try_init() .map_err(|v| color_eyre::eyre::eyre!("failed to install tracing: {v}"))?; @@ -73,8 +111,38 @@ const fn hello_world() -> &'static str { "Hello, world!" } -#[allow(clippy::needless_pass_by_value)] -#[poem::handler] -fn auth_test(auth: Authentication) -> String { - format!("{auth:?}") +#[cfg(test)] +mod tests { + use super::*; + + use poem::{ + http::{Method, StatusCode}, + Request, + }; + + #[tokio::test] + async fn test_hello_world() { + let app = create_route(); + + let resp = app + .call(Request::builder().method(Method::GET).uri_str("/").finish()) + .await; + assert!( + resp.is_ok(), + "Failed to get response: {}", + resp.expect_err("Failed to get response") + ); + + let resp = resp.expect("Failed to get response"); + + assert_eq!(resp.status(), StatusCode::OK); + let body = resp.into_body().into_string().await; + assert!( + body.is_ok(), + "Failed to get body: {}", + body.expect_err("Failed to get body") + ); + let body = body.expect("Failed to get body"); + assert_eq!(body, "Hello, world!"); + } } diff --git a/src/rest.rs b/src/rest.rs new file mode 100644 index 0000000..79f7248 --- /dev/null +++ b/src/rest.rs @@ -0,0 +1,7 @@ +use poem::{Endpoint, EndpointExt, Route}; + +mod ping; + +pub fn build() -> Box> { + Route::new().at("/ping", ping::ping).boxed() +} diff --git a/src/rest/ping.rs b/src/rest/ping.rs new file mode 100644 index 0000000..6c77623 --- /dev/null +++ b/src/rest/ping.rs @@ -0,0 +1,8 @@ +use crate::{authentication::Authentication, subsonic::SubsonicResponse}; + +#[poem::handler] +pub fn ping(auth: Authentication) -> SubsonicResponse { + dbg!(auth); + + SubsonicResponse::new_empty() +} diff --git a/src/subsonic.rs b/src/subsonic.rs new file mode 100644 index 0000000..3f3e426 --- /dev/null +++ b/src/subsonic.rs @@ -0,0 +1,155 @@ +#![allow(dead_code)] // TODO: Remove this + +use poem::{http::StatusCode, IntoResponse, Response}; +use serde::{ser::SerializeStruct, Serialize}; + +use crate::authentication::VersionTriple; + +impl IntoResponse for SubsonicResponse { + fn into_response(self) -> poem::Response { + let body = quick_xml::se::to_string(&self).expect("Failed to serialize response body"); + Response::builder().status(StatusCode::OK).body(body) + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct SubsonicResponse { + #[serde(rename = "@xmlns")] + pub xmlns: String, + #[serde(rename = "@status")] + pub status: ResponseStatus, + #[serde(rename = "@version")] + pub version: VersionTriple, + #[serde(rename = "$value")] + pub inner: SubResponseType, +} + +impl SubsonicResponse { + pub fn new(inner: SubResponseType) -> Self { + Self { + xmlns: "http://subsonic.org/restapi".to_string(), + status: ResponseStatus::Ok, + version: VersionTriple(1, 16, 1), + inner, + } + } + + pub fn new_empty() -> Self { + Self { + xmlns: "http://subsonic.org/restapi".to_string(), + status: ResponseStatus::Ok, + version: VersionTriple(1, 16, 1), + inner: SubResponseType::Empty, + } + } + + pub fn new_error(inner: Error) -> Self { + Self { + xmlns: "http://subsonic.org/restapi".to_string(), + status: ResponseStatus::Failed, + version: VersionTriple(1, 16, 1), + inner: SubResponseType::Error(inner), + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub enum SubResponseType { + #[serde(rename = "musicFolders")] + MusicFolders(MusicFolders), + #[serde(rename = "error")] + Error(Error), + Empty, +} + +#[derive(Debug, Clone, Serialize)] +pub struct MusicFolders {} + +#[derive(Debug, Clone, Copy)] +pub enum ResponseStatus { + Ok, + Failed, +} + +impl Serialize for ResponseStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(match self { + Self::Ok => "ok", + Self::Failed => "failed", + }) + } +} + +#[derive(Debug, Clone)] +pub enum Error { + Generic(Option), + RequiredParameterMissing(Option), + IncompatibleClientVersion(Option), + IncompatibleServerVersion(Option), + WrongUsernameOrPassword(Option), + TokenAuthenticationNotSupportedForLDAP(Option), + UserIsNotAuthorizedForGivenOperation(Option), + TrialPeriodExpired(Option), + RequestedDataWasNotFound(Option), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut error = serializer.serialize_struct("error", 2)?; + error.serialize_field("@code", &self.code())?; + error.serialize_field("@message", &self.message())?; + error.end() + } +} + +impl Error { + pub const fn code(&self) -> i32 { + match self { + Self::Generic(_) => 0, + Self::RequiredParameterMissing(_) => 10, + Self::IncompatibleClientVersion(_) => 20, + Self::IncompatibleServerVersion(_) => 30, + Self::WrongUsernameOrPassword(_) => 40, + Self::TokenAuthenticationNotSupportedForLDAP(_) => 41, + Self::UserIsNotAuthorizedForGivenOperation(_) => 50, + Self::TrialPeriodExpired(_) => 60, + Self::RequestedDataWasNotFound(_) => 70, + } + } + + pub fn message(&self) -> String { + match self { + Self::Generic(inner) => inner.clone().unwrap_or_else(|| "Generic error".to_string()), + Self::RequiredParameterMissing(inner) => inner + .clone() + .unwrap_or_else(|| "Required parameter missing".to_string()), + Self::IncompatibleClientVersion(inner) => inner + .clone() + .unwrap_or_else(|| "Incompatible client version".to_string()), + Self::IncompatibleServerVersion(inner) => inner + .clone() + .unwrap_or_else(|| "Incompatible server version".to_string()), + Self::WrongUsernameOrPassword(inner) => inner + .clone() + .unwrap_or_else(|| "Wrong username or password".to_string()), + Self::TokenAuthenticationNotSupportedForLDAP(inner) => inner + .clone() + .unwrap_or_else(|| "Token authentication not supported for LDAP".to_string()), + Self::UserIsNotAuthorizedForGivenOperation(inner) => inner + .clone() + .unwrap_or_else(|| "User is not authorized for given operation".to_string()), + Self::TrialPeriodExpired(inner) => inner + .clone() + .unwrap_or_else(|| "Trial period expired".to_string()), + Self::RequestedDataWasNotFound(inner) => inner + .clone() + .unwrap_or_else(|| "Requested data was not found".to_string()), + } + } +} diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..5adb718 --- /dev/null +++ b/src/user.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct User { + pub id: i32, + pub created_at: time::OffsetDateTime, + pub name: String, + /// I hate this. It's stored in plaintext. Why? + pub password: String, + pub is_admin: bool, +}