]> Untitled Git - lemmy.git/commitdiff
Halfway done with email, not fully working yet.
authorDessalines <tyhou13@gmx.com>
Wed, 30 Oct 2019 03:35:39 +0000 (20:35 -0700)
committerDessalines <tyhou13@gmx.com>
Wed, 30 Oct 2019 03:35:39 +0000 (20:35 -0700)
20 files changed:
server/.gitignore
server/Cargo.lock
server/Cargo.toml
server/migrations/2019-10-24-002614_create_password_reset_request/down.sql [new file with mode: 0644]
server/migrations/2019-10-24-002614_create_password_reset_request/up.sql [new file with mode: 0644]
server/src/api/mod.rs
server/src/api/user.rs
server/src/db/mod.rs
server/src/db/password_reset_request.rs [new file with mode: 0644]
server/src/db/src/schema.rs [deleted file]
server/src/db/user.rs
server/src/lib.rs
server/src/schema.rs
server/src/websocket/server.rs
ui/src/components/login.tsx
ui/src/components/navbar.tsx
ui/src/components/user.tsx
ui/src/interfaces.ts
ui/src/services/WebSocketService.ts
ui/src/translations/en.ts

index 93c43d03782babefea298b31d87e9bb215e1cc19..a68a6365c858e46328d648167d37a99ab0f31801 100644 (file)
@@ -1,3 +1,4 @@
 /target
 .env
 .idea
+env_setup.sh
index 72398c2ac83fe5d16ea4f1ec133dda4c99d9b0a3..e28b0f92a2c3a11f027c7a1c2d7d3a4b75effc6b 100644 (file)
@@ -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"
index 6c50c6953e14bdf410a0fbac1ceff0acfa887944..a8964e1729d59b9e1c957ec615b82be198633928 100644 (file)
@@ -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 (file)
index 0000000..33500df
--- /dev/null
@@ -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 (file)
index 0000000..15cfaa5
--- /dev/null
@@ -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()
+);
index cab8a77b576a3231b70e8c9e2a775378a6626817..6ccdef1a31d766648ecfd0c9933f7ac1e5879d61 100644 (file)
@@ -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)]
index 5ac2b4321def058f0c5419683029ac227e632eec..469c38a7235aea9bc6369195ce0943a86baf7f87 100644 (file)
@@ -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<LoginResponse> for Oper<Login> {
   fn perform(&self) -> Result<LoginResponse, Error> {
     let data: &Login = &self.data;
@@ -802,3 +821,92 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
     })
   }
 }
