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}