mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-22 19:01:32 +00:00
Password reset mostly working.
This commit is contained in:
parent
9f35b33dc7
commit
68e4b61808
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;
|
||||
|
||||
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_set_header X-Real-IP $remote_addr;
|
||||
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_URL=postgres://lemmy:password@lemmy_db:5432/lemmy
|
||||
JWT_SECRET=changeme
|
||||
|
||||
RATE_LIMIT_MESSAGE=30
|
||||
RATE_LIMIT_MESSAGE_PER_SECOND=60
|
||||
RATE_LIMIT_POST=3
|
||||
RATE_LIMIT_POST_PER_SECOND=600
|
||||
RATE_LIMIT_REGISTER=1
|
||||
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
|
||||
RUN yarn build
|
||||
|
||||
FROM rust:1.38 as rust
|
||||
|
||||
# Install musl
|
||||
RUN apt-get update
|
||||
RUN apt-get install musl-tools -y
|
||||
RUN rustup target add x86_64-unknown-linux-musl
|
||||
FROM ekidd/rust-musl-builder:1.38.0-openssl11 as rust
|
||||
|
||||
# Cache deps
|
||||
WORKDIR /app
|
||||
RUN sudo chown -R rust:rust .
|
||||
RUN USER=root cargo new server
|
||||
WORKDIR /app/server
|
||||
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
|
||||
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*
|
||||
COPY server/src ./src/
|
||||
COPY server/migrations ./migrations/
|
||||
|
||||
# build for release
|
||||
RUN RUSTFLAGS=-Clinker=musl-gcc cargo build --frozen --release --target=x86_64-unknown-linux-musl
|
||||
# Build for release
|
||||
RUN cargo build --frozen --release
|
||||
|
||||
# Get diesel-cli on there just in case
|
||||
# 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_REGISTER=${RATE_LIMIT_REGISTER}
|
||||
- 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
|
||||
depends_on:
|
||||
- lemmy_db
|
||||
|
|
37
server/Cargo.lock
generated
vendored
37
server/Cargo.lock
generated
vendored
|
@ -839,6 +839,11 @@ name = "futures"
|
|||
version = "0.1.28"
|
||||
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]]
|
||||
name = "generic-array"
|
||||
version = "0.12.3"
|
||||
|
@ -1019,9 +1024,9 @@ dependencies = [
|
|||
"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)",
|
||||
"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_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)",
|
||||
|
@ -1496,6 +1501,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
|
@ -1705,11 +1719,28 @@ dependencies = [
|
|||
"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]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.15"
|
||||
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]]
|
||||
name = "rustc_version"
|
||||
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-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 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 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"
|
||||
|
@ -2512,6 +2544,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.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.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"
|
||||
|
@ -2534,7 +2567,9 @@ dependencies = [
|
|||
"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 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-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 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"
|
||||
|
|
3
server/Cargo.toml
vendored
3
server/Cargo.toml
vendored
|
@ -25,3 +25,6 @@ strum_macros = "0.15.0"
|
|||
jsonwebtoken = "6.0.1"
|
||||
regex = "1.1.9"
|
||||
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 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);
|
||||
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) {
|
||||
Ok(_o) => _o,
|
||||
Err(_e) => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use super::*;
|
||||
use crate::schema::password_reset_request;
|
||||
use crate::schema::password_reset_request::dsl::*;
|
||||
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::digest::Digest;
|
||||
|
||||
#[derive(Queryable, Identifiable, PartialEq, Debug)]
|
||||
#[table_name = "password_reset_request"]
|
||||
|
@ -40,8 +40,9 @@ impl Crud<PasswordResetRequestForm> for PasswordResetRequest {
|
|||
|
||||
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 mut hasher = Sha256::new();
|
||||
hasher.input_str(token);
|
||||
let token_hash = hasher.result_str();
|
||||
|
||||
let form = PasswordResetRequestForm {
|
||||
user_id: from_user_id,
|
||||
|
@ -51,10 +52,13 @@ impl PasswordResetRequest {
|
|||
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)
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input_str(token);
|
||||
let token_hash = hasher.result_str();
|
||||
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 lettre;
|
||||
pub extern crate lettre_email;
|
||||
pub extern crate crypto;
|
||||
|
||||
pub mod api;
|
||||
pub mod apub;
|
||||
|
@ -63,7 +64,8 @@ impl Settings {
|
|||
fn get() -> Self {
|
||||
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 {
|
||||
smtp_server: env::var("SMTP_SERVER").expect("SMTP_SERVER 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 = 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)
|
||||
|
@ -168,15 +169,15 @@ pub fn send_email(subject: &str, to_email: &str, to_username: &str, html: &str)
|
|||
.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 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());
|
||||
|
||||
|
|
10
ui/src/components/login.tsx
vendored
10
ui/src/components/login.tsx
vendored
|
@ -9,7 +9,7 @@ import {
|
|||
PasswordResetForm,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import { msgOp } from '../utils';
|
||||
import { msgOp, validEmail } from '../utils';
|
||||
import { i18n } from '../i18next';
|
||||
import { T } from 'inferno-i18next';
|
||||
|
||||
|
@ -113,12 +113,13 @@ export class Login extends Component<any, State> {
|
|||
class="form-control"
|
||||
required
|
||||
/>
|
||||
<div
|
||||
<button
|
||||
disabled={!validEmail(this.state.loginForm.username_or_email)}
|
||||
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>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
|
@ -287,6 +288,7 @@ export class Login extends Component<any, State> {
|
|||
}
|
||||
|
||||
handlePasswordReset(i: Login) {
|
||||
event.preventDefault();
|
||||
let resetForm: PasswordResetForm = {
|
||||
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 { CreatePost } from './components/create-post';
|
||||
import { CreateCommunity } from './components/create-community';
|
||||
import { PasswordChange } from './components/password_change';
|
||||
import { Post } from './components/post';
|
||||
import { Community } from './components/community';
|
||||
import { Communities } from './components/communities';
|
||||
|
@ -74,6 +75,10 @@ class Index extends Component<any, any> {
|
|||
/>
|
||||
<Route path={`/search`} component={Search} />
|
||||
<Route path={`/sponsors`} component={Sponsors} />
|
||||
<Route
|
||||
path={`/password_change/:token`}
|
||||
component={PasswordChange}
|
||||
/>
|
||||
</Switch>
|
||||
<Symbols />
|
||||
</div>
|
||||
|
|
5
ui/src/services/WebSocketService.ts
vendored
5
ui/src/services/WebSocketService.ts
vendored
|
@ -31,6 +31,7 @@ import {
|
|||
UserSettingsForm,
|
||||
DeleteAccountForm,
|
||||
PasswordResetForm,
|
||||
PasswordChangeForm,
|
||||
} from '../interfaces';
|
||||
import { webSocket } from 'rxjs/webSocket';
|
||||
import { Subject } from 'rxjs';
|
||||
|
@ -279,6 +280,10 @@ export class WebSocketService {
|
|||
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) {
|
||||
let send = { op: UserOperation[op], data: data };
|
||||
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',
|
||||
forgot_password: 'forgot 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.",
|
||||
email: 'Email',
|
||||
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 {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue