Merge pull request 'feature-security' (#15) from feature-security into develop

Reviewed-on: ERRMS/ERRMS#15
This commit is contained in:
anghenfil 2020-12-30 12:39:11 +01:00
commit 0e42c63d7a
56 changed files with 1745 additions and 108 deletions

541
Cargo.lock generated
View File

@ -76,6 +76,12 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "ascii_utils"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
[[package]]
name = "atty"
version = "0.2.14"
@ -87,6 +93,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]]
name = "autocfg"
version = "1.0.1"
@ -103,6 +115,15 @@ dependencies = [
"safemem",
]
[[package]]
name = "base64"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
dependencies = [
"byteorder",
]
[[package]]
name = "base64"
version = "0.12.3"
@ -156,6 +177,12 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "bufstream"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]]
name = "byte-tools"
version = "0.3.1"
@ -168,12 +195,24 @@ version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cc"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.15"
@ -186,6 +225,15 @@ dependencies = [
"time",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "config"
version = "0.10.1"
@ -193,7 +241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
dependencies = [
"lazy_static",
"nom",
"nom 5.1.2",
"rust-ini",
"serde 1.0.115",
"serde-hjson",
@ -219,19 +267,35 @@ dependencies = [
"hkdf",
"hmac",
"percent-encoding 2.1.0",
"rand",
"rand 0.7.3",
"sha2",
"time",
]
[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if",
"autocfg 1.0.1",
"cfg-if 0.1.10",
"lazy_static",
]
@ -288,7 +352,7 @@ dependencies = [
"chrono",
"diesel_derives",
"pq-sys",
"uuid",
"uuid 0.8.1",
]
[[package]]
@ -321,6 +385,85 @@ dependencies = [
"generic-array",
]
[[package]]
name = "email"
version = "0.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4"
dependencies = [
"base64 0.9.3",
"chrono",
"encoding",
"lazy_static",
"rand 0.4.6",
"time",
"version_check 0.1.5",
]
[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
dependencies = [
"encoding-index-japanese",
"encoding-index-korean",
"encoding-index-simpchinese",
"encoding-index-singlebyte",
"encoding-index-tradchinese",
]
[[package]]
name = "encoding-index-japanese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-korean"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-simpchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-singlebyte"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding-index-tradchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
dependencies = [
"encoding_index_tests",
]
[[package]]
name = "encoding_index_tests"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]]
name = "env_logger"
version = "0.7.1"
@ -344,15 +487,17 @@ dependencies = [
"diesel_geometry",
"env_logger",
"iban_validate",
"lettre",
"lettre_email",
"log 0.4.11",
"rand",
"rand 0.7.3",
"rocket",
"rocket_contrib",
"rust-argon2",
"serde 1.0.115",
"serde_derive",
"serde_json",
"uuid",
"uuid 0.8.1",
]
[[package]]
@ -361,18 +506,42 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fast_chemail"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
dependencies = [
"ascii_utils",
]
[[package]]
name = "filetime"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
dependencies = [
"cfg-if",
"cfg-if 0.1.10",
"libc",
"redox_syscall",
"winapi 0.3.9",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fsevent"
version = "0.4.0"
@ -392,6 +561,12 @@ dependencies = [
"libc",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
@ -423,7 +598,7 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
dependencies = [
"cfg-if",
"cfg-if 0.1.10",
"libc",
"wasi",
]
@ -466,7 +641,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25"
dependencies = [
"autocfg",
"autocfg 1.0.1",
]
[[package]]
@ -498,6 +673,16 @@ dependencies = [
"digest",
]
[[package]]
name = "hostname"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
dependencies = [
"libc",
"winutil",
]
[[package]]
name = "httparse"
version = "1.3.4"
@ -523,7 +708,7 @@ dependencies = [
"httparse",
"language-tags",
"log 0.3.9",
"mime",
"mime 0.2.6",
"num_cpus",
"time",
"traitobject",
@ -558,7 +743,7 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9"
dependencies = [
"autocfg",
"autocfg 1.0.1",
"hashbrown",
]
@ -625,6 +810,38 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lettre"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338d9a248c4b3ef60c51941c678bb8f64e244c0a98f1eb71db027d1e777a5700"
dependencies = [
"base64 0.10.1",
"bufstream",
"fast_chemail",
"hostname",
"log 0.4.11",
"native-tls",
"nom 4.2.3",
"serde 1.0.115",
"serde_derive",
"serde_json",
]
[[package]]
name = "lettre_email"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5"
dependencies = [
"base64 0.10.1",
"email",
"lettre",
"mime 0.3.16",
"time",
"uuid 0.7.4",
]
[[package]]
name = "lexical-core"
version = "0.7.4"
@ -633,7 +850,7 @@ checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if",
"cfg-if 0.1.10",
"ryu",
"static_assertions",
]
@ -675,7 +892,7 @@ version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
"cfg-if",
"cfg-if 0.1.10",
]
[[package]]
@ -705,13 +922,19 @@ dependencies = [
"log 0.3.9",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mio"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
dependencies = [
"cfg-if",
"cfg-if 0.1.10",
"fuchsia-zircon",
"fuchsia-zircon-sys",
"iovec",
@ -748,17 +971,45 @@ dependencies = [
"ws2_32-sys",
]
[[package]]
name = "native-tls"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f"
dependencies = [
"lazy_static",
"libc",
"log 0.4.11",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "net2"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
dependencies = [
"cfg-if",
"cfg-if 0.1.10",
"libc",
"winapi 0.3.9",
]
[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
"memchr",
"version_check 0.1.5",
]
[[package]]
name = "nom"
version = "5.1.2"
@ -794,7 +1045,7 @@ version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
dependencies = [
"autocfg",
"autocfg 1.0.1",
"num-traits 0.2.12",
]
@ -813,7 +1064,7 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
"autocfg",
"autocfg 1.0.1",
]
[[package]]
@ -832,6 +1083,39 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "openssl"
version = "0.10.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"foreign-types",
"lazy_static",
"libc",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
[[package]]
name = "openssl-sys"
version = "0.9.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6"
dependencies = [
"autocfg 1.0.1",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "pear"
version = "0.1.4"
@ -909,13 +1193,19 @@ dependencies = [
"sha-1",
]
[[package]]
name = "pkg-config"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "polyval"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ec3341498978de3bfd12d1b22f1af1de22818f5473a11e8a6ef997989e3a212"
dependencies = [
"cfg-if",
"cfg-if 0.1.10",
"universal-hash",
]
@ -976,6 +1266,38 @@ dependencies = [
"proc-macro2 1.0.19",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi 0.3.9",
]
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.7",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg",
"rand_xorshift",
"winapi 0.3.9",
]
[[package]]
name = "rand"
version = "0.7.3"
@ -984,9 +1306,19 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.7",
"rand_core 0.3.1",
]
[[package]]
@ -996,9 +1328,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.5.1",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
@ -1008,13 +1355,84 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
"rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi 0.3.9",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi 0.3.9",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.7",
"rand_core 0.4.2",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
@ -1041,6 +1459,15 @@ version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "rocket"
version = "0.4.5"
@ -1148,6 +1575,39 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi 0.3.9",
]
[[package]]
name = "security-framework"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "0.8.23"
@ -1289,6 +1749,20 @@ dependencies = [
"unicode-xid 0.2.1",
]
[[package]]
name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
"cfg-if 0.1.10",
"libc",
"rand 0.7.3",
"redox_syscall",
"remove_dir_all",
"winapi 0.3.9",
]
[[package]]
name = "termcolor"
version = "1.1.0"
@ -1425,12 +1899,22 @@ dependencies = [
"percent-encoding 1.0.1",
]
[[package]]
name = "uuid"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
dependencies = [
"rand 0.6.5",
]
[[package]]
name = "uuid"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"rand 0.7.3",
"serde 1.0.115",
]
@ -1512,6 +1996,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winutil"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ws2_32-sys"
version = "0.2.1"

