fxa_client/
error.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
5use error_support::{ErrorHandling, GetErrorHandling};
6use std::string;
7
8/// Public error type thrown by many [`FirefoxAccount`] operations.
9///
10/// Precise details of the error are hidden from consumers. The type of the error indicates how the
11/// calling code should respond.
12#[derive(Debug, thiserror::Error)]
13pub enum FxaError {
14    /// Thrown when there was a problem with the authentication status of the account,
15    /// such as an expired token. The application should [check its authorization status](
16    /// FirefoxAccount::check_authorization_status) to see whether it has been disconnected,
17    /// or retry the operation with a freshly-generated token.
18    #[error("authentication error")]
19    Authentication,
20    /// Thrown when an authenticated account isn't allowed to perform some operation. Unlike
21    /// `Authentication`, there's no problem with the account status. In some cases it
22    /// might be possible to request additional scopes, and once granted, the operation
23    /// may succeed.
24    #[error("forbidden")]
25    Forbidden,
26    /// Thrown if an operation fails due to network access problems.
27    /// The application may retry at a later time once connectivity is restored.
28    #[error("network error")]
29    Network,
30    /// Thrown if the application attempts to complete an OAuth flow when no OAuth flow
31    /// has been initiated. This may indicate a user who navigated directly to the OAuth
32    /// `redirect_uri` for the application.
33    ///
34    /// **Note:** This error is currently only thrown in the Swift language bindings.
35    #[error("no authentication flow was active")]
36    NoExistingAuthFlow,
37    /// Thrown if the application attempts to complete an OAuth flow, but the state
38    /// tokens returned from the Firefox Account server do not match with the ones
39    /// expected by the client.
40    /// This may indicate a stale OAuth flow, or potentially an attempted hijacking
41    /// of the flow by an attacker. The signin attempt cannot be completed.
42    ///
43    /// **Note:** This error is currently only thrown in the Swift language bindings.
44    #[error("the requested authentication flow was not active")]
45    WrongAuthFlow,
46    /// Origin mismatch when handling a pairing flow
47    ///
48    /// The most likely cause of this is that a user tried to pair together two firefox instances
49    /// that are configured to use different servers.
50    #[error("Origin mismatch")]
51    OriginMismatch,
52    /// A scoped key was missing in the server response when requesting the OLD_SYNC scope.
53    #[error("The sync scoped key was missing")]
54    SyncScopedKeyMissingInServerResponse,
55    /// Thrown if there is a panic in the underlying Rust code.
56    ///
57    /// **Note:** This error is currently only thrown in the Kotlin language bindings.
58    #[error("panic in native code")]
59    Panic,
60    /// A catch-all for other unspecified errors.
61    #[error("other error: {0}")]
62    Other(String),
63}
64
65/// FxA internal error type
66/// These are used in the internal code. This error type is never returned to the consumer.
67#[derive(Debug, thiserror::Error)]
68pub enum Error {
69    #[error("Server asked the client to back off, please wait {0} seconds to try again")]
70    BackoffError(u64),
71
72    #[error("Unknown OAuth State")]
73    UnknownOAuthState,
74
75    #[error("No cached scoped keys for scope {0}")]
76    NoScopedKey(String),
77
78    #[error("No stored refresh token")]
79    NoRefreshToken,
80
81    #[error("No stored session token")]
82    NoSessionToken,
83
84    #[error("No stored migration data")]
85    NoMigrationData,
86
87    #[error("No stored current device id")]
88    NoCurrentDeviceId,
89
90    #[error("Device target is unknown (Device ID: {0})")]
91    UnknownTargetDevice(String),
92
93    #[error("Api client error {0}")]
94    ApiClientError(&'static str),
95
96    #[error("Illegal state: {0}")]
97    IllegalState(&'static str),
98
99    #[error("Unknown command: {0}")]
100    UnknownCommand(String),
101
102    #[error("Send Tab diagnosis error: {0}")]
103    SendTabDiagnosisError(&'static str),
104
105    #[error("Cannot xor arrays with different lengths: {0} and {1}")]
106    XorLengthMismatch(usize, usize),
107
108    #[error("Origin mismatch: {0}")]
109    OriginMismatch(String),
110
111    #[error("Remote key and local key mismatch")]
112    MismatchedKeys,
113
114    // For example, we requested an access token, the server responded with a 200, but the response body
115    // had no token. This is a little bit vague, because in many cases you would get a JSON error if the
116    // shape of the payload was entirely wrong.
117    #[error("The response from the server, or the content in that reponse, was unexpected")]
118    UnexpectedServerResponse,
119
120    // This should probably be rolled up into `UnexpectedServerResponse`, but the way it is implemented
121    // and even exposed to consumers makes that a little tricky - but at the end of the day, this can
122    // only happen when the server gave us a confused response.
123    #[error("The sync scoped key was missing in the server response")]
124    SyncScopedKeyMissingInServerResponse,
125
126    #[error("Client: {0} is not allowed to request scope: {1}")]
127    ScopeNotAllowed(String, String),
128
129    #[error("Unsupported command: {0}")]
130    UnsupportedCommand(&'static str),
131
132    #[error("Missing URL parameter: {0}")]
133    MissingUrlParameter(&'static str),
134
135    #[error("Null pointer passed to FFI")]
136    NullPointer,
137
138    #[error("Invalid buffer length: {0}")]
139    InvalidBufferLength(i32),
140
141    #[error("Too many calls to auth introspection endpoint")]
142    AuthCircuitBreakerError,
143
144    #[error("Remote server error: '{code}' '{errno}' '{error}' '{message}' '{info}'")]
145    RemoteError {
146        code: u64,
147        errno: u64,
148        error: String,
149        message: String,
150        info: String,
151    },
152
153    // Basically reimplement error_chain's foreign_links. (Ugh, this sucks).
154    #[error("Crypto/NSS error: {0}")]
155    CryptoError(#[from] rc_crypto::Error),
156
157    #[error("http-ece encryption error: {0}")]
158    EceError(#[from] rc_crypto::ece::Error),
159
160    #[error("Hex decode error: {0}")]
161    HexDecodeError(#[from] hex::FromHexError),
162
163    #[error("Base64 decode error: {0}")]
164    Base64Decode(#[from] base64::DecodeError),
165
166    #[error("JSON error: {0}")]
167    JsonError(#[from] serde_json::Error),
168
169    #[error("JWCrypto error: {0}")]
170    JwCryptoError(#[from] jwcrypto::JwCryptoError),
171
172    #[error("UTF8 decode error: {0}")]
173    UTF8DecodeError(#[from] string::FromUtf8Error),
174
175    #[error("Network error: {0}")]
176    RequestError(#[from] viaduct::ViaductError),
177
178    #[error("Malformed URL: {sanitized_url} ({when})")]
179    MalformedUrl {
180        /// URL without any query or fragment parts.
181        /// This is safe to send in an error report because there's no auth information.
182        sanitized_url: String,
183        when: String,
184    },
185
186    #[error("Unexpected HTTP status: {0}")]
187    UnexpectedStatus(#[from] viaduct::UnexpectedStatus),
188
189    #[error("Sync15 error: {0}")]
190    SyncError(#[from] sync15::Error),
191
192    #[error("Integer conversion error: {0}")]
193    IntegerConversionError(#[from] std::num::TryFromIntError),
194
195    #[error("Command not found by fxa")]
196    CommandNotFound,
197
198    #[error("Invalid Push Event")]
199    InvalidPushEvent,
200
201    #[error("Invalid state transition: {0}")]
202    InvalidStateTransition(String),
203
204    #[error("Internal error in the state machine: {0}")]
205    StateMachineLogicError(String),
206}
207
208// Define how our internal errors are handled and converted to external errors
209// See `support/error/README.md` for how this works, especially the warning about PII.
210impl GetErrorHandling for Error {
211    type ExternalError = FxaError;
212
213    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
214        match self {
215            Error::RemoteError { code: 401, .. }
216            | Error::NoRefreshToken
217            | Error::NoSessionToken
218            | Error::NoScopedKey(_) => {
219                ErrorHandling::convert(FxaError::Authentication).log_warning()
220            }
221            Error::RemoteError { code: 403, .. } => {
222                ErrorHandling::convert(FxaError::Forbidden).log_warning()
223            }
224            Error::RequestError(_) => ErrorHandling::convert(FxaError::Network).log_warning(),
225            Error::SyncScopedKeyMissingInServerResponse => {
226                ErrorHandling::convert(FxaError::SyncScopedKeyMissingInServerResponse)
227                    .report_error("fxa-client-scoped-key-missing")
228            }
229            Error::UnknownOAuthState => {
230                ErrorHandling::convert(FxaError::NoExistingAuthFlow).log_warning()
231            }
232            Error::BackoffError(_) => ErrorHandling::convert(FxaError::Other(self.to_string()))
233                .report_error("fxa-client-backoff"),
234            Error::InvalidStateTransition(_) | Error::StateMachineLogicError(_) => {
235                ErrorHandling::convert(FxaError::Other(self.to_string()))
236                    .report_error("fxa-state-machine-error")
237            }
238            Error::OriginMismatch(_) => ErrorHandling::convert(FxaError::OriginMismatch),
239            // Just log a warning for these.  They're already reported in `parse_url` and
240            // `join_url`.
241            Error::MalformedUrl { .. } => {
242                ErrorHandling::convert(FxaError::Other(self.to_string())).log_warning()
243            }
244            _ => ErrorHandling::convert(FxaError::Other(self.to_string()))
245                .report_error("fxa-client-other-error"),
246        }
247    }
248}