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}