+
+impl Perform<PasswordResetResponse> for Oper<PasswordReset> {
+  fn perform(&self) -> Result<PasswordResetResponse, Error> {
+    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!("<h1>Password Reset Request for {}</h1><br><a href={}/{}>Click here to reset your password</a>", 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<LoginResponse> for Oper<PasswordChange> {
+  fn perform(&self) -> Result<LoginResponse, Error> {
+    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(),
+    })
+  }
+}
index 2045692d5c6660116ae38f27e45f3c3299d7d5c9..8070041c613cda0af4f35f65829298637b62c343 100644 (file)
@@ -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<T> {
   fn create(conn: &PgConnection, form: &T) -> Result<Self, Error>
diff --git a/server/src/db/password_reset_request.rs b/server/src/db/password_reset_request.rs
new file mode 100644 (file)
index 0000000..e9968aa
--- /dev/null
@@ -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<PasswordResetRequestForm> for PasswordResetRequest {
+  fn read(conn: &PgConnection, password_reset_request_id: i32) -> Result<Self, Error> {
+    use crate::schema::password_reset_request::dsl::*;
+    password_reset_request.find(password_reset_request_id).first::<Self>(conn)
+  }
+  fn delete(conn: &PgConnection, password_reset_request_id: i32) -> Result<usize, Error> {
+    diesel::delete(password_reset_request.find(password_reset_request_id)).execute(conn)
+  }
+  fn create(conn: &PgConnection, form: &PasswordResetRequestForm) -> Result<Self, Error> {
+    insert_into(password_reset_request).values(form).get_result::<Self>(conn)
+  }
+  fn update(conn: &PgConnection, password_reset_request_id: i32, form: &PasswordResetRequestForm) -> Result<Self, Error> {
+    diesel::update(password_reset_request.find(password_reset_request_id))
+      .set(form)
+      .get_result::<Self>(conn)
+  }
+}
+
+impl PasswordResetRequest {
+  pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result<Self, Error> {
+    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<Self, Error> {
+    let token_hash =
+      hash(token, DEFAULT_COST).expect("Couldn't hash token");
+
+    password_reset_request.filter(token_encrypted.eq(token_hash)).first::<Self>(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 (file)
index 8693db2..0000000
+++ /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<Int4>,
-        content -> Text,
-        removed -> Bool,
-        read -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        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<Text>,
-        category_id -> Int4,
-        creator_id -> Int4,
-        removed -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        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<Bool>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_add_community (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        other_user_id -> Int4,
-        community_id -> Int4,
-        removed -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_ban (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        other_user_id -> Int4,
-        reason -> Nullable<Text>,
-        banned -> Nullable<Bool>,
-        expires -> Nullable<Timestamp>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_ban_from_community (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        other_user_id -> Int4,
-        community_id -> Int4,
-        reason -> Nullable<Text>,
-        banned -> Nullable<Bool>,
-        expires -> Nullable<Timestamp>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_lock_post (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        post_id -> Int4,
-        locked -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_remove_comment (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        comment_id -> Int4,
-        reason -> Nullable<Text>,
-        removed -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_remove_community (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        community_id -> Int4,
-        reason -> Nullable<Text>,
-        removed -> Nullable<Bool>,
-        expires -> Nullable<Timestamp>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_remove_post (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        post_id -> Int4,
-        reason -> Nullable<Text>,
-        removed -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    mod_sticky_post (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        post_id -> Int4,
-        stickied -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
-}
-
-table! {
-    post (id) {
-        id -> Int4,
-        name -> Varchar,
-        url -> Nullable<Text>,
-        body -> Nullable<Text>,
-        creator_id -> Int4,
-        community_id -> Int4,
-        removed -> Bool,
-        locked -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        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<Text>,
-        creator_id -> Int4,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-    }
-}
-
-table! {
-    user_ (id) {
-        id -> Int4,
-        name -> Varchar,
-        fedi_name -> Varchar,
-        preferred_username -> Nullable<Varchar>,
-        password_encrypted -> Text,
-        email -> Nullable<Text>,
-        icon -> Nullable<Bytea>,
-        admin -> Bool,
-        banned -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        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,
-);
index a378d3c25368b1b2fd22a6afdf9204acbe8c6ac1..da8e5dc2fd660b106a7469912b2c5b4619e968aa 100644 (file)
@@ -44,7 +44,6 @@ pub struct UserForm {
 
 impl Crud<UserForm> for User_ {
   fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
-    use crate::schema::user_::dsl::*;
     user_.find(user_id).first::<Self>(conn)
   }
   fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
@@ -69,6 +68,16 @@ impl User_ {
 
     Self::create(&conn, &edited_user)
   }
+
+  pub fn update_password(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
+    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<Self, Error> {
     user_.filter(name.eq(from_user_name)).first::<Self>(conn)
   }
@@ -129,6 +138,16 @@ impl User_ {
         .first::<User_>(conn)
     }
   }
+  
+  pub fn find_by_email(
+    conn: &PgConnection,
+    from_email: &str,
+  ) -> Result<Self, Error> {
+    user_
+      .filter(email.eq(from_email))
+      .first::<User_>(conn)
+  }
+
 
   pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result<Self, Error> {
     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();
index 715d9ef33ff0e20fc05579134bebf5190fb90991..b06f29be310936c09392a86f2a8ea1b5b1a16cc6 100644 (file)
@@ -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<EmailConfig>,
+}
+
+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", "<h1>HI there</h1>");
+  //   assert!(result.is_ok());
+  // }
 }
 
 lazy_static! {
index e087e20e1d4973b4b17f85d72141ac823d914b52..15e87bbee20ba853101a4412485abe4271fd847b 100644 (file)
@@ -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,
 );
index 1927421990a9eab92a11eec0d0b12d1d2cfacb96..5bcca297624a1f6b3707cb2be45a1a70c6de7e2c 100644 (file)
@@ -534,5 +534,15 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
       let res = Oper::new(user_operation, delete_account).perform()?;
       Ok(serde_json::to_string(&res)?)
     }
+    UserOperation::PasswordReset => {
+      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)?)
+    }
   }
 }
index 87fa39fe7cf4362435e18332a9f27bfb13641ea7..c2db7ee60d37d3c840bb19481d53686fcd0f74fb 100644 (file)
@@ -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<any, State> {
                 class="form-control"
                 required
               />
+              <div
+                onClick={linkEvent(this, this.handlePasswordReset)}
+                class="pointer d-inline-block float-right text-muted small font-weight-bold"
+              >
+                <T i18nKey="forgot_password">#</T>
+              </div>
             </div>
           </div>
           <div class="form-group row">
@@ -279,6 +286,13 @@ export class Login extends Component<any, State> {
     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<any, State> {
         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'));
       }
     }
   }
index 151559dfd327f7d0fe7ea96c20c7c097e6e3c72f..306dc74fb6a3fb6bac20fcdbc7f4e7a9ac657ebd 100644 (file)
@@ -21,7 +21,6 @@ import { T } from 'inferno-i18next';
 interface NavbarState {
   isLoggedIn: boolean;
   expanded: boolean;
-  expandUserDropdown: boolean;
   replies: Array<Comment>;
   mentions: Array<Comment>;
   fetchCount: number;
@@ -39,14 +38,12 @@ export class Navbar extends Component<any, NavbarState> {
     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<any, NavbarState> {
           <ul class="navbar-nav ml-auto mr-2">
             {this.state.isLoggedIn ? (
               <>
-                {
-                  <li className="nav-item">
-                    <Link class="nav-link" to="/inbox">
-                      <svg class="icon">
-                        <use xlinkHref="#icon-mail"></use>
-                      </svg>
-                      {this.state.unreadCount > 0 && (
-                        <span class="ml-1 badge badge-light">
-                          {this.state.unreadCount}
-                        </span>
-                      )}
-                    </Link>
-                  </li>
-                }
-                <li
-                  className={`nav-item dropdown ${this.state
-                    .expandUserDropdown && 'show'}`}
-                >
-                  <a
-                    class="pointer nav-link dropdown-toggle"
-                    onClick={linkEvent(this, this.expandUserDropdown)}
-                    role="button"
+                <li className="nav-item">
+                  <Link class="nav-link" to="/inbox">
+                    <svg class="icon">
+                      <use xlinkHref="#icon-mail"></use>
+                    </svg>
+                    {this.state.unreadCount > 0 && (
+                      <span class="ml-1 badge badge-light">
+                        {this.state.unreadCount}
+                      </span>
+                    )}
+                  </Link>
+                </li>
+                <li className="nav-item">
+                  <Link
+                    class="nav-link"
+                    to={`/u/${UserService.Instance.user.username}`}
                   >
                     {UserService.Instance.user.username}
-                  </a>
-                  <div
-                    className={`dropdown-menu dropdown-menu-right ${this.state
-                      .expandUserDropdown && 'show'}`}
-                  >
-                    <a
-                      role="button"
-                      class="dropdown-item pointer"
-                      onClick={linkEvent(this, this.handleOverviewClick)}
-                    >
-                      <T i18nKey="overview">#</T>
-                    </a>
-                    <a
-                      role="button"
-                      class="dropdown-item pointer"
-                      onClick={linkEvent(this, this.handleLogoutClick)}
-                    >
-                      <T i18nKey="logout">#</T>
-                    </a>
-                  </div>
+                  </Link>
                 </li>
               </>
             ) : (
@@ -194,24 +166,6 @@ export class Navbar extends Component<any, NavbarState> {
     );
   }
 
-  expandUserDropdown(i: Navbar) {
-    i.state.expandUserDropdown = !i.state.expandUserDropdown;
-    i.setState(i.state);
-  }
-
-  handleLogoutClick(i: Navbar) {
-    i.state.expandUserDropdown = false;
-    UserService.Instance.logout();
-    i.context.router.history.push('/');
-  }
-
-  handleOverviewClick(i: Navbar) {
-    i.state.expandUserDropdown = false;
-    i.setState(i.state);
-    let userPage = `/u/${UserService.Instance.user.username}`;
-    i.context.router.history.push(userPage);
-  }
-
   expandNavbar(i: Navbar) {
     i.state.expanded = !i.state.expanded;
     i.setState(i.state);
index 3006afc43b0b32193b3c55877719d85f7f086ac9..361ce633a50a2c61af63e33c95e0f3bdd6f7b597 100644 (file)
@@ -376,6 +376,14 @@ export class User extends Component<any, UserState> {
                 </tr>
               </table>
             </div>
+            {this.isCurrentUser && (
+              <button
+                class="btn btn-block btn-secondary mt-3"
+                onClick={linkEvent(this, this.handleLogoutClick)}
+              >
+                <T i18nKey="logout">#</T>
+              </button>
+            )}
           </div>
         </div>
       </div>
@@ -693,6 +701,11 @@ export class User extends Component<any, UserState> {
     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;
index 2f75efd9ba5ec475b96ce3b514ff61c73e9eeeca..9cd9bef46c64c638f5f0cc4cf071473f1a797230 100644 (file)
@@ -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;
+}
index ea6c7722eadf62edb2356b861821c5cbb3352a8c..c77816df2fe57c5311e1300be683414de2b3c97b 100644 (file)
@@ -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);
index a971ae6e21f892e0b6e77a3de66a22ba5a2eaa4a..4e0d81dbbdb72d1e32026c9abda897d14c00b3d0 100644 (file)
@@ -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',