| 
						
						
							
								
							
						
						
					 | 
					@ -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? | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					                } | 
					 | 
					 | 
					                } | 
				
			
			
		
	
		
		
			
				
					 | 
					 | 
					
 | 
					 | 
					 | 
					
 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
					
  |