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