~vpzom/lotide

32fee9a2e36802f2ed56b8c905aef19c4cae4b2a — Colin Reeder 2 months ago b6c6754
Add endpoint for checking forgot password key validity
2 files changed, 52 insertions(+), 3 deletions(-)

M res/lang/en.ftl
M src/routes/api/forgot_password.rs
M res/lang/en.ftl => res/lang/en.ftl +1 -0
@@ 10,6 10,7 @@ name_in_use = That name is already in use
no_password = No password set for this user
no_such_comment = No such comment
no_such_community = No such community
no_such_forgot_password_key = No such password reset key, or it has expired
no_such_local_user_by_email = No local user found by that email address
no_such_local_user_by_name = No local user found by that name
no_such_post = No such post

M src/routes/api/forgot_password.rs => src/routes/api/forgot_password.rs +51 -3
@@ 19,12 19,27 @@ impl ForgotPasswordKey {
    pub fn as_int(&self) -> i32 {
        self.value
    }
}

    pub fn to_str(&self) -> String {
// implementing this trait is discouraged in favor of Display, but bs58 doesn't do streaming output
impl std::string::ToString for ForgotPasswordKey {
    fn to_string(&self) -> String {
        bs58::encode(&self.value.to_be_bytes()).into_string()
    }
}

impl std::str::FromStr for ForgotPasswordKey {
    type Err = bs58::decode::Error;

    fn from_str(src: &str) -> Result<Self, Self::Err> {
        let mut buf = [0; 4];
        bs58::decode(src).into(&mut buf)?;
        Ok(Self {
            value: i32::from_be_bytes(buf),
        })
    }
}

async fn route_unstable_forgot_password_keys_create(
    _: (),
    ctx: Arc<crate::RouteContext>,


@@ 73,7 88,7 @@ async fn route_unstable_forgot_password_keys_create(
    let msg_body = lang
        .tr(
            "email_content_forgot_password",
            Some(&fluent::fluent_args!["key" => key.to_str(), "username" => username]),
            Some(&fluent::fluent_args!["key" => key.to_string(), "username" => username]),
        )
        .into_owned();



@@ 99,10 114,43 @@ async fn route_unstable_forgot_password_keys_create(
        .body("{}".into())?)
}

async fn route_unstable_forgot_password_keys_get(
    params: (ForgotPasswordKey,),
    ctx: Arc<crate::RouteContext>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let (key,) = params;

    let lang = crate::get_lang_for_req(&req);
    let db = ctx.db_pool.get().await?;

    let row = db.query_opt("SELECT created < (current_timestamp - INTERVAL '1 HOUR') FROM forgot_password_key WHERE key=$1", &[&key.as_int()]).await?;

    let found = match row {
        None => false,
        Some(row) => !row.get::<_, bool>(0),
    };

    if found {
        Ok(hyper::Response::builder()
            .header(hyper::header::CONTENT_TYPE, "application/json")
            .body("{}".into())?)
    } else {
        Err(crate::Error::UserError(crate::simple_response(
            hyper::StatusCode::NOT_FOUND,
            lang.tr("no_such_forgot_password_key", None).into_owned(),
        )))
    }
}

pub fn route_forgot_password() -> crate::RouteNode<()> {
    crate::RouteNode::new().with_child(
        "keys",
        crate::RouteNode::new()
            .with_handler_async("POST", route_unstable_forgot_password_keys_create),
            .with_handler_async("POST", route_unstable_forgot_password_keys_create)
            .with_child_parse::<ForgotPasswordKey, _>(
                crate::RouteNode::new()
                    .with_handler_async("GET", route_unstable_forgot_password_keys_get),
            ),
    )
}