Password reset mostly working.
This commit is contained in:
parent
7100d4d1ef
commit
b89d6305ba
15 changed files with 266 additions and 36 deletions
2
ansible/templates/nginx.conf
vendored
2
ansible/templates/nginx.conf
vendored
|
@ -50,7 +50,7 @@ server {
|
||||||
client_max_body_size 50M;
|
client_max_body_size 50M;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
rewrite (\/(user|u\/|inbox|post|community|c\/|create_post|create_community|login|search|setup|sponsors|communities|modlog|home)+) /static/index.html break;
|
rewrite (\/(user|u\/|inbox|post|community|c\/|create_post|create_community|login|search|setup|sponsors|communities|modlog|home|password_change)+) /static/index.html break;
|
||||||
proxy_pass http://0.0.0.0:8536;
|
proxy_pass http://0.0.0.0:8536;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|
7
docker/dev/.env
vendored
7
docker/dev/.env
vendored
|
@ -2,9 +2,16 @@ DOMAIN=my_domain
|
||||||
DATABASE_PASSWORD=password
|
DATABASE_PASSWORD=password
|
||||||
DATABASE_URL=postgres://lemmy:password@lemmy_db:5432/lemmy
|
DATABASE_URL=postgres://lemmy:password@lemmy_db:5432/lemmy
|
||||||
JWT_SECRET=changeme
|
JWT_SECRET=changeme
|
||||||
|
|
||||||
RATE_LIMIT_MESSAGE=30
|
RATE_LIMIT_MESSAGE=30
|
||||||
RATE_LIMIT_MESSAGE_PER_SECOND=60
|
RATE_LIMIT_MESSAGE_PER_SECOND=60
|
||||||
RATE_LIMIT_POST=3
|
RATE_LIMIT_POST=3
|
||||||
RATE_LIMIT_POST_PER_SECOND=600
|
RATE_LIMIT_POST_PER_SECOND=600
|
||||||
RATE_LIMIT_REGISTER=1
|
RATE_LIMIT_REGISTER=1
|
||||||
RATE_LIMIT_REGISTER_PER_SECOND=3600
|
RATE_LIMIT_REGISTER_PER_SECOND=3600
|
||||||
|
|
||||||
|
# Optional email fields
|
||||||
|
SMTP_SERVER=
|
||||||
|
SMTP_LOGIN=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
SMTP_FROM_ADDRESS=Domain.com Lemmy Admin <notifications@domain.com>
|
||||||
|
|
17
docker/dev/Dockerfile
vendored
17
docker/dev/Dockerfile
vendored
|
@ -10,27 +10,24 @@ RUN yarn install --pure-lockfile
|
||||||
COPY ui /app/ui
|
COPY ui /app/ui
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
FROM rust:1.38 as rust
|
FROM ekidd/rust-musl-builder:1.38.0-openssl11 as rust
|
||||||
|
|
||||||
# Install musl
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get install musl-tools -y
|
|
||||||
RUN rustup target add x86_64-unknown-linux-musl
|
|
||||||
|
|
||||||
# Cache deps
|
# Cache deps
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN sudo chown -R rust:rust .
|
||||||
RUN USER=root cargo new server
|
RUN USER=root cargo new server
|
||||||
WORKDIR /app/server
|
WORKDIR /app/server
|
||||||
COPY server/Cargo.toml server/Cargo.lock ./
|
COPY server/Cargo.toml server/Cargo.lock ./
|
||||||
RUN mkdir -p ./src/bin \
|
RUN sudo chown -R rust:rust .
|
||||||
|
RUN mkdir -p ./src/bin \
|
||||||
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
&& echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs
|
||||||
RUN RUSTFLAGS=-Clinker=musl-gcc cargo build --release --target=x86_64-unknown-linux-musl
|
RUN cargo build --release
|
||||||
RUN rm -f ./target/x86_64-unknown-linux-musl/release/deps/lemmy_server*
|
RUN rm -f ./target/x86_64-unknown-linux-musl/release/deps/lemmy_server*
|
||||||
COPY server/src ./src/
|
COPY server/src ./src/
|
||||||
COPY server/migrations ./migrations/
|
COPY server/migrations ./migrations/
|
||||||
|
|
||||||
# build for release
|
# Build for release
|
||||||
RUN RUSTFLAGS=-Clinker=musl-gcc cargo build --frozen --release --target=x86_64-unknown-linux-musl
|
RUN cargo build --frozen --release
|
||||||
|
|
||||||
# Get diesel-cli on there just in case
|
# Get diesel-cli on there just in case
|
||||||
# RUN cargo install diesel_cli --no-default-features --features postgres
|
# RUN cargo install diesel_cli --no-default-features --features postgres
|
||||||
|
|
4
docker/dev/docker-compose.yml
vendored
4
docker/dev/docker-compose.yml
vendored
|
@ -26,6 +26,10 @@ services:
|
||||||
- RATE_LIMIT_POST_PER_SECOND=${RATE_LIMIT_POST_PER_SECOND}
|
- RATE_LIMIT_POST_PER_SECOND=${RATE_LIMIT_POST_PER_SECOND}
|
||||||
- RATE_LIMIT_REGISTER=${RATE_LIMIT_REGISTER}
|
- RATE_LIMIT_REGISTER=${RATE_LIMIT_REGISTER}
|
||||||
- RATE_LIMIT_REGISTER_PER_SECOND=${RATE_LIMIT_REGISTER_PER_SECOND}
|
- RATE_LIMIT_REGISTER_PER_SECOND=${RATE_LIMIT_REGISTER_PER_SECOND}
|
||||||
|
- SMTP_SERVER=${SMTP_SERVER}
|
||||||
|
- SMTP_LOGIN=${SMTP_LOGIN}
|
||||||
|
- SMTP_PASSWORD=${SMTP_PASSWORD}
|
||||||
|
- SMTP_FROM_ADDRESS=${SMTP_FROM_ADDRESS}
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- lemmy_db
|
- lemmy_db
|
||||||
|
|
37
server/Cargo.lock
generated
vendored
37
server/Cargo.lock
generated
vendored
|
@ -839,6 +839,11 @@ name = "futures"
|
||||||
version = "0.1.28"
|
version = "0.1.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gcc"
|
||||||
|
version = "0.3.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -1019,9 +1024,9 @@ dependencies = [
|
||||||
"lazy_static 1.3.0 (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 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)",
|
"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)",
|
"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)",
|
"regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 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)",
|
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1496,6 +1501,15 @@ dependencies = [
|
||||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.3.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -1705,11 +1719,28 @@ dependencies = [
|
||||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-crypto"
|
||||||
|
version = "0.2.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.15"
|
version = "0.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-serialize"
|
||||||
|
version = "0.3.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -2441,6 +2472,7 @@ dependencies = [
|
||||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
"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"
|
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||||
"checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869"
|
"checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869"
|
||||||
|
"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||||
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
|
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
|
||||||
"checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55"
|
"checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55"
|
||||||
"checksum h2 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "a539b63339fbbb00e081e84b6e11bd1d9634a82d91da2984a18ac74a8823f392"
|
"checksum h2 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "a539b63339fbbb00e081e84b6e11bd1d9634a82d91da2984a18ac74a8823f392"
|
||||||
|
@ -2512,6 +2544,7 @@ dependencies = [
|
||||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
"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.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 quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||||
|
"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
|
||||||
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
"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.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 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c"
|
||||||
|
@ -2534,7 +2567,9 @@ dependencies = [
|
||||||
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
|
"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 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 ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c"
|
||||||
|
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
|
||||||
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
|
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
|
||||||
|
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
"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 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 safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
||||||
|
|
3
server/Cargo.toml
vendored
3
server/Cargo.toml
vendored
|
@ -25,3 +25,6 @@ strum_macros = "0.15.0"
|
||||||
jsonwebtoken = "6.0.1"
|
jsonwebtoken = "6.0.1"
|
||||||
regex = "1.1.9"
|
regex = "1.1.9"
|
||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
|
lettre = "0.9.2"
|
||||||
|
lettre_email = "0.9.2"
|
||||||
|
rust-crypto = "^0.2"
|
||||||
|
|
|
@ -849,7 +849,7 @@ impl Perform<PasswordResetResponse> for Oper<PasswordReset> {
|
||||||
let user_email = &user.email.expect("email");
|
let user_email = &user.email.expect("email");
|
||||||
let subject = &format!("Password reset for {}", user.name);
|
let subject = &format!("Password reset for {}", user.name);
|
||||||
let hostname = Settings::get().hostname;
|
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);
|
let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", user.name, hostname, &token);
|
||||||
match send_email(subject, user_email, &user.name, html) {
|
match send_email(subject, user_email, &user.name, html) {
|
||||||
Ok(_o) => _o,
|
Ok(_o) => _o,
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::schema::password_reset_request;
|
use crate::schema::password_reset_request;
|
||||||
use crate::schema::password_reset_request::dsl::*;
|
use crate::schema::password_reset_request::dsl::*;
|
||||||
|
use crypto::sha2::Sha256;
|
||||||
use bcrypt::{hash, DEFAULT_COST};
|
use crypto::digest::Digest;
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, PartialEq, Debug)]
|
#[derive(Queryable, Identifiable, PartialEq, Debug)]
|
||||||
#[table_name = "password_reset_request"]
|
#[table_name = "password_reset_request"]
|
||||||
|
@ -40,8 +40,9 @@ impl Crud<PasswordResetRequestForm> for PasswordResetRequest {
|
||||||
|
|
||||||
impl PasswordResetRequest {
|
impl PasswordResetRequest {
|
||||||
pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result<Self, Error> {
|
pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result<Self, Error> {
|
||||||
let token_hash =
|
let mut hasher = Sha256::new();
|
||||||
hash(token, DEFAULT_COST).expect("Couldn't hash token");
|
hasher.input_str(token);
|
||||||
|
let token_hash = hasher.result_str();
|
||||||
|
|
||||||
let form = PasswordResetRequestForm {
|
let form = PasswordResetRequestForm {
|
||||||
user_id: from_user_id,
|
user_id: from_user_id,
|
||||||
|
@ -51,10 +52,13 @@ impl PasswordResetRequest {
|
||||||
Self::create(&conn, &form)
|
Self::create(&conn, &form)
|
||||||
}
|
}
|
||||||
pub fn read_from_token(conn: &PgConnection, token: &str) -> Result<Self, Error> {
|
pub fn read_from_token(conn: &PgConnection, token: &str) -> Result<Self, Error> {
|
||||||
let token_hash =
|
let mut hasher = Sha256::new();
|
||||||
hash(token, DEFAULT_COST).expect("Couldn't hash token");
|
hasher.input_str(token);
|
||||||
|
let token_hash = hasher.result_str();
|
||||||
password_reset_request.filter(token_encrypted.eq(token_hash)).first::<Self>(conn)
|
password_reset_request
|
||||||
|
.filter(token_encrypted.eq(token_hash))
|
||||||
|
.filter(published.gt(now - 1.days()))
|
||||||
|
.first::<Self>(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub extern crate serde_json;
|
||||||
pub extern crate strum;
|
pub extern crate strum;
|
||||||
pub extern crate lettre;
|
pub extern crate lettre;
|
||||||
pub extern crate lettre_email;
|
pub extern crate lettre_email;
|
||||||
|
pub extern crate crypto;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod apub;
|
pub mod apub;
|
||||||
|
@ -63,7 +64,8 @@ impl Settings {
|
||||||
fn get() -> Self {
|
fn get() -> Self {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
let email_config = if env::var("SMTP_SERVER").is_ok() {
|
let email_config = if env::var("SMTP_SERVER").is_ok() &&
|
||||||
|
!env::var("SMTP_SERVER").unwrap().eq("") {
|
||||||
Some(EmailConfig {
|
Some(EmailConfig {
|
||||||
smtp_server: env::var("SMTP_SERVER").expect("SMTP_SERVER must be set"),
|
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_login: env::var("SMTP_LOGIN").expect("SMTP_LOGIN must be set"),
|
||||||
|
@ -160,7 +162,6 @@ pub fn send_email(subject: &str, to_email: &str, to_username: &str, html: &str)
|
||||||
let email_config = Settings::get().email_config.ok_or("no_email_setup")?;
|
let email_config = Settings::get().email_config.ok_or("no_email_setup")?;
|
||||||
|
|
||||||
let email = Email::builder()
|
let email = Email::builder()
|
||||||
// .to((to_email, username))
|
|
||||||
.to((to_email, to_username))
|
.to((to_email, to_username))
|
||||||
.from((email_config.smtp_login.to_owned(), email_config.smtp_from_address))
|
.from((email_config.smtp_login.to_owned(), email_config.smtp_from_address))
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
|
@ -168,15 +169,15 @@ pub fn send_email(subject: &str, to_email: &str, to_username: &str, html: &str)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut mailer = SmtpClient::new_simple(&email_config.smtp_server).unwrap()
|
let mut mailer = SmtpClient::new_simple(&email_config.smtp_server).unwrap()
|
||||||
.hello_name(ClientId::Domain("localhost".to_string()))
|
.hello_name(ClientId::Domain("localhost".to_string()))
|
||||||
.credentials(Credentials::new(
|
.credentials(Credentials::new(
|
||||||
email_config.smtp_login.to_owned(),
|
email_config.smtp_login.to_owned(),
|
||||||
email_config.smtp_password.to_owned()))
|
email_config.smtp_password.to_owned()))
|
||||||
.smtp_utf8(true)
|
.smtp_utf8(true)
|
||||||
.authentication_mechanism(Mechanism::Plain)
|
.authentication_mechanism(Mechanism::Plain)
|
||||||
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
|
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
|
||||||
.transport();
|
.transport();
|
||||||
|
|
||||||
let result = mailer.send(email.into());
|
let result = mailer.send(email.into());
|
||||||
|
|
||||||
|
|
10
ui/src/components/login.tsx
vendored
10
ui/src/components/login.tsx
vendored
|
@ -9,7 +9,7 @@ import {
|
||||||
PasswordResetForm,
|
PasswordResetForm,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp, validEmail } from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -113,12 +113,13 @@ export class Login extends Component<any, State> {
|
||||||
class="form-control"
|
class="form-control"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div
|
<button
|
||||||
|
disabled={!validEmail(this.state.loginForm.username_or_email)}
|
||||||
onClick={linkEvent(this, this.handlePasswordReset)}
|
onClick={linkEvent(this, this.handlePasswordReset)}
|
||||||
class="pointer d-inline-block float-right text-muted small font-weight-bold"
|
className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold"
|
||||||
>
|
>
|
||||||
<T i18nKey="forgot_password">#</T>
|
<T i18nKey="forgot_password">#</T>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -287,6 +288,7 @@ export class Login extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePasswordReset(i: Login) {
|
handlePasswordReset(i: Login) {
|
||||||
|
event.preventDefault();
|
||||||
let resetForm: PasswordResetForm = {
|
let resetForm: PasswordResetForm = {
|
||||||
email: i.state.loginForm.username_or_email,
|
email: i.state.loginForm.username_or_email,
|
||||||
};
|
};
|
||||||
|
|
160
ui/src/components/password_change.tsx
vendored
Normal file
160
ui/src/components/password_change.tsx
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import { Component, linkEvent } from 'inferno';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
UserOperation,
|
||||||
|
LoginResponse,
|
||||||
|
PasswordChangeForm,
|
||||||
|
} from '../interfaces';
|
||||||
|
import { WebSocketService, UserService } from '../services';
|
||||||
|
import { msgOp, capitalizeFirstLetter } from '../utils';
|
||||||
|
import { i18n } from '../i18next';
|
||||||
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
passwordChangeForm: PasswordChangeForm;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PasswordChange extends Component<any, State> {
|
||||||
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
emptyState: State = {
|
||||||
|
passwordChangeForm: {
|
||||||
|
token: this.props.match.params.token,
|
||||||
|
password: undefined,
|
||||||
|
password_verify: undefined,
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = this.emptyState;
|
||||||
|
|
||||||
|
this.subscription = WebSocketService.Instance.subject
|
||||||
|
.pipe(
|
||||||
|
retryWhen(errors =>
|
||||||
|
errors.pipe(
|
||||||
|
delay(3000),
|
||||||
|
take(10)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subscribe(
|
||||||
|
msg => this.parseMessage(msg),
|
||||||
|
err => console.error(err),
|
||||||
|
() => console.log('complete')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.title = `${i18n.t('password_change')} - ${
|
||||||
|
WebSocketService.Instance.site.name
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
|
||||||
|
<h5>
|
||||||
|
<T i18nKey="password_change">#</T>
|
||||||
|
</h5>
|
||||||
|
{this.passwordChangeForm()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordChangeForm() {
|
||||||
|
return (
|
||||||
|
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-2 col-form-label">
|
||||||
|
<T i18nKey="new_password">#</T>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={this.state.passwordChangeForm.password}
|
||||||
|
onInput={linkEvent(this, this.handlePasswordChange)}
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-2 col-form-label">
|
||||||
|
<T i18nKey="verify_password">#</T>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={this.state.passwordChangeForm.password_verify}
|
||||||
|
onInput={linkEvent(this, this.handleVerifyPasswordChange)}
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<button type="submit" class="btn btn-secondary">
|
||||||
|
{this.state.loading ? (
|
||||||
|
<svg class="icon icon-spinner spin">
|
||||||
|
<use xlinkHref="#icon-spinner"></use>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
capitalizeFirstLetter(i18n.t('save'))
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePasswordChange(i: PasswordChange, event: any) {
|
||||||
|
i.state.passwordChangeForm.password = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVerifyPasswordChange(i: PasswordChange, event: any) {
|
||||||
|
i.state.passwordChangeForm.password_verify = event.target.value;
|
||||||
|
i.setState(i.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePasswordChangeSubmit(i: PasswordChange, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
i.state.loading = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
|
||||||
|
WebSocketService.Instance.passwordChange(i.state.passwordChangeForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseMessage(msg: any) {
|
||||||
|
let op: UserOperation = msgOp(msg);
|
||||||
|
if (msg.error) {
|
||||||
|
alert(i18n.t(msg.error));
|
||||||
|
this.state.loading = false;
|
||||||
|
this.setState(this.state);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (op == UserOperation.PasswordChange) {
|
||||||
|
this.state = this.emptyState;
|
||||||
|
this.setState(this.state);
|
||||||
|
let res: LoginResponse = msg;
|
||||||
|
UserService.Instance.login(res);
|
||||||
|
this.props.history.push('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
ui/src/index.tsx
vendored
5
ui/src/index.tsx
vendored
|
@ -7,6 +7,7 @@ import { Footer } from './components/footer';
|
||||||
import { Login } from './components/login';
|
import { Login } from './components/login';
|
||||||
import { CreatePost } from './components/create-post';
|
import { CreatePost } from './components/create-post';
|
||||||
import { CreateCommunity } from './components/create-community';
|
import { CreateCommunity } from './components/create-community';
|
||||||
|
import { PasswordChange } from './components/password_change';
|
||||||
import { Post } from './components/post';
|
import { Post } from './components/post';
|
||||||
import { Community } from './components/community';
|
import { Community } from './components/community';
|
||||||
import { Communities } from './components/communities';
|
import { Communities } from './components/communities';
|
||||||
|
@ -74,6 +75,10 @@ class Index extends Component<any, any> {
|
||||||
/>
|
/>
|
||||||
<Route path={`/search`} component={Search} />
|
<Route path={`/search`} component={Search} />
|
||||||
<Route path={`/sponsors`} component={Sponsors} />
|
<Route path={`/sponsors`} component={Sponsors} />
|
||||||
|
<Route
|
||||||
|
path={`/password_change/:token`}
|
||||||
|
component={PasswordChange}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
<Symbols />
|
<Symbols />
|
||||||
</div>
|
</div>
|
||||||
|
|
5
ui/src/services/WebSocketService.ts
vendored
5
ui/src/services/WebSocketService.ts
vendored
|
@ -31,6 +31,7 @@ import {
|
||||||
UserSettingsForm,
|
UserSettingsForm,
|
||||||
DeleteAccountForm,
|
DeleteAccountForm,
|
||||||
PasswordResetForm,
|
PasswordResetForm,
|
||||||
|
PasswordChangeForm,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { webSocket } from 'rxjs/webSocket';
|
import { webSocket } from 'rxjs/webSocket';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
@ -279,6 +280,10 @@ export class WebSocketService {
|
||||||
this.subject.next(this.wsSendWrapper(UserOperation.PasswordReset, form));
|
this.subject.next(this.wsSendWrapper(UserOperation.PasswordReset, form));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public passwordChange(form: PasswordChangeForm) {
|
||||||
|
this.subject.next(this.wsSendWrapper(UserOperation.PasswordChange, form));
|
||||||
|
}
|
||||||
|
|
||||||
private wsSendWrapper(op: UserOperation, data: any) {
|
private wsSendWrapper(op: UserOperation, data: any) {
|
||||||
let send = { op: UserOperation[op], data: data };
|
let send = { op: UserOperation[op], data: data };
|
||||||
console.log(send);
|
console.log(send);
|
||||||
|
|
2
ui/src/translations/en.ts
vendored
2
ui/src/translations/en.ts
vendored
|
@ -118,6 +118,8 @@ export const en = {
|
||||||
verify_password: 'Verify Password',
|
verify_password: 'Verify Password',
|
||||||
forgot_password: 'forgot password',
|
forgot_password: 'forgot password',
|
||||||
reset_password_mail_sent: 'Sent an Email to reset your password.',
|
reset_password_mail_sent: 'Sent an Email to reset your password.',
|
||||||
|
password_change: 'Password Change',
|
||||||
|
new_password: 'New Password',
|
||||||
no_email_setup: "This server hasn't correctly set up email.",
|
no_email_setup: "This server hasn't correctly set up email.",
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
optional: 'Optional',
|
optional: 'Optional',
|
||||||
|
|
5
ui/src/utils.ts
vendored
5
ui/src/utils.ts
vendored
|
@ -152,6 +152,11 @@ export function validURL(str: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validEmail(email: string) {
|
||||||
|
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
return re.test(String(email).toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
export function capitalizeFirstLetter(str: string): string {
|
export function capitalizeFirstLetter(str: string): string {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue