From 9f35b33dc7238f0d6748beafa79ca0af192b5ca6 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 29 Oct 2019 20:35:39 -0700 Subject: [PATCH] Halfway done with email, not fully working yet. --- server/.gitignore | 1 + server/Cargo.lock | 251 +++++++++++++ server/Cargo.toml | 1 - .../down.sql | 1 + .../up.sql | 6 + server/src/api/mod.rs | 3 + server/src/api/user.rs | 108 ++++++ server/src/db/mod.rs | 1 + server/src/db/password_reset_request.rs | 108 ++++++ server/src/db/src/schema.rs | 345 ------------------ server/src/db/user.rs | 23 +- server/src/lib.rs | 74 ++++ server/src/schema.rs | 61 ++-- server/src/websocket/server.rs | 10 + ui/src/components/login.tsx | 16 + ui/src/components/navbar.tsx | 80 +--- ui/src/components/user.tsx | 13 + ui/src/interfaces.ts | 16 + ui/src/services/WebSocketService.ts | 5 + ui/src/translations/en.ts | 3 + 20 files changed, 691 insertions(+), 435 deletions(-) create mode 100644 server/migrations/2019-10-24-002614_create_password_reset_request/down.sql create mode 100644 server/migrations/2019-10-24-002614_create_password_reset_request/up.sql create mode 100644 server/src/db/password_reset_request.rs delete mode 100644 server/src/db/src/schema.rs diff --git a/server/.gitignore b/server/.gitignore index 93c43d03..a68a6365 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,3 +1,4 @@ /target .env .idea +env_setup.sh diff --git a/server/Cargo.lock b/server/Cargo.lock index 72398c2a..e28b0f92 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -348,6 +348,11 @@ name = "arc-swap" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ascii_utils" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "atty" version = "0.2.13" @@ -404,6 +409,15 @@ dependencies = [ "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "base64" version = "0.10.1" @@ -465,6 +479,11 @@ dependencies = [ "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bufstream" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.3.2" @@ -523,6 +542,20 @@ name = "copyless" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "crc32fast" version = "1.2.0" @@ -624,6 +657,20 @@ name = "either" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "email" +version = "0.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "encoding" version = "0.2.33" @@ -731,6 +778,14 @@ dependencies = [ "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fast_chemail" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "flate2" version = "1.0.9" @@ -747,6 +802,19 @@ name = "fnv" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -949,6 +1017,9 @@ dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", @@ -957,6 +1028,36 @@ dependencies = [ "strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lettre" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", + "hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lettre_email" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "email 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)", + "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libc" version = "0.2.60" @@ -1121,6 +1222,23 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "native-tls" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "net2" version = "0.2.33" @@ -1170,6 +1288,36 @@ name = "opaque-debug" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "openssl" +version = "0.10.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-sys" +version = "0.9.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "owning_ref" version = "0.4.0" @@ -1288,6 +1436,11 @@ dependencies = [ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pkg-config" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ppv-lite86" version = "0.2.5" @@ -1343,6 +1496,18 @@ dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (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.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.6.5" @@ -1510,6 +1675,14 @@ dependencies = [ "ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "resolv-conf" version = "0.6.2" @@ -1550,6 +1723,20 @@ name = "ryu" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "schannel" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scopeguard" version = "0.3.3" @@ -1560,6 +1747,25 @@ name = "scopeguard" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "security-framework" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "semver" version = "0.9.0" @@ -1744,6 +1950,19 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termcolor" version = "1.0.5" @@ -2017,6 +2236,14 @@ name = "utf8-ranges" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "uuid" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "v_escape" version = "0.7.2" @@ -2156,18 +2383,21 @@ dependencies = [ "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36b7aa1ccb7d7ea3f437cf025a2ab1c47cc6c1bc9fc84918ff449def12f5e282" "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" +"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" "checksum awc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4c4763e6aa29a801d761dc3464f081d439ea5249ba90c3c3bdfc8dd3f739d233" "checksum backtrace 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)" = "88fb679bc9af8fa639198790a77f52d345fe13656c08b43afa9424c206b731c6" "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" "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 bcrypt 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4fd6a91ff640809cfab4ea74312a892238a7bbae53adbf717b71122deb0c85" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" "checksum blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" "checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" "checksum brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +"checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" @@ -2176,6 +2406,8 @@ dependencies = [ "checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" +"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" @@ -2187,6 +2419,7 @@ dependencies = [ "checksum dotenv 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4424bad868b0ffe6ae351ee463526ba625bbca817978293bbe6bb7dc1804a175" "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" +"checksum email 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4" "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" "checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" @@ -2199,8 +2432,11 @@ dependencies = [ "checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" "checksum flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "550934ad4808d5d39365e5d61727309bf18b3b02c6c56b729cb92e7dd84bc3d8" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" @@ -2224,6 +2460,8 @@ dependencies = [ "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c66afaa5dfadbb81d4e00fd1d1ab057c7cd4c799c5a44e0009386d553587e728" +"checksum lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bbb68ca999042d965476e47bbdbacd52db0927348b6f8062c44dd04a3b1fd43b" "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" @@ -2243,12 +2481,16 @@ dependencies = [ "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" +"checksum openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2f372b2b53ce10fb823a337aaa674e3a7d072b957c6264d0f4ff0bd86e657449" +"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +"checksum openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)" = "c977d08e1312e2f7e4b86f9ebaa0ed3b19d1daff75fae88bbb88108afbd801fc" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7" @@ -2261,6 +2503,7 @@ dependencies = [ "checksum phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda" "checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" @@ -2269,6 +2512,7 @@ dependencies = [ "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" @@ -2287,13 +2531,18 @@ dependencies = [ "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b23da8dfd98a84bd7e08700190a5d9f7d2d38abd4369dd1dae651bc40bfd2cc" "checksum regex-syntax 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5485bf1523a9ed51c4964273f22f63f24e31632adb5dad134f488f86a3875c" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb" "checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" +"checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2" +"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)" = "d46b3dfedb19360a74316866cef04687cd4d6a70df8e6a506c63512790769b72" @@ -2317,6 +2566,7 @@ dependencies = [ "checksum syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)" = "bc945221ccf4a7e8c31222b9d1fc77aefdd6638eb901a6ce457a3dc29d4c31e8" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" @@ -2345,6 +2595,7 @@ dependencies = [ "checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" +"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum v_escape 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8865501b78eef9193c1b45486acf18ba889e5662eba98854d6fc59d8ecf3542d" "checksum v_escape_derive 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "306896ff4b75998501263a1dc000456de442e21d68fe8c8bdf75c66a33a58e23" "checksum v_htmlescape 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7fbbe0fa88dd36f9c8cf61a218d4b953ba669de4d0785832f33cc72bd081e1be" diff --git a/server/Cargo.toml b/server/Cargo.toml index 6c50c695..a8964e17 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -25,4 +25,3 @@ strum_macros = "0.15.0" jsonwebtoken = "6.0.1" regex = "1.1.9" lazy_static = "1.3.0" - diff --git a/server/migrations/2019-10-24-002614_create_password_reset_request/down.sql b/server/migrations/2019-10-24-002614_create_password_reset_request/down.sql new file mode 100644 index 00000000..33500dfe --- /dev/null +++ b/server/migrations/2019-10-24-002614_create_password_reset_request/down.sql @@ -0,0 +1 @@ +drop table password_reset_request; diff --git a/server/migrations/2019-10-24-002614_create_password_reset_request/up.sql b/server/migrations/2019-10-24-002614_create_password_reset_request/up.sql new file mode 100644 index 00000000..15cfaa59 --- /dev/null +++ b/server/migrations/2019-10-24-002614_create_password_reset_request/up.sql @@ -0,0 +1,6 @@ +create table password_reset_request ( + id serial primary key, + user_id int references user_ on update cascade on delete cascade not null, + token_encrypted text not null, + published timestamp not null default now() +); diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index cab8a77b..6ccdef1a 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -11,6 +11,7 @@ use crate::db::user::*; use crate::db::user_mention::*; use crate::db::user_mention_view::*; use crate::db::user_view::*; +use crate::db::password_reset_request::*; use crate::db::*; use crate::{extract_usernames, has_slurs, naive_from_unix, naive_now, remove_slurs, Settings}; use failure::Error; @@ -61,6 +62,8 @@ pub enum UserOperation { TransferCommunity, TransferSite, DeleteAccount, + PasswordReset, + PasswordChange, } #[derive(Fail, Debug)] diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 5ac2b432..469c38a7 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -1,6 +1,7 @@ use super::*; use bcrypt::verify; use std::str::FromStr; +use crate::{generate_random_string,send_email}; #[derive(Serialize, Deserialize, Debug)] pub struct Login { @@ -139,6 +140,24 @@ pub struct DeleteAccount { auth: String, } + +#[derive(Serialize, Deserialize)] +pub struct PasswordReset { + email: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct PasswordResetResponse { + op: String, +} + +#[derive(Serialize, Deserialize)] +pub struct PasswordChange { + token: String, + password: String, + password_verify: String, +} + impl Perform for Oper { fn perform(&self) -> Result { let data: &Login = &self.data; @@ -802,3 +821,92 @@ impl Perform for Oper { }) } } + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: &PasswordReset = &self.data; + let conn = establish_connection(); + + // Fetch that email + let user: User_ = match User_::find_by_email(&conn, &data.email) { + Ok(user) => user, + Err(_e) => { + return Err(APIError::err( + &self.op, + "couldnt_find_that_username_or_email", + ))? + } + }; + + // Generate a random token + let token = generate_random_string(); + + // Insert the row + PasswordResetRequest::create_token(&conn, user.id, &token)?; + + // Email the pure token to the user. + // TODO no i18n support here. + let user_email = &user.email.expect("email"); + let subject = &format!("Password reset for {}", user.name); + let hostname = Settings::get().hostname; + let html = &format!("

Password Reset Request for {}


Click here to reset your password", user.name, hostname, &token); + match send_email(subject, user_email, &user.name, html) { + Ok(_o) => _o, + Err(_e) => { + return Err(APIError::err( + &self.op, + &_e.to_string(), + ))? + } + }; + + Ok(PasswordResetResponse { + op: self.op.to_string(), + }) + } +} + +impl Perform for Oper { + fn perform(&self) -> Result { + let data: &PasswordChange = &self.data; + let conn = establish_connection(); + + // Fetch the user_id from the token + let user_id = PasswordResetRequest::read_from_token(&conn, &data.token)?.user_id; + + // Make sure passwords match + if &data.password != &data.password_verify { + return Err(APIError::err(&self.op, "passwords_dont_match"))?; + } + + // Fetch the user + let read_user = User_::read(&conn, user_id)?; + + // Update the user with the new password + let user_form = UserForm { + name: read_user.name, + fedi_name: read_user.fedi_name, + email: read_user.email, + password_encrypted: data.password.to_owned(), + preferred_username: read_user.preferred_username, + updated: Some(naive_now()), + admin: read_user.admin, + banned: read_user.banned, + show_nsfw: read_user.show_nsfw, + theme: read_user.theme, + default_sort_type: read_user.default_sort_type, + default_listing_type: read_user.default_listing_type, + }; + + let updated_user = match User_::update_password(&conn, user_id, &user_form) { + Ok(user) => user, + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, + }; + + // Return the jwt + Ok(LoginResponse { + op: self.op.to_string(), + jwt: updated_user.jwt(), + }) + } +} diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index 2045692d..8070041c 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -17,6 +17,7 @@ pub mod user; pub mod user_mention; pub mod user_mention_view; pub mod user_view; +pub mod password_reset_request; pub trait Crud { fn create(conn: &PgConnection, form: &T) -> Result diff --git a/server/src/db/password_reset_request.rs b/server/src/db/password_reset_request.rs new file mode 100644 index 00000000..e9968aa8 --- /dev/null +++ b/server/src/db/password_reset_request.rs @@ -0,0 +1,108 @@ +use super::*; +use crate::schema::password_reset_request; +use crate::schema::password_reset_request::dsl::*; + +use bcrypt::{hash, DEFAULT_COST}; + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name = "password_reset_request"] +pub struct PasswordResetRequest { + pub id: i32, + pub user_id: i32, + pub token_encrypted: String, + pub published: chrono::NaiveDateTime, +} + +#[derive(Insertable, AsChangeset, Clone)] +#[table_name = "password_reset_request"] +pub struct PasswordResetRequestForm { + pub user_id: i32, + pub token_encrypted: String, +} + +impl Crud for PasswordResetRequest { + fn read(conn: &PgConnection, password_reset_request_id: i32) -> Result { + use crate::schema::password_reset_request::dsl::*; + password_reset_request.find(password_reset_request_id).first::(conn) + } + fn delete(conn: &PgConnection, password_reset_request_id: i32) -> Result { + diesel::delete(password_reset_request.find(password_reset_request_id)).execute(conn) + } + fn create(conn: &PgConnection, form: &PasswordResetRequestForm) -> Result { + insert_into(password_reset_request).values(form).get_result::(conn) + } + fn update(conn: &PgConnection, password_reset_request_id: i32, form: &PasswordResetRequestForm) -> Result { + diesel::update(password_reset_request.find(password_reset_request_id)) + .set(form) + .get_result::(conn) + } +} + +impl PasswordResetRequest { + pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result { + let token_hash = + hash(token, DEFAULT_COST).expect("Couldn't hash token"); + + let form = PasswordResetRequestForm { + user_id: from_user_id, + token_encrypted: token_hash, + }; + + Self::create(&conn, &form) + } + pub fn read_from_token(conn: &PgConnection, token: &str) -> Result { + let token_hash = + hash(token, DEFAULT_COST).expect("Couldn't hash token"); + + password_reset_request.filter(token_encrypted.eq(token_hash)).first::(conn) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::super::user::*; + + #[test] + fn test_crud() { + let conn = establish_connection(); + + let new_user = UserForm { + name: "thommy prw".into(), + fedi_name: "rrf".into(), + preferred_username: None, + password_encrypted: "nope".into(), + email: None, + admin: false, + banned: false, + updated: None, + show_nsfw: false, + theme: "darkly".into(), + default_sort_type: SortType::Hot as i16, + default_listing_type: ListingType::Subscribed as i16, + }; + + let inserted_user = User_::create(&conn, &new_user).unwrap(); + + let new_password_reset_request = PasswordResetRequestForm { + user_id: inserted_user.id, + token_encrypted: "no".into(), + }; + + let inserted_password_reset_request = PasswordResetRequest::create(&conn, &new_password_reset_request).unwrap(); + + let expected_password_reset_request = PasswordResetRequest { + id: inserted_password_reset_request.id, + user_id: inserted_user.id, + token_encrypted: "no".into(), + published: inserted_password_reset_request.published, + }; + + let read_password_reset_request = PasswordResetRequest::read(&conn, inserted_password_reset_request.id).unwrap(); + let num_deleted = User_::delete(&conn, inserted_user.id).unwrap(); + + assert_eq!(expected_password_reset_request, read_password_reset_request); + assert_eq!(expected_password_reset_request, inserted_password_reset_request); + assert_eq!(1, num_deleted); + } +} diff --git a/server/src/db/src/schema.rs b/server/src/db/src/schema.rs deleted file mode 100644 index 8693db25..00000000 --- a/server/src/db/src/schema.rs +++ /dev/null @@ -1,345 +0,0 @@ -table! { - category (id) { - id -> Int4, - name -> Varchar, - } -} - -table! { - comment (id) { - id -> Int4, - creator_id -> Int4, - post_id -> Int4, - parent_id -> Nullable, - content -> Text, - removed -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - } -} - -table! { - comment_like (id) { - id -> Int4, - user_id -> Int4, - comment_id -> Int4, - post_id -> Int4, - score -> Int2, - published -> Timestamp, - } -} - -table! { - comment_saved (id) { - id -> Int4, - comment_id -> Int4, - user_id -> Int4, - published -> Timestamp, - } -} - -table! { - community (id) { - id -> Int4, - name -> Varchar, - title -> Varchar, - description -> Nullable, - category_id -> Int4, - creator_id -> Int4, - removed -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - nsfw -> Bool, - } -} - -table! { - community_follower (id) { - id -> Int4, - community_id -> Int4, - user_id -> Int4, - published -> Timestamp, - } -} - -table! { - community_moderator (id) { - id -> Int4, - community_id -> Int4, - user_id -> Int4, - published -> Timestamp, - } -} - -table! { - community_user_ban (id) { - id -> Int4, - community_id -> Int4, - user_id -> Int4, - published -> Timestamp, - } -} - -table! { - mod_add (id) { - id -> Int4, - mod_user_id -> Int4, - other_user_id -> Int4, - removed -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_add_community (id) { - id -> Int4, - mod_user_id -> Int4, - other_user_id -> Int4, - community_id -> Int4, - removed -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_ban (id) { - id -> Int4, - mod_user_id -> Int4, - other_user_id -> Int4, - reason -> Nullable, - banned -> Nullable, - expires -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_ban_from_community (id) { - id -> Int4, - mod_user_id -> Int4, - other_user_id -> Int4, - community_id -> Int4, - reason -> Nullable, - banned -> Nullable, - expires -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_lock_post (id) { - id -> Int4, - mod_user_id -> Int4, - post_id -> Int4, - locked -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_remove_comment (id) { - id -> Int4, - mod_user_id -> Int4, - comment_id -> Int4, - reason -> Nullable, - removed -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_remove_community (id) { - id -> Int4, - mod_user_id -> Int4, - community_id -> Int4, - reason -> Nullable, - removed -> Nullable, - expires -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_remove_post (id) { - id -> Int4, - mod_user_id -> Int4, - post_id -> Int4, - reason -> Nullable, - removed -> Nullable, - when_ -> Timestamp, - } -} - -table! { - mod_sticky_post (id) { - id -> Int4, - mod_user_id -> Int4, - post_id -> Int4, - stickied -> Nullable, - when_ -> Timestamp, - } -} - -table! { - post (id) { - id -> Int4, - name -> Varchar, - url -> Nullable, - body -> Nullable, - creator_id -> Int4, - community_id -> Int4, - removed -> Bool, - locked -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - nsfw -> Bool, - stickied -> Bool, - } -} - -table! { - post_like (id) { - id -> Int4, - post_id -> Int4, - user_id -> Int4, - score -> Int2, - published -> Timestamp, - } -} - -table! { - post_read (id) { - id -> Int4, - post_id -> Int4, - user_id -> Int4, - published -> Timestamp, - } -} - -table! { - post_saved (id) { - id -> Int4, - post_id -> Int4, - user_id -> Int4, - published -> Timestamp, - } -} - -table! { - site (id) { - id -> Int4, - name -> Varchar, - description -> Nullable, - creator_id -> Int4, - published -> Timestamp, - updated -> Nullable, - } -} - -table! { - user_ (id) { - id -> Int4, - name -> Varchar, - fedi_name -> Varchar, - preferred_username -> Nullable, - password_encrypted -> Text, - email -> Nullable, - icon -> Nullable, - admin -> Bool, - banned -> Bool, - published -> Timestamp, - updated -> Nullable, - show_nsfw -> Bool, - theme -> Varchar, - } -} - -table! { - user_ban (id) { - id -> Int4, - user_id -> Int4, - published -> Timestamp, - } -} - -table! { - user_mention (id) { - id -> Int4, - recipient_id -> Int4, - comment_id -> Int4, - read -> Bool, - published -> Timestamp, - } -} - -joinable!(comment -> post (post_id)); -joinable!(comment -> user_ (creator_id)); -joinable!(comment_like -> comment (comment_id)); -joinable!(comment_like -> post (post_id)); -joinable!(comment_like -> user_ (user_id)); -joinable!(comment_saved -> comment (comment_id)); -joinable!(comment_saved -> user_ (user_id)); -joinable!(community -> category (category_id)); -joinable!(community -> user_ (creator_id)); -joinable!(community_follower -> community (community_id)); -joinable!(community_follower -> user_ (user_id)); -joinable!(community_moderator -> community (community_id)); -joinable!(community_moderator -> user_ (user_id)); -joinable!(community_user_ban -> community (community_id)); -joinable!(community_user_ban -> user_ (user_id)); -joinable!(mod_add_community -> community (community_id)); -joinable!(mod_ban_from_community -> community (community_id)); -joinable!(mod_lock_post -> post (post_id)); -joinable!(mod_lock_post -> user_ (mod_user_id)); -joinable!(mod_remove_comment -> comment (comment_id)); -joinable!(mod_remove_comment -> user_ (mod_user_id)); -joinable!(mod_remove_community -> community (community_id)); -joinable!(mod_remove_community -> user_ (mod_user_id)); -joinable!(mod_remove_post -> post (post_id)); -joinable!(mod_remove_post -> user_ (mod_user_id)); -joinable!(mod_sticky_post -> post (post_id)); -joinable!(mod_sticky_post -> user_ (mod_user_id)); -joinable!(post -> community (community_id)); -joinable!(post -> user_ (creator_id)); -joinable!(post_like -> post (post_id)); -joinable!(post_like -> user_ (user_id)); -joinable!(post_read -> post (post_id)); -joinable!(post_read -> user_ (user_id)); -joinable!(post_saved -> post (post_id)); -joinable!(post_saved -> user_ (user_id)); -joinable!(site -> user_ (creator_id)); -joinable!(user_ban -> user_ (user_id)); -joinable!(user_mention -> comment (comment_id)); -joinable!(user_mention -> user_ (recipient_id)); - -allow_tables_to_appear_in_same_query!( - category, - comment, - comment_like, - comment_saved, - community, - community_follower, - community_moderator, - community_user_ban, - mod_add, - mod_add_community, - mod_ban, - mod_ban_from_community, - mod_lock_post, - mod_remove_comment, - mod_remove_community, - mod_remove_post, - mod_sticky_post, - post, - post_like, - post_read, - post_saved, - site, - user_, - user_ban, - user_mention, -); diff --git a/server/src/db/user.rs b/server/src/db/user.rs index a378d3c2..da8e5dc2 100644 --- a/server/src/db/user.rs +++ b/server/src/db/user.rs @@ -44,7 +44,6 @@ pub struct UserForm { impl Crud for User_ { fn read(conn: &PgConnection, user_id: i32) -> Result { - use crate::schema::user_::dsl::*; user_.find(user_id).first::(conn) } fn delete(conn: &PgConnection, user_id: i32) -> Result { @@ -69,6 +68,16 @@ impl User_ { Self::create(&conn, &edited_user) } + + pub fn update_password(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result { + let mut edited_user = form.clone(); + let password_hash = + hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password"); + edited_user.password_encrypted = password_hash; + + Self::update(&conn, user_id, &edited_user) + } + pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result { user_.filter(name.eq(from_user_name)).first::(conn) } @@ -129,6 +138,16 @@ impl User_ { .first::(conn) } } + + pub fn find_by_email( + conn: &PgConnection, + from_email: &str, + ) -> Result { + user_ + .filter(email.eq(from_email)) + .first::(conn) + } + pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result { let claims: Claims = Claims::decode(&jwt).expect("Invalid token").claims; @@ -139,6 +158,8 @@ impl User_ { #[cfg(test)] mod tests { use super::*; + use super::User_; + #[test] fn test_crud() { let conn = establish_connection(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 715d9ef3..b06f29be 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -18,6 +18,8 @@ pub extern crate regex; pub extern crate serde; pub extern crate serde_json; pub extern crate strum; +pub extern crate lettre; +pub extern crate lettre_email; pub mod api; pub mod apub; @@ -29,6 +31,13 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use dotenv::dotenv; use regex::Regex; use std::env; +use rand::{thread_rng, Rng}; +use rand::distributions::Alphanumeric; +use lettre::{SmtpClient, Transport}; +use lettre_email::{Email}; +use lettre::smtp::authentication::{Credentials, Mechanism}; +use lettre::smtp::extension::ClientId; +use lettre::smtp::ConnectionReuseParameters; pub struct Settings { db_url: String, @@ -40,11 +49,31 @@ pub struct Settings { rate_limit_post_per_second: i32, rate_limit_register: i32, rate_limit_register_per_second: i32, + email_config: Option, +} + +pub struct EmailConfig { + smtp_server: String, + smtp_login: String, + smtp_password: String, + smtp_from_address: String, } impl Settings { fn get() -> Self { dotenv().ok(); + + let email_config = if env::var("SMTP_SERVER").is_ok() { + Some(EmailConfig { + smtp_server: env::var("SMTP_SERVER").expect("SMTP_SERVER must be set"), + smtp_login: env::var("SMTP_LOGIN").expect("SMTP_LOGIN must be set"), + smtp_password: env::var("SMTP_PASSWORD").expect("SMTP_PASSWORD must be set"), + smtp_from_address: env::var("SMTP_FROM_ADDRESS").expect("SMTP_FROM_ADDRESS must be set") + }) + } else { + None + }; + Settings { db_url: env::var("DATABASE_URL").expect("DATABASE_URL must be set"), hostname: env::var("HOSTNAME").unwrap_or("rrr".to_string()), @@ -73,6 +102,7 @@ impl Settings { .unwrap_or("3600".to_string()) .parse() .unwrap(), + email_config: email_config, } } fn api_endpoint(&self) -> String { @@ -118,6 +148,44 @@ pub fn extract_usernames(test: &str) -> Vec<&str> { matches.iter().map(|t| &t[3..]).collect() } +pub fn generate_random_string() -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(30) + .collect() +} + +pub fn send_email(subject: &str, to_email: &str, to_username: &str, html: &str) -> Result<(), String> { + + let email_config = Settings::get().email_config.ok_or("no_email_setup")?; + + let email = Email::builder() + // .to((to_email, username)) + .to((to_email, to_username)) + .from((email_config.smtp_login.to_owned(), email_config.smtp_from_address)) + .subject(subject) + .html(html) + .build() + .unwrap(); + + let mut mailer = SmtpClient::new_simple(&email_config.smtp_server).unwrap() + .hello_name(ClientId::Domain("localhost".to_string())) + .credentials(Credentials::new( + email_config.smtp_login.to_owned(), + email_config.smtp_password.to_owned())) + .smtp_utf8(true) + .authentication_mechanism(Mechanism::Plain) + .connection_reuse(ConnectionReuseParameters::ReuseUnlimited) + .transport(); + + let result = mailer.send(email.into()); + + match result { + Ok(_) => Ok(()), + Err(_) => Err("no_email_setup".to_string()), + } +} + #[cfg(test)] mod tests { use crate::{extract_usernames, has_slurs, is_email_regex, remove_slurs, Settings}; @@ -152,6 +220,12 @@ mod tests { let expected = vec!["another", "testme"]; assert_eq!(usernames, expected); } + + // #[test] + // fn test_send_email() { + // let result = send_email("not a subject", "test_email@gmail.com", "ur user", "

HI there

"); + // assert!(result.is_ok()); + // } } lazy_static! { diff --git a/server/src/schema.rs b/server/src/schema.rs index e087e20e..15e87bbe 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -183,6 +183,15 @@ table! { } } +table! { + password_reset_request (id) { + id -> Int4, + user_id -> Int4, + token_encrypted -> Text, + published -> Timestamp, + } +} + table! { post (id) { id -> Int4, @@ -305,6 +314,7 @@ joinable!(mod_remove_post -> post (post_id)); joinable!(mod_remove_post -> user_ (mod_user_id)); joinable!(mod_sticky_post -> post (post_id)); joinable!(mod_sticky_post -> user_ (mod_user_id)); +joinable!(password_reset_request -> user_ (user_id)); joinable!(post -> community (community_id)); joinable!(post -> user_ (creator_id)); joinable!(post_like -> post (post_id)); @@ -319,29 +329,30 @@ joinable!(user_mention -> comment (comment_id)); joinable!(user_mention -> user_ (recipient_id)); allow_tables_to_appear_in_same_query!( - category, - comment, - comment_like, - comment_saved, - community, - community_follower, - community_moderator, - community_user_ban, - mod_add, - mod_add_community, - mod_ban, - mod_ban_from_community, - mod_lock_post, - mod_remove_comment, - mod_remove_community, - mod_remove_post, - mod_sticky_post, - post, - post_like, - post_read, - post_saved, - site, - user_, - user_ban, - user_mention, + category, + comment, + comment_like, + comment_saved, + community, + community_follower, + community_moderator, + community_user_ban, + mod_add, + mod_add_community, + mod_ban, + mod_ban_from_community, + mod_lock_post, + mod_remove_comment, + mod_remove_community, + mod_remove_post, + mod_sticky_post, + password_reset_request, + post, + post_like, + post_read, + post_saved, + site, + user_, + user_ban, + user_mention, ); diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 19274219..5bcca297 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -534,5 +534,15 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { + let password_reset: PasswordReset = serde_json::from_str(data)?; + let res = Oper::new(user_operation, password_reset).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::PasswordChange => { + let password_change: PasswordChange = serde_json::from_str(data)?; + let res = Oper::new(user_operation, password_change).perform()?; + Ok(serde_json::to_string(&res)?) + } } } diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index 87fa39fe..c2db7ee6 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -6,6 +6,7 @@ import { RegisterForm, LoginResponse, UserOperation, + PasswordResetForm, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { msgOp } from '../utils'; @@ -112,6 +113,12 @@ export class Login extends Component { class="form-control" required /> +
+ # +
@@ -279,6 +286,13 @@ export class Login extends Component { i.setState(i.state); } + handlePasswordReset(i: Login) { + let resetForm: PasswordResetForm = { + email: i.state.loginForm.username_or_email, + }; + WebSocketService.Instance.passwordReset(resetForm); + } + parseMessage(msg: any) { let op: UserOperation = msgOp(msg); if (msg.error) { @@ -299,6 +313,8 @@ export class Login extends Component { let res: LoginResponse = msg; UserService.Instance.login(res); this.props.history.push('/communities'); + } else if (op == UserOperation.PasswordReset) { + alert(i18n.t('reset_password_mail_sent')); } } } diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index 151559df..306dc74f 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -21,7 +21,6 @@ import { T } from 'inferno-i18next'; interface NavbarState { isLoggedIn: boolean; expanded: boolean; - expandUserDropdown: boolean; replies: Array; mentions: Array; fetchCount: number; @@ -39,14 +38,12 @@ export class Navbar extends Component { replies: [], mentions: [], expanded: false, - expandUserDropdown: false, siteName: undefined, }; constructor(props: any, context: any) { super(props, context); this.state = this.emptyState; - this.handleOverviewClick = this.handleOverviewClick.bind(this); this.keepFetchingUnreads(); @@ -137,50 +134,25 @@ export class Navbar extends Component {
+ {this.isCurrentUser && ( + + )} @@ -693,6 +701,11 @@ export class User extends Component { i.setState(i.state); } + handleLogoutClick(i: User) { + UserService.Instance.logout(); + i.context.router.history.push('/'); + } + handleDeleteAccount(i: User, event: any) { event.preventDefault(); i.state.deleteAccountLoading = true; diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 2f75efd9..9cd9bef4 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -36,6 +36,8 @@ export enum UserOperation { TransferCommunity, TransferSite, DeleteAccount, + PasswordReset, + PasswordChange, } export enum CommentSortType { @@ -686,3 +688,17 @@ export interface SearchResponse { export interface DeleteAccountForm { password: string; } + +export interface PasswordResetForm { + email: string; +} + +export interface PasswordResetResponse { + op: string; +} + +export interface PasswordChangeForm { + token: string; + password: string; + password_verify: string; +} diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index ea6c7722..c77816df 100644 --- a/ui/src/services/WebSocketService.ts +++ b/ui/src/services/WebSocketService.ts @@ -30,6 +30,7 @@ import { SearchForm, UserSettingsForm, DeleteAccountForm, + PasswordResetForm, } from '../interfaces'; import { webSocket } from 'rxjs/webSocket'; import { Subject } from 'rxjs'; @@ -274,6 +275,10 @@ export class WebSocketService { this.subject.next(this.wsSendWrapper(UserOperation.DeleteAccount, form)); } + public passwordReset(form: PasswordResetForm) { + this.subject.next(this.wsSendWrapper(UserOperation.PasswordReset, form)); + } + private wsSendWrapper(op: UserOperation, data: any) { let send = { op: UserOperation[op], data: data }; console.log(send); diff --git a/ui/src/translations/en.ts b/ui/src/translations/en.ts index a971ae6e..4e0d81db 100644 --- a/ui/src/translations/en.ts +++ b/ui/src/translations/en.ts @@ -116,6 +116,9 @@ export const en = { unread_messages: 'Unread Messages', password: 'Password', verify_password: 'Verify Password', + forgot_password: 'forgot password', + reset_password_mail_sent: 'Sent an Email to reset your password.', + no_email_setup: "This server hasn't correctly set up email.", email: 'Email', optional: 'Optional', expires: 'Expires', -- 2.44.1