bitwarden_user_crypto_management/
v2_migration_permission.rs1use bitwarden_core::key_management::V2EncryptedMigrationsGracePeriodStart;
8use chrono::{TimeDelta, Utc};
9use serde::{Deserialize, Serialize};
10
11use crate::UserCryptoManagementClient;
12
13const GRACE_PERIOD: TimeDelta = TimeDelta::weeks(2);
15
16#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
18#[bitwarden_ffi::wasm_record]
19#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
20pub enum MigrationPermission {
21 Wait,
23 Migrate,
25}
26
27#[bitwarden_ffi::wasm_export]
28#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
29impl UserCryptoManagementClient {
30 pub async fn request_permission_to_migrate_to_v2(&self) -> MigrationPermission {
47 let state_bridge = self.client.km_state_bridge();
48
49 match state_bridge
50 .get_v2_encrypted_migrations_grace_period_start()
51 .await
52 {
53 None => {
56 state_bridge
57 .set_v2_encrypted_migrations_grace_period_start(
58 &V2EncryptedMigrationsGracePeriodStart(Utc::now()),
59 )
60 .await;
61 MigrationPermission::Wait
62 }
63 Some(start) if Utc::now().signed_duration_since(start.0) < GRACE_PERIOD => {
64 MigrationPermission::Wait
65 }
66 Some(_) => MigrationPermission::Migrate,
67 }
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use bitwarden_core::{Client, key_management::state_bridge::test_support::InMemoryStateBridge};
74 use chrono::DateTime;
75
76 use super::*;
77 use crate::UserCryptoManagementClientExt;
78
79 fn client_with_bridge() -> Client {
81 let client = Client::new(None);
82 client
83 .km_state_bridge()
84 .register_bridge(Box::new(InMemoryStateBridge::default()));
85 client
86 }
87
88 async fn client_with_anchor(offset: TimeDelta) -> Client {
91 let client = client_with_bridge();
92 client
93 .km_state_bridge()
94 .set_v2_encrypted_migrations_grace_period_start(&V2EncryptedMigrationsGracePeriodStart(
95 Utc::now() + offset,
96 ))
97 .await;
98 client
99 }
100
101 async fn stored_anchor(client: &Client) -> Option<DateTime<Utc>> {
102 client
103 .km_state_bridge()
104 .get_v2_encrypted_migrations_grace_period_start()
105 .await
106 .map(|start| start.0)
107 }
108
109 #[tokio::test]
110 async fn test_unset_anchor_starts_the_window_and_reports_wait() {
111 let client = client_with_bridge();
112
113 assert_eq!(
114 client
115 .user_crypto_management()
116 .request_permission_to_migrate_to_v2()
117 .await,
118 MigrationPermission::Wait
119 );
120
121 let anchor = stored_anchor(&client).await.expect("the window was armed");
122 assert!(Utc::now().signed_duration_since(anchor) < TimeDelta::seconds(5));
123 }
124
125 #[tokio::test]
126 async fn test_second_call_does_not_move_the_anchor() {
127 let client = client_with_bridge();
128 let user_crypto_management = client.user_crypto_management();
129
130 assert_eq!(
131 user_crypto_management
132 .request_permission_to_migrate_to_v2()
133 .await,
134 MigrationPermission::Wait
135 );
136 let armed = stored_anchor(&client).await.expect("the window was armed");
137
138 assert_eq!(
139 user_crypto_management
140 .request_permission_to_migrate_to_v2()
141 .await,
142 MigrationPermission::Wait
143 );
144 assert_eq!(stored_anchor(&client).await, Some(armed));
145 }
146
147 #[tokio::test]
148 async fn test_one_week_old_anchor_waits() {
149 let client = client_with_anchor(-TimeDelta::weeks(1)).await;
150
151 assert_eq!(
152 client
153 .user_crypto_management()
154 .request_permission_to_migrate_to_v2()
155 .await,
156 MigrationPermission::Wait
157 );
158 }
159
160 #[tokio::test]
161 async fn test_three_week_old_anchor_migrates() {
162 let client = client_with_anchor(-TimeDelta::weeks(3)).await;
163
164 assert_eq!(
165 client
166 .user_crypto_management()
167 .request_permission_to_migrate_to_v2()
168 .await,
169 MigrationPermission::Migrate
170 );
171 }
172
173 #[tokio::test]
174 async fn test_exactly_two_weeks_migrates() {
175 let client = client_with_anchor(-GRACE_PERIOD).await;
178
179 assert_eq!(
180 client
181 .user_crypto_management()
182 .request_permission_to_migrate_to_v2()
183 .await,
184 MigrationPermission::Migrate
185 );
186 }
187
188 #[tokio::test]
189 async fn test_future_anchor_waits_and_is_untouched() {
190 let client = client_with_anchor(TimeDelta::weeks(1)).await;
191 let anchor = stored_anchor(&client).await;
192
193 assert_eq!(
194 client
195 .user_crypto_management()
196 .request_permission_to_migrate_to_v2()
197 .await,
198 MigrationPermission::Wait
199 );
200 assert_eq!(stored_anchor(&client).await, anchor);
201 }
202
203 #[tokio::test]
204 #[should_panic(expected = "StateBridge not registered")]
205 async fn test_without_a_state_bridge_panics() {
206 let client = Client::new(None);
207
208 client
209 .user_crypto_management()
210 .request_permission_to_migrate_to_v2()
211 .await;
212 }
213}