View File

@ -16,11 +16,13 @@ env_logger = "0.7.1"
rocket = "0.4.5"
diesel = { version = "1.4.5", features = ["postgres", "uuidv07", "chrono"] } #uuidv07 vs uuid to use uuid >= 0.7
diesel_geometry = "1.4.0"
uuid = { version = "0.8", features = ["serde"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
rust-argon2 = "0.8.2"
chrono = { version = "0.4.15", features = ["serde"] }
rand = "0.7.3"
iban_validate = "4"
lettre = "0.9"
lettre_email = "0.9"
[dependencies.rocket_contrib]
version = "0.4.5"

View File

@ -2,9 +2,21 @@
connection_string = "postgresql://postgres:qwertz@localhost:5432/postgres"
[application]
url = "http://localhost:8000/"
name = "einsatz.online"
default_language = "de-DE"
fallback_language = "en-US"
loglevel = "debug"
#Session timeout in seconds. Default is 15 minutes
session_timeout = 900
upload_path = "uploads/"
upload_path = "uploads/"
#Maximum login attempts until email is locked for login
max_login_attempts = 6
#Duration of email lock after max_login_attempts in seconds. Default is 30 minutes
login_lock_duration = 1800
#How long does it take until tokens expire?
reset_password_token_valid_duration = 3600
[mail]
from = "No Reply <noreply@localhost>"
reply_to = "support@localhost"

View File

@ -1,9 +1,13 @@
-- Your SQL goes here
create table users
(
id uuid default uuid_generate_v1() not null,
id uuid default uuid_generate_v1() not null
constraint pk___users___id
primary key,
password text,
email text,
constraint pk___users___id
primary key (id)
);
email text not null
);
create unique index users_email_uindex
on users (email);

View File

@ -1,29 +1,31 @@
create table members
(
entity_id uuid default uuid_generate_v1() not null
entity_id uuid default uuid_generate_v1() not null
constraint pk___members___id
primary key
constraint members_entities_entity_id_fk
references entities
on update cascade on delete cascade,
users_id uuid
users_id uuid
constraint fk___members___users_id___users
references users,
firstname text not null,
lastname text not null,
date_of_birth date,
sex smallint,
salutation text,
place_of_birth text,
academic_titles text,
references users
on update cascade on delete set null,
firstname text not null,
lastname text not null,
date_of_birth date,
sex smallint,
salutation text,
place_of_birth text,
academic_titles text,
personnel_number integer,
ui_language text,
nationality text,
entrance_date date,
birth_name text,
iban text,
bic text
ui_language text,
nationality text,
entrance_date date,
birth_name text,
iban text,
bic text
);
create unique index members_personnel_number_uindex
on members (personnel_number);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE login_attempts;

View File

@ -0,0 +1,7 @@
-- Your SQL goes here
create table login_attempts
(
id uuid default uuid_generate_v1() primary key,
email text not null,
timestamp timestamp default now() not null
);

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table password_resets;

View File

@ -0,0 +1,17 @@
-- Your SQL goes here
create table password_resets
(
token text not null
constraint password_resets_pk
primary key,
user_id uuid not null
constraint password_resets_users_id_fk
references users
on update cascade on delete cascade,
issued timestamp default CURRENT_TIMESTAMP not null
);
create unique index password_resets_token_uindex
on password_resets (token);

View File

@ -20,6 +20,9 @@ $( document ).ready(function() {
UnitModule.get_units();
$("#add_operation_unit_submit").on("click", UnitModule.add_unit_submit_listener);
$(".delete_unit_from_member_button").on("click", UnitModule.delete_unit);
$("#login_allowed").on("change", LoginModule.login_allowed_checkbox_listener);
$("#login_save_button").on("click", LoginModule.login_submit);
}
});
@ -419,4 +422,88 @@ UnitModule = (
delete_unit: delete_unit,
}
}
)();
)();
LoginModule = (function(){
var login_allowed_checkbox_listener = function(){
var ro = $("#login_email").prop('readonly');
if(ro){
$("#login_email").prop('readonly', false);
}else{
$("#login_email").prop('readonly', true);
}
};
var login_submit = function(){
var login_allowed = $("#login_allowed").prop('checked');
if(login_allowed){
var email = $("#login_email").val();
if(email.trim() === ""){
alert("Bitte eine Email-Adresse für den Login angeben!");
}else{
var user_id = $("#login_save_button").data("user-id");
if(user_id === ""){//New entry
var user = $();
user.email = email.trim();
user.member_id = member_id;
$.ajax({
type: "POST",
url: "/api/users/",
contentType: 'application/json',
data: JSON.stringify(user),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$("#login_save_button").data("user-id", data.user_id);
}
}
});
}else{ //Update existing entry
var user = $();
user.user_id = user_id;
user.email = email.trim();
user.member_id = member_id;
$.ajax({
type: "PUT",
url: "/api/users/"+user_id,
contentType: 'application/json',
data: JSON.stringify(user),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
}
}
});
}
}
}else{
//Delete user for member
$.ajax({
type: "DELETE",
url: "/api/users/"+$("#login_save_button").data("user-id"),
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$("#login_email").val("");
}
}
});
}
}
return{
login_allowed_checkbox_listener: login_allowed_checkbox_listener,
login_submit: login_submit,
}
})();

View File

@ -0,0 +1,7 @@
Jemand hat das Zurücksetzen des Passworts auf {{frontpage}} für die Email
{{email}}
angefordert.
Falls dies nicht beabsichtigt war, ignoriere einfach diese E-Mail. Dein altes Passwort bleibt wirksam.
Um dein Passwort zurückzusetzen, besuche folgende Adresse:
{{reset_url}}

View File

