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/. */
45use error_support::{ErrorHandling, GetErrorHandling};
6use rc_crypto::hawk;
7use std::string;
89/// Public error type thrown by many [`FirefoxAccount`] operations.
10///
11/// Precise details of the error are hidden from consumers. The type of the error indicates how the
12/// calling code should respond.
13#[derive(Debug, thiserror::Error)]
14pub enum FxaError {
15/// Thrown when there was a problem with the authentication status of the account,
16 /// such as an expired token. The application should [check its authorization status](
17 /// FirefoxAccount::check_authorization_status) to see whether it has been disconnected,
18 /// or retry the operation with a freshly-generated token.
19#[error("authentication error")]
20Authentication,
21/// Thrown if an operation fails due to network access problems.
22 /// The application may retry at a later time once connectivity is restored.
23#[error("network error")]
24Network,
25/// Thrown if the application attempts to complete an OAuth flow when no OAuth flow
26 /// has been initiated. This may indicate a user who navigated directly to the OAuth
27 /// `redirect_uri` for the application.
28 ///
29 /// **Note:** This error is currently only thrown in the Swift language bindings.
30#[error("no authentication flow was active")]
31NoExistingAuthFlow,
32/// Thrown if the application attempts to complete an OAuth flow, but the state
33 /// tokens returned from the Firefox Account server do not match with the ones
34 /// expected by the client.
35 /// This may indicate a stale OAuth flow, or potentially an attempted hijacking
36 /// of the flow by an attacker. The signin attempt cannot be completed.
37 ///
38 /// **Note:** This error is currently only thrown in the Swift language bindings.
39#[error("the requested authentication flow was not active")]
40WrongAuthFlow,
41/// Origin mismatch when handling a pairing flow
42 ///
43 /// The most likely cause of this is that a user tried to pair together two firefox instances
44 /// that are configured to use different servers.
45#[error("Origin mismatch")]
46OriginMismatch,
47/// A scoped key was missing in the server response when requesting the OLD_SYNC scope.
48#[error("The sync scoped key was missing")]
49SyncScopedKeyMissingInServerResponse,
50/// Thrown if there is a panic in the underlying Rust code.
51 ///
52 /// **Note:** This error is currently only thrown in the Kotlin language bindings.
53#[error("panic in native code")]
54Panic,
55/// A catch-all for other unspecified errors.
56#[error("other error: {0}")]
57Other(String),
58}
5960/// FxA internal error type
61/// These are used in the internal code. This error type is never returned to the consumer.
62#[derive(Debug, thiserror::Error)]
63pub enum Error {
64#[error("Server asked the client to back off, please wait {0} seconds to try again")]
65BackoffError(u64),
6667#[error("Unknown OAuth State")]
68UnknownOAuthState,
6970#[error("Multiple OAuth scopes requested")]
71MultipleScopesRequested,
7273#[error("No cached token for scope {0}")]
74NoCachedToken(String),
7576#[error("No cached scoped keys for scope {0}")]
77NoScopedKey(String),
7879#[error("No stored refresh token")]
80NoRefreshToken,
8182#[error("No stored session token")]
83NoSessionToken,
8485#[error("No stored migration data")]
86NoMigrationData,
8788#[error("No stored current device id")]
89NoCurrentDeviceId,
9091#[error("Device target is unknown (Device ID: {0})")]
92UnknownTargetDevice(String),
9394#[error("Api client error {0}")]
95ApiClientError(&'static str),
9697#[error("Illegal state: {0}")]
98IllegalState(&'static str),
99100#[error("Unknown command: {0}")]
101UnknownCommand(String),
102103#[error("Send Tab diagnosis error: {0}")]
104SendTabDiagnosisError(&'static str),
105106#[error("Cannot xor arrays with different lengths: {0} and {1}")]
107XorLengthMismatch(usize, usize),
108109#[error("Origin mismatch: {0}")]
110OriginMismatch(String),
111112#[error("Remote key and local key mismatch")]
113MismatchedKeys,
114115#[error("The sync scoped key was missing in the server response")]
116SyncScopedKeyMissingInServerResponse,
117118#[error("Client: {0} is not allowed to request scope: {1}")]
119ScopeNotAllowed(String, String),
120121#[error("Unsupported command: {0}")]
122UnsupportedCommand(&'static str),
123124#[error("Missing URL parameter: {0}")]
125MissingUrlParameter(&'static str),
126127#[error("Null pointer passed to FFI")]
128NullPointer,
129130#[error("Invalid buffer length: {0}")]
131InvalidBufferLength(i32),
132133#[error("Too many calls to auth introspection endpoint")]
134AuthCircuitBreakerError,
135136#[error("Remote server error: '{code}' '{errno}' '{error}' '{message}' '{info}'")]
137RemoteError {
138 code: u64,
139 errno: u64,
140 error: String,
141 message: String,
142 info: String,
143 },
144145// Basically reimplement error_chain's foreign_links. (Ugh, this sucks).
146#[error("Crypto/NSS error: {0}")]
147CryptoError(#[from] rc_crypto::Error),
148149#[error("http-ece encryption error: {0}")]
150EceError(#[from] rc_crypto::ece::Error),
151152#[error("Hex decode error: {0}")]
153HexDecodeError(#[from] hex::FromHexError),
154155#[error("Base64 decode error: {0}")]
156Base64Decode(#[from] base64::DecodeError),
157158#[error("JSON error: {0}")]
159JsonError(#[from] serde_json::Error),
160161#[error("JWCrypto error: {0}")]
162JwCryptoError(#[from] jwcrypto::JwCryptoError),
163164#[error("UTF8 decode error: {0}")]
165UTF8DecodeError(#[from] string::FromUtf8Error),
166167#[error("Network error: {0}")]
168RequestError(#[from] viaduct::Error),
169170#[error("Malformed URL error: {0}")]
171MalformedUrl(#[from] url::ParseError),
172173#[error("Unexpected HTTP status: {0}")]
174UnexpectedStatus(#[from] viaduct::UnexpectedStatus),
175176#[error("Sync15 error: {0}")]
177SyncError(#[from] sync15::Error),
178179#[error("HAWK error: {0}")]
180HawkError(#[from] hawk::Error),
181182#[error("Integer conversion error: {0}")]
183IntegerConversionError(#[from] std::num::TryFromIntError),
184185#[error("Command not found by fxa")]
186CommandNotFound,
187188#[error("Invalid Push Event")]
189InvalidPushEvent,
190191#[error("Invalid state transition: {0}")]
192InvalidStateTransition(String),
193194#[error("Internal error in the state machine: {0}")]
195StateMachineLogicError(String),
196}
197198// Define how our internal errors are handled and converted to external errors
199// See `support/error/README.md` for how this works, especially the warning about PII.
200impl GetErrorHandling for Error {
201type ExternalError = FxaError;
202203fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
204match self {
205 Error::RemoteError { code: 401, .. }
206 | Error::NoRefreshToken
207 | Error::NoScopedKey(_)
208 | Error::NoCachedToken(_) => {
209 ErrorHandling::convert(FxaError::Authentication).log_warning()
210 }
211 Error::RequestError(_) => ErrorHandling::convert(FxaError::Network).log_warning(),
212 Error::SyncScopedKeyMissingInServerResponse => {
213 ErrorHandling::convert(FxaError::SyncScopedKeyMissingInServerResponse)
214 .report_error("fxa-client-scoped-key-missing")
215 }
216 Error::UnknownOAuthState => {
217 ErrorHandling::convert(FxaError::NoExistingAuthFlow).log_warning()
218 }
219 Error::BackoffError(_) => ErrorHandling::convert(FxaError::Other(self.to_string()))
220 .report_error("fxa-client-backoff"),
221 Error::InvalidStateTransition(_) | Error::StateMachineLogicError(_) => {
222 ErrorHandling::convert(FxaError::Other(self.to_string()))
223 .report_error("fxa-state-machine-error")
224 }
225 Error::OriginMismatch(_) => ErrorHandling::convert(FxaError::OriginMismatch),
226_ => ErrorHandling::convert(FxaError::Other(self.to_string()))
227 .report_error("fxa-client-other-error"),
228 }
229 }
230}