mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-11 03:05:58 +03:00
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.
This commit is contained in:
@@ -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"))]
|
||||
|
Reference in New Issue
Block a user