fxa_client/state_machine/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! FxA state machine
//!
//! This presents a high-level API for logging in, logging out, dealing with authentication token issues, etc.

use error_support::breadcrumb;

use crate::{internal::FirefoxAccount, DeviceConfig, Error, FxaEvent, FxaState, Result};

pub mod checker;
mod display;
mod internal_machines;

/// Number of state transitions to perform before giving up and assuming the internal state machine
/// is stuck in an infinite loop
const MAX_INTERNAL_TRANSITIONS: usize = 20;

use internal_machines::InternalStateMachine;
use internal_machines::State as InternalState;

impl FirefoxAccount {
    /// Get the current state
    pub fn get_state(&self) -> FxaState {
        self.auth_state.clone()
    }

    /// Process an event (login, logout, etc).
    ///
    /// On success, returns the new state.
    /// On error, the state will remain the same.
    pub fn process_event(&mut self, event: FxaEvent) -> Result<FxaState> {
        match &self.auth_state {
            FxaState::Uninitialized => self.process_event_with_internal_state_machine(
                internal_machines::UninitializedStateMachine,
                event,
            ),
            FxaState::Disconnected => self.process_event_with_internal_state_machine(
                internal_machines::DisconnectedStateMachine,
                event,
            ),
            FxaState::Authenticating { .. } => self.process_event_with_internal_state_machine(
                internal_machines::AuthenticatingStateMachine,
                event,
            ),
            FxaState::Connected => self.process_event_with_internal_state_machine(
                internal_machines::ConnectedStateMachine,
                event,
            ),
            FxaState::AuthIssues => self.process_event_with_internal_state_machine(
                internal_machines::AuthIssuesStateMachine,
                event,
            ),
        }
    }

    fn process_event_with_internal_state_machine<T: InternalStateMachine>(
        &mut self,
        state_machine: T,
        event: FxaEvent,
    ) -> Result<FxaState> {
        let device_config = self.handle_state_machine_initialization(&event)?;

        breadcrumb!("FxaStateMachine.process_event starting: {event}");
        let mut internal_state = state_machine.initial_state(event)?;
        let mut count = 0;
        // Loop through internal state transitions until we reach a terminal state
        //
        // See `README.md` for details.
        loop {
            count += 1;
            if count > MAX_INTERNAL_TRANSITIONS {
                breadcrumb!("FxaStateMachine.process_event finished (MAX_INTERNAL_TRANSITIONS)");
                return Err(Error::StateMachineLogicError(
                    "infinite loop detected".to_owned(),
                ));
            }
            match internal_state {
                InternalState::Complete(new_state) => {
                    breadcrumb!("FxaStateMachine.process_event finished (Complete({new_state}))");
                    self.auth_state = new_state.clone();
                    return Ok(new_state);
                }
                InternalState::Cancel => {
                    breadcrumb!("FxaStateMachine.process_event finished (Cancel)");
                    return Ok(self.auth_state.clone());
                }
                state => {
                    let event = state.make_call(self, &device_config)?;
                    let event_msg = event.to_string();
                    internal_state = state_machine.next_state(state, event)?;
                    breadcrumb!("FxaStateMachine.process_event {event_msg} -> {internal_state}")
                }
            }
        }
    }

    /// Handles initialization before we process an event
    ///
    /// This checks that the first event we see is `FxaEvent::Initialize` and it returns the
    /// `DeviceConfig` from that event.
    fn handle_state_machine_initialization(&mut self, event: &FxaEvent) -> Result<DeviceConfig> {
        match &event {
            FxaEvent::Initialize { device_config } => match self.device_config {
                Some(_) => Err(Error::InvalidStateTransition(
                    "Initialize already sent".to_owned(),
                )),
                None => {
                    self.device_config = Some(device_config.clone());
                    Ok(device_config.clone())
                }
            },
            _ => match &self.device_config {
                Some(device_config) => Ok(device_config.clone()),
                None => Err(Error::InvalidStateTransition(
                    "Initialize not yet sent".to_owned(),
                )),
            },
        }
    }
}