~vpzom/lotide

1c3bad4cb70e5af61332000c44e1c5f7c238e441 — Colin Reeder 8 months ago f93374d
Add password & email_address to user edit fields
4 files changed, 59 insertions(+), 9 deletions(-)

M Cargo.lock
M Cargo.toml
M openapi/openapi.json
M src/routes/api/users.rs
M Cargo.lock => Cargo.lock +1 -0
@@ 946,6 946,7 @@ dependencies = [
 "async-trait",
 "bcrypt",
 "bs58",
 "bumpalo",
 "bytes",
 "chrono",
 "deadpool-postgres",

M Cargo.toml => Cargo.toml +1 -0
@@ 45,6 45,7 @@ fast_chemail = "0.9.6"
lettre = { version = "0.10.0-alpha.2", features = ["tokio02", "tokio02-native-tls"] }
rand = "0.7.3"
bs58 = "0.3.1"
bumpalo = "3.4.0"

[dev-dependencies]
rand = "0.7.3"

M openapi/openapi.json => openapi/openapi.json +3 -1
@@ 1424,7 1424,9 @@
							"schema": {
								"type": "object",
								"properties": {
									"description": {"type": "string"}
									"description": {"type": "string"},
									"email_address": {"type": "string", "format": "email"},
									"password": {"type": "string", "format": "password"}
								}
							}
						}

M src/routes/api/users.rs => src/routes/api/users.rs +54 -8
@@ 173,26 173,72 @@ async fn route_unstable_users_patch(
    ctx: Arc<crate::RouteContext>,
    req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
    let lang = crate::get_lang_for_req(&req);
    let db = ctx.db_pool.get().await?;

    let user = params.0.require_me(&req, &db).await?;
    let user_id = params.0.require_me(&req, &db).await?;

    #[derive(Deserialize)]
    struct UsersEditBody<'a> {
        description: Option<Cow<'a, str>>,
        email_address: Option<Cow<'a, str>>,
        password: Option<String>,
    }

    let body = hyper::body::to_bytes(req.into_body()).await?;
    let body: UsersEditBody = serde_json::from_slice(&body)?;

    if let Some(description) = body.description {
        db.execute(
            "UPDATE person SET description=$1 WHERE id=$2",
            &[&description, &user],
        )
        .await?;
    let arena = bumpalo::Bump::new();

    let mut changes = Vec::<(&str, &(dyn tokio_postgres::types::ToSql + Sync))>::new();

    if let Some(description) = body.description.as_ref() {
        changes.push(("description", description));
    }
    if let Some(email_address) = body.email_address.as_ref() {
        if !fast_chemail::is_valid_email(&email_address) {
            return Err(crate::Error::UserError(crate::simple_response(
                hyper::StatusCode::BAD_REQUEST,
                lang.tr("user_email_invalid", None).into_owned(),
            )));
        }

        changes.push(("email_address", email_address));
    }
    if let Some(password) = body.password {
        let passhash =
            tokio::task::spawn_blocking(move || bcrypt::hash(password, bcrypt::DEFAULT_COST))
                .await??;

        changes.push(("passhash", arena.alloc(passhash)));
    }

    if !changes.is_empty() {
        use std::fmt::Write;

        let mut sql = "UPDATE person SET ".to_owned();
        let mut values: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = changes
            .iter()
            .enumerate()
            .map(|(idx, (key, value))| {
                write!(
                    sql,
                    "{}{}=${}",
                    if idx == 0 { "" } else { "," },
                    key,
                    idx + 1
                )
                .unwrap();

                *value
            })
            .collect();
        values.push(&user_id);
        write!(sql, " WHERE id=${}", values.len()).unwrap();

        let sql: &str = &sql;

        // TODO maybe send this somewhere?
        db.execute(sql, &values).await?;
    }

    Ok(crate::empty_response())