diff options
author | BlackDex <[email protected]> | 2021-11-16 17:07:55 +0100 |
---|---|---|
committer | Daniel GarcĂa <[email protected]> | 2022-02-27 21:37:23 +0100 |
commit | 87e08b9e50083d5b4aa3219ae5dcb67213427486 (patch) | |
tree | c3d8e80293927817cf4317d034bfdcd4d512be35 /src/api/identity.rs | |
parent | 0b7d6bf6df5a83dcc95d063baa04d8818eb9d7db (diff) | |
download | vaultwarden-87e08b9e50083d5b4aa3219ae5dcb67213427486.tar.gz vaultwarden-87e08b9e50083d5b4aa3219ae5dcb67213427486.zip |
Async/Awaited all db methods
This is a rather large PR which updates the async branch to have all the
database methods as an async fn.
Some iter/map logic needed to be changed to a stream::iter().then(), but
besides that most changes were just adding async/await where needed.
Diffstat (limited to 'src/api/identity.rs')
-rw-r--r-- | src/api/identity.rs | 91 |
1 files changed, 48 insertions, 43 deletions
diff --git a/src/api/identity.rs b/src/api/identity.rs index 0ad8a1b5..2c04990b 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -23,13 +23,13 @@ pub fn routes() -> Vec<Route> { } #[post("/connect/token", data = "<data>")] -fn login(data: Form<ConnectData>, conn: DbConn, ip: ClientIp) -> JsonResult { +async fn login(data: Form<ConnectData>, conn: DbConn, ip: ClientIp) -> JsonResult { let data: ConnectData = data.into_inner(); match data.grant_type.as_ref() { "refresh_token" => { _check_is_some(&data.refresh_token, "refresh_token cannot be blank")?; - _refresh_login(data, conn) + _refresh_login(data, conn).await } "password" => { _check_is_some(&data.client_id, "client_id cannot be blank")?; @@ -41,34 +41,34 @@ fn login(data: Form<ConnectData>, conn: DbConn, ip: ClientIp) -> JsonResult { _check_is_some(&data.device_name, "device_name cannot be blank")?; _check_is_some(&data.device_type, "device_type cannot be blank")?; - _password_login(data, conn, &ip) + _password_login(data, conn, &ip).await } "client_credentials" => { _check_is_some(&data.client_id, "client_id cannot be blank")?; _check_is_some(&data.client_secret, "client_secret cannot be blank")?; _check_is_some(&data.scope, "scope cannot be blank")?; - _api_key_login(data, conn, &ip) + _api_key_login(data, conn, &ip).await } t => err!("Invalid type", t), } } -fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { +async fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { // Extract token let token = data.refresh_token.unwrap(); // Get device by refresh token - let mut device = Device::find_by_refresh_token(&token, &conn).map_res("Invalid refresh token")?; + let mut device = Device::find_by_refresh_token(&token, &conn).await.map_res("Invalid refresh token")?; let scope = "api offline_access"; let scope_vec = vec!["api".into(), "offline_access".into()]; // Common - let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap(); - let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); + let user = User::find_by_uuid(&device.user_uuid, &conn).await.unwrap(); + let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn).await; let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec); - device.save(&conn)?; + device.save(&conn).await?; Ok(Json(json!({ "access_token": access_token, @@ -86,7 +86,7 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { }))) } -fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { +async fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { // Validate scope let scope = data.scope.as_ref().unwrap(); if scope != "api offline_access" { @@ -99,7 +99,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult // Get the user let username = data.username.as_ref().unwrap(); - let user = match User::find_by_mail(username, &conn) { + let user = match User::find_by_mail(username, &conn).await { Some(user) => user, None => err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)), }; @@ -130,7 +130,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult user.last_verifying_at = Some(now); user.login_verify_count += 1; - if let Err(e) = user.save(&conn) { + if let Err(e) = user.save(&conn).await { error!("Error updating user: {:#?}", e); } @@ -144,9 +144,9 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult err!("Please verify your email before trying again.", format!("IP: {}. Username: {}.", ip.ip, username)) } - let (mut device, new_device) = get_device(&data, &conn, &user); + let (mut device, new_device) = get_device(&data, &conn, &user).await; - let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, &conn)?; + let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, &conn).await?; if CONFIG.mail_enabled() && new_device { if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name) { @@ -159,9 +159,9 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult } // Common - let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); + let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn).await; let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec); - device.save(&conn)?; + device.save(&conn).await?; let mut result = json!({ "access_token": access_token, @@ -187,7 +187,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult Ok(Json(result)) } -fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { +async fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { // Validate scope let scope = data.scope.as_ref().unwrap(); if scope != "api" { @@ -204,7 +204,7 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult Some(uuid) => uuid, None => err!("Malformed client_id", format!("IP: {}.", ip.ip)), }; - let user = match User::find_by_uuid(user_uuid, &conn) { + let user = match User::find_by_uuid(user_uuid, &conn).await { Some(user) => user, None => err!("Invalid client_id", format!("IP: {}.", ip.ip)), }; @@ -220,7 +220,7 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult err!("Incorrect client_secret", format!("IP: {}. Username: {}.", ip.ip, user.email)) } - let (mut device, new_device) = get_device(&data, &conn, &user); + let (mut device, new_device) = get_device(&data, &conn, &user).await; if CONFIG.mail_enabled() && new_device { let now = Utc::now().naive_utc(); @@ -234,9 +234,9 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult } // Common - let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); + let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn).await; let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec); - device.save(&conn)?; + device.save(&conn).await?; info!("User {} logged in successfully via API key. IP: {}", user.email, ip.ip); @@ -258,7 +258,7 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult } /// Retrieves an existing device or creates a new device from ConnectData and the User -fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) { +async fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) { // On iOS, device_type sends "iOS", on others it sends a number let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(0); let device_id = data.device_identifier.clone().expect("No device id provided"); @@ -266,7 +266,7 @@ fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) let mut new_device = false; // Find device or create new - let device = match Device::find_by_uuid(&device_id, conn) { + let device = match Device::find_by_uuid(&device_id, conn).await { Some(device) => { // Check if owned device, and recreate if not if device.user_uuid != user.uuid { @@ -286,28 +286,28 @@ fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) (device, new_device) } -fn twofactor_auth( +async fn twofactor_auth( user_uuid: &str, data: &ConnectData, device: &mut Device, ip: &ClientIp, conn: &DbConn, ) -> ApiResult<Option<String>> { - let twofactors = TwoFactor::find_by_user(user_uuid, conn); + let twofactors = TwoFactor::find_by_user(user_uuid, conn).await; // No twofactor token if twofactor is disabled if twofactors.is_empty() { return Ok(None); } - TwoFactorIncomplete::mark_incomplete(user_uuid, &device.uuid, &device.name, ip, conn)?; + TwoFactorIncomplete::mark_incomplete(user_uuid, &device.uuid, &device.name, ip, conn).await?; let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect(); let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, asume the first one let twofactor_code = match data.two_factor_token { Some(ref code) => code, - None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA token not provided"), + None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn).await?, "2FA token not provided"), }; let selected_twofactor = twofactors.into_iter().find(|tf| tf.atype == selected_id && tf.enabled); @@ -320,16 +320,18 @@ fn twofactor_auth( match TwoFactorType::from_i32(selected_id) { Some(TwoFactorType::Authenticator) => { - _tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn)? + _tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn).await? + } + Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn).await?, + Some(TwoFactorType::Webauthn) => { + _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn).await? } - Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?, - Some(TwoFactorType::Webauthn) => _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn)?, Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?, Some(TwoFactorType::Duo) => { - _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)? + _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn).await? } Some(TwoFactorType::Email) => { - _tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn)? + _tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn).await? } Some(TwoFactorType::Remember) => { @@ -338,14 +340,17 @@ fn twofactor_auth( remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time } _ => { - err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA Remember token not provided") + err_json!( + _json_err_twofactor(&twofactor_ids, user_uuid, conn).await?, + "2FA Remember token not provided" + ) } } } _ => err!("Invalid two factor provider"), } - TwoFactorIncomplete::mark_complete(user_uuid, &device.uuid, conn)?; + TwoFactorIncomplete::mark_complete(user_uuid, &device.uuid, conn).await?; if !CONFIG.disable_2fa_remember() && remember == 1 { Ok(Some(device.refresh_twofactor_remember())) @@ -359,7 +364,7 @@ fn _selected_data(tf: Option<TwoFactor>) -> ApiResult<String> { tf.map(|t| t.data).map_res("Two factor doesn't exist") } -fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> ApiResult<Value> { +async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> ApiResult<Value> { use crate::api::core::two_factor; let mut result = json!({ @@ -376,7 +381,7 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ } Some(TwoFactorType::U2f) if CONFIG.domain_set() => { - let request = two_factor::u2f::generate_u2f_login(user_uuid, conn)?; + let request = two_factor::u2f::generate_u2f_login(user_uuid, conn).await?; let mut challenge_list = Vec::new(); for key in request.registered_keys { @@ -396,17 +401,17 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api } Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => { - let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn)?; + let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn).await?; result["TwoFactorProviders2"][provider.to_string()] = request.0; } Some(TwoFactorType::Duo) => { - let email = match User::find_by_uuid(user_uuid, conn) { + let email = match User::find_by_uuid(user_uuid, conn).await { Some(u) => u.email, None => err!("User does not exist"), }; - let (signature, host) = duo::generate_duo_signature(&email, conn)?; + let (signature, host) = duo::generate_duo_signature(&email, conn).await?; result["TwoFactorProviders2"][provider.to_string()] = json!({ "Host": host, @@ -415,7 +420,7 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api } Some(tf_type @ TwoFactorType::YubiKey) => { - let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn) { + let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await { Some(tf) => tf, None => err!("No YubiKey devices registered"), }; @@ -430,14 +435,14 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api Some(tf_type @ TwoFactorType::Email) => { use crate::api::core::two_factor as _tf; - let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn) { + let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await { Some(tf) => tf, None => err!("No twofactor email registered"), }; // Send email immediately if email is the only 2FA option if providers.len() == 1 { - _tf::email::send_token(user_uuid, conn)? + _tf::email::send_token(user_uuid, conn).await? } let email_data = EmailTokenData::from_json(&twofactor.data)?; @@ -492,7 +497,7 @@ struct ConnectData { device_type: Option<String>, #[field(name = uncased("device_push_token"))] #[field(name = uncased("devicepushtoken"))] - device_push_token: Option<String>, // Unused; mobile device push not yet supported. + _device_push_token: Option<String>, // Unused; mobile device push not yet supported. // Needed for two-factor auth #[field(name = uncased("two_factor_provider"))] |