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//! These are methods for managing the signed-in state, such as authenticating via
8//! an OAuth flow or disconnecting from the user's account.
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 exposed by the [`begin_oauth_flow`](FirefoxAccount::begin_oauth_flow)
16//!      method.
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 exposed by the
21//!      [`begin_pairing_flow`](FirefoxAccount::begin_pairing_flow) method.
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    /// Sets the user data for a user agent
53    /// **Important**: This should only be used on user agents such as Firefox
54    /// that require the user's session token
55    pub fn set_user_data(&self, user_data: UserData) {
56        self.internal.lock().set_user_data(user_data)
57    }
58
59    /// Initiate a web-based OAuth sign-in flow.
60    ///
61    /// This method initializes some internal state and then returns a URL at which the
62    /// user may perform a web-based authorization flow to connect the application to
63    /// their account. The application should direct the user to the provided URL.
64    ///
65    /// When the resulting OAuth flow redirects back to the configured `redirect_uri`,
66    /// the query parameters should be extracting from the URL and passed to the
67    /// [`complete_oauth_flow`](FirefoxAccount::complete_oauth_flow) method to finalize
68    /// the signin.
69    ///
70    /// # Arguments
71    ///
72    ///   - `scopes` - list of OAuth scopes to request.
73    ///       - The requested scopes will determine what account-related data
74    ///         the application is able to access.
75    ///   - `entrypoint` - metrics identifier for UX entrypoint.
76    ///       - This parameter is used for metrics purposes, to identify the
77    ///         UX entrypoint from which the user triggered the signin request.
78    ///         For example, the application toolbar, on the onboarding flow.
79    ///   - `metrics` - optionally, additional metrics tracking parameters.
80    ///       - These will be included as query parameters in the resulting URL.
81    #[handle_error(Error)]
82    pub fn begin_oauth_flow<T: AsRef<str>>(
83        &self,
84        // Allow both &[String] and &[&str] since UniFFI can't represent `&[&str]` yet,
85        scopes: &[T],
86        entrypoint: &str,
87    ) -> ApiResult<String> {
88        let scopes = scopes.iter().map(T::as_ref).collect::<Vec<_>>();
89        self.internal.lock().begin_oauth_flow(&scopes, entrypoint)
90    }
91
92    /// Get the URL at which to begin a device-pairing signin flow.
93    ///
94    /// If the user wants to sign in using device pairing, call this method and then
95    /// direct them to visit the resulting URL on an already-signed-in device. Doing
96    /// so will trigger the other device to show a QR code to be scanned, and the result
97    /// from said QR code can be passed to [`begin_pairing_flow`](FirefoxAccount::begin_pairing_flow).
98    #[handle_error(Error)]
99    pub fn get_pairing_authority_url(&self) -> ApiResult<String> {
100        self.internal.lock().get_pairing_authority_url()
101    }
102
103    /// Initiate a device-pairing sign-in flow.
104    ///
105    /// Once the user has scanned a pairing QR code, pass the scanned value to this
106    /// method. It will return a URL to which the application should redirect the user
107    /// in order to continue the sign-in flow.
108    ///
109    /// When the resulting flow redirects back to the configured `redirect_uri`,
110    /// the resulting OAuth parameters should be extracting from the URL and passed
111    /// to [`complete_oauth_flow`](FirefoxAccount::complete_oauth_flow) to finalize
112    /// the signin.
113    ///
114    /// # Arguments
115    ///
116    ///   - `pairing_url` - the URL scanned from a QR code on another device.
117    ///   - `scopes` - list of OAuth scopes to request.
118    ///       - The requested scopes will determine what account-related data
119    ///         the application is able to access.
120    ///   - `entrypoint` - metrics identifier for UX entrypoint.
121    ///       - This parameter is used for metrics purposes, to identify the
122    ///         UX entrypoint from which the user triggered the signin request.
123    ///         For example, the application toolbar, on the onboarding flow.
124    ///   - `metrics` - optionally, additional metrics tracking parameters.
125    ///       - These will be included as query parameters in the resulting URL.
126    #[handle_error(Error)]
127    pub fn begin_pairing_flow(
128        &self,
129        pairing_url: &str,
130        scopes: &[String],
131        entrypoint: &str,
132    ) -> ApiResult<String> {
133        // UniFFI can't represent `&[&str]` yet, so convert it internally here.
134        let scopes = scopes.iter().map(String::as_str).collect::<Vec<_>>();
135        self.internal
136            .lock()
137            .begin_pairing_flow(pairing_url, &scopes, entrypoint)
138    }
139
140    /// Complete an OAuth flow.
141    ///
142    /// **💾 This method alters the persisted account state.**
143    ///
144    /// At the conclusion of an OAuth flow, the user will be redirect to the
145    /// application's registered `redirect_uri`. It should extract the `code`
146    /// and `state` parameters from the resulting URL and pass them to this
147    /// method in order to complete the sign-in.
148    ///
149    /// # Arguments
150    ///
151    ///   - `code` - the OAuth authorization code obtained from the redirect URI.
152    ///   - `state` - the OAuth state parameter obtained from the redirect URI.
153    #[handle_error(Error)]
154    pub fn complete_oauth_flow(&self, code: &str, state: &str) -> ApiResult<()> {
155        self.internal.lock().complete_oauth_flow(code, state)
156    }
157
158    /// Check authorization status for this application.
159    ///
160    /// **💾 This method alters the persisted account state.**
161    ///
162    /// Applications may call this method to check with the FxA server about the status
163    /// of their authentication tokens. It returns an [`AuthorizationInfo`] struct
164    /// with details about whether the tokens are still active.
165    #[handle_error(Error)]
166    pub fn check_authorization_status(&self) -> ApiResult<AuthorizationInfo> {
167        Ok(self.internal.lock().check_authorization_status()?.into())
168    }
169
170    /// Disconnect from the user's account.
171    ///
172    /// **💾 This method alters the persisted account state.**
173    ///
174    /// This method destroys any tokens held by the client, effectively disconnecting
175    /// from the user's account. Applications should call this when the user opts to
176    /// sign out.
177    ///
178    /// The persisted account state after calling this method will contain only the
179    /// user's last-seen profile information, if any. This may be useful in helping
180    /// the user to reconnect to their account. If reconnecting to the same account
181    /// is not desired then the application should discard the persisted account state.
182    pub fn disconnect(&self) {
183        self.internal.lock().disconnect()
184    }
185
186    /// Update the state based on authentication issues.
187    ///
188    /// **💾 This method alters the persisted account state.**
189    ///
190    /// Call this if you know there's an authentication / authorization issue that requires the
191    /// user to re-authenticated.  It transitions the user to the [FxaRustAuthState.AuthIssues] state.
192    pub fn on_auth_issues(&self) {
193        self.internal.lock().on_auth_issues()
194    }
195
196    /// Used by the application to test auth token issues
197    pub fn simulate_temporary_auth_token_issue(&self) {
198        self.internal.lock().simulate_temporary_auth_token_issue()
199    }
200
201    /// Used by the application to test auth token issues
202    pub fn simulate_permanent_auth_token_issue(&self) {
203        self.internal.lock().simulate_permanent_auth_token_issue()
204    }
205}
206
207/// Information about the authorization state of the application.
208///
209/// This struct represents metadata about whether the application is currently
210/// connected to the user's account.
211pub struct AuthorizationInfo {
212    pub active: bool,
213}
214
215/// High-level view of the authorization state
216///
217/// This is named `FxaRustAuthState` because it doesn't track all the states we want yet and needs
218/// help from the wrapper code.  The wrapper code defines the actual `FxaAuthState` type based on
219/// this, adding the extra data.
220///
221/// In the long-term, we should track that data in Rust, remove the wrapper, and rename this to
222/// `FxaAuthState`.
223#[derive(Clone, Debug, PartialEq, Eq)]
224pub enum FxaRustAuthState {
225    Disconnected,
226    Connected,
227    AuthIssues,
228}
229
230/// Fxa state
231///
232/// These are the states of [crate::FxaStateMachine] that consumers observe.
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub enum FxaState {
235    /// The state machine needs to be initialized via [Event::Initialize].
236    Uninitialized,
237    /// User has not connected to FxA or has logged out
238    Disconnected,
239    /// User is currently performing an OAuth flow
240    Authenticating { oauth_url: String },
241    /// User is currently connected to FxA
242    Connected,
243    /// User was connected to FxA, but we observed issues with the auth tokens.
244    /// The user needs to reauthenticate before the account can be used.
245    AuthIssues,
246}
247
248/// Fxa event
249///
250/// These are the events that consumers send to [crate::FxaStateMachine::process_event]
251#[derive(Clone, Debug, PartialEq, Eq)]
252pub enum FxaEvent {
253    /// Initialize the state machine.  This must be the first event sent.
254    Initialize { device_config: DeviceConfig },
255    /// Begin an oauth flow
256    ///
257    /// If successful, the state machine will transition the [FxaState::Authenticating].  The next
258    /// step is to navigate the user to the `oauth_url` and let them sign and authorize the client.
259    BeginOAuthFlow {
260        scopes: Vec<String>,
261        entrypoint: String,
262    },
263    /// Begin an oauth flow using a URL from a pairing code
264    ///
265    /// If successful, the state machine will transition the [FxaState::Authenticating].  The next
266    /// step is to navigate the user to the `oauth_url` and let them sign and authorize the client.
267    BeginPairingFlow {
268        pairing_url: String,
269        scopes: Vec<String>,
270        entrypoint: String,
271    },
272    /// Complete an OAuth flow.
273    ///
274    /// Send this event after the user has navigated through the OAuth flow and has reached the
275    /// redirect URI.  Extract `code` and `state` from the query parameters or web channel.  If
276    /// successful the state machine will transition to [FxaState::Connected].
277    CompleteOAuthFlow { code: String, state: String },
278    /// Cancel an OAuth flow.
279    ///
280    /// Use this to cancel an in-progress OAuth, returning to [FxaState::Disconnected] so the
281    /// process can begin again.
282    CancelOAuthFlow,
283    /// Check the authorization status for a connected account.
284    ///
285    /// Send this when issues are detected with the auth tokens for a connected account.  It will
286    /// double check for authentication issues with the account.  If it detects them, the state
287    /// machine will transition to [FxaState::AuthIssues].  From there you can start an OAuth flow
288    /// again to re-connect the user.
289    CheckAuthorizationStatus,
290    /// Disconnect the user
291    ///
292    /// Send this when the user is asking to be logged out.  The state machine will transition to
293    /// [FxaState::Disconnected].
294    Disconnect,
295    /// Force a call to [FirefoxAccount::get_profile]
296    ///
297    /// This is used for testing the auth/network retry code, since it hits the network and
298    /// requires and auth token.
299    CallGetProfile,
300}
301
302/// User data provided by the web content, meant to be consumed by user agents
303#[derive(Debug, Clone)]
304pub struct UserData {
305    pub(crate) session_token: String,
306    pub(crate) uid: String,
307    pub(crate) email: String,
308    pub(crate) verified: bool,
309}