mirror of
https://github.com/bitwarden/browser
synced 2025-12-31 07:33:23 +00:00
[PM-22090] Delete password on Windows desktop throws incorrect error (#15070)
* delete password on Windows desktop throws incorrect error * delete password on Windows desktop throws incorrect error * napi documentation improvements * napi documentation update * better logging verbosity * desktop native clippy errors * unit test coverage * napi TS documentation JS language friendly * fixing merge conflicts
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
use anyhow::Result;
|
||||
use security_framework::passwords::{
|
||||
delete_generic_password, get_generic_password, set_generic_password,
|
||||
};
|
||||
|
||||
pub async fn get_password(service: &str, account: &str) -> Result<String> {
|
||||
let result = String::from_utf8(get_generic_password(service, account)?)?;
|
||||
let password = get_generic_password(service, account).map_err(convert_error)?;
|
||||
let result = String::from_utf8(password)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -14,7 +16,7 @@ pub async fn set_password(service: &str, account: &str, password: &str) -> Resul
|
||||
}
|
||||
|
||||
pub async fn delete_password(service: &str, account: &str) -> Result<()> {
|
||||
delete_generic_password(service, account)?;
|
||||
delete_generic_password(service, account).map_err(convert_error)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -22,6 +24,15 @@ pub async fn is_available() -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn convert_error(e: security_framework::base::Error) -> anyhow::Error {
|
||||
match e.code() {
|
||||
security_framework_sys::base::errSecItemNotFound => {
|
||||
anyhow::anyhow!(PASSWORD_NOT_FOUND)
|
||||
}
|
||||
_ => anyhow::anyhow!(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -44,10 +55,7 @@ mod tests {
|
||||
// Ensure password is deleted
|
||||
match get_password("BitwardenTest", "BitwardenTest").await {
|
||||
Ok(_) => panic!("Got a result"),
|
||||
Err(e) => assert_eq!(
|
||||
"The specified item could not be found in the keychain.",
|
||||
e.to_string()
|
||||
),
|
||||
Err(e) => assert_eq!(PASSWORD_NOT_FOUND, e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +63,7 @@ mod tests {
|
||||
async fn test_error_no_password() {
|
||||
match get_password("Unknown", "Unknown").await {
|
||||
Ok(_) => panic!("Got a result"),
|
||||
Err(e) => assert_eq!(
|
||||
"The specified item could not be found in the keychain.",
|
||||
e.to_string()
|
||||
),
|
||||
Err(e) => assert_eq!(PASSWORD_NOT_FOUND, e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub const PASSWORD_NOT_FOUND: &str = "Password not found.";
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
#[cfg_attr(target_os = "linux", path = "unix.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
use anyhow::{anyhow, Result};
|
||||
use oo7::dbus::{self};
|
||||
use std::collections::HashMap;
|
||||
@@ -20,7 +21,7 @@ async fn get_password_new(service: &str, account: &str) -> Result<String> {
|
||||
let secret = res.secret().await?;
|
||||
Ok(String::from_utf8(secret.to_vec())?)
|
||||
}
|
||||
None => Err(anyhow!("no result")),
|
||||
None => Err(anyhow!(PASSWORD_NOT_FOUND)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ async fn get_password_legacy(service: &str, account: &str) -> Result<String> {
|
||||
set_password(service, account, &secret_string).await?;
|
||||
Ok(secret_string)
|
||||
}
|
||||
None => Err(anyhow!("no result")),
|
||||
None => Err(anyhow!(PASSWORD_NOT_FOUND)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +153,7 @@ mod tests {
|
||||
Ok(_) => {
|
||||
panic!("Got a result")
|
||||
}
|
||||
Err(e) => assert_eq!("no result", e.to_string()),
|
||||
Err(e) => assert_eq!(PASSWORD_NOT_FOUND, e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +161,7 @@ mod tests {
|
||||
async fn test_error_no_password() {
|
||||
match get_password("Unknown", "Unknown").await {
|
||||
Ok(_) => panic!("Got a result"),
|
||||
Err(e) => assert_eq!("no result", e.to_string()),
|
||||
Err(e) => assert_eq!(PASSWORD_NOT_FOUND, e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::password::PASSWORD_NOT_FOUND;
|
||||
use anyhow::{anyhow, Result};
|
||||
use widestring::{U16CString, U16String};
|
||||
use windows::{
|
||||
@@ -79,7 +80,9 @@ pub async fn set_password(service: &str, account: &str, password: &str) -> Resul
|
||||
pub async fn delete_password(service: &str, account: &str) -> Result<()> {
|
||||
let target_name = U16CString::from_str(target_name(service, account))?;
|
||||
|
||||
unsafe { CredDeleteW(PCWSTR(target_name.as_ptr()), CRED_TYPE_GENERIC, None)? };
|
||||
let result = unsafe { CredDeleteW(PCWSTR(target_name.as_ptr()), CRED_TYPE_GENERIC, None) };
|
||||
|
||||
result.map_err(|e| anyhow!(convert_error(e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -95,7 +98,7 @@ fn target_name(service: &str, account: &str) -> String {
|
||||
// Convert the internal WIN32 errors to descriptive messages
|
||||
fn convert_error(e: windows::core::Error) -> String {
|
||||
if e == ERROR_NOT_FOUND.into() {
|
||||
return "Password not found.".to_string();
|
||||
return PASSWORD_NOT_FOUND.to_string();
|
||||
}
|
||||
e.to_string()
|
||||
}
|
||||
@@ -122,7 +125,7 @@ mod tests {
|
||||
// Ensure password is deleted
|
||||
match get_password("BitwardenTest", "BitwardenTest").await {
|
||||
Ok(_) => panic!("Got a result"),
|
||||
Err(e) => assert_eq!("Password not found.", e.to_string()),
|
||||
Err(e) => assert_eq!(PASSWORD_NOT_FOUND, e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +133,7 @@ mod tests {
|
||||
async fn test_error_no_password() {
|
||||
match get_password("BitwardenTest", "BitwardenTest").await {
|
||||
Ok(_) => panic!("Got a result"),
|
||||
Err(e) => assert_eq!("Password not found.", e.to_string()),
|
||||
Err(e) => assert_eq!(PASSWORD_NOT_FOUND, e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
apps/desktop/desktop_native/napi/index.d.ts
vendored
17
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -4,18 +4,31 @@
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
export declare namespace passwords {
|
||||
/** Fetch the stored password from the keychain. */
|
||||
/** The error message returned when a password is not found during retrieval or deletion. */
|
||||
export const PASSWORD_NOT_FOUND: string
|
||||
/**
|
||||
* Fetch the stored password from the keychain.
|
||||
* Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist.
|
||||
*/
|
||||
export function getPassword(service: string, account: string): Promise<string>
|
||||
/** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */
|
||||
export function setPassword(service: string, account: string, password: string): Promise<void>
|
||||
/** Delete the stored password from the keychain. */
|
||||
/**
|
||||
* Delete the stored password from the keychain.
|
||||
* Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist.
|
||||
*/
|
||||
export function deletePassword(service: string, account: string): Promise<void>
|
||||
/** Checks if the os secure storage is available */
|
||||
export function isAvailable(): Promise<boolean>
|
||||
}
|
||||
export declare namespace biometrics {
|
||||
export function prompt(hwnd: Buffer, message: string): Promise<boolean>
|
||||
export function available(): Promise<boolean>
|
||||
export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string): Promise<string>
|
||||
/**
|
||||
* Retrieves the biometric secret for the given service and account.
|
||||
* Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist.
|
||||
*/
|
||||
export function getBiometricSecret(service: string, account: string, keyMaterial?: KeyMaterial | undefined | null): Promise<string>
|
||||
/**
|
||||
* Derives key material from biometric data. Returns a string encoded with a
|
||||
|
||||
@@ -6,7 +6,12 @@ mod registry;
|
||||
|
||||
#[napi]
|
||||
pub mod passwords {
|
||||
/// The error message returned when a password is not found during retrieval or deletion.
|
||||
#[napi]
|
||||
pub const PASSWORD_NOT_FOUND: &str = desktop_core::password::PASSWORD_NOT_FOUND;
|
||||
|
||||
/// Fetch the stored password from the keychain.
|
||||
/// Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist.
|
||||
#[napi]
|
||||
pub async fn get_password(service: String, account: String) -> napi::Result<String> {
|
||||
desktop_core::password::get_password(&service, &account)
|
||||
@@ -27,6 +32,7 @@ pub mod passwords {
|
||||
}
|
||||
|
||||
/// Delete the stored password from the keychain.
|
||||
/// Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist.
|
||||
#[napi]
|
||||
pub async fn delete_password(service: String, account: String) -> napi::Result<()> {
|
||||
desktop_core::password::delete_password(&service, &account)
|
||||
@@ -34,7 +40,7 @@ pub mod passwords {
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
// Checks if the os secure storage is available
|
||||
/// Checks if the os secure storage is available
|
||||
#[napi]
|
||||
pub async fn is_available() -> napi::Result<bool> {
|
||||
desktop_core::password::is_available()
|
||||
@@ -84,6 +90,8 @@ pub mod biometrics {
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
/// Retrieves the biometric secret for the given service and account.
|
||||
/// Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist.
|
||||
#[napi]
|
||||
pub async fn get_biometric_secret(
|
||||
service: String,
|
||||
|
||||
Reference in New Issue
Block a user