fxa_client/state_machine/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! FxA state machine
6//!
7//! This presents a high-level API for logging in, logging out, dealing with authentication token issues, etc.
8
9use error_support::breadcrumb;
10
11use crate::{internal::FirefoxAccount, DeviceConfig, Error, FxaEvent, FxaState, Result};
12
13pub mod checker;
14mod display;
15mod internal_machines;
16
17/// Number of state transitions to perform before giving up and assuming the internal state machine
18/// is stuck in an infinite loop
19const MAX_INTERNAL_TRANSITIONS: usize = 20;
20
21use internal_machines::InternalStateMachine;
22use internal_machines::State as InternalState;
23
24impl FirefoxAccount {
25    /// Get the current state
26    pub fn get_state(&self) -> FxaState {
27        self.auth_state.clone()
28    }
29
30    /// Process an event (login, logout, etc).
31    ///
32    /// On success, returns the new state.
33    /// On error, the state will remain the same.
34    pub fn process_event(&mut self, event: FxaEvent) -> Result<FxaState> {
35        match &self.auth_state {
36            FxaState::Uninitialized => self.process_event_with_internal_state_machine(
37                internal_machines::UninitializedStateMachine,
38                event,
39            ),
40            FxaState::Disconnected => self.process_event_with_internal_state_machine(
41                internal_machines::DisconnectedStateMachine,
42                event,
43            ),
44            FxaState::Authenticating { .. } => self.process_event_with_internal_state_machine(
45                internal_machines::AuthenticatingStateMachine,
46                event,
47            ),
48            FxaState::Connected => self.process_event_with_internal_state_machine(
49                internal_machines::ConnectedStateMachine,
50                event,
51            ),
52            FxaState::AuthIssues => self.process_event_with_internal_state_machine(
53                internal_machines::AuthIssuesStateMachine,
54                event,
55            ),
56        }
57    }
58
59    fn process_event_with_internal_state_machine<T: InternalStateMachine>(
60        &mut self,
61        state_machine: T,
62        event: FxaEvent,
63    ) -> Result<FxaState> {
64        let device_config = self.handle_state_machine_initialization(&event)?;
65
66        breadcrumb!("FxaStateMachine.process_event starting: {event}");
67        let mut internal_state = state_machine.initial_state(event)?;
68        let mut count = 0;
69        // Loop through internal state transitions until we reach a terminal state
70        //
71        // See `README.md` for details.
72        loop {
73            count += 1;
74            if count > MAX_INTERNAL_TRANSITIONS {
75                breadcrumb!("FxaStateMachine.process_event finished (MAX_INTERNAL_TRANSITIONS)");
76                return Err(Error::StateMachineLogicError(
77                    "infinite loop detected".to_owned(),
78                ));
79            }
80            match internal_state {
81                InternalState::Complete(new_state) => {
82                    breadcrumb!("FxaStateMachine.process_event finished (Complete({new_state}))");
83                    self.auth_state = new_state.clone();
84                    return Ok(new_state);
85                }
86                InternalState::Cancel => {
87                    breadcrumb!("FxaStateMachine.process_event finished (Cancel)");
88                    return Ok(self.auth_state.clone());
89                }
90                state => {
91                    let event = state.make_call(self, &device_config)?;
92                    let event_msg = event.to_string();
93                    internal_state = state_machine.next_state(state, event)?;
94                    breadcrumb!("FxaStateMachine.process_event {event_msg} -> {internal_state}")
95                }
96            }
97        }
98    }
99
100    /// Handles initialization before we process an event
101    ///
102    /// This checks that the first event we see is `FxaEvent::Initialize` and it returns the
103    /// `DeviceConfig` from that event.
104    fn handle_state_machine_initialization(&mut self, event: &FxaEvent) -> Result<DeviceConfig> {
105        match &event {
106            FxaEvent::Initialize { device_config } => match self.device_config {
107                Some(_) => Err(Error::InvalidStateTransition(
108                    "Initialize already sent".to_owned(),
109                )),
110                None => {
111                    self.device_config = Some(device_config.clone());
112                    Ok(device_config.clone())
113                }
114            },
115            _ => match &self.device_config {
116                Some(device_config) => Ok(device_config.clone()),
117                None => Err(Error::InvalidStateTransition(
118                    "Initialize not yet sent".to_owned(),
119                )),
120            },
121        }
122    }
123}