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