fxa_client/internal/
state_manager.rs1use std::collections::{HashMap, HashSet};
6
7use crate::{
8 internal::{
9 oauth::{AccessTokenInfo, RefreshToken},
10 profile::Profile,
11 state_persistence::state_to_json,
12 CachedResponse, Config, OAuthFlow, PersistedState,
13 },
14 DeviceCapability, FxaRustAuthState, LocalDevice, Result, ScopedKey,
15};
16
17pub struct StateManager {
22 persisted_state: PersistedState,
24 flow_store: HashMap<String, OAuthFlow>,
26}
27
28impl StateManager {
29 pub(crate) fn new(persisted_state: PersistedState) -> Self {
30 Self {
31 persisted_state,
32 flow_store: HashMap::new(),
33 }
34 }
35
36 pub fn serialize_persisted_state(&self) -> Result<String> {
37 state_to_json(&self.persisted_state)
38 }
39
40 pub fn config(&self) -> &Config {
41 &self.persisted_state.config
42 }
43
44 pub fn refresh_token(&self) -> Option<&RefreshToken> {
45 self.persisted_state.refresh_token.as_ref()
46 }
47
48 pub fn session_token(&self) -> Option<&str> {
49 self.persisted_state.session_token.as_deref()
50 }
51
52 pub fn device_capabilities(&self) -> &HashSet<DeviceCapability> {
56 &self.persisted_state.device_capabilities
57 }
58
59 pub fn set_device_capabilities(
61 &mut self,
62 capabilities_set: impl IntoIterator<Item = DeviceCapability>,
63 ) {
64 self.persisted_state.device_capabilities = HashSet::from_iter(capabilities_set);
65 }
66
67 pub fn server_local_device_info(&self) -> Option<&LocalDevice> {
69 self.persisted_state.server_local_device_info.as_ref()
70 }
71
72 pub fn update_server_local_device_info(&mut self, local_device: LocalDevice) {
74 self.persisted_state.server_local_device_info = Some(local_device)
75 }
76
77 pub fn clear_server_local_device_info(&mut self) {
83 self.persisted_state.server_local_device_info = None
84 }
85
86 pub fn get_commands_data(&self, key: &str) -> Option<&str> {
87 self.persisted_state
88 .commands_data
89 .get(key)
90 .map(String::as_str)
91 }
92
93 pub fn set_commands_data(&mut self, key: &str, data: String) {
94 self.persisted_state
95 .commands_data
96 .insert(key.to_string(), data);
97 }
98
99 pub fn clear_commands_data(&mut self, key: &str) {
100 self.persisted_state.commands_data.remove(key);
101 }
102
103 pub fn last_handled_command_index(&self) -> Option<u64> {
104 self.persisted_state.last_handled_command
105 }
106
107 pub fn set_last_handled_command_index(&mut self, idx: u64) {
108 self.persisted_state.last_handled_command = Some(idx)
109 }
110
111 pub fn current_device_id(&self) -> Option<&str> {
112 self.persisted_state.current_device_id.as_deref()
113 }
114
115 pub fn set_current_device_id(&mut self, device_id: String) {
116 self.persisted_state.current_device_id = Some(device_id);
117 }
118
119 pub fn get_scoped_key(&self, scope: &str) -> Option<&ScopedKey> {
120 self.persisted_state.scoped_keys.get(scope)
121 }
122
123 pub(crate) fn last_seen_profile(&self) -> Option<&CachedResponse<Profile>> {
124 self.persisted_state.last_seen_profile.as_ref()
125 }
126
127 pub(crate) fn set_last_seen_profile(&mut self, profile: CachedResponse<Profile>) {
128 self.persisted_state.last_seen_profile = Some(profile)
129 }
130
131 pub fn clear_last_seen_profile(&mut self) {
132 self.persisted_state.last_seen_profile = None
133 }
134
135 pub fn get_cached_access_token(&mut self, scope: &str) -> Option<&AccessTokenInfo> {
136 self.persisted_state.access_token_cache.get(scope)
137 }
138
139 pub fn add_cached_access_token(&mut self, scope: impl Into<String>, token: AccessTokenInfo) {
140 self.persisted_state
141 .access_token_cache
142 .insert(scope.into(), token);
143 }
144
145 pub fn clear_access_token_cache(&mut self) {
146 self.persisted_state.access_token_cache.clear()
147 }
148
149 pub fn begin_oauth_flow(&mut self, state: impl Into<String>, flow: OAuthFlow) {
152 self.flow_store.insert(state.into(), flow);
153 }
154
155 pub fn pop_oauth_flow(&mut self, state: &str) -> Option<OAuthFlow> {
160 self.flow_store.remove(state)
161 }
162
163 pub fn complete_oauth_flow(
165 &mut self,
166 scoped_keys: Vec<(String, ScopedKey)>,
167 refresh_token: RefreshToken,
168 new_session_token: Option<String>,
169 ) {
170 self.clear_server_local_device_info();
173
174 for (scope, key) in scoped_keys {
175 self.persisted_state.scoped_keys.insert(scope, key);
176 }
177 self.persisted_state.refresh_token = Some(refresh_token);
178 if let (None, Some(new_session_token)) = (self.session_token(), new_session_token) {
181 self.set_session_token(new_session_token)
182 }
183 self.persisted_state.logged_out_from_auth_issues = false;
184 self.flow_store.clear();
185 }
186
187 pub fn clear_oauth_flows(&mut self) {
189 self.flow_store.clear();
190 }
191
192 pub fn disconnect(&mut self) {
195 self.persisted_state.current_device_id = None;
196 self.persisted_state.refresh_token = None;
197 self.persisted_state.scoped_keys = HashMap::new();
198 self.persisted_state.last_handled_command = None;
199 self.persisted_state.commands_data = HashMap::new();
200 self.persisted_state.access_token_cache = HashMap::new();
201 self.persisted_state.device_capabilities = HashSet::new();
202 self.persisted_state.server_local_device_info = None;
203 self.persisted_state.session_token = None;
204 self.persisted_state.logged_out_from_auth_issues = false;
205 self.flow_store.clear();
206 }
207
208 pub fn on_auth_issues(&mut self) {
218 self.persisted_state.refresh_token = None;
219 self.persisted_state.scoped_keys = HashMap::new();
220 self.persisted_state.commands_data = HashMap::new();
221 self.persisted_state.access_token_cache = HashMap::new();
222 self.persisted_state.server_local_device_info = None;
223 self.persisted_state.session_token = None;
224 self.persisted_state.logged_out_from_auth_issues = true;
225 self.flow_store.clear();
226 }
227
228 pub fn on_begin_oauth(&mut self) {
234 self.persisted_state.refresh_token = None;
235 self.persisted_state.scoped_keys = HashMap::new();
236 self.persisted_state.commands_data = HashMap::new();
237 self.persisted_state.access_token_cache = HashMap::new();
238 self.persisted_state.session_token = None;
239 }
240
241 pub fn get_auth_state(&self) -> FxaRustAuthState {
242 if self.persisted_state.refresh_token.is_some() {
243 FxaRustAuthState::Connected
244 } else if self.persisted_state.logged_out_from_auth_issues {
245 FxaRustAuthState::AuthIssues
246 } else {
247 FxaRustAuthState::Disconnected
248 }
249 }
250
251 pub fn update_tokens(&mut self, session_token: String, refresh_token: RefreshToken) {
256 self.persisted_state.session_token = Some(session_token);
257 self.persisted_state.refresh_token = Some(refresh_token);
258 self.persisted_state.access_token_cache.clear();
259 self.persisted_state.server_local_device_info = None;
260 }
261
262 pub fn simulate_temporary_auth_token_issue(&mut self) {
264 for (_, access_token) in self.persisted_state.access_token_cache.iter_mut() {
265 "invalid-data".clone_into(&mut access_token.token)
266 }
267 }
268
269 pub fn simulate_permanent_auth_token_issue(&mut self) {
271 self.persisted_state.session_token = None;
272 self.persisted_state.refresh_token = None;
273 self.persisted_state.access_token_cache.clear();
274 }
275 pub fn set_session_token(&mut self, token: String) {
276 self.persisted_state.session_token = Some(token)
277 }
278}
279
280#[cfg(test)]
281impl StateManager {
282 pub fn is_access_token_cache_empty(&self) -> bool {
283 self.persisted_state.access_token_cache.is_empty()
284 }
285
286 pub fn force_refresh_token(&mut self, token: RefreshToken) {
287 self.persisted_state.refresh_token = Some(token)
288 }
289
290 pub fn force_current_device_id(&mut self, device_id: impl Into<String>) {
291 self.persisted_state.current_device_id = Some(device_id.into())
292 }
293
294 pub fn insert_scoped_key(&mut self, scope: impl Into<String>, key: ScopedKey) {
295 self.persisted_state.scoped_keys.insert(scope.into(), key);
296 }
297}