fxa_client/state_machine/internal_machines/
authenticating.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
5use super::{invalid_transition, Event, InternalStateMachine, State};
6use crate::{Error, FxaEvent, FxaState, Result};
7
8pub struct AuthenticatingStateMachine;
9
10// Save some typing
11use Event::*;
12use State::*;
13
14impl InternalStateMachine for AuthenticatingStateMachine {
15    fn initial_state(&self, event: FxaEvent) -> Result<State> {
16        match event {
17            FxaEvent::CompleteOAuthFlow { code, state } => Ok(CompleteOAuthFlow {
18                code: code.clone(),
19                state: state.clone(),
20            }),
21            FxaEvent::CancelOAuthFlow => Ok(Complete(FxaState::Disconnected)),
22            // These next 2 cases allow apps to begin a new oauth flow when we're already in the
23            // middle of an existing one.
24            FxaEvent::BeginOAuthFlow { scopes, entrypoint } => {
25                Ok(State::BeginOAuthFlow { scopes, entrypoint })
26            }
27            FxaEvent::BeginPairingFlow {
28                pairing_url,
29                scopes,
30                entrypoint,
31            } => Ok(State::BeginPairingFlow {
32                pairing_url,
33                scopes,
34                entrypoint,
35            }),
36            e => Err(Error::InvalidStateTransition(format!(
37                "Authenticating -> {e}"
38            ))),
39        }
40    }
41
42    fn next_state(&self, state: State, event: Event) -> Result<State> {
43        Ok(match (state, event) {
44            (CompleteOAuthFlow { .. }, CompleteOAuthFlowSuccess) => InitializeDevice,
45            (CompleteOAuthFlow { .. }, CallError) => Complete(FxaState::Disconnected),
46            (InitializeDevice, InitializeDeviceSuccess) => Complete(FxaState::Connected),
47            (InitializeDevice, CallError) => Complete(FxaState::Disconnected),
48            (BeginOAuthFlow { .. }, BeginOAuthFlowSuccess { oauth_url }) => {
49                Complete(FxaState::Authenticating { oauth_url })
50            }
51            (BeginPairingFlow { .. }, BeginPairingFlowSuccess { oauth_url }) => {
52                Complete(FxaState::Authenticating { oauth_url })
53            }
54            (BeginOAuthFlow { .. }, CallError) => Complete(FxaState::Disconnected),
55            (BeginPairingFlow { .. }, CallError) => Complete(FxaState::Disconnected),
56            (state, event) => return invalid_transition(state, event),
57        })
58    }
59}
60
61#[cfg(test)]
62mod test {
63    use super::super::StateMachineTester;
64    use super::*;
65
66    #[test]
67    fn test_complete_oauth_flow() {
68        let mut tester = StateMachineTester::new(
69            AuthenticatingStateMachine,
70            FxaEvent::CompleteOAuthFlow {
71                code: "test-code".to_owned(),
72                state: "test-state".to_owned(),
73            },
74        );
75        assert_eq!(
76            tester.state,
77            CompleteOAuthFlow {
78                code: "test-code".to_owned(),
79                state: "test-state".to_owned(),
80            }
81        );
82        assert_eq!(
83            tester.peek_next_state(CallError),
84            Complete(FxaState::Disconnected)
85        );
86
87        tester.next_state(CompleteOAuthFlowSuccess);
88        assert_eq!(tester.state, InitializeDevice);
89        assert_eq!(
90            tester.peek_next_state(CallError),
91            Complete(FxaState::Disconnected)
92        );
93        assert_eq!(
94            tester.peek_next_state(InitializeDeviceSuccess),
95            Complete(FxaState::Connected)
96        );
97    }
98
99    #[test]
100    fn test_cancel_oauth_flow() {
101        let tester = StateMachineTester::new(AuthenticatingStateMachine, FxaEvent::CancelOAuthFlow);
102        assert_eq!(tester.state, Complete(FxaState::Disconnected));
103    }
104
105    /// Test what happens if we get the `BeginOAuthFlow` when we're already in the middle of
106    /// authentication.
107    ///
108    /// In this case, we should start a new flow.  Note: the code to handle the `BeginOAuthFlow`
109    /// internal state will cancel the previous flow.
110    #[test]
111    fn test_begin_oauth_flow() {
112        let tester = StateMachineTester::new(
113            AuthenticatingStateMachine,
114            FxaEvent::BeginOAuthFlow {
115                scopes: vec!["profile".to_owned()],
116                entrypoint: "test-entrypoint".to_owned(),
117            },
118        );
119        assert_eq!(
120            tester.state,
121            BeginOAuthFlow {
122                scopes: vec!["profile".to_owned()],
123                entrypoint: "test-entrypoint".to_owned(),
124            }
125        );
126        assert_eq!(
127            tester.peek_next_state(CallError),
128            Complete(FxaState::Disconnected)
129        );
130        assert_eq!(
131            tester.peek_next_state(BeginOAuthFlowSuccess {
132                oauth_url: "http://example.com/oauth-start".to_owned(),
133            }),
134            Complete(FxaState::Authenticating {
135                oauth_url: "http://example.com/oauth-start".to_owned(),
136            })
137        );
138    }
139
140    /// Same as `test_begin_oauth_flow`, but for a paring flow
141    #[test]
142    fn test_begin_pairing_flow() {
143        let tester = StateMachineTester::new(
144            AuthenticatingStateMachine,
145            FxaEvent::BeginPairingFlow {
146                pairing_url: "https://example.com/pairing-url".to_owned(),
147                scopes: vec!["profile".to_owned()],
148                entrypoint: "test-entrypoint".to_owned(),
149            },
150        );
151        assert_eq!(
152            tester.state,
153            BeginPairingFlow {
154                pairing_url: "https://example.com/pairing-url".to_owned(),
155                scopes: vec!["profile".to_owned()],
156                entrypoint: "test-entrypoint".to_owned(),
157            }
158        );
159        assert_eq!(
160            tester.peek_next_state(CallError),
161            Complete(FxaState::Disconnected)
162        );
163        assert_eq!(
164            tester.peek_next_state(BeginPairingFlowSuccess {
165                oauth_url: "http://example.com/oauth-start".to_owned(),
166            }),
167            Complete(FxaState::Authenticating {
168                oauth_url: "http://example.com/oauth-start".to_owned(),
169            })
170        );
171    }
172}