From 735d3d79728d90f13e8f3aa84decaf524a8370f6 Mon Sep 17 00:00:00 2001 From: Mark Hammond Date: Tue, 7 Apr 2026 16:55:46 +1000 Subject: [PATCH] Add new "Authorization" states and functions to authorize additional scopes. This supports desktop-like flows, where a user is already signed in to (say) Sync, but not wants to authorize "vpn" or "smartwindow". I used the term "Authorization" to try and reflect we aren't expected to "authenticate" here - we are already authenticated - we typically expect just a confirmation button. I'm not entirely convinced the distinction between "authorize" and "authenticate" is enough, so the names might be confusing - I started with something like "UpgradeScopes" etc. Regardless, better names welcome here. I've tried to keep the names consistent, which made them verbose. Better names welcome here too. --- .../appservices/fxaclient/FxaClient.kt | 24 ++++ components/fxa-client/src/auth.rs | 80 ++++++++++- components/fxa-client/src/fxa_client.udl | 38 ++++- components/fxa-client/src/internal/oauth.rs | 59 ++++++-- .../fxa-client/src/state_machine/display.rs | 9 ++ .../internal_machines/authenticating.rs | 12 ++ .../internal_machines/authorizing.rs | 136 ++++++++++++++++++ .../internal_machines/connected.rs | 7 + .../state_machine/internal_machines/mod.rs | 21 +++ .../fxa-client/src/state_machine/mod.rs | 9 +- 10 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 components/fxa-client/src/state_machine/internal_machines/authorizing.rs diff --git a/components/fxa-client/android/src/main/java/mozilla/appservices/fxaclient/FxaClient.kt b/components/fxa-client/android/src/main/java/mozilla/appservices/fxaclient/FxaClient.kt index 0c9833e4f2..afe20c13ad 100644 --- a/components/fxa-client/android/src/main/java/mozilla/appservices/fxaclient/FxaClient.kt +++ b/components/fxa-client/android/src/main/java/mozilla/appservices/fxaclient/FxaClient.kt @@ -160,6 +160,30 @@ class FxaClient(inner: FirefoxAccount, persistCallback: PersistCallback?) : Auto this.tryPersistState() } + /** + * Constructs a URL used to begin an OAuth scope authorization flow for an already-connected + * account, to authorize additional scopes without risking disconnection. + * + * This performs network requests, and should not be used on the main thread. + * + * @param scopes List of OAuth scopes for which the client wants access + * @param entrypoint to be used for metrics + * @return String URL to present to the user for authorization + */ + fun beginOAuthScopeAuthorizationFlow(scopes: Array, entrypoint: String): String { + return this.inner.beginOauthScopeAuthorizationFlow(scopes.toList(), entrypoint) + } + + /** + * Completes an OAuth scope authorization flow initiated by [beginOAuthScopeAuthorizationFlow]. + * + * This performs network requests, and should not be used on the main thread. + */ + fun completeOAuthScopeAuthorizationFlow(code: String, state: String) { + this.inner.completeOauthScopeAuthorizationFlow(code, state) + this.tryPersistState() + } + /** * Fetches the profile object for the current client either from the existing cached account, * or from the server (requires the client to have access to the profile scope). diff --git a/components/fxa-client/src/auth.rs b/components/fxa-client/src/auth.rs index 1487fd265f..56da5e99ed 100644 --- a/components/fxa-client/src/auth.rs +++ b/components/fxa-client/src/auth.rs @@ -155,6 +155,59 @@ impl FirefoxAccount { self.internal.lock().complete_oauth_flow(code, state) } + /// Initiate an OAuth scope authorization flow for an already-connected account. + /// + /// Call this when the application needs to request additional scopes from a user who is + /// already signed in. It returns a URL at which the user may authorize the additional + /// scopes. The application should direct the user to that URL. + /// + /// Unlike [`begin_oauth_flow`](FirefoxAccount::begin_oauth_flow), if this flow fails or + /// is cancelled the user remains connected — they are not signed out. + /// + /// When the resulting flow redirects back to the configured `redirect_uri`, the query + /// parameters should be extracted from the URL and passed to + /// [`complete_oauth_scope_authorization_flow`](FirefoxAccount::complete_oauth_scope_authorization_flow) + /// to finalize the authorization. + /// + /// # Arguments + /// + /// - `scopes` - list of additional OAuth scopes to request. + /// - `entrypoint` - metrics identifier for the UX entrypoint. + #[handle_error(Error)] + pub fn begin_oauth_scope_authorization_flow>( + &self, + scopes: &[T], + entrypoint: &str, + ) -> ApiResult { + let scopes = scopes.iter().map(T::as_ref).collect::>(); + self.internal + .lock() + .begin_oauth_scope_authorization_flow(&scopes, entrypoint) + } + + /// Complete an OAuth scope authorization flow. + /// + /// **💾 This method alters the persisted account state.** + /// + /// At the conclusion of a scope authorization flow, the user will be redirected to the + /// application's registered `redirect_uri`. It should extract the `code` and `state` + /// parameters from the resulting URL and pass them to this method. + /// + /// # Arguments + /// + /// - `code` - the OAuth authorization code obtained from the redirect URI. + /// - `state` - the OAuth state parameter obtained from the redirect URI. + #[handle_error(Error)] + pub fn complete_oauth_scope_authorization_flow( + &self, + code: &str, + state: &str, + ) -> ApiResult<()> { + self.internal + .lock() + .complete_oauth_scope_authorization_flow(code, state) + } + /// Check authorization status for this application. /// /// **💾 This method alters the persisted account state.** @@ -236,8 +289,10 @@ pub enum FxaState { Uninitialized, /// User has not connected to FxA or has logged out Disconnected, - /// User is currently performing an OAuth flow + /// User is disconnected and currently performing an initial OAuth flow to authenticate this device. Authenticating { oauth_url: String }, + /// User is connected and currently authorizing additional scopes. + Authorizing { oauth_url: String }, /// User is currently connected to FxA Connected, /// User was connected to FxA, but we observed issues with the auth tokens. @@ -290,8 +345,29 @@ pub enum FxaEvent { /// Use this to cancel an in-progress OAuth, returning to [FxaState::Disconnected] so the /// process can begin again. /// - /// This event is valid for the `Authenticating` state. + /// This event is valid for the `Authenticating` and `Authorizing` states. CancelOAuthFlow, + /// Begin an OAuth scope authorization flow for an already connected account. + /// + /// If successful, the state machine will transition to [FxaState::Authorizing]. The next + /// step is to navigate the user to the `oauth_url` and let them authorize the additional scopes. + /// + /// On failure or cancellation, the state machine returns to [FxaState::Connected]. + /// + /// This event is valid for the `Connected` and `Authorizing` states. If the state machine + /// is in the `Authorizing` state, then this will forget the current flow and start a new one. + BeginOAuthScopeAuthorizationFlow { + scopes: Vec, + entrypoint: String, + }, + /// Complete an OAuth scope authorization flow. + /// + /// Send this event after the user has navigated through the scope authorization flow and has + /// reached the redirect URI. Extract `code` and `state` from the query parameters or web + /// channel. The state machine will transition to [FxaState::Connected]. + /// + /// This event is valid for the `Authorizing` state. + CompleteOAuthScopeAuthorizationFlow { code: String, state: String }, /// Check the authorization status for a connected account. /// /// Send this when issues are detected with the auth tokens for a connected account. It will diff --git a/components/fxa-client/src/fxa_client.udl b/components/fxa-client/src/fxa_client.udl index 6c67d7bdce..e6da6f516a 100644 --- a/components/fxa-client/src/fxa_client.udl +++ b/components/fxa-client/src/fxa_client.udl @@ -251,7 +251,40 @@ interface FirefoxAccount { /// [Throws=FxaError] void complete_oauth_flow([ByRef] string code, [ByRef] string state ); - + + /// Initiate an OAuth scope authorization flow for an already-connected account. + /// + /// Call this when the application needs to request additional scopes from a user who is + /// already signed in. It returns a URL at which the user may authorize the additional + /// scopes. The application should direct the user to that URL. + /// + /// Unlike `begin_oauth_flow`, if this flow fails or is cancelled the user remains + /// connected — they are not signed out. + /// + /// When the resulting flow redirects back to the configured `redirect_uri`, the query + /// parameters should be extracted and passed to `complete_oauth_scope_authorization_flow`. + /// + /// # Arguments + /// + /// - `scopes` - list of additional OAuth scopes to request. + /// - `entrypoint` - metrics identifier for the UX entrypoint. + [Throws=FxaError] + string begin_oauth_scope_authorization_flow([ByRef] sequence scopes, [ByRef] string entrypoint); + + /// Complete an OAuth scope authorization flow. + /// + /// **💾 This method alters the persisted account state.** + /// + /// At the conclusion of a scope authorization flow, the user will be redirected to the + /// application's registered `redirect_uri`. It should extract the `code` and `state` + /// parameters from the resulting URL and pass them to this method. + /// + /// # Arguments + /// + /// - `code` - the OAuth authorization code obtained from the redirect URI. + /// - `state` - the OAuth state parameter obtained from the redirect URI. + [Throws=FxaError] + void complete_oauth_scope_authorization_flow([ByRef] string code, [ByRef] string state); /// Check authorization status for this application. /// @@ -998,6 +1031,7 @@ interface FxaState { Uninitialized(); Disconnected(); Authenticating(string oauth_url); + Authorizing(string oauth_url); Connected(); AuthIssues(); }; @@ -1009,6 +1043,8 @@ interface FxaEvent { BeginPairingFlow(string pairing_url, sequence scopes, string entrypoint); CompleteOAuthFlow(string code, string state); CancelOAuthFlow(); + BeginOAuthScopeAuthorizationFlow(sequence scopes, string entrypoint); + CompleteOAuthScopeAuthorizationFlow(string code, string state); CheckAuthorizationStatus(); Disconnect(); CallGetProfile(); diff --git a/components/fxa-client/src/internal/oauth.rs b/components/fxa-client/src/internal/oauth.rs index 86e20fe4e4..71207034fd 100644 --- a/components/fxa-client/src/internal/oauth.rs +++ b/components/fxa-client/src/internal/oauth.rs @@ -12,7 +12,7 @@ use super::{ util, FirefoxAccount, }; use crate::auth::UserData; -use crate::{warn, AuthorizationParameters, Error, FxaServer, Result, ScopedKey}; +use crate::{debug, info, warn, AuthorizationParameters, Error, FxaServer, Result, ScopedKey}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use jwcrypto::{EncryptionAlgorithm, EncryptionParameters}; use rate_limiter::RateLimiter; @@ -172,19 +172,15 @@ impl FirefoxAccount { self.oauth_flow(url, scopes) } - /// Initiate an OAuth login flow and return a URL that should be navigated to. - /// - /// * `scopes` - Space-separated list of requested scopes. - /// * `entrypoint` - The entrypoint to be used for metrics - /// * `metrics` - Optional metrics parameters - pub fn begin_oauth_flow(&mut self, scopes: &[&str], entrypoint: &str) -> Result { - self.state.on_begin_oauth(); - let mut url = if self.state.last_seen_profile().is_some() { - self.state.config().oauth_force_auth_url()? - } else { - self.state.config().authorization_endpoint()? - }; - + // We support oauth flows for an initial signin, and also to authorize additional scopes. + // This is code common between the 2. + fn begin_general_oauth_flow( + &mut self, + mut url: Url, + scopes: &[&str], + entrypoint: &str, + ) -> Result { + info!("starting oauth flow via {url} for scopes={scopes:?}, entrypoint={entrypoint:?}"); url.query_pairs_mut() .append_pair("action", "email") .append_pair("response_type", "code") @@ -209,9 +205,44 @@ impl FirefoxAccount { None => scopes.iter().map(ToString::to_string).collect(), }; let scopes: Vec<&str> = scopes.iter().map(<_>::as_ref).collect(); + debug!("oauth flow final set of requested scopes now {scopes:?}"); self.oauth_flow(url, &scopes) } + /// Initiate an OAuth login flow and return a URL that should be navigated to. + /// + /// * `scopes` - Space-separated list of requested scopes. + /// * `entrypoint` - The entrypoint to be used for metrics + /// * `metrics` - Optional metrics parameters + pub fn begin_oauth_flow(&mut self, scopes: &[&str], entrypoint: &str) -> Result { + self.state.on_begin_oauth(); + let url = if self.state.last_seen_profile().is_some() { + self.state.config().oauth_force_auth_url()? + } else { + self.state.config().authorization_endpoint()? + }; + self.begin_general_oauth_flow(url, scopes, entrypoint) + } + + pub fn begin_oauth_scope_authorization_flow( + &mut self, + scopes: &[&str], + entrypoint: &str, + ) -> Result { + // We do not want to kill our refresh token or access tokens in this flow. + let url = self.state.config().authorization_endpoint()?; + self.begin_general_oauth_flow(url, scopes, entrypoint) + } + + pub fn complete_oauth_scope_authorization_flow( + &mut self, + code: &str, + state: &str, + ) -> Result<()> { + // `complete_oauth_flow` already handles everything we need. + self.complete_oauth_flow(code, state) + } + /// Fetch an OAuth code for a particular client using a session token from the account state. /// /// * `auth_params` Authorization parameters which includes: diff --git a/components/fxa-client/src/state_machine/display.rs b/components/fxa-client/src/state_machine/display.rs index 99e090016a..0ec5c7d642 100644 --- a/components/fxa-client/src/state_machine/display.rs +++ b/components/fxa-client/src/state_machine/display.rs @@ -19,6 +19,7 @@ impl fmt::Display for FxaState { Self::Uninitialized => "Uninitialized", Self::Disconnected => "Disconnected", Self::Authenticating { .. } => "Athenticating", + Self::Authorizing { .. } => "Athorizing", Self::Connected => "Connected", Self::AuthIssues => "AthIssues", }; @@ -34,6 +35,8 @@ impl fmt::Display for FxaEvent { Self::BeginPairingFlow { .. } => "BeginPairingFlow", Self::CompleteOAuthFlow { .. } => "CompleteOAthFlow", Self::CancelOAuthFlow => "CancelOAthFlow", + Self::BeginOAuthScopeAuthorizationFlow { .. } => "BeginOAthScopeAthorizationFlow", + Self::CompleteOAuthScopeAuthorizationFlow { .. } => "CompleteOAthScopeAthorizationFlow", Self::CheckAuthorizationStatus => "CheckAuthorizationStatus", Self::Disconnect => "Disconnect", Self::CallGetProfile => "CallGetProfile", @@ -49,6 +52,12 @@ impl fmt::Display for internal_machines::State { Self::BeginOAuthFlow { .. } => write!(f, "BeginOAthFlow"), Self::BeginPairingFlow { .. } => write!(f, "BeginPairingFlow"), Self::CompleteOAuthFlow { .. } => write!(f, "CompleteOAthFlow"), + Self::BeginOAuthScopeAuthorizationFlow { .. } => { + write!(f, "BeginOAthScopeAthorizationFlow") + } + Self::CompleteOAuthScopeAuthorizationFlow { .. } => { + write!(f, "CompleteOAthScopeAthorizationFlow") + } Self::InitializeDevice => write!(f, "InitializeDevice"), Self::EnsureDeviceCapabilities => write!(f, "EnsureDeviceCapabilities"), Self::CheckAuthorizationStatus => write!(f, "CheckAuthorizationStatus"), diff --git a/components/fxa-client/src/state_machine/internal_machines/authenticating.rs b/components/fxa-client/src/state_machine/internal_machines/authenticating.rs index c70df81047..40ef5bd102 100644 --- a/components/fxa-client/src/state_machine/internal_machines/authenticating.rs +++ b/components/fxa-client/src/state_machine/internal_machines/authenticating.rs @@ -146,6 +146,18 @@ mod test { ); } + #[test] + fn test_begin_oauth_scope_authorization_flow_is_invalid() { + // Scope authorization requires an already-connected account; it is not valid from + // the Authenticating state where the user has not yet completed sign-in. + let result = + AuthenticatingStateMachine.initial_state(FxaEvent::BeginOAuthScopeAuthorizationFlow { + scopes: vec!["profile".to_owned()], + entrypoint: "test-entrypoint".to_owned(), + }); + assert!(result.is_err()); + } + /// Same as `test_begin_oauth_flow`, but for a paring flow #[test] fn test_begin_pairing_flow() { diff --git a/components/fxa-client/src/state_machine/internal_machines/authorizing.rs b/components/fxa-client/src/state_machine/internal_machines/authorizing.rs new file mode 100644 index 0000000000..d3968e699b --- /dev/null +++ b/components/fxa-client/src/state_machine/internal_machines/authorizing.rs @@ -0,0 +1,136 @@ +/* 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/. */ + +use super::{invalid_transition, Event, InternalStateMachine, State}; +use crate::{Error, FxaEvent, FxaState, Result}; +use error_support::report_error; + +pub struct AuthorizingStateMachine; + +// Save some typing +use Event::*; +use State::*; + +impl InternalStateMachine for AuthorizingStateMachine { + fn initial_state(&self, event: FxaEvent) -> Result { + match event { + FxaEvent::CompleteOAuthScopeAuthorizationFlow { code, state } => { + Ok(CompleteOAuthScopeAuthorizationFlow { code, state }) + } + FxaEvent::CancelOAuthFlow => Ok(Complete(FxaState::Connected)), + // Allow apps to begin a new scope authorization flow when one is already in progress. + FxaEvent::BeginOAuthScopeAuthorizationFlow { scopes, entrypoint } => { + Ok(State::BeginOAuthScopeAuthorizationFlow { scopes, entrypoint }) + } + FxaEvent::Disconnect => Ok(Disconnect), + e => Err(Error::InvalidStateTransition(format!("Authorizing -> {e}"))), + } + } + + fn next_state(&self, state: State, event: Event) -> Result { + Ok(match (state, event) { + // On success, go directly to Connected — no InitializeDevice step needed since + // the device is already initialized. + (CompleteOAuthScopeAuthorizationFlow { .. }, CompleteOAuthFlowSuccess) => { + Complete(FxaState::Connected) + } + // On failure, return to Connected rather than Disconnected. + (CompleteOAuthScopeAuthorizationFlow { .. }, CallError) => { + Complete(FxaState::Connected) + } + (BeginOAuthScopeAuthorizationFlow { .. }, BeginOAuthFlowSuccess { oauth_url }) => { + Complete(FxaState::Authorizing { oauth_url }) + } + // On failure to begin, stay Connected via Cancel (handled by the outer loop). + (BeginOAuthScopeAuthorizationFlow { .. }, CallError) => Cancel, + (Disconnect, DisconnectSuccess) => Complete(FxaState::Disconnected), + (Disconnect, CallError) => { + // disconnect() is currently infallible, but let's handle errors anyway in case we + // refactor it in the future. + report_error!("fxa-state-machine-error", "saw CallError after Disconnect"); + Complete(FxaState::Disconnected) + } + (state, event) => return invalid_transition(state, event), + }) + } +} + +#[cfg(test)] +mod test { + use super::super::StateMachineTester; + use super::*; + + #[test] + fn test_complete_scope_authorization_flow() { + let tester = StateMachineTester::new( + AuthorizingStateMachine, + FxaEvent::CompleteOAuthScopeAuthorizationFlow { + code: "test-code".to_owned(), + state: "test-state".to_owned(), + }, + ); + assert_eq!( + tester.state, + CompleteOAuthScopeAuthorizationFlow { + code: "test-code".to_owned(), + state: "test-state".to_owned(), + } + ); + assert_eq!( + tester.peek_next_state(CallError), + Complete(FxaState::Connected) + ); + assert_eq!( + tester.peek_next_state(CompleteOAuthFlowSuccess), + Complete(FxaState::Connected) + ); + } + + #[test] + fn test_cancel_oauth_flow() { + let tester = StateMachineTester::new(AuthorizingStateMachine, FxaEvent::CancelOAuthFlow); + assert_eq!(tester.state, Complete(FxaState::Connected)); + } + + /// Test restarting the scope authorization flow when one is already in progress. + #[test] + fn test_begin_oauth_scope_authorization_flow() { + let tester = StateMachineTester::new( + AuthorizingStateMachine, + FxaEvent::BeginOAuthScopeAuthorizationFlow { + scopes: vec!["profile".to_owned()], + entrypoint: "test-entrypoint".to_owned(), + }, + ); + assert_eq!( + tester.state, + BeginOAuthScopeAuthorizationFlow { + scopes: vec!["profile".to_owned()], + entrypoint: "test-entrypoint".to_owned(), + } + ); + assert_eq!(tester.peek_next_state(CallError), Cancel); + assert_eq!( + tester.peek_next_state(BeginOAuthFlowSuccess { + oauth_url: "http://example.com/oauth-start".to_owned(), + }), + Complete(FxaState::Authorizing { + oauth_url: "http://example.com/oauth-start".to_owned(), + }) + ); + } + + #[test] + fn test_disconnect_during_oauth_flow() { + let tester = StateMachineTester::new(AuthorizingStateMachine, FxaEvent::Disconnect); + assert_eq!( + tester.peek_next_state(CallError), + Complete(FxaState::Disconnected) + ); + assert_eq!( + tester.peek_next_state(DisconnectSuccess), + Complete(FxaState::Disconnected) + ); + } +} diff --git a/components/fxa-client/src/state_machine/internal_machines/connected.rs b/components/fxa-client/src/state_machine/internal_machines/connected.rs index d635d02f2e..69ec20b29d 100644 --- a/components/fxa-client/src/state_machine/internal_machines/connected.rs +++ b/components/fxa-client/src/state_machine/internal_machines/connected.rs @@ -18,6 +18,9 @@ impl InternalStateMachine for ConnectedStateMachine { FxaEvent::Disconnect => Ok(Disconnect), FxaEvent::CheckAuthorizationStatus => Ok(CheckAuthorizationStatus), FxaEvent::CallGetProfile => Ok(GetProfile), + FxaEvent::BeginOAuthScopeAuthorizationFlow { scopes, entrypoint } => { + Ok(BeginOAuthScopeAuthorizationFlow { scopes, entrypoint }) + } e => Err(Error::InvalidStateTransition(format!("Connected -> {e}"))), } } @@ -41,6 +44,10 @@ impl InternalStateMachine for ConnectedStateMachine { (GetProfile, GetProfileSuccess) => Complete(FxaState::Connected), (GetProfile, CallError) => Complete(FxaState::AuthIssues), (CheckAuthorizationStatus, CallError) => Complete(FxaState::AuthIssues), + (BeginOAuthScopeAuthorizationFlow { .. }, BeginOAuthFlowSuccess { oauth_url }) => { + Complete(FxaState::Authorizing { oauth_url }) + } + (BeginOAuthScopeAuthorizationFlow { .. }, CallError) => Cancel, (state, event) => return invalid_transition(state, event), }) } diff --git a/components/fxa-client/src/state_machine/internal_machines/mod.rs b/components/fxa-client/src/state_machine/internal_machines/mod.rs index f7d200653f..45fe34651a 100644 --- a/components/fxa-client/src/state_machine/internal_machines/mod.rs +++ b/components/fxa-client/src/state_machine/internal_machines/mod.rs @@ -6,6 +6,7 @@ mod auth_issues; mod authenticating; +mod authorizing; mod connected; mod disconnected; mod uninitialized; @@ -16,6 +17,7 @@ use crate::{ }; pub use auth_issues::AuthIssuesStateMachine; pub use authenticating::AuthenticatingStateMachine; +pub use authorizing::AuthorizingStateMachine; pub use connected::ConnectedStateMachine; pub use disconnected::DisconnectedStateMachine; use error_support::convert_log_report_error; @@ -51,6 +53,14 @@ pub enum State { code: String, state: String, }, + BeginOAuthScopeAuthorizationFlow { + scopes: Vec, + entrypoint: String, + }, + CompleteOAuthScopeAuthorizationFlow { + code: String, + state: String, + }, InitializeDevice, EnsureDeviceCapabilities, CheckAuthorizationStatus, @@ -144,6 +154,17 @@ impl State { account.complete_oauth_flow(code, state)?; Event::CompleteOAuthFlowSuccess } + State::BeginOAuthScopeAuthorizationFlow { scopes, entrypoint } => { + account.cancel_existing_oauth_flows(); + let scopes: Vec<&str> = scopes.iter().map(String::as_str).collect(); + let oauth_url = + account.begin_oauth_scope_authorization_flow(&scopes, entrypoint)?; + Event::BeginOAuthFlowSuccess { oauth_url } + } + State::CompleteOAuthScopeAuthorizationFlow { code, state } => { + account.complete_oauth_scope_authorization_flow(code, state)?; + Event::CompleteOAuthFlowSuccess + } State::InitializeDevice => { account.initialize_device( &device_config.name, diff --git a/components/fxa-client/src/state_machine/mod.rs b/components/fxa-client/src/state_machine/mod.rs index 231118c4db..c91b09a84d 100644 --- a/components/fxa-client/src/state_machine/mod.rs +++ b/components/fxa-client/src/state_machine/mod.rs @@ -46,6 +46,10 @@ impl FirefoxAccount { internal_machines::AuthenticatingStateMachine, event, )?, + FxaState::Authorizing { .. } => self.process_event_with_internal_state_machine( + internal_machines::AuthorizingStateMachine, + event, + )?, FxaState::Connected => self.process_event_with_internal_state_machine( internal_machines::ConnectedStateMachine, event, @@ -95,8 +99,9 @@ impl FirefoxAccount { 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}") + let next_state = state_machine.next_state(state, event); + breadcrumb!("FxaStateMachine.process_event {event_msg} -> {next_state:?}"); + internal_state = next_state?; } } }