bitwarden_auth/login/login_via_password/
password_prelogin_response.rs1use std::num::NonZeroU32;
2
3use bitwarden_api_identity::models::{KdfType, PasswordPreloginResponseModel};
4use bitwarden_core::{MissingFieldError, require};
5use bitwarden_crypto::Kdf;
6use serde::{Deserialize, Serialize};
7
8#[derive(Serialize, Deserialize, Debug)]
10#[serde(rename_all = "camelCase")]
11#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] #[cfg_attr(
13 feature = "wasm",
14 derive(tsify::Tsify),
15 tsify(into_wasm_abi, from_wasm_abi)
16)] pub struct PasswordPreloginResponse {
18 pub kdf: Kdf,
20
21 pub salt: String,
24}
25
26impl TryFrom<PasswordPreloginResponseModel> for PasswordPreloginResponse {
27 type Error = MissingFieldError;
28
29 fn try_from(response: PasswordPreloginResponseModel) -> Result<Self, Self::Error> {
30 let kdf_settings = require!(response.kdf_settings);
31
32 let kdf = match kdf_settings.kdf_type {
33 KdfType::PBKDF2_SHA256 => Kdf::PBKDF2 {
34 iterations: NonZeroU32::new(kdf_settings.iterations as u32)
35 .expect("Non-zero number"),
36 },
37 KdfType::Argon2id => Kdf::Argon2id {
38 iterations: NonZeroU32::new(kdf_settings.iterations as u32)
39 .expect("Non-zero number"),
40 memory: NonZeroU32::new(require!(kdf_settings.memory) as u32)
41 .expect("Non-zero number"),
42 parallelism: NonZeroU32::new(require!(kdf_settings.parallelism) as u32)
43 .expect("Non-zero number"),
44 },
45 KdfType::__Unknown(_) => {
46 return Err(MissingFieldError("response.kdf_settings.kdf_type"));
47 }
48 };
49
50 Ok(PasswordPreloginResponse {
51 kdf,
52 salt: require!(response.salt),
53 })
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use bitwarden_api_identity::models::KdfSettings;
60
61 use super::*;
62
63 const TEST_SALT: &str = "test-salt";
64
65 #[test]
66 fn test_try_from_pbkdf2_with_iterations() {
67 let kdf_settings = KdfSettings {
68 kdf_type: KdfType::PBKDF2_SHA256,
69 iterations: 100000,
70 memory: None,
71 parallelism: None,
72 };
73
74 let response = PasswordPreloginResponseModel {
75 kdf: None,
76 kdf_iterations: None,
77 kdf_memory: None,
78 kdf_parallelism: None,
79 kdf_settings: Some(Box::new(kdf_settings)),
80 salt: Some(TEST_SALT.to_string()),
81 };
82
83 let result = PasswordPreloginResponse::try_from(response).unwrap();
84
85 assert_eq!(
86 result.kdf,
87 Kdf::PBKDF2 {
88 iterations: NonZeroU32::new(100000).unwrap()
89 }
90 );
91 assert_eq!(result.salt, TEST_SALT);
92 }
93
94 #[test]
95 fn test_try_from_argon2id_with_all_params() {
96 let kdf_settings = KdfSettings {
97 kdf_type: KdfType::Argon2id,
98 iterations: 4,
99 memory: Some(64),
100 parallelism: Some(4),
101 };
102
103 let response = PasswordPreloginResponseModel {
104 kdf: None,
105 kdf_iterations: None,
106 kdf_memory: None,
107 kdf_parallelism: None,
108 kdf_settings: Some(Box::new(kdf_settings)),
109 salt: Some(TEST_SALT.to_string()),
110 };
111
112 let result = PasswordPreloginResponse::try_from(response).unwrap();
113
114 assert_eq!(
115 result.kdf,
116 Kdf::Argon2id {
117 iterations: NonZeroU32::new(4).unwrap(),
118 memory: NonZeroU32::new(64).unwrap(),
119 parallelism: NonZeroU32::new(4).unwrap(),
120 }
121 );
122 assert_eq!(result.salt, TEST_SALT);
123 }
124
125 #[test]
126 fn test_try_from_missing_kdf_settings() {
127 let response = PasswordPreloginResponseModel {
128 kdf: None,
129 kdf_iterations: None,
130 kdf_memory: None,
131 kdf_parallelism: None,
132 kdf_settings: None, salt: Some(TEST_SALT.to_string()),
134 };
135
136 let result = PasswordPreloginResponse::try_from(response);
137
138 assert!(result.is_err());
139 assert!(matches!(result.unwrap_err(), MissingFieldError { .. }));
140 }
141
142 #[test]
143 fn test_try_from_missing_salt() {
144 let kdf_settings = KdfSettings {
145 kdf_type: KdfType::PBKDF2_SHA256,
146 iterations: 100000,
147 memory: None,
148 parallelism: None,
149 };
150
151 let response = PasswordPreloginResponseModel {
152 kdf: None,
153 kdf_iterations: None,
154 kdf_memory: None,
155 kdf_parallelism: None,
156 kdf_settings: Some(Box::new(kdf_settings)),
157 salt: None, };
159
160 let result = PasswordPreloginResponse::try_from(response);
161
162 assert!(result.is_err());
163 assert!(matches!(result.unwrap_err(), MissingFieldError { .. }));
164 }
165}