bitwarden_vault/cipher_risk/
types.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4#[cfg(feature = "wasm")]
5use {tsify::Tsify, wasm_bindgen::prelude::*};
6
7use crate::CipherId;
8
9/// Result of checking password exposure via HIBP API.
10#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
11#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
12#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
13#[serde(tag = "type", content = "value")]
14pub enum ExposedPasswordResult {
15    /// Password exposure check was not performed (check_exposed was false or password was empty)
16    NotChecked,
17    /// Successfully checked, found in this many breaches
18    Found(u32),
19    /// HIBP API request failed with error message
20    Error(String),
21}
22
23/// Login cipher data needed for risk evaluation.
24#[derive(Serialize, Deserialize, Debug, Clone)]
25#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
26#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
27pub struct CipherLoginDetails {
28    /// Cipher ID to identify which cipher in results.
29    pub id: CipherId,
30    /// The decrypted password to evaluate.
31    pub password: String,
32    /// Username or email (login ciphers only have one field).
33    pub username: Option<String>,
34}
35
36/// Password reuse map wrapper for WASM compatibility.
37#[derive(Serialize, Deserialize, Debug, Clone)]
38#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
39#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
40#[serde(transparent)]
41pub struct PasswordReuseMap {
42    /// Map of passwords to their occurrence count.
43    #[cfg_attr(feature = "wasm", tsify(type = "Record<string, number>"))]
44    pub map: HashMap<String, u32>,
45}
46
47impl PasswordReuseMap {
48    /// Create a new PasswordReuseMap from a list of passwords.
49    pub fn new(passwords: Vec<CipherLoginDetails>) -> Self {
50        let mut map = HashMap::new();
51        for details in passwords {
52            if !details.password.is_empty() {
53                *map.entry(details.password).or_insert(0) += 1;
54            }
55        }
56        Self { map }
57    }
58}
59
60/// Options for configuring risk computation.
61#[derive(Serialize, Deserialize, Debug, Clone, Default)]
62#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
63#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
64#[serde(rename_all = "camelCase")]
65pub struct CipherRiskOptions {
66    /// Pre-computed password reuse map (password → count).
67    /// If provided, enables reuse detection across ciphers.
68    #[serde(default)]
69    pub password_map: Option<PasswordReuseMap>,
70    /// Whether to check passwords against Have I Been Pwned API.
71    /// When true, makes network requests to check for exposed passwords.
72    #[serde(default)]
73    pub check_exposed: bool,
74    /// Optional HIBP API base URL override. When None, uses the production HIBP URL.
75    /// Can be used for testing or alternative password breach checking services.
76    #[serde(default)]
77    pub hibp_base_url: Option<String>,
78}
79
80/// Risk evaluation result for a single cipher.
81#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
82#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
83#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
84pub struct CipherRiskResult {
85    /// Cipher ID matching the input CipherLoginDetails.
86    pub id: CipherId,
87    /// Password strength score from 0 (weakest) to 4 (strongest).
88    /// Calculated using zxcvbn with cipher-specific context.
89    pub password_strength: u8,
90    /// Result of checking password exposure via HIBP API.
91    /// - `NotChecked`: check_exposed was false, or password was empty
92    /// - `Found(n)`: Successfully checked, found in n breaches
93    /// - `Error(msg)`: HIBP API request failed for this cipher with the given error message
94    pub exposed_result: ExposedPasswordResult,
95    /// Number of times this password appears in the provided password_map.
96    /// None if not found or if no password_map was provided.
97    pub reuse_count: Option<u32>,
98}