@ -251,6 +251,24 @@
</table>
</div>
</div>{{/if_in_list}}
{{#if_in_list ../../caller_permissions "modules.member_management.profile.login.view"}}
<div class="card bg-light mb-3">
<div class="card-header">Login</div>
<div class="card-body">
<div class="col">
<p>Achtung: Wird der Login deaktiviert, werden Email-Adresse und Passwort dauerhaft gelöscht und müssen nach dann ggf. neu gesetzt werden.</p>
<div class="form-check">
<input type="checkbox" class="form-check-input" {{#if login.login_allowed}}checked{{/if}} id="login_allowed" {{#if ../readonly}}disabled{{/if}}>
<label class="form-check-label" for="login_allowed">Login aktiviert</label>
</div><hr>
<div class="form-group row">
<label for="login_email" class="col-sm-3">Email-Adresse</label>
<input type="email" class="form-control col-sm-9" id="login_email" value="{{login.email}}" {{#if (not login.login_allowed)}}readonly{{/if}}{{#if ../readonly}}readonly{{/if}}>
</div>
{{#if (not ../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.login.edit"}}<button type="button" id="login_save_button" class="btn btn-primary" data-user-id="{{login.user_id}}" style="float: right">Speichern</button>{{/if_in_list}}{{/if}}
</div>
</div>
</div>{{/if_in_list}}
</div>
<div class="col-lg-6">
<div class="card bg-light mb-3">

View File

@ -2,7 +2,7 @@
<div class="jumbotron">
<div class="row">
<div class="col-lq">
<h1>Welcome to ERMS!</h1>
<h1>Willkommen bei einsatz.online!</h1>
</div>
{{#if logo_path}}
<div class="col">
@ -17,25 +17,26 @@
{{#if alert}}
{{> alert}}
{{/if}}
<h3>Login</h3>
<h3>Anmelden</h3>
<div class="form-group">
<label for="login_email">Email address</label>
<label for="login_email">E-Mail Adresse</label>
<input type="email" class="form-control" name="login_email" id="login_email" required>
</div>
<div class="form-group">
<label for="login_password">Password</label>
<label for="login_password">Passwort</label>
<input type="password" class="form-control" name="login_password" id="login_password" required>
</div>
<button type="submit" class="login_submit btn btn-primary">Login</button>
<button type="submit" class="login_submit btn btn-primary">Anmelden</button>
</form>
</div>
<div class="col">
<form>
<h3>Register</h3>
<form method="post" action="/password_reset">
<h3>Passwort vergessen?</h3>
<div class="form-group">
<label for="register_email">Email address</label>
<input type="email" class="form-control" name="register_email" id="register_email" required>
<label for="email">E-Mail Adresse</label>
<input type="email" class="form-control" name="email" id="email" required>
</div>
<button type="submit" class="login_submit btn btn-secondary">Passwort zurücksetzen</button>
</form>
</div>
</div>

View File

@ -0,0 +1,30 @@
{{> header }}
<div class="jumbotron">
<div class="row">
<div class="col-lq">
<h1>Willkommen bei einsatz.online!</h1>
</div>
{{#if logo_path}}
<div class="col">
<img class="welcome_logo" src="{{logo_path}}">
</div>
{{/if}}
</div>
</div>
<div class="row">
<div class="col-10">
<form method="post">
{{#if alert}}
{{> alert}}
{{/if}}
<h3>Neues Passwort festlegen</h3>
<p>Ihr Passwort muss mindestens 10 Zeichen lang sein und sollte Buchstaben, Zahlen und Sonderzeichen enthalten.</p>
<div class="form-group">
<label for="password">Passwort</label>
<input type="password" class="form-control" name="password" id="password" required>
</div>
<button type="submit" class="login_submit btn btn-primary">Passwort ändern</button>
</form>
</div>
</div>
{{> footer }}

View File

@ -0,0 +1,47 @@
use rocket::State;
use crate::helper::settings::Settings;
use crate::database::controller::connector::establish_connection;
use diesel::{sql_query, RunQueryDsl, ExpressionMethods};
use diesel::sql_types::{Integer, Text};
use crate::database::model::login_protection::LoginAttemptsResult;
use chrono::{NaiveDateTime, Duration};
use std::ops::Add;
use crate::schema::login_attempts::dsl::login_attempts;
/// Checks if maximum login attempts exceeded. Locks account if exceeded
/// Returns:
/// * Ok(true) if login attempts exceeded and account got locked
/// * Ok(false) if login attempts not exceeded
/// * Err(diesel::result::Error) if database error occured
pub fn login_attempts_exceeded(settings: &State<Settings>, email: String) -> Result<bool, diesel::result::Error>{
let connection = establish_connection(settings);
let result : Result<LoginAttemptsResult, diesel::result::Error> = sql_query("SELECT COUNT(*) AS count, MAX(timestamp) AS last_timestamp FROM login_attempts WHERE email=$1 AND timestamp > (now() - interval '1800 seconds')").bind::<Text, _>(email.clone()).get_result(&connection);
let result = match result{
Ok(res) => res,
Err(e) => {
error!("Couldn't check for login attempts: {}", e);
return Err(e)
}
};
if result.count > settings.application.max_login_attempts as i64 {
Ok(true)
}else{
add_login_attempt(settings, email)?;
Ok(false)
}
}
fn add_login_attempt(settings: &State<Settings>, email2: String) -> Result<(), diesel::result::Error>{
use crate::schema::login_attempts::dsl::{login_attempts, email};
let connection = establish_connection(settings);
match diesel::insert_into(login_attempts).values(email.eq(email2)).execute(&connection){
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't write login attempt into DB! {}", e);
Err(e)
}
}
}

View File

@ -15,4 +15,6 @@ pub mod roles;
pub mod users;
pub mod units;
pub mod units_members;
pub mod create_member;
pub mod create_member;
pub mod login_protection;
pub mod password_resets;

View File

@ -0,0 +1,109 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::database::controller::connector::establish_connection;
use crate::schema::password_resets::dsl::{password_resets};
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use diesel::{ExpressionMethods, RunQueryDsl};
use chrono::{NaiveDateTime, Utc};
use diesel::query_dsl::filter_dsl::FilterDsl;
use diesel::query_dsl::select_dsl::SelectDsl;
use argon2::Config;
use crate::schema::users::dsl::users;
use diesel::result::Error;
/// Adds password reset token to database and returns it
pub fn add_token(settings: &State<Settings>, user_id: uuid::Uuid) -> Result<String, diesel::result::Error>{
let connection = establish_connection(settings);
let token : String = thread_rng()
.sample_iter(&Alphanumeric)
.take(60)
.collect();
match diesel::insert_into(password_resets).values((crate::schema::password_resets::token.eq(token.clone()), crate::schema::password_resets::user_id.eq(user_id))).execute(&connection){
Ok(_) => Ok(token),
Err(e) => {
error!("Couldn't create password reset token: {}", e);
Err(e)
}
}
}
pub fn remove_token(settings: &State<Settings>, token2: String) -> Result<(), diesel::result::Error>{
let connection = establish_connection(settings);
match diesel::delete(password_resets).filter(crate::schema::password_resets::dsl::token.eq(token2)).execute(&connection){
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't delete token: {}", e);
Err(e)
}
}
}
pub fn validate_token(settings: &State<Settings>, token2: String) -> Result<uuid::Uuid, diesel::result::Error>{
let connection = establish_connection(settings);
match password_resets.select((crate::schema::password_resets::dsl::user_id, crate::schema::password_resets::issued)).filter(crate::schema::password_resets::dsl::token.eq(token2.clone())).get_result::<(uuid::Uuid, NaiveDateTime)>(&connection){
Ok(data) => {
let issued = data.1;
let now = Utc::now().naive_local();
let duration = now.signed_duration_since(issued).num_seconds();
if duration > settings.application.reset_password_token_valid_duration{
remove_token(settings, token2);
return Err(diesel::result::Error::NotFound)
}
Ok(data.0)
},
Err(e) => match e{
diesel::result::Error::NotFound => {
return Err(e)
},
_ => {
error!("Couldn't validate token: {}", e);
return Err(e)
}
}
}
}
pub fn change_password_with_token(settings: &State<Settings>, token2: String, password: String) -> Result<(), ()>{
let user_id = match validate_token(settings, token2.clone()){
Ok(user_id) => user_id,
Err(e) => return Err(())
};
let connection =establish_connection(settings);
let salt = rand::thread_rng().gen::<[u8; 32]>();
let hashed_password = match argon2::hash_encoded(password.as_bytes(), &salt, &Config::default()){
Ok(pw) => pw,
Err(e) => {
error!("Couldn't hash password: {}", e);
return Err(())
}
};
match diesel::update(users).filter(crate::schema::users::dsl::id.eq(user_id)).set(crate::schema::users::dsl::password.eq(hashed_password)).execute(&connection){
Ok(_) => (),
Err(e) => {
error!("Couldn't set new password: {}", e);
return Err(())
}
};
match remove_token(settings, token2){
Ok(_) => Ok(()),
Err(_) => Err(())
}
}
pub fn validate_password(password: String) -> bool{
if password.len() < 10{
return false
}
true
}

View File

@ -5,6 +5,11 @@ use crate::diesel::QueryDsl;
use crate::helper::settings::Settings;
use diesel::ExpressionMethods;
use rocket::State;
use crate::schema::members::columns::entity_id;
use diesel::result::Error;
use crate::modules::member_management::model::login::Login;
use crate::schema::users::dsl::users;
use crate::schema::members::dsl::members;
pub fn get_user_by_email(email: String, settings: &State<Settings>) -> Option<User> {
use crate::schema::users::dsl::email as email1;
@ -17,3 +22,77 @@ pub fn get_user_by_email(email: String, settings: &State<Settings>) -> Option<Us
Err(_) => None,
}
}
/// Tries to get user associated to member entity id
/// Returns:
/// * Ok<Some<User>> if user found
/// * Ok<None> if no user found for member entity id
/// * Err<diesel::result::Error> if database error occured
pub fn get_user_by_member(settings: &State<Settings>, member_uuid: uuid::Uuid) -> Result<Option<User>, diesel::result::Error>{
use crate::schema::members::dsl::members;
use crate::schema::users::dsl::*;
let connection = establish_connection(settings);
let user : Result<User, diesel::result::Error> = members.inner_join(crate::schema::users::table).filter(entity_id.eq(member_uuid)).select((id, password, email)).first(&connection);
match user{
Ok(user) => Ok(Some(user)),
Err(e) => {
match e{
Error::NotFound => {Ok(None)}
_ => {
error!("Couldn't get user for member: {}", e);
Err(e)
}
}
}
}
}
pub fn add_user_to_member(settings: &State<Settings>, member_uuid: uuid::Uuid, email: String) -> Result<User, diesel::result::Error>{
let connection = establish_connection(settings);
let user : Result<User, diesel::result::Error> = diesel::insert_into(users).values(crate::schema::users::email.eq(email)).get_result(&connection);
match user{
Ok(user) => {
match diesel::update(members.filter(crate::schema::members::entity_id.eq(member_uuid))).set(crate::schema::members::users_id.eq(user.id)).execute(&connection){
Ok(_) => Ok(user),
Err(e) => {
error!("Couldn't add user to member: {}", e);
Err(e)
}
}
}
Err(e) => {
error!("Couldn't create new user: {}", e);
Err(e)
}
}
}
pub fn remove_user(settings: &State<Settings>, user_id: uuid::Uuid) -> Result<(), diesel::result::Error>{
use crate::schema::users::id;
let connection = establish_connection(settings);
match diesel::delete(users.filter(id.eq(user_id))).execute(&connection){
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't delete user: {}", e);
Err(e)
}
}
}
pub fn update_user_email(settings: &State<Settings>, user_id: uuid::Uuid, email: String) -> Result<User, diesel::result::Error>{
let connection = establish_connection(settings);
match diesel::update(users.filter(crate::schema::users::id.eq(user_id))).set(crate::schema::users::email.eq(email)).get_result(&connection){
Ok(user) => Ok(user),
Err(e) => {
error!("Couldn't update user (id: {}) email: {}", user_id, e);
Err(e)
}
}
}

View File

@ -0,0 +1,11 @@
use chrono::NaiveDateTime;
use crate::schema::login_attempts;
use diesel::sql_types::*;
#[derive(QueryableByName)]
pub struct LoginAttemptsResult{
#[sql_type = "BigInt"]
pub count : i64,
#[sql_type = "Nullable<Timestamp>"]
pub last_timestamp: Option<NaiveDateTime>,
}

View File

@ -8,4 +8,5 @@ pub mod member_qualifications;
pub mod members;
pub mod roles;
pub mod users;
pub mod units;
pub mod units;
pub mod login_protection;

View File

@ -2,5 +2,5 @@
pub struct User {
pub(crate) id: uuid::Uuid,
pub(crate) password: Option<String>,
pub(crate) email: Option<String>,
pub(crate) email: String,
}

View File

@ -0,0 +1,2 @@
pub mod queue;
pub mod worker;

View File

@ -0,0 +1,102 @@
use std::sync::RwLock;
use std::collections::VecDeque;
use chrono::NaiveDateTime;
use std::io::{Error, Write, Read};
use std::fs::File;
use crate::helper::mail_queue::worker::send_mail;
#[derive(Deserialize, Serialize)]
pub struct MailQueue{
pub(crate) mails: RwLock<VecDeque<Mail>>
}
#[derive(Deserialize, Serialize, Clone)]
pub struct Mail{
pub(crate) uuid: uuid::Uuid,
pub(crate) from: String,
pub(crate) to: Vec<String>,
pub(crate) subject: String,
pub(crate) cc: Vec<String>,
pub(crate) bcc: Vec<String>,
pub(crate) reply_to: Option<String>,
pub(crate) body: String,
pub(crate) deliver_until: Option<NaiveDateTime>
}
impl MailQueue{
pub fn add_mail(&self, mail: Mail) -> Result<(), std::sync::PoisonError<std::sync::RwLockWriteGuard<'_, std::collections::VecDeque<Mail>>>>{
self.mails.write()?.push_back(mail);
match self.save_to_disk(){
Ok(_) => {}
Err(e) => error!("Couldn't save mail_queue to disk: {}", e)
}
Ok(())
}
pub fn get_next(&self) -> Result<Option<Mail>, std::sync::PoisonError<std::sync::RwLockWriteGuard<'_, std::collections::VecDeque<Mail>>>>{
// TODO: Check if mail expired (deliver_until check)
Ok(self.mails.write()?.pop_front())
}
pub fn process_next(&self) -> Result<(), std::sync::PoisonError<std::sync::RwLockWriteGuard<'_, std::collections::VecDeque<Mail>>>>{
let mail = self.get_next()?;
match mail{
Some(mail) => {
match send_mail(mail.clone()){
Ok(_) => (),
Err(_) => {
return self.add_mail(mail)
}
}
Ok(())
},
None => Ok(())
}
}
pub fn load_or_create_new() -> MailQueue {
match std::fs::File::open("mail_queue.txt"){
Ok(mut file) => {
let mut contents = String::new();
match file.read_to_string(&mut contents){
Ok(_) => {
match serde_json::from_str::<MailQueue>(&contents){
Ok(queue) => return queue,
Err(e) => {
panic!("Couldn't parse mail queue file: {}", e);
}
}
}
Err(e) => {
panic!("Couldn't read mail_queue.txt! {}", e);
}
}
}
Err(e) => {
warn!("Couldn't read mail queue from disk. Creating new empty queue.");
MailQueue{
mails: RwLock::new(VecDeque::new())
}
}
}
}
fn save_to_disk(&self) -> std::io::Result<()> {
let serialized = serde_json::to_string(&self)?;
let mut file = std::fs::File::create("mail_queue.txt")?;
file.write_all(serialized.as_bytes())
}
}
impl Mail{
/// Create Mail with from, to, subject, cc, bcc, reply_to, deliver_until
pub(crate) fn new(from: String, to: Vec<String>, subject: String, cc: Vec<String>, bcc: Vec<String>, reply_to: Option<String>, body: String, deliver_until: Option<NaiveDateTime>) -> Mail {
Mail{
uuid: uuid::Uuid::new_v4(),
from,
to,
subject,
cc,
bcc,
reply_to,
body,
deliver_until
}
}
}

View File

@ -0,0 +1,61 @@
use crate::helper::mail_queue::queue::Mail;
use lettre_email::EmailBuilder;
use std::process::{Command, Output};
use std::io::Error;
pub fn send_mail(mail: Mail) -> Result<(), ()> {
if mail.to.is_empty() || mail.subject.is_empty() || mail.from.is_empty() || mail.body.is_empty() {
error!("Couldn't deliver mail {} because to, subject, from or body missing! Deleting mail!", mail.uuid);
return Ok(())
}
let body = mail.body.escape_default().to_string();
let subject = mail.subject.escape_default().to_string();
let mut arg = String::from("echo \"");
arg.push_str(&body);
arg.push_str("\" | mailx --append='FROM: ");
arg.push_str(&mail.from);
arg.push_str("' ");
if !mail.cc.is_empty(){
arg.push_str("--append='CC:");
for mail in mail.cc{
arg.push_str(&(mail+","));
}
arg.push_str("' ")
}
if !mail.bcc.is_empty(){
arg.push_str("--append='BCC:");
for mail in mail.bcc{
arg.push_str(&(mail+","));
}
arg.push_str("' ")
}
match mail.reply_to{
Some(reply_to) => {
arg.push_str(&format!("--append='Reply-To: {} ' ", reply_to))
},
None => ()
}
arg.push_str("-s \"");
arg.push_str(&subject);
arg.push_str("\" ");
for receiver in mail.to{
arg.push_str(&(receiver+" "));
}
match Command::new("sh").arg("-c").arg(arg).output(){
Ok(output) => {
if !output.status.success(){
error!("Couldn't send mail: {} {} {}", output.status, String::from_utf8_lossy(&output.stderr), String::from_utf8_lossy(&output.stdout));
Err(())
}else {
Ok(())
}
},
Err(e) => {
error!("Couldn't send mail: {}", e);
Err(())
}
}
}

View File

@ -0,0 +1,5 @@
use rocket_contrib::templates::handlebars::Handlebars;
pub struct MailTemplates{
pub registry: Handlebars,
}

View File

@ -11,3 +11,5 @@ pub mod settings;
pub mod sitebuilder;
pub mod translate_diesel_error;
pub mod user_request_guard;
pub mod mail_queue;
pub mod mail_templates;

View File

@ -130,7 +130,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(
@ -155,7 +155,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(
@ -181,7 +181,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(
@ -204,7 +204,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(

View File

@ -8,17 +8,29 @@ pub struct Database {
#[derive(Debug, Deserialize, Default)]
pub struct Application {
pub url: String,
pub default_language: String,
pub fallback_language: String,
pub loglevel: String,
pub session_timeout: i64,
pub upload_path: String,
pub max_login_attempts: i32,
pub login_lock_duration: i32,
pub name: String,
pub reset_password_token_valid_duration: i64,
}
#[derive(Debug, Deserialize, Default)]
pub struct Mail{
pub from : String,
pub reply_to : String,
}
#[derive(Debug, Deserialize, Default)]
pub struct Settings {
pub database: Database,
pub application: Application,
pub mail: Mail
}
impl Settings {

View File

@ -20,6 +20,13 @@ use crate::helper::session_cookies::model::SessionCookieStorage;
use helper::settings::Settings;
use rocket_contrib::serve::StaticFiles;
use rocket_contrib::templates::Template;
use std::{thread, time};
use chrono::{DateTime, Utc};
use crate::helper::mail_queue::queue::{MailQueue, Mail};
use std::sync::{PoisonError, RwLockWriteGuard, Arc};
use std::collections::VecDeque;
use rocket_contrib::templates::handlebars::{Handlebars, TemplateFileError};
use crate::helper::mail_templates::MailTemplates;
pub mod database;
pub mod helper;
@ -38,16 +45,38 @@ fn main() {
}
};
// Initialize storage for session cookies
let cookie_storage = SessionCookieStorage::new();
debug!(
"Hello, world! Default Language: {}",
settings.application.default_language
);
// Initialize mail queue for second thread handling outgoing mails
// We are using Arc to access mail queue in all threads
let mail_queue = Arc::new(MailQueue::load_or_create_new());
let c_lock = Arc::clone(&mail_queue);
thread::spawn(move ||{
loop {
match c_lock.process_next(){
Ok(_) => {}
Err(e) => {error!("MailQueue poisoned: {}", e)}
}
thread::sleep(time::Duration::from_millis(500)) // Only check for new mails ever 500 ms
}
});
let mut mail_templates = MailTemplates{ registry: Handlebars::new() };
match mail_templates.registry.register_templates_directory(".hbs", "resources/mail_templates"){
Ok(_) => {}
Err(e) => {
error!("Couldn't register mail templates: {}",e);
std::process::exit(1);
}
}
rocket::ignite()
.manage(settings)
.manage(cookie_storage)
.manage(mail_queue)
.manage(mail_templates)
.register(catchers![helper::server_errors::unauthorized, helper::server_errors::forbidden, helper::server_errors::notfound, helper::server_errors::notimplemented])
.mount(
"/",
@ -55,12 +84,15 @@ fn main() {
modules::dashboard::view::dashboard,
modules::welcome::view::welcome_get::welcome_get,
modules::welcome::view::welcome_post::welcome_post,
modules::welcome::view::welcome_post::password_reset_post,
modules::welcome::view::welcome_post::password_change_post,
modules::welcome::view::welcome_post::password_change_get,
modules::welcome::view::login_select_get::login_select_get,
modules::welcome::view::logout::logout_get,
modules::member_management::view::selection_get::member_management_selection_get,
modules::member_management::view::selection_post::member_management_selection_post,
modules::member_management::view::core_data_get::member_management_core_data_get,
modules::member_management::view::core_data_post::member_management_core_data_post,
modules::member_management::view::profile_get::member_management_core_data_get,
modules::member_management::view::profile_post::member_management_core_data_post,
modules::member_management::view::groups_get::member_management_groups_get,
modules::api::member_management::view::member_qualifications::api_member_remove_qualification,
modules::api::member_management::view::member_qualifications::api_member_get_qualifications,
@ -85,6 +117,9 @@ fn main() {
modules::api::units::read::read_unit_list,
modules::api::units::update::put_member_in_unit,
modules::api::units::delete::delete_member_from_unit,
modules::api::users::create::create_user,
modules::api::users::delete::delete_user,
modules::api::users::update::update_user,
modules::member_management::view::personal_profile::member_management_personal_profile_get,
modules::member_management::view::personal_profile::member_management_personal_profile_post,
modules::member_management::view::create_member::member_management_add_member,

View File

@ -2,4 +2,5 @@ pub mod groups;
pub mod member_management;
pub mod members;
pub mod model;
pub mod units;
pub mod units;
pub mod users;

View File

@ -14,7 +14,7 @@ pub fn read_unit_list(
settings: State<Settings>,
cookie: SessionCookie,
) -> Result<Json<Vec<RawUnit>>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
let _caller = parse_member_cookie(cookie.member)?;
match get_units(&settings){
Ok(units) => Ok(Json(units)),

View File

@ -0,0 +1,41 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::helper::session_cookies::model::SessionCookie;
use crate::modules::member_management::model::login::Login;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use rocket_contrib::json::Json;
use crate::modules::api::member_management::controller::parser::parse_member_cookie;
use crate::helper::check_access::check_access;
use crate::database::controller::groups::get_groups_for_member;
use rocket::request::Form;
use crate::database::controller::users::add_user_to_member;
use crate::helper::translate_diesel_error::translate_diesel;
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct CreateUserData{
pub(crate) email: String,
pub(crate) member_id: uuid::Uuid,
}
#[post("/api/users", format = "json", data = "<create_user_data>")]
pub fn create_user(settings: State<Settings>, cookie: SessionCookie, create_user_data: Json<CreateUserData>) -> Result<Json<Login>, Json<ApiErrorWrapper>>{
let caller = parse_member_cookie(cookie.member)?;
let data = create_user_data.into_inner();
let member_groups = get_groups_for_member(&settings, data.member_id);
if caller.entity_id != data.member_id { //Skip permission check if user edits own login
if !check_access(&settings, data.member_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {
return Err(Json(ApiError::new(401, "Keine Rechte Login für dieses Mitglied anzulegen!".to_string()).to_wrapper()))
}
}
match add_user_to_member(&settings, data.member_id, data.email){
Ok(user) => Ok(Json(Login{
user_id: Some(user.id),
email: Some(user.email),
login_allowed: true
})),
Err(e) => Err(translate_diesel(e))
}
}

View File

@ -0,0 +1,38 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::helper::session_cookies::model::SessionCookie;
use crate::modules::member_management::model::login::Login;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use rocket_contrib::json::Json;
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_uuid};
use crate::helper::check_access::check_access;
use crate::database::controller::groups::get_groups_for_member;
use rocket::request::Form;
use crate::database::controller::users::{add_user_to_member, remove_user};
use crate::helper::translate_diesel_error::translate_diesel;
use crate::database::controller::members::get_members_by_user_uuid;
#[delete("/api/users/<user_id>", format = "json")]
pub fn delete_user(settings: State<Settings>, cookie: SessionCookie, user_id: String) -> Result<(), Json<ApiErrorWrapper>>{
let caller = parse_member_cookie(cookie.member)?;
let user_id = parse_uuid(user_id)?;
let member = get_members_by_user_uuid(user_id, &settings);
let member = match member.first(){
Some(member) => member,
None => return Err(Json(ApiError::new(404, "Nicht gefunden.".to_string()).to_wrapper()))
};
let member_groups = get_groups_for_member(&settings, member.entity_id);
if caller.entity_id != member.entity_id { //Skip permission check if user edits own login
if !check_access(&settings, member.entity_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {
return Err(Json(ApiError::new(401, "Keine Rechte Login für dieses Mitglied anzulegen!".to_string()).to_wrapper()))
}
}
match remove_user(&settings, user_id){
Ok(_) => Ok(()),
Err(e) => Err(translate_diesel(e))
}
}

View File

@ -0,0 +1,4 @@
pub mod create;
pub mod delete;
pub mod update;
pub mod read;

View File

View File

@ -0,0 +1,48 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::helper::session_cookies::model::SessionCookie;
use crate::modules::member_management::model::login::Login;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use rocket_contrib::json::Json;
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_uuid};
use crate::helper::check_access::check_access;
use crate::database::controller::groups::get_groups_for_member;
use rocket::request::Form;
use crate::database::controller::users::{add_user_to_member, update_user_email};
use crate::helper::translate_diesel_error::translate_diesel;
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct UpdateUserData{
pub(crate) user_id: uuid::Uuid,
pub(crate) email: String,
pub(crate) member_id: uuid::Uuid,
}
#[put("/api/users/<user_id>", format = "json", data = "<update_user_data>")]
pub fn update_user(settings: State<Settings>, cookie: SessionCookie, user_id: String, update_user_data: Json<UpdateUserData>) -> Result<Json<Login>, Json<ApiErrorWrapper>>{
let caller = parse_member_cookie(cookie.member)?;
let data = update_user_data.into_inner();
let user_id = parse_uuid(user_id)?;
if user_id != data.user_id{
return Err(Json(ApiError::new(400, "User id's doesn't match".to_string()).to_wrapper()))
}
let member_groups = get_groups_for_member(&settings, data.member_id);
if caller.entity_id != data.member_id { //Skip permission check if user edits own login
if !check_access(&settings, data.member_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {
return Err(Json(ApiError::new(401, "Keine Rechte Login für dieses Mitglied zu verändern!".to_string()).to_wrapper()))
}
}
match update_user_email(&settings, user_id, data.email){
Ok(user) => Ok(Json(Login{
user_id: Some(user.id),
email: Some(user.email),
login_allowed: true
})),
Err(e) => Err(translate_diesel(e))
}
}

View File

@ -0,0 +1,31 @@
use crate::database::model::users::User;
use crate::modules::member_management::model::login::Login;
use diesel::result::Error;
impl From<Result<Option<User>, diesel::result::Error>> for Login{
fn from(u: Result<Option<User>, Error>) -> Self {
match u{
Ok(user_option) => match user_option{
Some(user) => {
Login{
user_id: Some(user.id),
email: Some(user.email),
login_allowed: true
}
},
None => {
Login{
user_id: None,
email: None,
login_allowed: false
}
}
},
Err(e) => Login{
user_id: None,
email: None,
login_allowed: false
}
}
}
}

View File

@ -5,6 +5,7 @@ use crate::database::controller::member_qualifications::get_qualifcation_categor
use crate::helper::age_calculator::calculate_age;
use crate::helper::caller_permissions::get_permissions_for_context;
use crate::helper::check_access::check_access_legacy;
use crate::modules::member_management::model::login::Login;
use crate::helper::settings::Settings;
use crate::helper::sitebuilder::model::general::{Footer, Header, Script, Stylesheet};
use crate::helper::sitebuilder::model::sidebar::Sidebar;
@ -16,6 +17,7 @@ use rocket::http::Status;
use rocket::State;
use rocket_contrib::templates::Template;
use crate::database::controller::units_members::get_members_units;
use crate::database::controller::users::get_user_by_member;
pub fn handle_view(
settings: &State<Settings>,
@ -35,7 +37,7 @@ pub fn handle_view(
},
],
};
let mut sidebar = Sidebar::new(member.clone());
let sidebar = Sidebar::new(member.clone());
let age = match member.date_of_birth {
Some(date) => Some(calculate_age(date)),
@ -49,9 +51,11 @@ pub fn handle_view(
let caller_permissions = get_permissions_for_context(settings, &member, &member);
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_personal_profile",
MemberModuleProfile {
@ -67,6 +71,7 @@ pub fn handle_view(
sidebar,
caller_permissions,
units,
login
},
));
}
@ -89,7 +94,7 @@ pub fn handle_edit(
},
],
};
let mut sidebar = Sidebar::new(member.clone());
let sidebar = Sidebar::new(member.clone());
let age = match member.date_of_birth {
Some(date) => Some(calculate_age(date)),
@ -104,9 +109,11 @@ pub fn handle_edit(
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_personal_profile",
MemberModuleProfile {
@ -121,7 +128,8 @@ pub fn handle_edit(
readonly: false,
sidebar,
caller_permissions,
units
units,
login
},
));
}

View File

@ -9,6 +9,7 @@ use crate::helper::settings::Settings;
use crate::helper::sitebuilder::model::general::{Footer, Header, Script, Stylesheet};
use crate::helper::sitebuilder::model::sidebar::Sidebar;
use crate::modules::member_management::model::member::Member;
use crate::modules::member_management::model::login::Login;
use crate::modules::member_management::model::member_module::{
EmptyMemberModuleProfile, MemberModuleProfile,
};
@ -16,6 +17,7 @@ use rocket::http::Status;
use rocket::State;
use rocket_contrib::templates::Template;
use crate::database::controller::units_members::get_members_units;
use crate::database::controller::users::get_user_by_member;
/// No member submitted, only show searchbar
pub fn handle_empty(caller: Member) -> Result<Template, Status> {
@ -86,9 +88,11 @@ pub fn handle_view(
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_profile",
MemberModuleProfile {
@ -104,6 +108,7 @@ pub fn handle_view(
sidebar,
caller_permissions,
units,
login,
},
));
}
@ -155,9 +160,11 @@ pub fn handle_edit(
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_profile",
MemberModuleProfile {
@ -172,7 +179,8 @@ pub fn handle_edit(
readonly: false,
sidebar,
caller_permissions,
units
units,
login
},
));
}

View File

@ -6,4 +6,5 @@ pub mod member_profile;
pub mod member_trait;
pub mod render;
pub mod member_personal_profile;
pub mod create_member;
pub mod create_member;
pub mod login_trait;

View File

@ -0,0 +1,6 @@
#[derive(Serialize)]
pub struct Login{
pub(crate) user_id: Option<uuid::Uuid>,
pub(crate) email: Option<String>,
pub(crate) login_allowed: bool,
}

View File

@ -8,6 +8,7 @@ use crate::modules::member_management::model::member::{Member, MemberWithAccess}
use crate::modules::member_management::model::qualifications::QualificationCategory;
use std::collections::HashMap;
use crate::database::model::units::Unit;
use crate::modules::member_management::model::login::Login;
#[derive(Serialize)]
pub struct MemberModuleSelection {
@ -39,6 +40,7 @@ pub struct MemberModuleProfile {
pub sidebar: Sidebar,
pub caller_permissions: Vec<String>,
pub units: Vec<Unit>,
pub login: Login,
}
#[derive(Serialize)]

View File

@ -4,3 +4,4 @@ pub mod licenses;
pub mod member;
pub mod member_module;
pub mod qualifications;
pub mod login;

View File

@ -40,16 +40,12 @@ pub fn member_management_add_member_post(
None => return Err(Status::Unauthorized),
};
debug!("Test");
if !caller.has_permission("modules.member_management.profile.create".to_string()){
return Err(Status::Unauthorized)
}
debug!("Test2");
match create_member(&settings, create_member_form.into_inner()){
Ok(entity_id) => Ok(Redirect::to(format!("/portal/mm/profile?action=view&id={}",entity_id))),
Err(e) => Err(Status::InternalServerError),
Err(_) => Err(Status::InternalServerError),
}
}

View File

@ -1,5 +1,5 @@
pub mod core_data_get;
pub mod core_data_post;
pub mod profile_get;
pub mod profile_post;
pub mod groups_get;
pub mod selection_get;
pub mod selection_post;

View File

@ -1,20 +1,53 @@
use crate::database::controller::users::get_user_by_email;
use crate::database::model::users::User;
use crate::helper::session_cookies::model::SessionCookieStorage;
use crate::modules::welcome::model::login_error_type::LoginError;
use crate::helper::settings::Settings;
use crate::modules::welcome::model::login_form::LoginForm;
use chrono::{Duration, Utc};
use rocket::http::{Cookie, Cookies};
use rocket::State;
use crate::database::controller::login_protection::login_attempts_exceeded;
pub fn check_login(login_form: LoginForm, settings: &State<Settings>) -> Result<User, LoginError> {
let user: User = match get_user_by_email(login_form.login_email.clone(), &settings){
Some(user) => match login_attempts_exceeded(settings, login_form.login_email){
Ok(result) => {
if result{
return Err(LoginError::MaxLoginAttemptsExceeded)
}else{
user
}
},
Err(_) => {
return Err(LoginError::DatabaseError)
}
}
None => {
match login_attempts_exceeded(settings, login_form.login_email){
Ok(result) => {
if result{
return Err(LoginError::MaxLoginAttemptsExceeded)
}else{
return Err(LoginError::UserNotFound)
}
},
Err(_) => {
return Err(LoginError::DatabaseError)
}
}
}
};
let password_hash = match user.password.clone(){
None => {return Err(LoginError::NoPassword);}
Some(pw) => pw
};
pub fn check_login(login_form: LoginForm, settings: &State<Settings>) -> Option<User> {
let user: User = get_user_by_email(login_form.login_email, &settings)?;
let password_hash = user.password.clone()?;
trace!("Comparing password hash for {}", user.id);
if argon2::verify_encoded(&password_hash, login_form.login_password.as_ref()).unwrap() {
Some(user)
Ok(user)
} else {
None
Err(LoginError::PasswordIncorrect)
}
}

View File

@ -1,2 +1,3 @@
pub mod login;
pub mod render;
pub mod password_reset;

View File

@ -0,0 +1,42 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::database::controller::users::get_user_by_email;
use crate::database::controller::password_resets::add_token;
use crate::helper::mail_templates::MailTemplates;
use crate::helper::mail_queue::queue::{Mail, MailQueue};
use std::sync::Arc;
#[derive(Serialize)]
pub struct PasswortResetMail{
frontpage: String,
email: String,
reset_url: String,
}
/// Checks if email belongs to user, if so resets password
pub fn request_password_reset(settings: &State<Settings>, mt: &State<MailTemplates>, mq: State<Arc<MailQueue>>, email: String) -> Result<(), ()>{
let user = match get_user_by_email(email.clone(), settings){ //Check if email belongs to user, if not return
Some(user) => user,
None => return Ok(()),
};
let token = match add_token(settings, user.id){
Ok(token) => token,
Err(e) => return Err(()),
};
let pwrm = PasswortResetMail{
frontpage: settings.application.url.clone(),
email: email.clone(),
reset_url: format!("{}password_reset?token={}", settings.application.url.clone(), token)
};
let body = match mt.registry.render("password-reset-de", &pwrm){
Ok(body) => body,
Err(e) => return Err(()),
};
let mail = Mail::new(settings.mail.from.clone(), vec![email], format!("[{}] - Passwort Zurücksetzen", settings.application.name.clone()), vec![], vec![], Some(settings.mail.reply_to.clone()), body, None); //TODO: Add deliver_until
match mq.add_mail(mail){
Ok(_) => Ok(()),
Err(_) => Err(())
}
}

View File

@ -4,8 +4,8 @@ use crate::modules::welcome::model::welcome_module::WelcomeModule;
pub fn get_context(alert: Option<Alert>) -> WelcomeModule {
let header = Header {
html_language: "en".to_string(),
site_title: "ERRMS".to_string(),
html_language: "de".to_string(),
site_title: "einsatz.online".to_string(),
stylesheets: vec![Stylesheet {
path: "/css/errms.css".to_string(),
}],

View File

@ -0,0 +1,33 @@
use std::error;
use std::error::Error as _;
use std::fmt;
#[derive(Debug)]
pub enum LoginError {
UserNotFound,
MaxLoginAttemptsExceeded,
PasswordIncorrect,
DatabaseError,
NoPassword
}
impl fmt::Display for LoginError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LoginError::UserNotFound =>
write!(f, "User not found"),
LoginError::MaxLoginAttemptsExceeded =>
write!(f, "Maximum login attempts exceeded"),
LoginError::PasswordIncorrect =>
write!(f, "Password incorrect"),
LoginError::DatabaseError => write!(f, "Database Error occured!"),
LoginError::NoPassword => write!(f, "User missing password! No login possible."),
}
}
}
impl error::Error for LoginError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}

View File

@ -1,2 +1,3 @@
pub mod login_form;
pub mod welcome_module;
pub mod login_error_type;

View File

@ -2,6 +2,8 @@ use crate::helper::sitebuilder::model::alerts::{Alert, AlertClass};
use crate::modules::welcome::controller::render::get_context;
use rocket::http::Status;
use rocket_contrib::templates::Template;
use rocket::State;
use std::sync::Arc;
#[get("/?<error>")]
pub fn welcome_get(error: Option<String>) -> Result<Template, Status> {
@ -15,6 +17,11 @@ pub fn welcome_get(error: Option<String>) -> Result<Template, Status> {
AlertClass::Success,
"Sie wurden erfolgreich abgemeldet!".to_string(),
)),
"password_reset_success" => Some(Alert::new(
AlertClass::Success,
"Ihr Passwort wurde erfolgreich zurückgesetzt!".to_string(),
)),
"password_reset_token_invalid" => Some(Alert::new(AlertClass::Danger, "Der Passwort zurücksetzen Token ist ungültig! Bitte einen neuen Anfordern!".to_string())),
"notimplemented" => Some(Alert::new(
AlertClass::Danger,
"Fehler: Diese Funktion wurde noch nicht implementiert!".to_string(),

View File

@ -9,6 +9,13 @@ use rocket::request::Form;
use rocket::response::Redirect;
use rocket::State;
use rocket_contrib::templates::Template;
use crate::modules::welcome::model::login_error_type::LoginError;
use crate::modules::welcome::controller::password_reset::request_password_reset;
use crate::helper::mail_templates::MailTemplates;
use crate::helper::mail_queue::queue::MailQueue;
use std::sync::Arc;
use crate::helper::sitebuilder::model::general::{Footer, Header, Stylesheet};
use crate::database::controller::password_resets::{validate_token, change_password_with_token, validate_password};
#[post("/", data = "<login_form>")]
pub fn welcome_post(
@ -19,22 +26,149 @@ pub fn welcome_post(
) -> Result<Redirect, Template> {
let user = check_login(login_form.into_inner(), &settings);
if log_enabled!(log::Level::Trace) {
match user.clone() {
Some(user) => trace!("LOGIN: {}", user.id),
None => trace!("LOGIN FAILED"),
}
}
let alert: Option<Alert> = match user {
Some(user) => {
Ok(user) => {
add_session_cookie(user, &settings, cookie_storage, cookies);
return Ok(Redirect::to("/loginselect"));
}
None => Some(Alert::new(
AlertClass::Danger,
"Login failed. Incorrect email or password!".to_string(),
)),
Err(e) => match e{
LoginError::UserNotFound => {
Some(Alert::new(
AlertClass::Danger,
"Anmelden fehlgeschlagen! Email oder Passwort falsch!".to_string()))
},
LoginError::MaxLoginAttemptsExceeded => {
Some(Alert::new(
AlertClass::Danger,
format!("Es wurden zu viele Anmeldeversuche durchgeführt. Der Account wurde für {} Minuten gesperrt! Bitte nutzen Sie ggf. die Passwort vergessen Funktion!", settings.application.login_lock_duration/60)))
},
LoginError::PasswordIncorrect => {
Some(Alert::new(
AlertClass::Danger,
"Anmelden fehlgeschlagen! Email oder Passwort falsch!".to_string()))
},
LoginError::DatabaseError => {
Some(Alert::new(
AlertClass::Danger,
"Es konnte keine Datenbankverbindung hergestellt werden!".to_string()))
}
LoginError::NoPassword => {
Some(Alert::new(
AlertClass::Danger,
"Anmelden fehlgeschlagen! Email oder Passwort falsch!".to_string()))
}
}
};
Err(Template::render("module_welcome", &get_context(alert)))
}
#[derive(Serialize, Deserialize, FromForm)]
pub struct PasswordResetForm{
pub(crate) email: String,
}
#[post("/password_reset", data = "<password_reset_form>")]
pub fn password_reset_post(
password_reset_form: Form<PasswordResetForm>,
settings: State<Settings>,
mt: State<MailTemplates>,
mq: State<Arc<MailQueue>>,
) -> Template {
let mut alert : Option<Alert> = None;
match request_password_reset(&settings, &mt, mq, password_reset_form.email.clone()){
Ok(_) => {
alert = Some(Alert::new(
AlertClass::Success,
"Falls ein Benutzer mit der angegebenen Email existiert wurde ein Link zum Zurücksetzen des Passwortes per Email verschickt!".to_string()))
}
Err(_) => {
alert = Some(Alert::new(
AlertClass::Danger,
"Das Passwort konnte nicht zurückgesetzt werden. Bitte versuchen Sie es später erneut.".to_string()))
}
}
Template::render("module_welcome", &get_context(alert))
}
#[derive(Serialize)]
pub struct PasswordResetPage{
header: Header,
footer: Footer,
logo_path: String,
alert: Option<Alert>,
}
#[get("/password_reset?<token>")]
pub fn password_change_get(
settings: State<Settings>,
token: String,
) -> Result<Template, Redirect> {
match validate_token(&settings, token){
Ok(_) => (),
Err(_) => {
return Err(Redirect::to("/?error=password_reset_token_invalid"))
}
}
let header = Header {
html_language: "de".to_string(),
site_title: "einsatz.online".to_string(),
stylesheets: vec![Stylesheet {
path: "/css/errms.css".to_string(),
}],
};
let footer = Footer { scripts: vec![] };
let render = PasswordResetPage{
header,
footer,
logo_path: "/img/logo.jpg".to_string(),
alert: None
};
Ok(Template::render("password_reset", &render))
}
#[derive(Serialize, Deserialize, FromForm)]
pub struct PasswordChangeForm{
pub(crate) password: String,
}
#[post("/password_reset?<token>", data = "<password_change_form>")]
pub fn password_change_post(
settings: State<Settings>,
password_change_form: Form<PasswordChangeForm>,
token: String,
) -> Result<Redirect, Template> {
let mut alert = None;
let password_change_form = password_change_form.into_inner();
if !validate_password(password_change_form.password.clone()){
alert = Some(Alert::new(AlertClass::Danger, "Das Passwort muss mindestens 10 Zeichen lang sein!".to_string()));
}else {
match change_password_with_token(&settings, token, password_change_form.password) {
Ok(_) => {
return Ok(Redirect::to("/?error=password_reset_success"))
},
Err(_) => {
alert = Some(Alert::new(AlertClass::Danger, "Es ist ein interner Fehler aufgetreten, das Passwort konnte nicht geändert werden.".to_string()));
}
}
}
let header = Header {
html_language: "de".to_string(),
site_title: "einsatz.online".to_string(),
stylesheets: vec![Stylesheet {
path: "/css/errms.css".to_string(),
}],
};
let footer = Footer { scripts: vec![] };
let render = PasswordResetPage{
header,
footer,
logo_path: "/img/logo.jpg".to_string(),
alert,
};
Err(Template::render("password_reset", &render))
}

View File

@ -57,6 +57,26 @@ table! {
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
cost_centres (short_id) {
short_id -> Int4,
description -> Nullable<Text>,
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
cost_centres_members (member_entity_id, cost_centre_shortid) {
member_entity_id -> Uuid,
cost_centre_shortid -> Int4,
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
@ -108,6 +128,17 @@ table! {
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
login_attempts (id) {
id -> Uuid,
email -> Text,
timestamp -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
@ -142,6 +173,17 @@ table! {
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
password_resets (token) {
token -> Text,
user_id -> Uuid,
issued -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
@ -244,7 +286,7 @@ table! {
users (id) {
id -> Uuid,
password -> Nullable<Text>,
email -> Nullable<Text>,
email -> Text,
}
}
@ -269,6 +311,8 @@ joinable!(addresses_entities -> entities (entitiy_id));
joinable!(buildings -> entities (entity_id));
joinable!(communication_targets -> communication_types (com_type));
joinable!(communication_targets -> entities (entity_id));
joinable!(cost_centres_members -> cost_centres (cost_centre_shortid));
joinable!(cost_centres_members -> members (member_entity_id));
joinable!(groups -> entities (entity_id));
joinable!(groups_entities -> entities (entity_id));
joinable!(groups_entities -> groups (group_id));
@ -278,6 +322,7 @@ joinable!(members -> entities (entity_id));
joinable!(members -> users (users_id));
joinable!(members_roles -> entities (member_id));
joinable!(members_roles -> roles (role_id));
joinable!(password_resets -> users (user_id));
joinable!(qualifications -> qualification_categories (category));
joinable!(qualifications_members -> members (member_id));
joinable!(qualifications_members -> qualifications (qualification_id));
@ -297,13 +342,17 @@ allow_tables_to_appear_in_same_query!(
buildings,
communication_targets,
communication_types,
cost_centres,
cost_centres_members,
entities,
groups,
groups_entities,
license_categories,
licenses_members,
login_attempts,
members,
members_roles,
password_resets,
permissions,
qualification_categories,
qualifications,