@ -17,15 +17,14 @@ use crate::{
accounts ::{ PreloginData , RegisterData , kdf_upgrade , prelogin , register } ,
log_user_event ,
two_factor ::{
authenticator , duo , duo_oidc , email , enforce_2fa_policy , is_twofactor_provider_usable , webauthn ,
yubikey ,
authenticator , duo_oidc , email , enforce_2fa_policy , is_twofactor_provider_usable , webauthn , yubikey ,
} ,
} ,
master_password_policy ,
push ::register_push_device ,
} ,
auth ,
auth ::{ AuthMethod , ClientHeaders , ClientIp , ClientVersion , Secure , generate_organization_api_key_login_claims } ,
auth ::{ AuthMethod , ClientHeaders , ClientIp , Secure , generate_organization_api_key_login_claims } ,
crypto ,
db ::{
DbConn ,
@ -46,7 +45,6 @@ pub fn routes() -> Vec<Route> {
login ,
post_prelogin ,
prelogin_password ,
identity_register ,
register_verification_email ,
register_finish ,
prevalidate ,
@ -57,12 +55,7 @@ pub fn routes() -> Vec<Route> {
}
#[ post( " /connect/token " , data = " <data> " ) ]
async fn login (
data : Form < ConnectData > ,
client_header : ClientHeaders ,
client_version : Option < ClientVersion > ,
conn : DbConn ,
) -> JsonResult {
async fn login ( data : Form < ConnectData > , client_header : ClientHeaders , conn : DbConn ) -> JsonResult {
let data : ConnectData = data . into_inner ( ) ;
let mut user_id : Option < UserId > = None ;
@ -83,7 +76,7 @@ async fn login(
check_is_some ( data . device_name . as_ref ( ) , "device_name cannot be blank" ) ? ;
check_is_some ( data . device_type . as_ref ( ) , "device_type cannot be blank" ) ? ;
password_login ( data , & mut user_id , & conn , & client_header . ip , client_version . as_ref ( ) ) . await
password_login ( data , & mut user_id , & conn , & client_header . ip ) . await
}
"client_credentials" = > {
check_is_some ( data . client_id . as_ref ( ) , "client_id cannot be blank" ) ? ;
@ -105,7 +98,7 @@ async fn login(
check_is_some ( data . device_name . as_ref ( ) , "device_name cannot be blank" ) ? ;
check_is_some ( data . device_type . as_ref ( ) , "device_type cannot be blank" ) ? ;
sso_login ( data , & mut user_id , & conn , & client_header . ip , client_version . as_ref ( ) ) . await
sso_login ( data , & mut user_id , & conn , & client_header . ip ) . await
}
"authorization_code" = > err ! ( "SSO sign-in is not available" ) ,
"send_access" = > {
@ -190,13 +183,7 @@ async fn refresh_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> JsonR
}
// After exchanging the code we need to check first if 2FA is needed before continuing
async fn sso_login (
data : ConnectData ,
user_id : & mut Option < UserId > ,
conn : & DbConn ,
ip : & ClientIp ,
client_version : Option < & ClientVersion > ,
) -> JsonResult {
async fn sso_login ( data : ConnectData , user_id : & mut Option < UserId > , conn : & DbConn , ip : & ClientIp ) -> JsonResult {
AuthMethod ::Sso . check_scope ( data . scope . as_ref ( ) ) ? ;
// Ratelimit the login
@ -353,7 +340,7 @@ async fn sso_login(
Some ( ( mut user , sso_user ) ) = > {
let mut device = get_device ( & data , conn , & user ) . await ? ;
let twofactor_token = twofactor_auth ( & mut user , & data , & mut device , ip , client_version , c onn ) . await ? ;
let twofactor_token = twofactor_auth ( & mut user , & data , & mut device , ip , conn ) . await ? ;
if user . private_key . is_none ( ) {
// User was invited a stub was created
@ -385,13 +372,7 @@ async fn sso_login(
authenticated_response ( & user , & mut device , auth_tokens , twofactor_token , conn , ip ) . await
}
async fn password_login (
data : ConnectData ,
user_id : & mut Option < UserId > ,
conn : & DbConn ,
ip : & ClientIp ,
client_version : Option < & ClientVersion > ,
) -> JsonResult {
async fn password_login ( data : ConnectData , user_id : & mut Option < UserId > , conn : & DbConn , ip : & ClientIp ) -> JsonResult {
// Validate scope
AuthMethod ::Password . check_scope ( data . scope . as_ref ( ) ) ? ;
@ -400,24 +381,14 @@ async fn password_login(
// Get the user
let username = data . username . as_ref ( ) . unwrap ( ) . trim ( ) ;
let log_username = username . escape_debug ( ) ;
let Some ( mut user ) = User ::find_by_mail ( username , conn ) . await else {
err ! ( "Username or password is incorrect. Try again" , format ! ( "IP: {}. Username: {username}." , ip . ip ) )
err ! ( "Username or password is incorrect. Try again" , format ! ( "IP: {}. Username: {log_ username}." , ip . ip ) )
} ;
// Set the user_id here to be passed back used for event logging.
* user_id = Some ( user . uuid . clone ( ) ) ;
// Check if the user is disabled
if ! user . enabled {
err ! (
"This user has been disabled" ,
format ! ( "IP: {}. Username: {username}." , ip . ip ) ,
ErrorEvent {
event : EventType ::UserFailedLogIn
}
)
}
let password = data . password . as_ref ( ) . unwrap ( ) ;
// If we get an auth request, we don't check the user's password, but the access code of the auth request
@ -425,7 +396,7 @@ async fn password_login(
let Some ( auth_request ) = AuthRequest ::find_by_uuid_and_user ( auth_request_id , & user . uuid , conn ) . await else {
err ! (
"Auth request not found. Try again." ,
format ! ( "IP: {}. Username: {username}." , ip . ip ) ,
format ! ( "IP: {}. Username: {log_ username}." , ip . ip ) ,
ErrorEvent {
event : EventType ::UserFailedLogIn ,
}
@ -443,7 +414,7 @@ async fn password_login(
{
err ! (
"Username or access code is incorrect. Try again" ,
format ! ( "IP: {}. Username: {username}." , ip . ip ) ,
format ! ( "IP: {}. Username: {log_ username}." , ip . ip ) ,
ErrorEvent {
event : EventType ::UserFailedLogIn ,
}
@ -452,13 +423,24 @@ async fn password_login(
} else if ! user . check_valid_password ( password ) {
err ! (
"Username or password is incorrect. Try again" ,
format ! ( "IP: {}. Username: {username}." , ip . ip ) ,
format ! ( "IP: {}. Username: {log_ username}." , ip . ip ) ,
ErrorEvent {
event : EventType ::UserFailedLogIn ,
}
)
}
// Check if the user is disabled
if ! user . enabled {
err ! (
"This user has been disabled" ,
format ! ( "IP: {}. Username: {log_username}." , ip . ip ) ,
ErrorEvent {
event : EventType ::UserFailedLogIn
}
)
}
// Change the KDF Iterations (only when not logging in with an auth request)
if data . auth_request . is_none ( ) {
kdf_upgrade ( & mut user , password , conn ) . await ? ;
@ -491,7 +473,7 @@ async fn password_login(
// We still want the login to fail until they actually verified the email address
err ! (
"Please verify your email before trying again." ,
format ! ( "IP: {}. Username: {username}." , ip . ip ) ,
format ! ( "IP: {}. Username: {log_ username}." , ip . ip ) ,
ErrorEvent {
event : EventType ::UserFailedLogIn
}
@ -500,7 +482,7 @@ async fn password_login(
let mut device = get_device ( & data , conn , & user ) . await ? ;
let twofactor_token = twofactor_auth ( & mut user , & data , & mut device , ip , client_version , c onn ) . await ? ;
let twofactor_token = twofactor_auth ( & mut user , & data , & mut device , ip , conn ) . await ? ;
let auth_tokens = auth ::AuthTokens ::new ( & device , & user , AuthMethod ::Password , data . client_id ) ;
@ -583,7 +565,6 @@ async fn authenticated_response(
"KdfIterations" : user . client_kdf_iter ,
"KdfMemory" : user . client_kdf_memory ,
"KdfParallelism" : user . client_kdf_parallelism ,
"ResetMasterPassword" : false , // TODO: Same as above
"ForcePasswordReset" : false ,
"MasterPasswordPolicy" : master_password_policy ,
"scope" : auth_tokens . scope ( ) ,
@ -737,7 +718,6 @@ async fn user_api_key_login(
"KdfIterations" : user . client_kdf_iter ,
"KdfMemory" : user . client_kdf_memory ,
"KdfParallelism" : user . client_kdf_parallelism ,
"ResetMasterPassword" : false , // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
"ForcePasswordReset" : false ,
"scope" : AuthMethod ::UserApiKey . scope ( ) ,
"AccountKeys" : account_keys ,
@ -803,7 +783,6 @@ async fn twofactor_auth(
data : & ConnectData ,
device : & mut Device ,
ip : & ClientIp ,
client_version : Option < & ClientVersion > ,
conn : & DbConn ,
) -> ApiResult < Option < String > > {
let twofactors = TwoFactor ::find_by_user ( & user . uuid , conn ) . await ;
@ -832,17 +811,12 @@ async fn twofactor_auth(
if ! [ TwoFactorType ::Remember as i32 , TwoFactorType ::RecoveryCode as i32 ] . contains ( & selected_id )
& & ! twofactor_ids . contains ( & selected_id )
{
err_json ! (
json_err_twofactor ( & twofactor_ids , & user . uuid , data , client_version , conn ) . await ? ,
"Invalid two factor provider"
)
err_json ! ( json_err_twofactor ( & twofactor_ids , & user . uuid , data , conn ) . await ? , "Invalid two factor provider" )
}
let Some ( ref twofactor_code ) = data . two_factor_token else {
err_json ! (
json_err_twofactor ( & twofactor_ids , & user . uuid , data , client_version , conn ) . await ? ,
"2FA token not provided"
)
// Like upstream, a blank token counts as not provided
let Some ( twofactor_code ) = data . two_factor_token . as_deref ( ) . filter ( | t | ! t . trim ( ) . is_empty ( ) ) else {
err_json ! ( json_err_twofactor ( & twofactor_ids , & user . uuid , data , conn ) . await ? , "2FA token not provided" )
} ;
let selected_twofactor = twofactors . into_iter ( ) . find ( | tf | tf . atype = = selected_id & & tf . enabled ) ;
@ -856,11 +830,6 @@ async fn twofactor_auth(
Some ( TwoFactorType ::Webauthn ) = > webauthn ::validate_webauthn_login ( & user . uuid , twofactor_code , conn ) . await ? ,
Some ( TwoFactorType ::YubiKey ) = > yubikey ::validate_yubikey_login ( twofactor_code , & selected_data ? ) . await ? ,
Some ( TwoFactorType ::Duo ) = > {
if CONFIG . duo_use_iframe ( ) {
// Legacy iframe prompt flow
duo ::validate_duo_login ( & user . email , twofactor_code , conn ) . await ? ;
} else {
// OIDC based flow
duo_oidc ::validate_duo_login (
& user . email ,
twofactor_code ,
@ -870,7 +839,6 @@ async fn twofactor_auth(
)
. await ? ;
}
}
Some ( TwoFactorType ::Email ) = > {
email ::validate_email_code_str ( & user . uuid , twofactor_code , & selected_data ? , & ip . ip , conn ) . await ? ;
}
@ -891,7 +859,7 @@ async fn twofactor_auth(
device . save ( true , conn ) . await ? ;
}
err_json ! (
json_err_twofactor ( & twofactor_ids , & user . uuid , data , client_version , c onn ) . await ? ,
json_err_twofactor ( & twofactor_ids , & user . uuid , data , conn ) . await ? ,
"2FA Remember token not provided or expired"
)
}
@ -946,7 +914,6 @@ async fn json_err_twofactor(
providers : & [ i32 ] ,
user_id : & UserId ,
data : & ConnectData ,
client_version : Option < & ClientVersion > ,
conn : & DbConn ,
) -> ApiResult < Value > {
let mut result = json ! ( {
@ -975,15 +942,6 @@ async fn json_err_twofactor(
err ! ( "User does not exist" )
} ;
if CONFIG . duo_use_iframe ( ) {
// Legacy iframe prompt flow
let ( signature , host ) = duo ::generate_duo_signature ( & email , conn ) . await ? ;
result [ "TwoFactorProviders2" ] [ provider . to_string ( ) ] = json ! ( {
"Host" : host ,
"Signature" : signature ,
} ) ;
} else {
// OIDC based flow
let auth_url = duo_oidc ::get_duo_auth_url (
& email ,
data . client_id . as_ref ( ) . unwrap ( ) ,
@ -996,7 +954,6 @@ async fn json_err_twofactor(
"AuthUrl" : auth_url ,
} ) ;
}
}
Some ( tf_type @ TwoFactorType ::YubiKey ) = > {
let Some ( twofactor ) = TwoFactor ::find_by_user_and_type ( user_id , tf_type as i32 , conn ) . await else {
@ -1015,19 +972,6 @@ async fn json_err_twofactor(
err ! ( "No twofactor email registered" )
} ;
// Starting with version 2025.5.0 the client will call `/api/two-factor/send-email-login`.
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 ? ;
}
let email_data = email ::EmailTokenData ::from_json ( & twofactor . data ) ? ;
result [ "TwoFactorProviders2" ] [ provider . to_string ( ) ] = json ! ( {
"Email" : email ::obscure_email ( & email_data . email ) ,
@ -1065,11 +1009,6 @@ async fn prelogin_password(data: Json<PreloginData>, ip: ClientIp, conn: DbConn)
prelogin ( data , ip , conn ) . await
}
#[ post( " /accounts/register " , data = " <data> " ) ]
async fn identity_register ( data : Json < RegisterData > , conn : DbConn ) -> JsonResult {
register ( data , false , conn ) . await
}
#[ derive(Debug, Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct RegisterVerificationData {
@ -1141,8 +1080,9 @@ async fn register_verification_email(
}
#[ post( " /accounts/register/finish " , data = " <data> " ) ]
async fn register_finish ( data : Json < RegisterData > , conn : DbConn ) -> JsonResult {
register ( data , true , conn ) . await
async fn register_finish ( data : Json < RegisterData > , ip : ClientIp , conn : DbConn ) -> JsonResult {
crate ::ratelimit ::check_limit_unauthenticated ( & ip . ip ) ? ;
register ( data , conn ) . await
}
// https://github.com/bitwarden/jslib/blob/master/common/src/models/request/tokenRequest.ts