fxa_client/
auth.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//! # Signing in and out
6//!
7//! Signing in and out is driven through the state machine: by sending the relevant
8//! [`FxaEvent`] to [`FirefoxAccount::process_event`].
9//!
10//! The Firefox Accounts system supports two methods for connecting an application
11//! to a user's account:
12//!
13//!    - A traditional OAuth flow, where the user is directed to a webpage to enter
14//!      their account credentials and then redirected back to the application.
15//!      This is driven by the [`FxaEvent::BeginOAuthFlow`] and
16//!      [`FxaEvent::CompleteOAuthFlow`] events.
17//!
18//!    - A device pairing flow, where the user scans a QRCode presented by another
19//!      app that is already connected to the account, which then directs them to
20//!      a webpage for a simplified signing flow. This is driven by the
21//!      [`FxaEvent::BeginPairingFlow`] event.
22//!
23//! Technical details of the pairing flow can be found in the [Firefox Accounts
24//! documentation hub](https://mozilla.github.io/ecosystem-platform/docs/features/firefox-accounts/pairing).
25
26use crate::{ApiResult, DeviceConfig, Error, FirefoxAccount};
27use error_support::handle_error;
28
29impl FirefoxAccount {
30    /// Get the current state
31    pub fn get_state(&self) -> FxaState {
32        self.internal.lock().get_state()
33    }
34
35    /// Process an event (login, logout, etc).
36    ///
37    /// On success, returns the new state.
38    /// On error, the state will remain the same.
39    #[handle_error(Error)]
40    pub fn process_event(&self, event: FxaEvent) -> ApiResult<FxaState> {
41        self.internal.lock().process_event(event)
42    }
43
44    /// Get the high-level authentication state of the client
45    ///
46    /// TODO: remove this and the FxaRustAuthState type from the public API
47    /// https://bugzilla.mozilla.org/show_bug.cgi?id=1868614
48    pub fn get_auth_state(&self) -> FxaRustAuthState {
49        self.internal.lock().get_auth_state()
50    }
51
52    /// Stores the session token from a WebChannel login JSON payload without exposing it
53    /// to the browser layer.
54    ///
55    /// The `json_payload` is the `data` object from the `fxaccounts:login` WebChannel
56    /// command. The session token is extracted and stored internally; callers never hold
57    /// the raw token value.
58    ///
59    /// **💾 This method alters the persisted account state.**
60    #[handle_error(Error)]
61    pub fn handle_web_channel_login(&self, json_payload: String) -> ApiResult<()> {
62        self.internal.lock().handle_web_channel_login(&json_payload)
63    }
64
65    /// Get the URL at which to begin a device-pairing signin flow.
66    ///
67    /// If the user wants to sign in using device pairing, call this method and then
68    /// direct them to visit the resulting URL on an already-signed-in device. Doing
69    /// so will trigger the other device to show a QR code to be scanned, and the result
70    /// from said QR code can be passed to the [`FxaEvent::BeginPairingFlow`] event.
71    #[handle_error(Error)]
72    pub fn get_pairing_authority_url(&self) -> ApiResult<String> {
73        self.internal.lock().get_pairing_authority_url()
74    }
75
76    /// Check authorization status for this application.
77    ///
78    /// **💾 This method alters the persisted account state.**
79    ///
80    /// Applications may call this method to check with the FxA server about the status
81    /// of their authentication tokens. It returns an [`AuthorizationInfo`] struct
82    /// with details about whether the tokens are still active.
83    #[handle_error(Error)]
84    pub fn check_authorization_status(&self) -> ApiResult<AuthorizationInfo> {
85        Ok(self.internal.lock().check_authorization_status()?.into())
86    }
87
88    /// Disconnect from the user's account.
89    ///
90    /// **💾 This method alters the persisted account state.**
91    ///
92    /// This method destroys any tokens held by the client, effectively disconnecting
93    /// from the user's account. Applications should call this when the user opts to
94    /// sign out.
95    ///
96    /// The persisted account state after calling this method will contain only the
97    /// user's last-seen profile information, if any. This may be useful in helping
98    /// the user to reconnect to their account. If reconnecting to the same account
99    /// is not desired then the application should discard the persisted account state.
100    pub fn disconnect(&self) {
101        self.internal.lock().disconnect()
102    }
103
104    /// Update the state based on authentication issues.
105    ///
106    /// **💾 This method alters the persisted account state.**
107    ///
108    /// Call this if you know there's an authentication / authorization issue that requires the
109    /// user to re-authenticated.  It transitions the user to the [FxaRustAuthState.AuthIssues] state.
110    pub fn on_auth_issues(&self) {
111        self.internal.lock().on_auth_issues()
112    }
113
114    /// Used by the application to test auth token issues
115    pub fn simulate_temporary_auth_token_issue(&self) {
116        self.internal.lock().simulate_temporary_auth_token_issue()
117    }
118
119    /// Used by the application to test auth token issues
120    pub fn simulate_permanent_auth_token_issue(&self) {
121        self.internal.lock().simulate_permanent_auth_token_issue()
122    }
123}
124
125/// Information about the authorization state of the application.
126///
127/// This struct represents metadata about whether the application is currently
128/// connected to the user's account.
129pub struct AuthorizationInfo {
130    pub active: bool,
131}
132
133/// High-level view of the authorization state
134///
135/// This is named `FxaRustAuthState` because it doesn't track all the states we want yet and needs
136/// help from the wrapper code.  The wrapper code defines the actual `FxaAuthState` type based on
137/// this, adding the extra data.
138///
139/// In the long-term, we should track that data in Rust, remove the wrapper, and rename this to
140/// `FxaAuthState`.
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
142pub enum FxaRustAuthState {
143    Disconnected,
144    Connected,
145    AuthIssues,
146}
147
148/// Fxa state
149///
150/// These are the states of [crate::FxaStateMachine] that consumers observe.
151#[derive(Clone, Debug, PartialEq, Eq)]
152pub enum FxaState {
153    /// The state machine needs to be initialized via [Event::Initialize].
154    Uninitialized,
155    /// User has not connected to FxA or has logged out
156    Disconnected,
157    /// User is currently performing an OAuth flow - our existing initial state
158    /// when we transition to this state will influence what this means exactly.
159    Authenticating {
160        oauth_url: String,
161        initial_state: FxaRustAuthState,
162    },
163    /// User is currently connected to FxA
164    Connected,
165    /// User was connected to FxA, but we observed issues with the auth tokens.
166    /// The user needs to reauthenticate before the account can be used.
167    AuthIssues,
168}
169
170impl From<FxaRustAuthState> for FxaState {
171    fn from(value: FxaRustAuthState) -> Self {
172        match value {
173            FxaRustAuthState::Connected => FxaState::Connected,
174            FxaRustAuthState::Disconnected => FxaState::Disconnected,
175            FxaRustAuthState::AuthIssues => FxaState::AuthIssues,
176        }
177    }
178}
179
180/// Fxa event
181///
182/// These are the events that consumers send to [crate::FxaStateMachine::process_event]
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub enum FxaEvent {
185    /// Initialize the state machine.  This must be the first event sent.
186    Initialize { device_config: DeviceConfig },
187    /// Begin an oauth flow
188    ///
189    /// If successful, the state machine will transition the [FxaState::Authenticating].  The next
190    /// step is to navigate the user to the `oauth_url` and let them sign and authorize the client.
191    ///
192    /// This event is valid for the `Disconnected`, `AuthIssues`, and `Authenticating` states.  If
193    /// the state machine is in the `Authenticating` state, then this will forget the current OAuth
194    /// flow and start a new one.
195    BeginOAuthFlow {
196        service: String,
197        scopes: Vec<String>,
198        entrypoint: String,
199    },
200    /// Begin an oauth flow using a URL from a pairing code
201    ///
202    /// If successful, the state machine will transition the [FxaState::Authenticating].  The next
203    /// step is to navigate the user to the `oauth_url` and let them sign and authorize the client.
204    ///
205    /// This event is valid for the `Disconnected`, `AuthIssues`, and `Authenticating` states.  If
206    /// the state machine is in the `Authenticating` state, then this will forget the current OAuth
207    /// flow and start a new one.
208    BeginPairingFlow {
209        pairing_url: String,
210        service: String,
211        scopes: Vec<String>,
212        entrypoint: String,
213    },
214    /// Complete an OAuth flow.
215    ///
216    /// Send this event after the user has navigated through the OAuth flow and has reached the
217    /// redirect URI.  Extract `code` and `state` from the query parameters or web channel.  If
218    /// successful the state machine will transition to [FxaState::Connected].
219    ///
220    /// This event is valid for the `Authenticating` state.
221    CompleteOAuthFlow { code: String, state: String },
222    /// An `fxaccounts:change_password` WebChannel message arrived on the device that just changed
223    /// its password. `json_payload` is the `data` object of that message and contains the new
224    /// session token. The state machine swaps the session token for a new refresh token and
225    /// re-initialises the device record.
226    ///
227    /// This event is valid for the `Connected` and `AuthIssues` states. In `Authenticating` it
228    /// is a no-op so the in-progress OAuth flow is not disrupted.
229    WebChannelPasswordChange { json_payload: String },
230    /// Cancel an OAuth flow.
231    ///
232    /// Use this to cancel an in-progress OAuth, returning to [FxaState::Disconnected] so the
233    /// process can begin again.
234    ///
235    /// This event is valid for the `Authenticating` state.
236    CancelOAuthFlow,
237    /// Check the authorization status for a connected account.
238    ///
239    /// Send this when issues are detected with the auth tokens for a connected account.  It will
240    /// double check for authentication issues with the account.  If it detects them, the state
241    /// machine will transition to [FxaState::AuthIssues].  From there you can start an OAuth flow
242    /// again to re-connect the user.
243    ///
244    /// This event is valid for the `Connected` state.
245    CheckAuthorizationStatus,
246    /// Disconnect the user
247    ///
248    /// Send this when the user is asking to be logged out.  The state machine will transition to
249    /// [FxaState::Disconnected].
250    ///
251    /// This event is valid for the `Connected` state.
252    Disconnect,
253    /// Force a call to [FirefoxAccount::get_profile]
254    ///
255    /// This is used for testing the auth/network retry code, since it hits the network and
256    /// requires and auth token.
257    ///
258    /// This event is valid for the `Connected` state.
259    CallGetProfile,
260}