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 /// Used by the application to test auth token issues
116 pub fn simulate_temporary_auth_token_issue(&self) {
117 self.internal.lock().simulate_temporary_auth_token_issue()
118 }
119
120 /// Used by the application to test auth token issues
121 pub fn simulate_permanent_auth_token_issue(&self) {
122 self.internal.lock().simulate_permanent_auth_token_issue()
123 }
124}
125
126#[derive(uniffi::Record)]
127/// Information about the authorization state of the application.
128///
129/// This struct represents metadata about whether the application is currently
130/// connected to the user's account.
131pub struct AuthorizationInfo {
132 pub active: bool,
133}
134
135#[derive(uniffi::Enum, Clone, Copy, Debug, PartialEq, Eq)]
136/// High-level view of the authorization state
137///
138/// This is named `FxaRustAuthState` because it doesn't track all the states we want yet and needs
139/// help from the wrapper code. The wrapper code defines the actual `FxaAuthState` type based on
140/// this, adding the extra data.
141///
142/// In the long-term, we should track that data in Rust, remove the wrapper, and rename this to
143/// `FxaAuthState`.
144pub enum FxaRustAuthState {
145 Disconnected,
146 Connected,
147 AuthIssues,
148}
149
150#[derive(uniffi::Enum, Clone, Debug, PartialEq, Eq)]
151/// Fxa state
152///
153/// These are the states of [crate::FxaStateMachine] that consumers observe.
154pub enum FxaState {
155 /// The state machine needs to be initialized via [Event::Initialize].
156 Uninitialized,
157 /// User has not connected to FxA or has logged out
158 Disconnected,
159 /// User is currently performing an OAuth flow - our existing initial state
160 /// when we transition to this state will influence what this means exactly.
161 Authenticating {
162 oauth_url: String,
163 initial_state: FxaRustAuthState,
164 },
165 /// User is currently connected to FxA
166 Connected,
167 /// User was connected to FxA, but we observed issues with the auth tokens.
168 /// The user needs to reauthenticate before the account can be used.
169 AuthIssues,
170}
171
172impl From<FxaRustAuthState> for FxaState {
173 fn from(value: FxaRustAuthState) -> Self {
174 match value {
175 FxaRustAuthState::Connected => FxaState::Connected,
176 FxaRustAuthState::Disconnected => FxaState::Disconnected,
177 FxaRustAuthState::AuthIssues => FxaState::AuthIssues,
178 }
179 }
180}
181
182#[derive(uniffi::Enum, Clone, Debug, PartialEq, Eq)]
183/// Fxa event
184///
185/// These are the events that consumers send to [crate::FxaStateMachine::process_event]
186pub enum FxaEvent {
187 /// Initialize the state machine. This must be the first event sent.
188 Initialize { device_config: DeviceConfig },
189 /// Begin an oauth flow
190 ///
191 /// If successful, the state machine will transition the [FxaState::Authenticating]. The next
192 /// step is to navigate the user to the `oauth_url` and let them sign and authorize the client.
193 ///
194 /// This event is valid for the `Disconnected`, `AuthIssues`, and `Authenticating` states. If
195 /// the state machine is in the `Authenticating` state, then this will forget the current OAuth
196 /// flow and start a new one.
197 BeginOAuthFlow {
198 service: String,
199 scopes: Vec<String>,
200 entrypoint: String,
201 },
202 /// Begin an oauth flow using a URL from a pairing code
203 ///
204 /// If successful, the state machine will transition the [FxaState::Authenticating]. The next
205 /// step is to navigate the user to the `oauth_url` and let them sign and authorize the client.
206 ///
207 /// This event is valid for the `Disconnected`, `AuthIssues`, and `Authenticating` states. If
208 /// the state machine is in the `Authenticating` state, then this will forget the current OAuth
209 /// flow and start a new one.
210 BeginPairingFlow {
211 pairing_url: String,
212 service: String,
213 scopes: Vec<String>,
214 entrypoint: String,
215 },
216 /// Complete an OAuth flow.
217 ///
218 /// Send this event after the user has navigated through the OAuth flow and has reached the
219 /// redirect URI. Extract `code` and `state` from the query parameters or web channel. If
220 /// successful the state machine will transition to [FxaState::Connected].
221 ///
222 /// This event is valid for the `Authenticating` state.
223 CompleteOAuthFlow { code: String, state: String },
224 /// Cancel an OAuth flow.
225 ///
226 /// Use this to cancel an in-progress OAuth, returning to [FxaState::Disconnected] so the
227 /// process can begin again.
228 ///
229 /// This event is valid for the `Authenticating` state.
230 CancelOAuthFlow,
231 /// Check the authorization status for a connected account.
232 ///
233 /// Send this when issues are detected with the auth tokens for a connected account. It will
234 /// double check for authentication issues with the account. If it detects them, the state
235 /// machine will transition to [FxaState::AuthIssues]. From there you can start an OAuth flow
236 /// again to re-connect the user.
237 ///
238 /// This event is valid for the `Connected` state.
239 CheckAuthorizationStatus,
240 /// An `fxaccounts:change_password` WebChannel message arrived on the device that just changed
241 /// its password. `json_payload` is the `data` object of that message and contains the new
242 /// session token. The state machine swaps the session token for a new refresh token and
243 /// re-initialises the device record.
244 ///
245 /// This event is valid for the `Connected` and `AuthIssues` states. In `Authenticating` it
246 /// is a no-op so the in-progress OAuth flow is not disrupted.
247 WebChannelPasswordChange { json_payload: String },
248 /// Disconnect the user
249 ///
250 /// Send this when the user is asking to be logged out. The state machine will transition to
251 /// [FxaState::Disconnected].
252 ///
253 /// This event is valid for the `Connected` state.
254 Disconnect,
255 /// Force a call to [FirefoxAccount::get_profile]
256 ///
257 /// This is used for testing the auth/network retry code, since it hits the network and
258 /// requires and auth token.
259 ///
260 /// This event is valid for the `Connected` state.
261 CallGetProfile,
262}