|
@ -17,7 +17,7 @@ use crate::{ |
|
|
push::register_push_device, |
|
|
push::register_push_device, |
|
|
ApiResult, EmptyResult, JsonResult, |
|
|
ApiResult, EmptyResult, JsonResult, |
|
|
}, |
|
|
}, |
|
|
auth::{generate_organization_api_key_login_claims, ClientHeaders, ClientIp}, |
|
|
auth::{generate_organization_api_key_login_claims, ClientHeaders, ClientIp, ClientVersion}, |
|
|
db::{models::*, DbConn}, |
|
|
db::{models::*, DbConn}, |
|
|
error::MapResult, |
|
|
error::MapResult, |
|
|
mail, util, CONFIG, |
|
|
mail, util, CONFIG, |
|
@ -28,7 +28,12 @@ pub fn routes() -> Vec<Route> { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
#[post("/connect/token", data = "<data>")] |
|
|
#[post("/connect/token", data = "<data>")] |
|
|
async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn: DbConn) -> JsonResult { |
|
|
async fn login( |
|
|
|
|
|
data: Form<ConnectData>, |
|
|
|
|
|
client_header: ClientHeaders, |
|
|
|
|
|
client_version: Option<ClientVersion>, |
|
|
|
|
|
mut conn: DbConn, |
|
|
|
|
|
) -> JsonResult { |
|
|
let data: ConnectData = data.into_inner(); |
|
|
let data: ConnectData = data.into_inner(); |
|
|
|
|
|
|
|
|
let mut user_id: Option<UserId> = None; |
|
|
let mut user_id: Option<UserId> = None; |
|
@ -48,7 +53,7 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn: |
|
|
_check_is_some(&data.device_name, "device_name cannot be blank")?; |
|
|
_check_is_some(&data.device_name, "device_name cannot be blank")?; |
|
|
_check_is_some(&data.device_type, "device_type cannot be blank")?; |
|
|
_check_is_some(&data.device_type, "device_type cannot be blank")?; |
|
|
|
|
|
|
|
|
_password_login(data, &mut user_id, &mut conn, &client_header.ip).await |
|
|
_password_login(data, &mut user_id, &mut conn, &client_header.ip, &client_version).await |
|
|
} |
|
|
} |
|
|
"client_credentials" => { |
|
|
"client_credentials" => { |
|
|
_check_is_some(&data.client_id, "client_id cannot be blank")?; |
|
|
_check_is_some(&data.client_id, "client_id cannot be blank")?; |
|
@ -144,6 +149,7 @@ async fn _password_login( |
|
|
user_id: &mut Option<UserId>, |
|
|
user_id: &mut Option<UserId>, |
|
|
conn: &mut DbConn, |
|
|
conn: &mut DbConn, |
|
|
ip: &ClientIp, |
|
|
ip: &ClientIp, |
|
|
|
|
|
client_version: &Option<ClientVersion>, |
|
|
) -> JsonResult { |
|
|
) -> JsonResult { |
|
|
// Validate scope
|
|
|
// Validate scope
|
|
|
let scope = data.scope.as_ref().unwrap(); |
|
|
let scope = data.scope.as_ref().unwrap(); |
|
@ -262,7 +268,7 @@ async fn _password_login( |
|
|
|
|
|
|
|
|
let (mut device, new_device) = get_device(&data, conn, &user).await; |
|
|
let (mut device, new_device) = get_device(&data, conn, &user).await; |
|
|
|
|
|
|
|
|
let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, conn).await?; |
|
|
let twofactor_token = twofactor_auth(&user, &data, &mut device, ip, client_version, conn).await?; |
|
|
|
|
|
|
|
|
if CONFIG.mail_enabled() && new_device { |
|
|
if CONFIG.mail_enabled() && new_device { |
|
|
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device).await { |
|
|
if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device).await { |
|
@ -520,6 +526,7 @@ async fn twofactor_auth( |
|
|
data: &ConnectData, |
|
|
data: &ConnectData, |
|
|
device: &mut Device, |
|
|
device: &mut Device, |
|
|
ip: &ClientIp, |
|
|
ip: &ClientIp, |
|
|
|
|
|
client_version: &Option<ClientVersion>, |
|
|
conn: &mut DbConn, |
|
|
conn: &mut DbConn, |
|
|
) -> ApiResult<Option<String>> { |
|
|
) -> ApiResult<Option<String>> { |
|
|
let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await; |
|
|
let twofactors = TwoFactor::find_by_user(&user.uuid, conn).await; |
|
@ -538,7 +545,10 @@ async fn twofactor_auth( |
|
|
let twofactor_code = match data.two_factor_token { |
|
|
let twofactor_code = match data.two_factor_token { |
|
|
Some(ref code) => code, |
|
|
Some(ref code) => code, |
|
|
None => { |
|
|
None => { |
|
|
err_json!(_json_err_twofactor(&twofactor_ids, &user.uuid, data, conn).await?, "2FA token not provided") |
|
|
err_json!( |
|
|
|
|
|
_json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?, |
|
|
|
|
|
"2FA token not provided" |
|
|
|
|
|
) |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -585,7 +595,7 @@ async fn twofactor_auth( |
|
|
} |
|
|
} |
|
|
_ => { |
|
|
_ => { |
|
|
err_json!( |
|
|
err_json!( |
|
|
_json_err_twofactor(&twofactor_ids, &user.uuid, data, conn).await?, |
|
|
_json_err_twofactor(&twofactor_ids, &user.uuid, data, client_version, conn).await?, |
|
|
"2FA Remember token not provided" |
|
|
"2FA Remember token not provided" |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
@ -617,6 +627,7 @@ async fn _json_err_twofactor( |
|
|
providers: &[i32], |
|
|
providers: &[i32], |
|
|
user_id: &UserId, |
|
|
user_id: &UserId, |
|
|
data: &ConnectData, |
|
|
data: &ConnectData, |
|
|
|
|
|
client_version: &Option<ClientVersion>, |
|
|
conn: &mut DbConn, |
|
|
conn: &mut DbConn, |
|
|
) -> ApiResult<Value> { |
|
|
) -> ApiResult<Value> { |
|
|
let mut result = json!({ |
|
|
let mut result = json!({ |
|
@ -689,8 +700,16 @@ async fn _json_err_twofactor( |
|
|
err!("No twofactor email registered") |
|
|
err!("No twofactor email registered") |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// Send email immediately if email is the only 2FA option
|
|
|
// Starting with version 2025.5.0 the client will call `/api/two-factor/send-email-login`.
|
|
|
if providers.len() == 1 { |
|
|
let disabled_send = if let Some(cv) = client_version { |
|
|
|
|
|
let ver_match = semver::VersionReq::parse(">=2025.5.0").unwrap(); |
|
|
|
|
|
ver_match.matches(&cv.0) |
|
|
|
|
|
} else { |
|
|
|
|
|
false |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Send email immediately if email is the only 2FA option.
|
|
|
|
|
|
if providers.len() == 1 && !disabled_send { |
|
|
email::send_token(user_id, conn).await? |
|
|
email::send_token(user_id, conn).await? |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|