diff --git a/Cargo.lock b/Cargo.lock index f042dc5..ba7aa94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "zstd", + "zstd 0.12.4", ] [[package]] @@ -213,6 +213,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.7.6" @@ -336,6 +347,17 @@ version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "atoi" version = "2.0.0" @@ -372,6 +394,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "base64" version = "0.21.2" @@ -478,6 +506,27 @@ dependencies = [ "bytes", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cc" version = "1.0.82" @@ -518,6 +567,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -582,6 +641,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.4.0" @@ -667,6 +732,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + [[package]] name = "der" version = "0.7.8" @@ -733,6 +804,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -915,6 +998,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -1045,6 +1138,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.9" @@ -1097,6 +1201,17 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.4.0" @@ -1127,6 +1242,33 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.3", + "widestring", + "windows-sys", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + [[package]] name = "is-terminal" version = "0.4.9" @@ -1275,6 +1417,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -1315,6 +1463,85 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "mail-auth" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b0969bac270a60560d3a6f89c812b3e39b2f4b3ca8d866063f482030d14f19" +dependencies = [ + "ahash 0.8.3", + "flate2", + "lru-cache", + "mail-builder", + "mail-parser", + "parking_lot", + "quick-xml", + "ring", + "rustls-pemfile", + "serde", + "serde_json", + "trust-dns-resolver", + "zip", +] + +[[package]] +name = "mail-builder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765969f4385f88a62738e8ed63e2fa630571d7ed6fd96ca6932d699513dd8c28" +dependencies = [ + "gethostname", +] + +[[package]] +name = "mail-parser" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4158a1c18963244e083888b21465846dfb68d6170850ed1ab4742edd57c9d47" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "mail-send" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6d2b8d0cb56f199d36f527ff96453cf3b1cdfdacb5e4d154ac1d8fcd89873c2" +dependencies = [ + "base64 0.20.0", + "gethostname", + "mail-auth", + "mail-builder", + "md5", + "rand", + "rustls 0.21.6", + "smtp-proto", + "tokio", + "tokio-rustls 0.24.1", + "webpki-roots 0.23.1", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "md-5" version = "0.10.5" @@ -1324,6 +1551,12 @@ dependencies = [ "digest", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.5.0" @@ -1375,7 +1608,8 @@ dependencies = [ "env_logger", "libinjection", "log", - "pbkdf2", + "mail-send", + "pbkdf2 0.12.2", "serde", "sha2", "sqlx", @@ -1507,6 +1741,17 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -1524,6 +1769,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash 0.4.2", + "sha2", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -1532,7 +1789,7 @@ checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", - "password-hash", + "password-hash 0.5.0", "sha2", ] @@ -1631,6 +1888,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.32" @@ -1708,6 +1980,31 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rsa" version = "0.9.2" @@ -1764,6 +2061,59 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.3", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.2", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.15" @@ -1776,6 +2126,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "semver" version = "1.0.18" @@ -1887,6 +2247,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +[[package]] +name = "smtp-proto" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b756ac662e92a0e5b360349bea5f0b0784d4be4541eff2972049dfdfd7f862" + [[package]] name = "socket2" version = "0.4.9" @@ -2312,6 +2678,27 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.8", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.6", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -2370,6 +2757,59 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand", + "ring", + "rustls 0.20.8", + "rustls-pemfile", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "tokio-rustls 0.23.4", + "tracing", + "url", + "webpki", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot", + "resolv-conf", + "rustls 0.20.8", + "smallvec", + "thiserror", + "tokio", + "tokio-rustls 0.23.4", + "tracing", + "trust-dns-proto", + "webpki-roots 0.22.6", +] + [[package]] name = "typenum" version = "1.16.0" @@ -2409,6 +2849,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.4.0" @@ -2416,7 +2862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna", + "idna 0.4.0", "percent-encoding", ] @@ -2513,6 +2959,44 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +dependencies = [ + "rustls-webpki 0.100.1", +] + [[package]] name = "which" version = "4.4.0" @@ -2530,6 +3014,12 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -2636,19 +3126,68 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] + [[package]] name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time 0.3.25", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + [[package]] name = "zstd" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" dependencies = [ - "zstd-safe", + "zstd-safe 6.0.6", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 66027b0..19baacf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,5 @@ clap = { version = "4.3.21", features = ["derive"] } actix-web-httpauth = "0.8.0" sqlx = { version = "0.7.1", features = ["runtime-tokio", "mysql", "chrono"] } uuid = { version = "1.4.1", features = ["v4"] } -chrono = "0.4" \ No newline at end of file +chrono = "0.4" +mail-send = "0.4.0" \ No newline at end of file diff --git a/src/accounts.rs b/src/accounts.rs new file mode 100644 index 0000000..17b2eb9 --- /dev/null +++ b/src/accounts.rs @@ -0,0 +1,118 @@ +use anyhow::{Error, Result}; +use pbkdf2::{ + password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Pbkdf2, +}; +use sqlx::{mysql::MySqlPool, types::chrono as sqlx_chrono}; + +pub struct Account { + pub id: u64, + pub username: String, + pub email: String, + pub salt: String, + pub password: String, + pub joined: sqlx_chrono::NaiveDateTime, + pub verified: bool, +} + +impl Account { + /// This doesn't check if an account with that name is already existing! + pub async fn new( + pool: &MySqlPool, + username: &String, + email: &String, + password: &String, + ) -> Result { + let salt = SaltString::generate(&mut OsRng); + let hash = Pbkdf2 + .hash_password(password.as_bytes(), &salt) + .map_err(|_| anyhow::Error::msg("Failed to hash the password"))? + .to_string(); + + let joined = sqlx_chrono::Utc::now().naive_utc(); + + sqlx::query!( + r#"INSERT INTO Accounts (username, email, salt, password, joined, verified) VALUES (?, ?, ?, ?, ?, false);"#, + username, + email, + salt.to_string(), + hash, + joined, + ) + .execute(pool) + .await?; + + match Account::from_username(pool, &username).await? { + Some(a) => Ok(a), + None => Err(Error::msg( + "The just created account can't be found in the database!", + )), + } + } + + pub async fn from_username(pool: &MySqlPool, username: &String) -> Result> { + match sqlx::query!(r#"SELECT * FROM Accounts WHERE username = ?;"#, username) + .fetch_one(pool) + .await + { + Ok(row) => { + let account = Account { + id: row.id, + username: row.username, + email: row.email, + salt: row.salt, + password: row.password, + joined: row.joined, + verified: row.verified != 0, + }; + Ok(Some(account)) + } + Err(sqlx::Error::RowNotFound) => Ok(None), + Err(e) => Err(Error::new(e)), + } + } + + pub async fn from_id(pool: &MySqlPool, id: u64) -> Result> { + match sqlx::query!(r#"SELECT * FROM Accounts WHERE id = ?;"#, id) + .fetch_one(pool) + .await + { + Ok(row) => { + let account = Account { + id: row.id, + username: row.username, + email: row.email, + salt: row.salt, + password: row.password, + joined: row.joined, + verified: row.verified != 0, + }; + Ok(Some(account)) + } + Err(sqlx::Error::RowNotFound) => Ok(None), + Err(e) => Err(Error::new(e)), + } + } + + pub async fn from_email(pool: &MySqlPool, email: &String) -> Result> { + match sqlx::query!(r#"SELECT * FROM Accounts WHERE email = ?;"#, email) + .fetch_one(pool) + .await + { + Ok(row) => { + let account = Account { + id: row.id, + username: row.username, + email: row.email, + salt: row.salt, + password: row.password, + joined: row.joined, + verified: row.verified != 0, + }; + Ok(Some(account)) + } + Err(sqlx::Error::RowNotFound) => Ok(None), + Err(e) => Err(Error::new(e)), + } + } +} diff --git a/src/api/account/calls.rs b/src/api/account/calls.rs index 0fe72e9..9023b82 100644 --- a/src/api/account/calls.rs +++ b/src/api/account/calls.rs @@ -9,7 +9,7 @@ async fn register( data: web::Data, body: web::Json, ) -> impl Responder { - match handlers::register(body.into_inner()).await { + match handlers::register(&data.pool, body.into_inner()).await { Ok(resp) => match resp { data::RegisterResponse::Success => HttpResponse::Ok().finish(), data::RegisterResponse::Conflict(b) => HttpResponse::Conflict().json(web::Json(b)), @@ -25,7 +25,7 @@ async fn register( #[post("/account/verify")] async fn verify(data: web::Data, body: web::Json) -> impl Responder { - match handlers::verify(body.into_inner()).await { + match handlers::verify(&data.pool, body.into_inner()).await { Ok(resp) => match resp { data::VerifyResponse::Success => HttpResponse::Ok().finish(), data::VerifyResponse::TokenUnknown => HttpResponse::NotFound().finish(), @@ -43,7 +43,7 @@ async fn authenticate( data: web::Data, body: web::Json, ) -> impl Responder { - match handlers::authenticate(body.into_inner()).await { + match handlers::authenticate(&data.pool, body.into_inner()).await { Ok(resp) => match resp { data::AuthenticateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)), data::AuthenticateResponse::WrongPassword => HttpResponse::Unauthorized().finish(), @@ -59,7 +59,7 @@ async fn authenticate( #[delete("/account/delete")] async fn delete(data: web::Data, auth: BearerAuth) -> impl Responder { - match handlers::delete(auth.token().to_string()).await { + match handlers::delete(&data.pool, auth.token().to_string()).await { Ok(resp) => match resp { data::DeleteResponse::Success => HttpResponse::Ok().finish(), data::DeleteResponse::Unauthorized => HttpResponse::Unauthorized().finish(), diff --git a/src/api/account/handlers.rs b/src/api/account/handlers.rs index 96f12c4..d159b8b 100644 --- a/src/api/account/handlers.rs +++ b/src/api/account/handlers.rs @@ -1,6 +1,12 @@ -use crate::api::account::data; +use crate::{ + accounts::Account, + api::account::data, + tokens::{AuthToken, VerificationToken}, +}; use anyhow::Result; use log::info; +use mail_send::{mail_builder::MessageBuilder, SmtpClient, SmtpClientBuilder}; +use sqlx::MySqlPool; fn is_sql_injection(string: &String) -> bool { match libinjection::sqli(string) { @@ -19,15 +25,55 @@ impl AlphaExt for String { } } -pub async fn register(request: data::RegisterRequest) -> Result { +pub async fn register( + pool: &MySqlPool, + request: data::RegisterRequest, +) -> Result { if is_sql_injection(&request.username) || is_sql_injection(&request.email) { return Ok(data::RegisterResponse::Blocked); } + if Account::from_username(pool, &request.username).await?.is_some() { + return Ok(data::RegisterResponse::Conflict( + data::RegisterConflict::Username, + )); + } + + if Account::from_email(pool, &request.email).await?.is_some() { + return Ok(data::RegisterResponse::Conflict( + data::RegisterConflict::Email, + )); + } + + let account = Account::new(pool, &request.username, &request.email, &request.password).await?; + + let token = VerificationToken::new(pool, account.id, chrono::Duration::minutes(10)).await?; + + let message = MessageBuilder::new() + .from(("Nerdcult Account Management (noreply)", "account@nerdcult.net")) + .to(vec![ + (request.username.as_str(), request.email.as_str()), + ]) + .subject("Account Verification") + .text_body(format!("Please verify your NerdCult account. Your code is {} (will be replaced with a link in future).", token.token)); + + SmtpClientBuilder::new(&std::env::var("SMTP_HOST_URL")?, 465) + .implicit_tls(true) + .credentials(mail_send::Credentials::Plain { username: &std::env::var("SMTP_USER")?, secret: &std::env::var("SMTP_PASSWORD")? }) + .connect() + .await? + .send(message) + .await?; + + info!("Verification Token: {:#?}", token); + Ok(data::RegisterResponse::Success) } -pub async fn verify(request: data::VerifyRequest) -> Result { +pub async fn verify( + pool: &MySqlPool, + request: data::VerifyRequest, +) -> Result { if !request.token.is_alpha() { return Ok(data::VerifyResponse::Blocked); } @@ -36,6 +82,7 @@ pub async fn verify(request: data::VerifyRequest) -> Result Result { if is_sql_injection(&request.username) { @@ -49,7 +96,7 @@ pub async fn authenticate( )) } -pub async fn delete(token: String) -> Result { +pub async fn delete(pool: &MySqlPool, token: String) -> Result { if !token.is_alpha() { return Ok(data::DeleteResponse::Blocked); } diff --git a/src/api/mod.rs b/src/api/mod.rs index f4a7ed2..6467dc3 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -15,7 +15,7 @@ pub async fn start(port: u16, pool: MySqlPool) -> Result<()> { sqlx::query!( r#" CREATE TABLE IF NOT EXISTS Accounts ( - id INT8 UNSIGNED NOT NULL AUTO_INCREMENT, + id INT8 UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, username VARCHAR(32) NOT NULL, email TEXT NOT NULL, salt VARCHAR(22) NOT NULL, diff --git a/src/main.rs b/src/main.rs index 2f1f8d1..5b1d372 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod accounts; mod api; mod tokens; @@ -32,6 +33,16 @@ async fn main() -> Result<()> { })?) .await?; + let _ = &std::env::var("SMTP_HOST_URL").map_err(|_| { + anyhow::Error::msg("Environment variable SMTP_HOST_URL needs to be specified!") + })?; + let _ = &std::env::var("SMTP_USER").map_err(|_| { + anyhow::Error::msg("Environment variable SMTP_HOST_URL needs to be specified!") + })?; + let _ = &std::env::var("SMTP_PASSWORD").map_err(|_| { + anyhow::Error::msg("Environment variable SMTP_HOST_URL needs to be specified!") + })?; + api::start(port, pool).await?; Ok(()) diff --git a/src/tokens/mod.rs b/src/tokens.rs similarity index 94% rename from src/tokens/mod.rs rename to src/tokens.rs index 804fd65..37d4688 100644 --- a/src/tokens/mod.rs +++ b/src/tokens.rs @@ -3,15 +3,15 @@ use sqlx::{mysql::MySqlPool, types::chrono as sqlx_chrono}; #[derive(Debug)] pub struct AuthToken { - token: String, - account: u64, - expire: sqlx_chrono::NaiveDateTime, + pub token: String, + pub account: u64, + pub expire: sqlx_chrono::NaiveDateTime, } impl AuthToken { pub async fn new( pool: &MySqlPool, - account_id: usize, + account_id: u64, lifetime: chrono::Duration, ) -> Result { let expire = match sqlx_chrono::Utc::now() @@ -83,15 +83,15 @@ impl AuthToken { #[derive(Debug)] pub struct VerificationToken { - token: String, - account: u64, - expire: sqlx_chrono::NaiveDateTime, + pub token: String, + pub account: u64, + pub expire: sqlx_chrono::NaiveDateTime, } impl VerificationToken { pub async fn new( pool: &MySqlPool, - account_id: usize, + account_id: u64, lifetime: chrono::Duration, ) -> Result { let expire = match sqlx_chrono::Utc::now()