summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/routes/forgot_password.rs305
-rw-r--r--src/routes/mod.rs5
2 files changed, 310 insertions, 0 deletions
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()