Skip to main content

bitwarden_vault/cipher_risk/
types.rs

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