Compare commits

..

3 Commits

Author SHA1 Message Date
Stefan Melmuk 0c6817cb4e hide password hints via CSS (#6726) 2026-01-18 15:25:20 +01:00
Stefan Melmuk 25a71d913f use email instead of empty name for webauhn (#6733)
* if empty use email instead of name for webauhn

* use email as display name if name is empty
2026-01-18 15:23:21 +01:00
Mathijs van Veluw b2cd556f3e Fix User API Key login (#6712)
When using the latest Bitwarden CLI and logging in using the API Key, it expects some extra fields, same as for normal login.
This PR adds those fields and login is possible again via API Key.

Fixes #6709

Signed-off-by: BlackDex <black.dex@gmail.com>
2026-01-14 13:11:43 +01:00
6 changed files with 52 additions and 5 deletions
+1 -1
View File
@@ -3207,7 +3207,7 @@ async fn put_reset_password(
// Sending email before resetting password to ensure working email configuration and the resulting // Sending email before resetting password to ensure working email configuration and the resulting
// user notification. Also this might add some protection against security flaws and misuse // user notification. Also this might add some protection against security flaws and misuse
if let Err(e) = mail::send_admin_reset_password(&user.email, &user.name, &org.name).await { if let Err(e) = mail::send_admin_reset_password(&user.email, user.display_name(), &org.name).await {
err!(format!("Error sending user reset password email: {e:#?}")); err!(format!("Error sending user reset password email: {e:#?}"));
} }
+1 -1
View File
@@ -144,7 +144,7 @@ async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Hea
let (mut challenge, state) = WEBAUTHN.start_passkey_registration( let (mut challenge, state) = WEBAUTHN.start_passkey_registration(
Uuid::from_str(&user.uuid).expect("Failed to parse UUID"), // Should never fail Uuid::from_str(&user.uuid).expect("Failed to parse UUID"), // Should never fail
&user.email, &user.email,
&user.name, user.display_name(),
Some(registrations), Some(registrations),
)?; )?;
+26 -2
View File
@@ -266,7 +266,7 @@ async fn _sso_login(
Some((user, _)) if !user.enabled => { Some((user, _)) if !user.enabled => {
err!( err!(
"This user has been disabled", "This user has been disabled",
format!("IP: {}. Username: {}.", ip.ip, user.name), format!("IP: {}. Username: {}.", ip.ip, user.display_name()),
ErrorEvent { ErrorEvent {
event: EventType::UserFailedLogIn event: EventType::UserFailedLogIn
} }
@@ -521,7 +521,7 @@ async fn authenticated_response(
result["TwoFactorToken"] = Value::String(token); result["TwoFactorToken"] = Value::String(token);
} }
info!("User {} logged in successfully. IP: {}", &user.name, ip.ip); info!("User {} logged in successfully. IP: {}", user.display_name(), ip.ip);
Ok(Json(result)) Ok(Json(result))
} }
@@ -610,6 +610,25 @@ async fn _user_api_key_login(
info!("User {} logged in successfully via API key. IP: {}", user.email, ip.ip); info!("User {} logged in successfully via API key. IP: {}", user.email, ip.ip);
let has_master_password = !user.password_hash.is_empty();
let master_password_unlock = if has_master_password {
json!({
"Kdf": {
"KdfType": user.client_kdf_type,
"Iterations": user.client_kdf_iter,
"Memory": user.client_kdf_memory,
"Parallelism": user.client_kdf_parallelism
},
// This field is named inconsistently and will be removed and replaced by the "wrapped" variant in the apps.
// https://github.com/bitwarden/android/blob/release/2025.12-rc41/network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordUnlockDataJson.kt#L22-L26
"MasterKeyEncryptedUserKey": user.akey,
"MasterKeyWrappedUserKey": user.akey,
"Salt": user.email
})
} else {
Value::Null
};
// Note: No refresh_token is returned. The CLI just repeats the // Note: No refresh_token is returned. The CLI just repeats the
// client_credentials login flow when the existing token expires. // client_credentials login flow when the existing token expires.
let result = json!({ let result = json!({
@@ -625,6 +644,11 @@ async fn _user_api_key_login(
"KdfParallelism": user.client_kdf_parallelism, "KdfParallelism": user.client_kdf_parallelism,
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
"scope": AuthMethod::UserApiKey.scope(), "scope": AuthMethod::UserApiKey.scope(),
"UserDecryptionOptions": {
"HasMasterPassword": has_master_password,
"MasterPasswordUnlock": master_password_unlock,
"Object": "userDecryptionOptions"
},
}); });
Ok(Json(result)) Ok(Json(result))
+2 -1
View File
@@ -60,11 +60,12 @@ fn vaultwarden_css() -> Cached<Css<String>> {
"mail_2fa_enabled": CONFIG._enable_email_2fa(), "mail_2fa_enabled": CONFIG._enable_email_2fa(),
"mail_enabled": CONFIG.mail_enabled(), "mail_enabled": CONFIG.mail_enabled(),
"sends_allowed": CONFIG.sends_allowed(), "sends_allowed": CONFIG.sends_allowed(),
"password_hints_allowed": CONFIG.password_hints_allowed(),
"signup_disabled": CONFIG.is_signup_disabled(), "signup_disabled": CONFIG.is_signup_disabled(),
"sso_enabled": CONFIG.sso_enabled(), "sso_enabled": CONFIG.sso_enabled(),
"sso_only": CONFIG.sso_enabled() && CONFIG.sso_only(), "sso_only": CONFIG.sso_enabled() && CONFIG.sso_only(),
"yubico_enabled": CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some(),
"webauthn_2fa_supported": CONFIG.is_webauthn_2fa_supported(), "webauthn_2fa_supported": CONFIG.is_webauthn_2fa_supported(),
"yubico_enabled": CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some(),
}); });
let scss = match CONFIG.render_template("scss/vaultwarden.scss", &css_options) { let scss = match CONFIG.render_template("scss/vaultwarden.scss", &css_options) {
+9
View File
@@ -231,6 +231,15 @@ impl User {
pub fn reset_stamp_exception(&mut self) { pub fn reset_stamp_exception(&mut self) {
self.stamp_exception = None; self.stamp_exception = None;
} }
pub fn display_name(&self) -> &str {
// default to email if name is empty
if !&self.name.is_empty() {
&self.name
} else {
&self.email
}
}
} }
/// Database methods /// Database methods
@@ -192,6 +192,19 @@ bit-nav-item[route="sends"] {
@extend %vw-hide; @extend %vw-hide;
} }
{{/unless}} {{/unless}}
{{#unless password_hints_allowed}}
/* Hide password hints if not allowed */
a[routerlink="/hint"],
{{#if (webver "<2025.12.2")}}
app-change-password > form > .form-group:nth-child(5),
auth-input-password > form > bit-form-field:nth-child(4) {
{{else}}
.vw-password-hint {
{{/if}}
@extend %vw-hide;
}
{{/unless}}
/**** End Dynamic Vaultwarden Changes ****/ /**** End Dynamic Vaultwarden Changes ****/
/**** Include a special user stylesheet for custom changes ****/ /**** Include a special user stylesheet for custom changes ****/
{{#if load_user_scss}} {{#if load_user_scss}}