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}