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 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")]
24    Network,
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")]
31    NoExistingAuthFlow,
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")]
40    WrongAuthFlow,
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")]
46    OriginMismatch,
47    /// A scoped key was missing in the server response when requesting the OLD_SYNC scope.
48    #[error("The sync scoped key was missing")]
49    SyncScopedKeyMissingInServerResponse,
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")]
54    Panic,
55    /// A catch-all for other unspecified errors.
56    #[error("other error: {0}")]
57    Other(String),
58}
59
60/// 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")]
65    BackoffError(u64),
66
67    #[error("Unknown OAuth State")]
68    UnknownOAuthState,
69
70    #[error("Multiple OAuth scopes requested")]
71    MultipleScopesRequested,
72
73    #[error("No cached token for scope {0}")]
74    NoCachedToken(String),
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    #[error("The sync scoped key was missing in the server response")]
116    SyncScopedKeyMissingInServerResponse,
117
118    #[error("Client: {0} is not allowed to request scope: {1}")]
119    ScopeNotAllowed(String, String),
120
121    #[error("Unsupported command: {0}")]
122    UnsupportedCommand(&'static str),
123
124    #[error("Missing URL parameter: {0}")]
125    MissingUrlParameter(&'static str),
126
127    #[error("Null pointer passed to FFI")]
128    NullPointer,
129
130    #[error("Invalid buffer length: {0}")]
131    InvalidBufferLength(i32),
132
133    #[error("Too many calls to auth introspection endpoint")]
134    AuthCircuitBreakerError,
135
136    #[error("Remote server error: '{code}' '{errno}' '{error}' '{message}' '{info}'")]
137    RemoteError {
138        code: u64,
139        errno: u64,
140        error: String,
141        message: String,
142        info: String,
143    },
144
145    // Basically reimplement error_chain's foreign_links. (Ugh, this sucks).
146    #[error("Crypto/NSS error: {0}")]
147    CryptoError(#[from] rc_crypto::Error),
148
149    #[error("http-ece encryption error: {0}")]
150    EceError(#[from] rc_crypto::ece::Error),
151
152    #[error("Hex decode error: {0}")]
153    HexDecodeError(#[from] hex::FromHexError),
154
155    #[error("Base64 decode error: {0}")]
156    Base64Decode(#[from] base64::DecodeError),
157
158    #[error("JSON error: {0}")]
159    JsonError(#[from] serde_json::Error),
160
161    #[error("JWCrypto error: {0}")]
162    JwCryptoError(#[from] jwcrypto::JwCryptoError),
163
164    #[error("UTF8 decode error: {0}")]
165    UTF8DecodeError(#[from] string::FromUtf8Error),
166
167    #[error("Network error: {0}")]
168    RequestError(#[from] viaduct::ViaductError),
169
170    #[error("Malformed URL: {sanitized_url} ({when})")]
171    MalformedUrl {
172        /// URL without any query or fragment parts.
173        /// This is safe to send in an error report because there's no auth information.
174        sanitized_url: String,
175        when: String,
176    },
177
178    #[error("Unexpected HTTP status: {0}")]
179    UnexpectedStatus(#[from] viaduct::UnexpectedStatus),
180
181    #[error("Sync15 error: {0}")]
182    SyncError(#[from] sync15::Error),
183
184    #[error("HAWK error: {0}")]
185    HawkError(#[from] hawk::Error),
186
187    #[error("Integer conversion error: {0}")]
188    IntegerConversionError(#[from] std::num::TryFromIntError),
189
190    #[error("Command not found by fxa")]
191    CommandNotFound,
192
193    #[error("Invalid Push Event")]
194    InvalidPushEvent,
195
196    #[error("Invalid state transition: {0}")]
197    InvalidStateTransition(String),
198
199    #[error("Internal error in the state machine: {0}")]
200    StateMachineLogicError(String),
201}
202
203// Define how our internal errors are handled and converted to external errors
204// See `support/error/README.md` for how this works, especially the warning about PII.
205impl GetErrorHandling for Error {
206    type ExternalError = FxaError;
207
208    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
209        match self {
210            Error::RemoteError { code: 401, .. }
211            | Error::NoRefreshToken
212            | Error::NoScopedKey(_)
213            | Error::NoCachedToken(_) => {
214                ErrorHandling::convert(FxaError::Authentication).log_warning()
215            }
216            Error::RequestError(_) => ErrorHandling::convert(FxaError::Network).log_warning(),
217            Error::SyncScopedKeyMissingInServerResponse => {
218                ErrorHandling::convert(FxaError::SyncScopedKeyMissingInServerResponse)
219                    .report_error("fxa-client-scoped-key-missing")
220            }
221            Error::UnknownOAuthState => {
222                ErrorHandling::convert(FxaError::NoExistingAuthFlow).log_warning()
223            }
224            Error::BackoffError(_) => ErrorHandling::convert(FxaError::Other(self.to_string()))
225                .report_error("fxa-client-backoff"),
226            Error::InvalidStateTransition(_) | Error::StateMachineLogicError(_) => {
227                ErrorHandling::convert(FxaError::Other(self.to_string()))
228                    .report_error("fxa-state-machine-error")
229            }
230            Error::OriginMismatch(_) => ErrorHandling::convert(FxaError::OriginMismatch),
231            _ => ErrorHandling::convert(FxaError::Other(self.to_string()))
232                .report_error("fxa-client-other-error"),
233        }
234    }
235}