From 3d1b91c031d2e67e2f9265dc8366c684ce286d28 Mon Sep 17 00:00:00 2001 From: Colin Reeder Date: Sat, 26 Sep 2020 18:29:31 -0600 Subject: Add Forgot Password --- res/lang/en.ftl | 7 + src/routes/forgot_password.rs | 305 ++++++++++++++++++++++++++++++++++++++++++ src/routes/mod.rs | 5 + 3 files changed, 317 insertions(+) create mode 100644 src/routes/forgot_password.rs 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, + req: hyper::Request, +) -> Result, 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, + headers: &hyper::header::HeaderMap, + cookies: &CookieMap<'_>, + display_error: Option, +) -> Result, 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! { + +

{title.as_ref()}

+
+

{lang.tr("forgot_password_info", None)}

+ { + display_error.map(|msg| { + render::rsx! { +
{msg}
+ } + }) + } +
+ +
+ +
+
+ })) +} + +async fn page_forgot_password_code( + _: (), + ctx: Arc, + req: hyper::Request, +) -> Result, 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, + headers: &hyper::header::HeaderMap, + cookies: &CookieMap<'_>, + display_error: Option, +) -> Result, 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! { + +

{title.as_ref()}

+
+

{lang.tr("forgot_password_code_info", None)}

+ { + display_error.map(|msg| { + render::rsx! { +
{msg}
+ } + }) + } +
+ +
+ +
+
+ })) +} + +async fn page_forgot_password_code_reset_inner( + key: &str, + ctx: Arc, + headers: &hyper::header::HeaderMap, + cookies: &CookieMap<'_>, + display_error: Option, +) -> Result, 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! { + +

{title.as_ref()}

+
+ { + display_error.map(|msg| { + render::rsx! { +
{msg}
+ } + }) + } + +
+ +
+ +
+
+ })) +} + +async fn handler_forgot_password_code_submit( + _: (), + ctx: Arc, + req: hyper::Request, +) -> Result, crate::Error> { + #[derive(Deserialize)] + struct CodeSubmitBody<'a> { + key: Cow<'a, str>, + new_password: Option>, + } + + 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! { + +

{title.as_ref()}

+

+ {lang.tr("forgot_password_complete", None)}{" "} + {lang.tr("login", None)} +

+
+ })) + } + 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, + req: hyper::Request, +) -> Result, 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(

{lang.tr("or_start", None)}{" "}{lang.tr("login_signup_link", None)}

+

+ {lang.tr("forgot_password", None)} +

})) } @@ -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() -- cgit v1.2.3