summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <vpzomtrrfrt@gmail.com>2020-09-26 18:29:31 -0600
committerColin Reeder <vpzomtrrfrt@gmail.com>2020-09-26 18:29:31 -0600
commit3d1b91c031d2e67e2f9265dc8366c684ce286d28 (patch)
treedfe94a273b694fbfbfd208c07a2e89ab67311627
parent03f0616a7c2b00ac0e47247795308c0a35a12e88 (diff)
Add Forgot Password
-rw-r--r--res/lang/en.ftl7
-rw-r--r--src/routes/forgot_password.rs305
-rw-r--r--src/routes/mod.rs5
3 files changed, 317 insertions, 0 deletions
diff --git a/res/lang/en.ftl b/res/lang/en.ftl
index 49218f8..35a2167 100644
--- a/res/lang/en.ftl
+++ b/res/lang/en.ftl
@@ -30,6 +30,13 @@ fetch = Fetch
follow = Follow
follow_request_sent = Follow request sent!
follow_undo = Unfollow
+forgot_password = Forgot Password
+forgot_password_code_info = You should have received a code via email.
+forgot_password_code_prompt = Enter it here:
+forgot_password_complete = Password successfully reset.
+forgot_password_email_prompt = Email Address:
+forgot_password_info = If your account has an attached email address, you can reset your password here.
+forgot_password_new_password_prompt = New Password:
home_follow_prompt1 = Why not
home_follow_prompt2 = follow some communities?
liked_by = Liked by:
diff --git a/src/routes/forgot_password.rs b/src/routes/forgot_password.rs
new file mode 100644
index 0000000..14b827d
--- /dev/null
+++ b/src/routes/forgot_password.rs
@@ -0,0 +1,305 @@
+use crate::routes::{
+ fetch_base_data, for_client, get_cookie_map_for_headers, get_cookie_map_for_req, html_response,
+ res_to_error, CookieMap, HTPage,
+};
+use serde_derive::Deserialize;
+use std::borrow::Cow;
+use std::sync::Arc;
+
+async fn page_forgot_password(
+ _: (),
+ ctx: Arc<crate::RouteContext>,
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let cookies = get_cookie_map_for_req(&req)?;
+
+ page_forgot_password_inner(ctx, req.headers(), &cookies, None).await
+}
+
+async fn page_forgot_password_inner(
+ ctx: Arc<crate::RouteContext>,
+ headers: &hyper::header::HeaderMap,
+ cookies: &CookieMap<'_>,
+ display_error: Option<String>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let lang = crate::get_lang_for_headers(headers);
+ let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, &cookies).await?;
+
+ let title = lang.tr("forgot_password", None);
+
+ Ok(html_response(render::html! {
+ <HTPage base_data={&base_data} lang={&lang} title={&title}>
+ <h1>{title.as_ref()}</h1>
+ <form method={"POST"} action={"/forgot_password/submit"}>
+ <p>{lang.tr("forgot_password_info", None)}</p>
+ {
+ display_error.map(|msg| {
+ render::rsx! {
+ <div class={"errorBox"}>{msg}</div>
+ }
+ })
+ }
+ <div>
+ <label>
+ {lang.tr("forgot_password_email_prompt", None)}
+ {" "}
+ <input type={"email"} name={"email_address"} required={"required"} />
+ </label>
+ </div>
+ <button type={"submit"}>{lang.tr("submit", None)}</button>
+ </form>
+ </HTPage>
+ }))
+}
+
+async fn page_forgot_password_code(
+ _: (),
+ ctx: Arc<crate::RouteContext>,
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let cookies = get_cookie_map_for_req(&req)?;
+
+ page_forgot_password_code_inner(ctx, req.headers(), &cookies, None).await
+}
+
+async fn page_forgot_password_code_inner(
+ ctx: Arc<crate::RouteContext>,
+ headers: &hyper::header::HeaderMap,
+ cookies: &CookieMap<'_>,
+ display_error: Option<String>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let lang = crate::get_lang_for_headers(headers);
+ let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, &cookies).await?;
+
+ let title = lang.tr("forgot_password", None);
+
+ Ok(html_response(render::html! {
+ <HTPage base_data={&base_data} lang={&lang} title={&title}>
+ <h1>{title.as_ref()}</h1>
+ <form method={"POST"} action={"/forgot_password/code/submit"}>
+ <p>{lang.tr("forgot_password_code_info", None)}</p>
+ {
+ display_error.map(|msg| {
+ render::rsx! {
+ <div class={"errorBox"}>{msg}</div>
+ }
+ })
+ }
+ <div>
+ <label>
+ {lang.tr("forgot_password_code_prompt", None)}
+ {" "}
+ <input type={"text"} name={"key"} required={"required"} />
+ </label>
+ </div>
+ <button type={"submit"}>{lang.tr("submit", None)}</button>
+ </form>
+ </HTPage>
+ }))
+}
+
+async fn page_forgot_password_code_reset_inner(
+ key: &str,
+ ctx: Arc<crate::RouteContext>,
+ headers: &hyper::header::HeaderMap,
+ cookies: &CookieMap<'_>,
+ display_error: Option<String>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let lang = crate::get_lang_for_headers(headers);
+ let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, &cookies).await?;
+
+ let title = lang.tr("forgot_password", None);
+
+ Ok(html_response(render::html! {
+ <HTPage base_data={&base_data} lang={&lang} title={&title}>
+ <h1>{title.as_ref()}</h1>
+ <form method={"POST"} action={"/forgot_password/code/submit"}>
+ {
+ display_error.map(|msg| {
+ render::rsx! {
+ <div class={"errorBox"}>{msg}</div>
+ }
+ })
+ }
+ <input type={"hidden"} name={"key"} value={key} />
+ <div>
+ <label>
+ {lang.tr("forgot_password_new_password_prompt", None)}
+ {" "}
+ <input type={"password"} name={"new_password"} required={"required"} />
+ </label>
+ </div>
+ <button type={"submit"}>{lang.tr("submit", None)}</button>
+ </form>
+ </HTPage>
+ }))
+}
+
+async fn handler_forgot_password_code_submit(
+ _: (),
+ ctx: Arc<crate::RouteContext>,
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ #[derive(Deserialize)]
+ struct CodeSubmitBody<'a> {
+ key: Cow<'a, str>,
+ new_password: Option<Cow<'a, str>>,
+ }
+
+ let (req_parts, body) = req.into_parts();
+
+ let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
+
+ let body = hyper::body::to_bytes(body).await?;
+ let body: CodeSubmitBody = serde_urlencoded::from_bytes(&body)?;
+
+ if let Some(new_password) = body.new_password {
+ let api_res = res_to_error(
+ ctx.http_client
+ .request(for_client(
+ hyper::Request::post(format!(
+ "{}/api/unstable/forgot_password/keys/{}/reset",
+ ctx.backend_host,
+ urlencoding::encode(&body.key),
+ ))
+ .body(
+ serde_json::to_vec(&serde_json::json!({ "new_password": new_password }))?
+ .into(),
+ )?,
+ &req_parts.headers,
+ &cookies,
+ )?)
+ .await?,
+ )
+ .await;
+
+ match api_res {
+ Ok(_) => {
+ let base_data = fetch_base_data(
+ &ctx.backend_host,
+ &ctx.http_client,
+ &req_parts.headers,
+ &cookies,
+ )
+ .await?;
+
+ let lang = crate::get_lang_for_headers(&req_parts.headers);
+
+ let title = lang.tr("forgot_password", None);
+
+ Ok(html_response(render::html! {
+ <HTPage base_data={&base_data} lang={&lang} title={&title}>
+ <h1>{title.as_ref()}</h1>
+ <p>
+ {lang.tr("forgot_password_complete", None)}{" "}
+ <a href={"/login"}>{lang.tr("login", None)}</a>
+ </p>
+ </HTPage>
+ }))
+ }
+ Err(crate::Error::RemoteError((_, message))) => {
+ page_forgot_password_code_reset_inner(
+ &body.key,
+ ctx,
+ &req_parts.headers,
+ &cookies,
+ Some(message),
+ )
+ .await
+ }
+ Err(other) => Err(other),
+ }
+ } else {
+ let api_res = res_to_error(
+ ctx.http_client
+ .request(for_client(
+ hyper::Request::get(format!(
+ "{}/api/unstable/forgot_password/keys/{}",
+ ctx.backend_host,
+ urlencoding::encode(&body.key),
+ ))
+ .body(Default::default())?,
+ &req_parts.headers,
+ &cookies,
+ )?)
+ .await?,
+ )
+ .await;
+
+ match api_res {
+ Ok(_) => {
+ page_forgot_password_code_reset_inner(
+ &body.key,
+ ctx,
+ &req_parts.headers,
+ &cookies,
+ None,
+ )
+ .await
+ }
+ Err(crate::Error::RemoteError((_, message))) => {
+ page_forgot_password_code_inner(ctx, &req_parts.headers, &cookies, Some(message))
+ .await
+ }
+ Err(other) => Err(other),
+ }
+ }
+}
+
+async fn handler_forgot_password_submit(
+ _: (),
+ ctx: Arc<crate::RouteContext>,
+ req: hyper::Request<hyper::Body>,
+) -> Result<hyper::Response<hyper::Body>, crate::Error> {
+ let (req_parts, body) = req.into_parts();
+
+ let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
+
+ let body = hyper::body::to_bytes(body).await?;
+ let body: serde_json::Value = serde_urlencoded::from_bytes(&body)?;
+
+ let api_res = res_to_error(
+ ctx.http_client
+ .request(for_client(
+ hyper::Request::post(format!(
+ "{}/api/unstable/forgot_password/keys",
+ ctx.backend_host,
+ ))
+ .body(serde_json::to_vec(&body)?.into())?,
+ &req_parts.headers,
+ &cookies,
+ )?)
+ .await?,
+ )
+ .await;
+
+ match api_res {
+ Ok(_) => Ok(hyper::Response::builder()
+ .status(hyper::StatusCode::SEE_OTHER)
+ .header(hyper::header::LOCATION, "/forgot_password/code")
+ .body("Request submitted.".into())?),
+ Err(crate::Error::RemoteError((_, message))) => {
+ page_forgot_password_inner(ctx, &req_parts.headers, &cookies, Some(message)).await
+ }
+ Err(other) => Err(other),
+ }
+}
+
+pub fn route_forgot_password() -> crate::RouteNode<()> {
+ crate::RouteNode::new()
+ .with_handler_async("GET", page_forgot_password)
+ .with_child(
+ "code",
+ crate::RouteNode::new()
+ .with_handler_async("GET", page_forgot_password_code)
+ .with_child(
+ "submit",
+ crate::RouteNode::new()
+ .with_handler_async("POST", handler_forgot_password_code_submit),
+ ),
+ )
+ .with_child(
+ "submit",
+ crate::RouteNode::new().with_handler_async("POST", handler_forgot_password_submit),
+ )
+}
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index d7684a7..fe959a1 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -14,6 +14,7 @@ use crate::util::author_is_me;
use crate::PageBaseData;
mod communities;
+mod forgot_password;
mod posts;
mod r#static;
@@ -648,6 +649,9 @@ async fn page_login_inner(
<p>
{lang.tr("or_start", None)}{" "}<a href={"/signup"}>{lang.tr("login_signup_link", None)}</a>
</p>
+ <p>
+ <a href={"/forgot_password"}>{lang.tr("forgot_password", None)}</a>
+ </p>
</HTPage>
}))
}
@@ -1568,6 +1572,7 @@ pub fn route_root() -> crate::RouteNode<()> {
),
)
.with_child("communities", communities::route_communities())
+ .with_child("forgot_password", forgot_password::route_forgot_password())
.with_child(
"login",
crate::RouteNode::new()