aboutsummaryrefslogtreecommitdiff
path: root/src/api/identity.rs
diff options
context:
space:
mode:
authorBlackDex <[email protected]>2021-11-16 17:07:55 +0100
committerDaniel GarcĂ­a <[email protected]>2022-02-27 21:37:23 +0100
commit87e08b9e50083d5b4aa3219ae5dcb67213427486 (patch)
treec3d8e80293927817cf4317d034bfdcd4d512be35 /src/api/identity.rs
parent0b7d6bf6df5a83dcc95d063baa04d8818eb9d7db (diff)
downloadvaultwarden-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.rs91
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"))]