From a1dc47b82646d3b2ba8092c5cca7af2d04c68735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Fri, 25 Jan 2019 18:23:51 +0100 Subject: [PATCH] Change config to thread-safe system, needed for a future config panel. Improved some two factor methods. --- Cargo.lock | 99 ++++++++++---- Cargo.toml | 3 + src/api/admin.rs | 28 ++-- src/api/core/accounts.rs | 8 +- src/api/core/ciphers.rs | 4 +- src/api/core/organizations.rs | 18 +-- src/api/core/two_factor.rs | 130 ++++++++---------- src/api/icons.rs | 10 +- src/api/identity.rs | 2 +- src/api/notifications.rs | 11 +- src/api/web.rs | 14 +- src/auth.rs | 18 +-- src/config.rs | 245 ++++++++++++++++++++++++++++++++++ src/db/mod.rs | 4 +- src/db/models/attachment.rs | 4 +- src/db/models/user.rs | 4 +- src/mail.rs | 8 +- src/main.rs | 239 +++------------------------------ src/util.rs | 2 +- 19 files changed, 457 insertions(+), 394 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 18b3030..5cd8f01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,7 +95,7 @@ dependencies = [ [[package]] name = "base64" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -119,7 +119,7 @@ dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "derive_more 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "diesel 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "fern 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -135,6 +135,7 @@ dependencies = [ "num-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "oath 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "paste 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", "rmpv 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -427,7 +428,7 @@ dependencies = [ [[package]] name = "diesel" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -739,7 +740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "h2" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -833,13 +834,13 @@ dependencies = [ [[package]] name = "hyper" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -875,7 +876,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.22 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.23 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1108,7 +1109,7 @@ name = "migrations_internals" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "diesel 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1453,6 +1454,26 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "paste" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "paste-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "paste-impl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pear" version = "0.1.2" @@ -1565,6 +1586,16 @@ dependencies = [ "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" version = "0.4.26" @@ -1608,7 +1639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1633,7 +1664,7 @@ dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1648,7 +1679,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1656,12 +1687,20 @@ name = "rand_core" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.3.0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1669,7 +1708,7 @@ name = "rand_hc" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1677,7 +1716,7 @@ name = "rand_isaac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1688,7 +1727,7 @@ dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1698,7 +1737,7 @@ name = "rand_pcg" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1707,7 +1746,7 @@ name = "rand_xorshift" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1715,7 +1754,7 @@ name = "rdrand" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1756,12 +1795,12 @@ name = "reqwest" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "encoding_rs 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.22 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.23 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "libflate 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1814,7 +1853,7 @@ name = "rocket" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2640,7 +2679,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "block-modes 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2666,7 +2705,7 @@ dependencies = [ "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" "checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" -"checksum base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" @@ -2704,7 +2743,7 @@ dependencies = [ "checksum devise 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74e04ba2d03c5fa0d954c061fc8c9c288badadffc272ebb87679a89846de3ed3" "checksum devise_codegen 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "066ceb7928ca93a9bedc6d0e612a8a0424048b0ab1f75971b203d01420c055d7" "checksum devise_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf41c59b22b5e3ec0ea55c7847e5f358d340f3a8d6d53a5cf4f1564967f96487" -"checksum diesel 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "66d7d3a2f8a24763a1a52b5324737b4d24141bb294440ed9094db60bd6cd29ee" +"checksum diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a2469cbcf1dfb9446e491cac4c493c2554133f87f7d041e892ac82e5cd36e863" "checksum diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62a27666098617d52c487a41f70de23d44a1dc1f3aa5877ceba2790fb1f1cab4" "checksum diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c" "checksum digest 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a68d759d7a66a4f63d5bd2a2b14ad7e8cf93fe8c9be227031cd4e72ab0e9ee8" @@ -2742,7 +2781,7 @@ dependencies = [ "checksum generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe043cf9b85297937897087de81f590361686e1ac2d4d471b45435de5dfb6a6" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum groupable 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32619942b8be646939eaf3db0602b39f5229b74575b67efc897811ded1db4e57" -"checksum h2 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "30e0b8e55b4d7ffedade2b9605851f8e85f5010663e7ad170ef3c0f0681bc43f" +"checksum h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e" "checksum handlebars 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d82e5750d8027a97b9640e3fefa66bbaf852a35228e1c90790efd13c4b09c166" "checksum hmac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb5aa9647ba4711e9d6968dc1c810cd23989ed435443ca962e1bf6d8b8b83ff" "checksum hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f127a908633569f208325f86f71255d3363c79721d7f9fe31cd5569908819771" @@ -2750,7 +2789,7 @@ dependencies = [ "checksum http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1a10e5b573b9a0146545010f50772b9e8b1dd0a256564cc4307694c68832a2f5" "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" "checksum hyper 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)" = "df0caae6b71d266b91b4a83111a61d2b94ed2e2bea024c532b933dcff867e58c" -"checksum hyper 0.12.22 (registry+https://github.com/rust-lang/crates.io-index)" = "622db8120387cf64e3159e57343233b5a37253176fcda7f8a7e48f5a9c4c98fd" +"checksum hyper 0.12.23 (registry+https://github.com/rust-lang/crates.io-index)" = "860faf61a9957c9cb0e23e69f1c8290e92f6eb660fcdd1f2d6777043a2ae1a46" "checksum hyper-sync-rustls 0.3.0-rc.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d1a443a90413a118ac6739e024f6a5180aa3b3f43f7de65f9d388a961cff19b" "checksum hyper-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "32cd73f14ad370d3b4d4b7dce08f69b81536c82e39fcc89731930fe5788cd661" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" @@ -2812,6 +2851,8 @@ dependencies = [ "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum paste 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f50392d1265092fbee9273414cc40eb6d47d307bd66222c477bb8450c8504f9d" +"checksum paste-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a3cd512fe3a55e8933b2dcad913e365639db86d512e4004c3084b86864d9467a" "checksum pear 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c26d2b92e47063ffce70d3e3b1bd097af121a9e0db07ca38a6cc1cf0cc85ff25" "checksum pear_codegen 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "336db4a192cc7f54efeb0c4e11a9245394824cc3bcbd37ba3ff51240c35d7a6e" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" @@ -2825,6 +2866,7 @@ dependencies = [ "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" "checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" +"checksum proc-macro-hack 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3e90aa19cd73dedc2d0e1e8407473f073d735fef0ab521438de6da8ee449ab66" "checksum proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)" = "38fddd23d98b2144d197c0eca5705632d4fe2667d14a6be5df8934f8d74f1978" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" @@ -2835,7 +2877,8 @@ dependencies = [ "checksum rand 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3906503e80ac6cbcacb2c2973fa8e473f24d7e2747c8c92bb230c2441cad96b5" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" "checksum rand_os 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f46fbd5550acf75b0c2730f5dd1873751daf9beb8f11b44027778fae50d7feca" diff --git a/Cargo.toml b/Cargo.toml index 9b9efeb..0b16852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,9 @@ native-tls = "0.2.2" # Template library handlebars = "1.1.0" +# Macro ident-combining library +paste = "0.1.4" + [patch.crates-io] # Add support for Timestamp type rmp = { git = 'https://github.com/dani-garcia/msgpack-rust' } diff --git a/src/api/admin.rs b/src/api/admin.rs index cb5edca..63629bf 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -1,4 +1,3 @@ -use rocket_contrib::json::Json; use serde_json::Value; use rocket::http::{Cookie, Cookies, SameSite}; @@ -6,7 +5,7 @@ use rocket::request::{self, FlashMessage, Form, FromRequest, Request}; use rocket::response::{content::Html, Flash, Redirect}; use rocket::{Outcome, Route}; -use crate::api::{JsonResult, JsonUpcase}; +use crate::api::{ApiResult, EmptyResult, JsonUpcase}; use crate::auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp}; use crate::db::{models::*, DbConn}; use crate::error::Error; @@ -14,7 +13,7 @@ use crate::mail; use crate::CONFIG; pub fn routes() -> Vec { - if CONFIG.admin_token.is_none() { + if CONFIG.admin_token().is_none() { return Vec::new(); } @@ -54,7 +53,7 @@ impl AdminTemplateData { } #[get("/", rank = 2)] -fn admin_login(flash: Option) -> Result, Error> { +fn admin_login(flash: Option) -> ApiResult> { // If there is an error, show it let msg = flash.map(|msg| format!("{}: {}", msg.name(), msg.msg())); @@ -97,14 +96,14 @@ fn post_admin_login(data: Form, mut cookies: Cookies, ip: ClientIp) - } fn _validate_token(token: &str) -> bool { - match CONFIG.admin_token.as_ref() { + match CONFIG.admin_token().as_ref() { None => false, Some(t) => t == token, } } #[get("/", rank = 1)] -fn admin_page(_token: AdminToken, conn: DbConn) -> Result, Error> { +fn admin_page(_token: AdminToken, conn: DbConn) -> ApiResult> { let users = User::get_all(&conn); let users_json: Vec = users.iter().map(|u| u.to_json(&conn)).collect(); @@ -119,39 +118,36 @@ struct InviteData { } #[post("/invite", data = "")] -fn invite_user(data: JsonUpcase, _token: AdminToken, conn: DbConn) -> JsonResult { +fn invite_user(data: JsonUpcase, _token: AdminToken, conn: DbConn) -> EmptyResult { let data: InviteData = data.into_inner().data; let email = data.Email.clone(); if User::find_by_mail(&data.Email, &conn).is_some() { err!("User already exists") } - if !CONFIG.invitations_allowed { + if !CONFIG.invitations_allowed() { err!("Invitations are not allowed") } - if let Some(ref mail_config) = CONFIG.mail { + if let Some(ref mail_config) = CONFIG.mail() { let mut user = User::new(email); user.save(&conn)?; let org_name = "bitwarden_rs"; - mail::send_invite(&user.email, &user.uuid, None, None, &org_name, None, mail_config)?; + mail::send_invite(&user.email, &user.uuid, None, None, &org_name, None, mail_config) } else { let mut invitation = Invitation::new(data.Email); - invitation.save(&conn)?; + invitation.save(&conn) } - - Ok(Json(json!({}))) } #[post("/users//delete")] -fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> JsonResult { +fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { let user = match User::find_by_uuid(&uuid, &conn) { Some(user) => user, None => err!("User doesn't exist"), }; - user.delete(&conn)?; - Ok(Json(json!({}))) + user.delete(&conn) } pub struct AdminToken {} diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index e60e568..770f1da 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -79,14 +79,14 @@ fn register(data: JsonUpcase, conn: DbConn) -> EmptyResult { } user - } else if CONFIG.signups_allowed { + } else if CONFIG.signups_allowed() { err!("Account with this email already exists") } else { err!("Registration not allowed") } } None => { - if CONFIG.signups_allowed || Invitation::take(&data.Email, &conn) { + if CONFIG.signups_allowed() || Invitation::take(&data.Email, &conn) { User::new(data.Email.clone()) } else { err!("Registration not allowed") @@ -419,9 +419,9 @@ fn password_hint(data: JsonUpcase, conn: DbConn) -> EmptyResul None => return Ok(()), }; - if let Some(ref mail_config) = CONFIG.mail { + if let Some(ref mail_config) = CONFIG.mail() { mail::send_password_hint(&data.Email, hint, mail_config)?; - } else if CONFIG.show_password_hint { + } else if CONFIG.show_password_hint() { if let Some(hint) = hint { err!(format!("Your password hint is: {}", &hint)); } else { diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index cf4d368..8aaefb7 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -302,7 +302,7 @@ pub fn update_cipher_from_data( cipher.fields = data.Fields.map(|f| f.to_string()); cipher.data = type_data.to_string(); cipher.password_history = data.PasswordHistory.map(|f| f.to_string()); - + cipher.save(&conn)?; cipher.move_to_folder(data.FolderId, &headers.user.uuid, &conn)?; @@ -651,7 +651,7 @@ fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers let boundary_pair = params.next().expect("No boundary provided"); let boundary = boundary_pair.1; - let base_path = Path::new(&CONFIG.attachments_folder).join(&cipher.uuid); + let base_path = Path::new(&CONFIG.attachments_folder()).join(&cipher.uuid); let mut attachment_key = None; diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 12edd3b..4bc2282 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -486,17 +486,17 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade } for email in data.Emails.iter() { - let mut user_org_status = match CONFIG.mail { + let mut user_org_status = match CONFIG.mail() { Some(_) => UserOrgStatus::Invited as i32, None => UserOrgStatus::Accepted as i32, // Automatically mark user as accepted if no email invites }; let user = match User::find_by_mail(&email, &conn) { None => { - if !CONFIG.invitations_allowed { + if !CONFIG.invitations_allowed() { err!(format!("User email does not exist: {}", email)) } - if CONFIG.mail.is_none() { + if CONFIG.mail().is_none() { let mut invitation = Invitation::new(email.clone()); invitation.save(&conn)?; } @@ -535,7 +535,7 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade new_user.save(&conn)?; - if let Some(ref mail_config) = CONFIG.mail { + if let Some(ref mail_config) = CONFIG.mail() { let org_name = match Organization::find_by_uuid(&org_id, &conn) { Some(org) => org.name, None => err!("Error looking up organization"), @@ -558,11 +558,11 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade #[post("/organizations//users//reinvite")] fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - if !CONFIG.invitations_allowed { + if !CONFIG.invitations_allowed() { err!("Invitations are not allowed.") } - if CONFIG.mail.is_none() { + if CONFIG.mail().is_none() { err!("SMTP is not configured.") } @@ -585,7 +585,7 @@ fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: None => err!("Error looking up organization."), }; - if let Some(ref mail_config) = CONFIG.mail { + if let Some(ref mail_config) = CONFIG.mail() { mail::send_invite( &user.email, &user.uuid, @@ -637,7 +637,7 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase err!("Invited user not found"), } - if let Some(ref mail_config) = CONFIG.mail { + if let Some(ref mail_config) = CONFIG.mail() { let mut org_name = String::from("bitwarden_rs"); if let Some(org_id) = &claims.org_id { org_name = match Organization::find_by_uuid(&org_id, &conn) { @@ -686,7 +686,7 @@ fn confirm_invite( None => err!("Invalid key provided"), }; - if let Some(ref mail_config) = CONFIG.mail { + if let Some(ref mail_config) = CONFIG.mail() { let org_name = match Organization::find_by_uuid(&org_id, &conn) { Some(org) => org.name, None => err!("Error looking up organization."), diff --git a/src/api/core/two_factor.rs b/src/api/core/two_factor.rs index cac3e9f..5682d9f 100644 --- a/src/api/core/two_factor.rs +++ b/src/api/core/two_factor.rs @@ -179,7 +179,7 @@ fn activate_authenticator(data: JsonUpcase, headers: He Some(n) => n as u64, None => err!("Malformed token"), }; - + let mut user = headers.user; if !user.check_valid_password(&password_hash) { @@ -236,13 +236,13 @@ use crate::CONFIG; const U2F_VERSION: &str = "U2F_V2"; lazy_static! { - static ref APP_ID: String = format!("{}/app-id.json", &CONFIG.domain); + static ref APP_ID: String = format!("{}/app-id.json", &CONFIG.domain()); static ref U2F: U2f = U2f::new(APP_ID.clone()); } #[post("/two-factor/get-u2f", data = "")] fn generate_u2f(data: JsonUpcase, headers: Headers, conn: DbConn) -> JsonResult { - if !CONFIG.domain_set { + if !CONFIG.domain_set() { err!("`DOMAIN` environment variable is not set. U2F disabled") } @@ -286,6 +286,8 @@ fn generate_u2f_challenge(data: JsonUpcase, headers: Headers, conn #[derive(Deserialize, Debug)] #[allow(non_snake_case)] struct EnableU2FData { + Id: NumberOrString, // 1..5 + Name: String, MasterPasswordHash: String, DeviceResponse: String, } @@ -321,54 +323,52 @@ fn activate_u2f(data: JsonUpcase, headers: Headers, conn: DbConn) err!("Invalid password"); } - let tf_challenge = - TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::U2fRegisterChallenge as i32, &conn); + let tf_type = TwoFactorType::U2fRegisterChallenge as i32; + let tf_challenge = match TwoFactor::find_by_user_and_type(&user.uuid, tf_type, &conn) { + Some(c) => c, + None => err!("Can't recover challenge"), + }; - if let Some(tf_challenge) = tf_challenge { - let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; + let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; + tf_challenge.delete(&conn)?; - tf_challenge.delete(&conn)?; + let response_copy: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?; - let response_copy: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?; + let error_code = response_copy + .error_code + .clone() + .map_or("0".into(), NumberOrString::into_string); - let error_code = response_copy - .error_code - .clone() - .map_or("0".into(), NumberOrString::into_string); - - if error_code != "0" { - err!("Error registering U2F token") - } - - let response = response_copy.into_response(); - - let registration = U2F.register_response(challenge.clone(), response)?; - // TODO: Allow more than one U2F device - let mut registrations = Vec::new(); - registrations.push(registration); - - let tf_registration = TwoFactor::new( - user.uuid.clone(), - TwoFactorType::U2f, - serde_json::to_string(®istrations).unwrap(), - ); - tf_registration.save(&conn)?; - - _generate_recover_code(&mut user, &conn); - - Ok(Json(json!({ - "Enabled": true, - "Challenge": { - "UserId": user.uuid, - "AppId": APP_ID.to_string(), - "Challenge": challenge, - "Version": U2F_VERSION, - }, - "Object": "twoFactorU2f" - }))) - } else { - err!("Can't recover challenge") + if error_code != "0" { + err!("Error registering U2F token") } + + let response = response_copy.into_response(); + + let registration = U2F.register_response(challenge.clone(), response)?; + // TODO: Allow more than one U2F device + let mut registrations = Vec::new(); + registrations.push(registration); + + let tf_registration = TwoFactor::new( + user.uuid.clone(), + TwoFactorType::U2f, + serde_json::to_string(®istrations).unwrap(), + ); + tf_registration.save(&conn)?; + + _generate_recover_code(&mut user, &conn); + + Ok(Json(json!({ + "Enabled": true, + "Challenge": { + "UserId": user.uuid, + "AppId": APP_ID.to_string(), + "Challenge": challenge, + "Version": U2F_VERSION, + }, + "Object": "twoFactorU2f" + }))) } #[put("/two-factor/u2f", data = "")] @@ -493,29 +493,9 @@ use yubico::config::Config; use yubico::Yubico; fn parse_yubikeys(data: &EnableYubikeyData) -> Vec { - let mut yubikeys: Vec = Vec::new(); + let data_keys = [&data.Key1, &data.Key2, &data.Key3, &data.Key4, &data.Key5]; - if data.Key1.is_some() { - yubikeys.push(data.Key1.as_ref().unwrap().to_owned()); - } - - if data.Key2.is_some() { - yubikeys.push(data.Key2.as_ref().unwrap().to_owned()); - } - - if data.Key3.is_some() { - yubikeys.push(data.Key3.as_ref().unwrap().to_owned()); - } - - if data.Key4.is_some() { - yubikeys.push(data.Key4.as_ref().unwrap().to_owned()); - } - - if data.Key5.is_some() { - yubikeys.push(data.Key5.as_ref().unwrap().to_owned()); - } - - yubikeys + data_keys.into_iter().filter_map(|e| e.as_ref().cloned()).collect() } fn jsonify_yubikeys(yubikeys: Vec) -> serde_json::Value { @@ -529,17 +509,17 @@ fn jsonify_yubikeys(yubikeys: Vec) -> serde_json::Value { } fn verify_yubikey_otp(otp: String) -> JsonResult { - if !CONFIG.yubico_cred_set { + if !CONFIG.yubico_cred_set() { err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled") } let yubico = Yubico::new(); let config = Config::default() - .set_client_id(CONFIG.yubico_client_id.to_owned()) - .set_key(CONFIG.yubico_secret_key.to_owned()); + .set_client_id(CONFIG.yubico_client_id()) + .set_key(CONFIG.yubico_secret_key()); - let result = match CONFIG.yubico_server { - Some(ref server) => yubico.verify(otp, config.set_api_hosts(vec![server.to_owned()])), + let result = match CONFIG.yubico_server() { + Some(server) => yubico.verify(otp, config.set_api_hosts(vec![server])), None => yubico.verify(otp, config), }; @@ -551,7 +531,7 @@ fn verify_yubikey_otp(otp: String) -> JsonResult { #[post("/two-factor/get-yubikey", data = "")] fn generate_yubikey(data: JsonUpcase, headers: Headers, conn: DbConn) -> JsonResult { - if !CONFIG.yubico_cred_set { + if !CONFIG.yubico_cred_set() { err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled") } @@ -637,7 +617,7 @@ fn activate_yubikey(data: JsonUpcase, headers: Headers, conn: serde_json::to_string(&yubikey_metadata).unwrap(), ); yubikey_registration.save(&conn)?; - + _generate_recover_code(&mut user, &conn); let mut result = jsonify_yubikeys(yubikey_metadata.Keys); diff --git a/src/api/icons.rs b/src/api/icons.rs index 1232110..2d54471 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -32,7 +32,7 @@ fn icon(domain: String) -> Content> { } fn get_icon(domain: &str) -> Vec { - let path = format!("{}/{}.png", CONFIG.icon_cache_folder, domain); + let path = format!("{}/{}.png", CONFIG.icon_cache_folder(), domain); if let Some(icon) = get_cached_icon(&path) { return icon; @@ -87,7 +87,7 @@ fn file_is_expired(path: &str, ttl: u64) -> Result { fn icon_is_negcached(path: &str) -> bool { let miss_indicator = path.to_owned() + ".miss"; - let expired = file_is_expired(&miss_indicator, CONFIG.icon_cache_negttl); + let expired = file_is_expired(&miss_indicator, CONFIG.icon_cache_negttl()); match expired { // No longer negatively cached, drop the marker @@ -110,12 +110,12 @@ fn mark_negcache(path: &str) { } fn icon_is_expired(path: &str) -> bool { - let expired = file_is_expired(path, CONFIG.icon_cache_ttl); + let expired = file_is_expired(path, CONFIG.icon_cache_ttl()); expired.unwrap_or(true) } fn get_icon_url(domain: &str) -> String { - if CONFIG.local_icon_extractor { + if CONFIG.local_icon_extractor() { format!("http://{}/favicon.ico", domain) } else { format!("https://icons.bitwarden.com/{}/icon.png", domain) @@ -135,7 +135,7 @@ fn download_icon(url: &str) -> Result, Error> { } fn save_icon(path: &str, icon: &[u8]) { - create_dir_all(&CONFIG.icon_cache_folder).expect("Error creating icon cache"); + create_dir_all(&CONFIG.icon_cache_folder()).expect("Error creating icon cache"); if let Ok(mut f) = File::create(path) { f.write_all(icon).expect("Error writing icon file"); diff --git a/src/api/identity.rs b/src/api/identity.rs index fb52d30..b6092a3 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -234,7 +234,7 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api match TwoFactorType::from_i32(*provider) { Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ } - Some(TwoFactorType::U2f) if CONFIG.domain_set => { + Some(TwoFactorType::U2f) if CONFIG.domain_set() => { let request = two_factor::generate_u2f_login(user_uuid, conn)?; let mut challenge_list = Vec::new(); diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 177eb20..4f6aae6 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -25,7 +25,7 @@ fn negotiate(_headers: Headers, _conn: DbConn) -> JsonResult { let conn_id = BASE64URL.encode(&crypto::get_random(vec![0u8; 16])); let mut available_transports: Vec = Vec::new(); - if CONFIG.websocket_enabled { + if CONFIG.websocket_enabled() { available_transports.push(json!({"transport":"WebSockets", "transferFormats":["Text","Binary"]})); } @@ -90,7 +90,7 @@ fn serialize_date(date: NaiveDateTime) -> Value { let seconds: i64 = date.timestamp(); let nanos: i64 = date.timestamp_subsec_nanos() as i64; let timestamp = nanos << 34 | seconds; - + let bs = timestamp.to_be_bytes(); // -1 is Timestamp @@ -349,9 +349,12 @@ pub fn start_notification_server() -> WebSocketUsers { let factory = WSFactory::init(); let users = factory.users.clone(); - if CONFIG.websocket_enabled { + if CONFIG.websocket_enabled() { thread::spawn(move || { - WebSocket::new(factory).unwrap().listen(&CONFIG.websocket_url).unwrap(); + WebSocket::new(factory) + .unwrap() + .listen(&CONFIG.websocket_url()) + .unwrap(); }); } diff --git a/src/api/web.rs b/src/api/web.rs index 6625dc4..9b08a72 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -8,11 +8,11 @@ use rocket::Route; use rocket_contrib::json::Json; use serde_json::Value; -use crate::CONFIG; use crate::util::Cached; +use crate::CONFIG; pub fn routes() -> Vec { - if CONFIG.web_vault_enabled { + if CONFIG.web_vault_enabled() { routes![web_index, app_id, web_files, attachments, alive] } else { routes![attachments, alive] @@ -21,7 +21,9 @@ pub fn routes() -> Vec { #[get("/")] fn web_index() -> Cached> { - Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder).join("index.html"))) + Cached::short(NamedFile::open( + Path::new(&CONFIG.web_vault_folder()).join("index.html"), + )) } #[get("/app-id.json")] @@ -35,7 +37,7 @@ fn app_id() -> Cached>> { { "version": { "major": 1, "minor": 0 }, "ids": [ - &CONFIG.domain, + &CONFIG.domain(), "ios:bundle-id:com.8bit.bitwarden", "android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI" ] }] @@ -45,12 +47,12 @@ fn app_id() -> Cached>> { #[get("/", rank = 10)] // Only match this if the other routes don't match fn web_files(p: PathBuf) -> Cached> { - Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder).join(p))) + Cached::long(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join(p))) } #[get("/attachments//")] fn attachments(uuid: String, file: PathBuf) -> io::Result { - NamedFile::open(Path::new(&CONFIG.attachments_folder).join(uuid).join(file)) + NamedFile::open(Path::new(&CONFIG.attachments_folder()).join(uuid).join(file)) } #[get("/alive")] diff --git a/src/auth.rs b/src/auth.rs index 3b888fa..6aad62a 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -16,21 +16,21 @@ const JWT_ALGORITHM: Algorithm = Algorithm::RS256; lazy_static! { pub static ref DEFAULT_VALIDITY: Duration = Duration::hours(2); static ref JWT_HEADER: Header = Header::new(JWT_ALGORITHM); - pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain); - pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain); - pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain); - static ref PRIVATE_RSA_KEY: Vec = match read_file(&CONFIG.private_rsa_key) { + pub static ref JWT_LOGIN_ISSUER: String = format!("{}|login", CONFIG.domain()); + pub static ref JWT_INVITE_ISSUER: String = format!("{}|invite", CONFIG.domain()); + pub static ref JWT_ADMIN_ISSUER: String = format!("{}|admin", CONFIG.domain()); + static ref PRIVATE_RSA_KEY: Vec = match read_file(&CONFIG.private_rsa_key()) { Ok(key) => key, Err(e) => panic!( "Error loading private RSA Key from {}\n Error: {}", - CONFIG.private_rsa_key, e + CONFIG.private_rsa_key(), e ), }; - static ref PUBLIC_RSA_KEY: Vec = match read_file(&CONFIG.public_rsa_key) { + static ref PUBLIC_RSA_KEY: Vec = match read_file(&CONFIG.public_rsa_key()) { Ok(key) => key, Err(e) => panic!( "Error loading public RSA Key from {}\n Error: {}", - CONFIG.public_rsa_key, e + CONFIG.public_rsa_key(), e ), }; } @@ -185,8 +185,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for Headers { let headers = request.headers(); // Get host - let host = if CONFIG.domain_set { - CONFIG.domain.clone() + let host = if CONFIG.domain_set() { + CONFIG.domain() } else if let Some(referer) = headers.get_one("Referer") { referer.to_string() } else { diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..faad46a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,245 @@ +use std::process::exit; +use std::sync::RwLock; + +use handlebars::Handlebars; + +lazy_static! { + pub static ref CONFIG: Config = Config::load(); +} + +macro_rules! make_config { + ( $( $name:ident: $ty:ty ),+ $(,)* ) => { + + pub struct Config { inner: RwLock<_Config> } + + #[derive(Default)] + struct _Config { + _templates: Handlebars, + $(pub $name: $ty),+ + } + + paste::item! { + #[allow(unused)] + impl Config { + $( + pub fn $name(&self) -> $ty { + self.inner.read().unwrap().$name.clone() + } + pub fn [](&self, value: $ty) { + self.inner.write().unwrap().$name = value; + } + )+ + } + } + + }; +} + +make_config! { + database_url: String, + icon_cache_folder: String, + attachments_folder: String, + + icon_cache_ttl: u64, + icon_cache_negttl: u64, + + private_rsa_key: String, + private_rsa_key_pem: String, + public_rsa_key: String, + + web_vault_folder: String, + web_vault_enabled: bool, + + websocket_enabled: bool, + websocket_url: String, + + extended_logging: bool, + log_file: Option, + + local_icon_extractor: bool, + signups_allowed: bool, + invitations_allowed: bool, + admin_token: Option, + password_iterations: i32, + show_password_hint: bool, + + domain: String, + domain_set: bool, + + yubico_cred_set: bool, + yubico_client_id: String, + yubico_secret_key: String, + yubico_server: Option, + + mail: Option, + templates_folder: String, + reload_templates: bool, +} + +fn load_templates(path: &str) -> Handlebars { + let mut hb = Handlebars::new(); + // Error on missing params + hb.set_strict_mode(true); + + macro_rules! reg { + ($name:expr) => {{ + let template = include_str!(concat!("static/templates/", $name, ".hbs")); + hb.register_template_string($name, template).unwrap(); + }}; + } + + // First register default templates here + reg!("email/invite_accepted"); + reg!("email/invite_confirmed"); + reg!("email/pw_hint_none"); + reg!("email/pw_hint_some"); + reg!("email/send_org_invite"); + + reg!("admin/base"); + reg!("admin/login"); + reg!("admin/page"); + + // And then load user templates to overwrite the defaults + // Use .hbs extension for the files + // Templates get registered with their relative name + hb.register_templates_directory(".hbs", path).unwrap(); + + hb +} + +impl Config { + pub fn render_template( + &self, + name: &str, + data: &T, + ) -> Result { + if CONFIG.reload_templates() { + warn!("RELOADING TEMPLATES"); + let hb = load_templates(CONFIG.templates_folder().as_ref()); + hb.render(name, data).map_err(Into::into) + } else { + let hb = &CONFIG.inner.read().unwrap()._templates; + hb.render(name, data).map_err(Into::into) + } + } + + fn load() -> Self { + use crate::util::{get_env, get_env_or}; + dotenv::dotenv().ok(); + + let df = get_env_or("DATA_FOLDER", "data".to_string()); + let key = get_env_or("RSA_KEY_FILENAME", format!("{}/{}", &df, "rsa_key")); + + let domain = get_env("DOMAIN"); + + let yubico_client_id = get_env("YUBICO_CLIENT_ID"); + let yubico_secret_key = get_env("YUBICO_SECRET_KEY"); + + let templates_folder = get_env_or("TEMPLATES_FOLDER", format!("{}/{}", &df, "templates")); + + let cfg = _Config { + database_url: get_env_or("DATABASE_URL", format!("{}/{}", &df, "db.sqlite3")), + icon_cache_folder: get_env_or("ICON_CACHE_FOLDER", format!("{}/{}", &df, "icon_cache")), + attachments_folder: get_env_or("ATTACHMENTS_FOLDER", format!("{}/{}", &df, "attachments")), + _templates: load_templates(&templates_folder), + templates_folder, + reload_templates: get_env_or("RELOAD_TEMPLATES", false), + + // icon_cache_ttl defaults to 30 days (30 * 24 * 60 * 60 seconds) + icon_cache_ttl: get_env_or("ICON_CACHE_TTL", 2_592_000), + // icon_cache_negttl defaults to 3 days (3 * 24 * 60 * 60 seconds) + icon_cache_negttl: get_env_or("ICON_CACHE_NEGTTL", 259_200), + + private_rsa_key: format!("{}.der", &key), + private_rsa_key_pem: format!("{}.pem", &key), + public_rsa_key: format!("{}.pub.der", &key), + + web_vault_folder: get_env_or("WEB_VAULT_FOLDER", "web-vault/".into()), + web_vault_enabled: get_env_or("WEB_VAULT_ENABLED", true), + + websocket_enabled: get_env_or("WEBSOCKET_ENABLED", false), + websocket_url: format!( + "{}:{}", + get_env_or("WEBSOCKET_ADDRESS", "0.0.0.0".to_string()), + get_env_or("WEBSOCKET_PORT", 3012) + ), + + extended_logging: get_env_or("EXTENDED_LOGGING", true), + log_file: get_env("LOG_FILE"), + + local_icon_extractor: get_env_or("LOCAL_ICON_EXTRACTOR", false), + signups_allowed: get_env_or("SIGNUPS_ALLOWED", true), + admin_token: get_env("ADMIN_TOKEN"), + invitations_allowed: get_env_or("INVITATIONS_ALLOWED", true), + password_iterations: get_env_or("PASSWORD_ITERATIONS", 100_000), + show_password_hint: get_env_or("SHOW_PASSWORD_HINT", true), + + domain_set: domain.is_some(), + domain: domain.unwrap_or("http://localhost".into()), + + yubico_cred_set: yubico_client_id.is_some() && yubico_secret_key.is_some(), + yubico_client_id: yubico_client_id.unwrap_or("00000".into()), + yubico_secret_key: yubico_secret_key.unwrap_or("AAAAAAA".into()), + yubico_server: get_env("YUBICO_SERVER"), + + mail: MailConfig::load(), + }; + + Config { + inner: RwLock::new(cfg), + } + } +} + +#[derive(Debug, Clone)] +pub struct MailConfig { + pub smtp_host: String, + pub smtp_port: u16, + pub smtp_ssl: bool, + pub smtp_from: String, + pub smtp_from_name: String, + pub smtp_username: Option, + pub smtp_password: Option, +} + +impl MailConfig { + fn load() -> Option { + use crate::util::{get_env, get_env_or}; + + // When SMTP_HOST is absent, we assume the user does not want to enable it. + let smtp_host = match get_env("SMTP_HOST") { + Some(host) => host, + None => return None, + }; + + let smtp_from = get_env("SMTP_FROM").unwrap_or_else(|| { + error!("Please specify SMTP_FROM to enable SMTP support."); + exit(1); + }); + + let smtp_from_name = get_env_or("SMTP_FROM_NAME", "Bitwarden_RS".into()); + + let smtp_ssl = get_env_or("SMTP_SSL", true); + let smtp_port = get_env("SMTP_PORT").unwrap_or_else(|| if smtp_ssl { 587u16 } else { 25u16 }); + + let smtp_username = get_env("SMTP_USERNAME"); + let smtp_password = get_env("SMTP_PASSWORD").or_else(|| { + if smtp_username.as_ref().is_some() { + error!("SMTP_PASSWORD is mandatory when specifying SMTP_USERNAME."); + exit(1); + } else { + None + } + }); + + Some(MailConfig { + smtp_host, + smtp_port, + smtp_ssl, + smtp_from, + smtp_from_name, + smtp_username, + smtp_password, + }) + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs index cc011ba..54ecfb1 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -25,13 +25,13 @@ pub mod schema; /// Initializes a database pool. pub fn init_pool() -> Pool { - let manager = ConnectionManager::new(&*CONFIG.database_url); + let manager = ConnectionManager::new(CONFIG.database_url()); r2d2::Pool::builder().build(manager).expect("Failed to create pool") } pub fn get_connection() -> Result { - Connection::establish(&CONFIG.database_url) + Connection::establish(&CONFIG.database_url()) } /// Attempts to retrieve a single connection from the managed database pool. If diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index b86095d..b27704f 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -28,7 +28,7 @@ impl Attachment { } pub fn get_file_path(&self) -> String { - format!("{}/{}/{}", CONFIG.attachments_folder, self.cipher_uuid, self.id) + format!("{}/{}/{}", CONFIG.attachments_folder(), self.cipher_uuid, self.id) } pub fn to_json(&self, host: &str) -> Value { @@ -86,7 +86,7 @@ impl Attachment { pub fn find_by_id(id: &str, conn: &DbConn) -> Option { let id = id.to_lowercase(); - + attachments::table .filter(attachments::id.eq(id)) .first::(&**conn) diff --git a/src/db/models/user.rs b/src/db/models/user.rs index 0225432..608f87b 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -56,7 +56,7 @@ impl User { password_hash: Vec::new(), salt: crypto::get_random_64(), - password_iterations: CONFIG.password_iterations, + password_iterations: CONFIG.password_iterations(), security_stamp: crate::util::get_uuid(), @@ -242,7 +242,7 @@ impl Invitation { } pub fn take(mail: &str, conn: &DbConn) -> bool { - CONFIG.invitations_allowed + CONFIG.invitations_allowed() && match Self::find_by_mail(mail, &conn) { Some(invitation) => invitation.delete(&conn).is_ok(), None => false, diff --git a/src/mail.rs b/src/mail.rs index 5246a26..1d9e45f 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -6,8 +6,8 @@ use native_tls::{Protocol, TlsConnector}; use crate::api::EmptyResult; use crate::auth::{encode_jwt, generate_invite_claims}; +use crate::config::MailConfig; use crate::error::Error; -use crate::MailConfig; use crate::CONFIG; fn mailer(config: &MailConfig) -> SmtpTransport { @@ -85,7 +85,7 @@ pub fn send_invite( let (subject, body) = get_text( "email/send_org_invite", json!({ - "url": CONFIG.domain, + "url": CONFIG.domain(), "org_id": org_id.unwrap_or("_".to_string()), "org_user_id": org_user_id.unwrap_or("_".to_string()), "email": address, @@ -101,7 +101,7 @@ pub fn send_invite_accepted(new_user_email: &str, address: &str, org_name: &str, let (subject, body) = get_text( "email/invite_accepted", json!({ - "url": CONFIG.domain, + "url": CONFIG.domain(), "email": new_user_email, "org_name": org_name, }), @@ -114,7 +114,7 @@ pub fn send_invite_confirmed(address: &str, org_name: &str, config: &MailConfig) let (subject, body) = get_text( "email/invite_confirmed", json!({ - "url": CONFIG.domain, + "url": CONFIG.domain(), "org_name": org_name, }), )?; diff --git a/src/main.rs b/src/main.rs index 130ee9a..261c99d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,6 @@ extern crate derive_more; #[macro_use] extern crate num_derive; -use handlebars::Handlebars; use rocket::{fairing::AdHoc, Rocket}; use std::{ @@ -32,11 +31,14 @@ use std::{ mod error; mod api; mod auth; +mod config; mod crypto; mod db; mod mail; mod util; +pub use config::CONFIG; + fn init_rocket() -> Rocket { rocket::ignite() .mount("/", api::web_routes()) @@ -68,7 +70,7 @@ mod migrations { } fn main() { - if CONFIG.extended_logging { + if CONFIG.extended_logging() { init_logging().ok(); } @@ -99,7 +101,7 @@ fn init_logging() -> Result<(), fern::InitError> { .level_for("multipart", log::LevelFilter::Info) .chain(std::io::stdout()); - if let Some(log_file) = CONFIG.log_file.as_ref() { + if let Some(log_file) = CONFIG.log_file() { logger = logger.chain(fern::log_file(log_file)?); } @@ -133,7 +135,8 @@ fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch { } fn check_db() { - let path = Path::new(&CONFIG.database_url); + let url = CONFIG.database_url(); + let path = Path::new(&url); if let Some(parent) = path.parent() { use std::fs; @@ -153,7 +156,7 @@ fn check_db() { fn check_rsa_keys() { // If the RSA keys don't exist, try to create them - if !util::file_exists(&CONFIG.private_rsa_key) || !util::file_exists(&CONFIG.public_rsa_key) { + if !util::file_exists(&CONFIG.private_rsa_key()) || !util::file_exists(&CONFIG.public_rsa_key()) { info!("JWT keys don't exist, checking if OpenSSL is available..."); Command::new("openssl").arg("version").output().unwrap_or_else(|_| { @@ -166,7 +169,7 @@ fn check_rsa_keys() { let mut success = Command::new("openssl") .arg("genrsa") .arg("-out") - .arg(&CONFIG.private_rsa_key_pem) + .arg(&CONFIG.private_rsa_key_pem()) .output() .expect("Failed to create private pem file") .status @@ -175,11 +178,11 @@ fn check_rsa_keys() { success &= Command::new("openssl") .arg("rsa") .arg("-in") - .arg(&CONFIG.private_rsa_key_pem) + .arg(&CONFIG.private_rsa_key_pem()) .arg("-outform") .arg("DER") .arg("-out") - .arg(&CONFIG.private_rsa_key) + .arg(&CONFIG.private_rsa_key()) .output() .expect("Failed to create private der file") .status @@ -188,14 +191,14 @@ fn check_rsa_keys() { success &= Command::new("openssl") .arg("rsa") .arg("-in") - .arg(&CONFIG.private_rsa_key) + .arg(&CONFIG.private_rsa_key()) .arg("-inform") .arg("DER") .arg("-RSAPublicKey_out") .arg("-outform") .arg("DER") .arg("-out") - .arg(&CONFIG.public_rsa_key) + .arg(&CONFIG.public_rsa_key()) .output() .expect("Failed to create public der file") .status @@ -211,11 +214,11 @@ fn check_rsa_keys() { } fn check_web_vault() { - if !CONFIG.web_vault_enabled { + if !CONFIG.web_vault_enabled() { return; } - let index_path = Path::new(&CONFIG.web_vault_folder).join("index.html"); + let index_path = Path::new(&CONFIG.web_vault_folder()).join("index.html"); if !index_path.exists() { error!("Web vault is not found. Please follow the steps in the README to install it"); @@ -232,215 +235,3 @@ fn unofficial_warning() -> AdHoc { warn!("\\--------------------------------------------------------------------/"); }) } - -lazy_static! { - // Load the config from .env or from environment variables - static ref CONFIG: Config = Config::load(); -} - -#[derive(Debug)] -pub struct MailConfig { - smtp_host: String, - smtp_port: u16, - smtp_ssl: bool, - smtp_from: String, - smtp_from_name: String, - smtp_username: Option, - smtp_password: Option, -} - -impl MailConfig { - fn load() -> Option { - use crate::util::{get_env, get_env_or}; - - // When SMTP_HOST is absent, we assume the user does not want to enable it. - let smtp_host = match get_env("SMTP_HOST") { - Some(host) => host, - None => return None, - }; - - let smtp_from = get_env("SMTP_FROM").unwrap_or_else(|| { - error!("Please specify SMTP_FROM to enable SMTP support."); - exit(1); - }); - - let smtp_from_name = get_env_or("SMTP_FROM_NAME", "Bitwarden_RS".into()); - - let smtp_ssl = get_env_or("SMTP_SSL", true); - let smtp_port = get_env("SMTP_PORT").unwrap_or_else(|| if smtp_ssl { 587u16 } else { 25u16 }); - - let smtp_username = get_env("SMTP_USERNAME"); - let smtp_password = get_env("SMTP_PASSWORD").or_else(|| { - if smtp_username.as_ref().is_some() { - error!("SMTP_PASSWORD is mandatory when specifying SMTP_USERNAME."); - exit(1); - } else { - None - } - }); - - Some(MailConfig { - smtp_host, - smtp_port, - smtp_ssl, - smtp_from, - smtp_from_name, - smtp_username, - smtp_password, - }) - } -} - -#[derive(Debug)] -pub struct Config { - database_url: String, - icon_cache_folder: String, - attachments_folder: String, - - icon_cache_ttl: u64, - icon_cache_negttl: u64, - - private_rsa_key: String, - private_rsa_key_pem: String, - public_rsa_key: String, - - web_vault_folder: String, - web_vault_enabled: bool, - - websocket_enabled: bool, - websocket_url: String, - - extended_logging: bool, - log_file: Option, - - local_icon_extractor: bool, - signups_allowed: bool, - invitations_allowed: bool, - admin_token: Option, - password_iterations: i32, - show_password_hint: bool, - - domain: String, - domain_set: bool, - - yubico_cred_set: bool, - yubico_client_id: String, - yubico_secret_key: String, - yubico_server: Option, - - mail: Option, - templates: Handlebars, - templates_folder: String, - reload_templates: bool, -} - -fn load_templates(path: &str) -> Handlebars { - let mut hb = Handlebars::new(); - // Error on missing params - hb.set_strict_mode(true); - - macro_rules! reg { - ($name:expr) => {{ - let template = include_str!(concat!("static/templates/", $name, ".hbs")); - hb.register_template_string($name, template).unwrap(); - }}; - } - - // First register default templates here - reg!("email/invite_accepted"); - reg!("email/invite_confirmed"); - reg!("email/pw_hint_none"); - reg!("email/pw_hint_some"); - reg!("email/send_org_invite"); - - reg!("admin/base"); - reg!("admin/login"); - reg!("admin/page"); - - // And then load user templates to overwrite the defaults - // Use .hbs extension for the files - // Templates get registered with their relative name - hb.register_templates_directory(".hbs", path).unwrap(); - - hb -} - -impl Config { - pub fn render_template(&self, name: &str, data: &T) -> Result { - // We add this to signal the compiler not to drop the result of 'load_templates' - let hb_owned; - - let hb = if CONFIG.reload_templates { - warn!("RELOADING TEMPLATES"); - hb_owned = load_templates(&self.templates_folder); - &hb_owned - } else { - &self.templates - }; - - hb.render(name, data).map_err(Into::into) - } - - fn load() -> Self { - use crate::util::{get_env, get_env_or}; - dotenv::dotenv().ok(); - - let df = get_env_or("DATA_FOLDER", "data".to_string()); - let key = get_env_or("RSA_KEY_FILENAME", format!("{}/{}", &df, "rsa_key")); - - let domain = get_env("DOMAIN"); - - let yubico_client_id = get_env("YUBICO_CLIENT_ID"); - let yubico_secret_key = get_env("YUBICO_SECRET_KEY"); - - let templates_folder = get_env_or("TEMPLATES_FOLDER", format!("{}/{}", &df, "templates")); - - Config { - database_url: get_env_or("DATABASE_URL", format!("{}/{}", &df, "db.sqlite3")), - icon_cache_folder: get_env_or("ICON_CACHE_FOLDER", format!("{}/{}", &df, "icon_cache")), - attachments_folder: get_env_or("ATTACHMENTS_FOLDER", format!("{}/{}", &df, "attachments")), - templates: load_templates(&templates_folder), - templates_folder, - reload_templates: get_env_or("RELOAD_TEMPLATES", false), - - // icon_cache_ttl defaults to 30 days (30 * 24 * 60 * 60 seconds) - icon_cache_ttl: get_env_or("ICON_CACHE_TTL", 2_592_000), - // icon_cache_negttl defaults to 3 days (3 * 24 * 60 * 60 seconds) - icon_cache_negttl: get_env_or("ICON_CACHE_NEGTTL", 259_200), - - private_rsa_key: format!("{}.der", &key), - private_rsa_key_pem: format!("{}.pem", &key), - public_rsa_key: format!("{}.pub.der", &key), - - web_vault_folder: get_env_or("WEB_VAULT_FOLDER", "web-vault/".into()), - web_vault_enabled: get_env_or("WEB_VAULT_ENABLED", true), - - websocket_enabled: get_env_or("WEBSOCKET_ENABLED", false), - websocket_url: format!( - "{}:{}", - get_env_or("WEBSOCKET_ADDRESS", "0.0.0.0".to_string()), - get_env_or("WEBSOCKET_PORT", 3012) - ), - - extended_logging: get_env_or("EXTENDED_LOGGING", true), - log_file: get_env("LOG_FILE"), - - local_icon_extractor: get_env_or("LOCAL_ICON_EXTRACTOR", false), - signups_allowed: get_env_or("SIGNUPS_ALLOWED", true), - admin_token: get_env("ADMIN_TOKEN"), - invitations_allowed: get_env_or("INVITATIONS_ALLOWED", true), - password_iterations: get_env_or("PASSWORD_ITERATIONS", 100_000), - show_password_hint: get_env_or("SHOW_PASSWORD_HINT", true), - - domain_set: domain.is_some(), - domain: domain.unwrap_or("http://localhost".into()), - - yubico_cred_set: yubico_client_id.is_some() && yubico_secret_key.is_some(), - yubico_client_id: yubico_client_id.unwrap_or("00000".into()), - yubico_secret_key: yubico_secret_key.unwrap_or("AAAAAAA".into()), - yubico_server: get_env("YUBICO_SERVER"), - - mail: MailConfig::load(), - } - } -} diff --git a/src/util.rs b/src/util.rs index 5bd3710..06ea386 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,8 +2,8 @@ // Web Headers and caching // use rocket::fairing::{Fairing, Info, Kind}; -use rocket::{Request, Response}; use rocket::response::{self, Responder}; +use rocket::{Request, Response}; pub struct AppHeaders();