matrix_sdk/authentication/oauth/
error.rs

1// Copyright 2025 Kévin Commaille
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Error types used in the [`OAuth`](super::OAuth) API.
16
17use matrix_sdk_base::deserialized_responses::PrivOwnedStr;
18use oauth2::ErrorResponseType;
19pub use oauth2::{
20    basic::{
21        BasicErrorResponse, BasicErrorResponseType, BasicRequestTokenError,
22        BasicRevocationErrorResponse,
23    },
24    ConfigurationError, HttpClientError, RequestTokenError, RevocationErrorResponseType,
25    StandardErrorResponse,
26};
27use ruma::{
28    api::client::discovery::get_authorization_server_metadata::msc2965::AuthorizationServerMetadataUrlError,
29    serde::{PartialEqAsRefStr, StringEnum},
30};
31
32#[cfg(feature = "e2e-encryption")]
33pub use super::cross_process::CrossProcessRefreshLockError;
34#[cfg(not(target_arch = "wasm32"))]
35pub use super::registration_store::OAuthRegistrationStoreError;
36
37/// An error when interacting with the OAuth 2.0 authorization server.
38pub type OAuthRequestError<T> =
39    RequestTokenError<HttpClientError<reqwest::Error>, StandardErrorResponse<T>>;
40
41/// An error when trying to parse the query of a redirect URI.
42#[derive(Debug, Clone, thiserror::Error)]
43#[non_exhaustive]
44pub enum RedirectUriQueryParseError {
45    /// There is no query part in the URI.
46    #[error("No query in URI")]
47    MissingQuery,
48
49    /// Deserialization failed.
50    #[error("Query is not using one of the defined formats")]
51    UnknownFormat,
52}
53
54/// All errors that can occur when using the OAuth 2.0 API.
55#[derive(Debug, thiserror::Error)]
56#[non_exhaustive]
57pub enum OAuthError {
58    /// An error occurred when discovering the authorization server's issuer.
59    #[error("authorization server discovery failed: {0}")]
60    Discovery(#[from] OAuthDiscoveryError),
61
62    /// An error occurred when registering the client with the authorization
63    /// server.
64    #[error("client registration failed: {0}")]
65    ClientRegistration(#[from] OAuthClientRegistrationError),
66
67    /// The client has not registered while the operation requires it.
68    #[error("client not registered")]
69    NotRegistered,
70
71    /// The client is not authenticated while the request requires it.
72    #[error("client not authenticated")]
73    NotAuthenticated,
74
75    /// An error occurred using the OAuth 2.0 authorization code grant.
76    #[error("authorization code grant failed: {0}")]
77    AuthorizationCode(#[from] OAuthAuthorizationCodeError),
78
79    /// An error occurred interacting with the OAuth 2.0 authorization server
80    /// while refreshing the access token.
81    #[error("failed to refresh token: {0}")]
82    RefreshToken(OAuthRequestError<BasicErrorResponseType>),
83
84    /// An error occurred revoking an OAuth 2.0 access token.
85    #[error("failed to log out: {0}")]
86    Logout(#[from] OAuthTokenRevocationError),
87
88    /// An error occurred caused by the cross-process locks.
89    #[cfg(feature = "e2e-encryption")]
90    #[error(transparent)]
91    LockError(#[from] CrossProcessRefreshLockError),
92
93    /// The user logged into a session that is different than the one the client
94    /// is already using.
95    ///
96    /// This only happens if the session was already restored, and the user logs
97    /// into a new session that is different than the old one.
98    #[error("new logged-in session is different than current client session")]
99    SessionMismatch,
100}
101
102/// All errors that can occur when discovering the OAuth 2.0 server metadata.
103#[derive(Debug, thiserror::Error)]
104#[non_exhaustive]
105pub enum OAuthDiscoveryError {
106    /// OAuth 2.0 is not supported by the homeserver.
107    #[error("OAuth 2.0 is not supported by the homeserver")]
108    NotSupported,
109
110    /// An error occurred when making a request to the homeserver.
111    #[error(transparent)]
112    Http(#[from] crate::HttpError),
113
114    /// The server metadata is invalid.
115    #[error(transparent)]
116    Json(#[from] serde_json::Error),
117
118    /// The server metadata URLs are insecure.
119    #[error(transparent)]
120    Validation(#[from] AuthorizationServerMetadataUrlError),
121
122    /// An error occurred when building the OpenID Connect provider
123    /// configuration URL.
124    #[error(transparent)]
125    Url(#[from] url::ParseError),
126
127    /// An error occurred when making a request to the OpenID Connect provider.
128    #[error(transparent)]
129    Oidc(#[from] OAuthRequestError<BasicErrorResponseType>),
130}
131
132impl OAuthDiscoveryError {
133    /// Whether this error occurred because OAuth 2.0 is not supported by the
134    /// homeserver.
135    pub fn is_not_supported(&self) -> bool {
136        matches!(self, Self::NotSupported)
137    }
138}
139
140/// All errors that can occur when using the Authorization Code grant with the
141/// OAuth 2.0 API.
142#[derive(Debug, thiserror::Error)]
143#[non_exhaustive]
144pub enum OAuthAuthorizationCodeError {
145    /// The query of the redirect URI doesn't have the expected format.
146    #[error(transparent)]
147    RedirectUri(#[from] RedirectUriQueryParseError),
148
149    /// The user cancelled the authorization in the web UI.
150    #[error("authorization cancelled by the user")]
151    Cancelled,
152
153    /// An error occurred when getting the authorization from the user in the
154    /// web UI.
155    #[error("authorization failed: {0}")]
156    Authorization(StandardErrorResponse<AuthorizationCodeErrorResponseType>),
157
158    /// The state used to complete authorization doesn't match any of the
159    /// ongoing authorizations.
160    #[error("authorization state value is unexpected")]
161    InvalidState,
162
163    /// An error occurred interacting with the OAuth 2.0 authorization server
164    /// while exchanging the authorization code for an access token.
165    #[error("failed to request token: {0}")]
166    RequestToken(OAuthRequestError<BasicErrorResponseType>),
167}
168
169impl From<StandardErrorResponse<AuthorizationCodeErrorResponseType>>
170    for OAuthAuthorizationCodeError
171{
172    fn from(value: StandardErrorResponse<AuthorizationCodeErrorResponseType>) -> Self {
173        if *value.error() == AuthorizationCodeErrorResponseType::AccessDenied {
174            // The user cancelled the login in the web view.
175            Self::Cancelled
176        } else {
177            Self::Authorization(value)
178        }
179    }
180}
181
182/// Error response returned by server after requesting an authorization code.
183///
184/// The fields in this structure are defined in [Section 4.1.2.1 of RFC 6749].
185///
186/// [Section 4.1.2.1 of RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
187#[derive(Clone, StringEnum, PartialEqAsRefStr, Eq)]
188#[ruma_enum(rename_all = "snake_case")]
189#[non_exhaustive]
190pub enum AuthorizationCodeErrorResponseType {
191    /// The request is invalid.
192    ///
193    /// It is missing a required parameter, includes an invalid parameter value,
194    /// includes a parameter more than once, or is otherwise malformed.
195    InvalidRequest,
196
197    /// The client is not authorized to request an authorization code using this
198    /// method.
199    UnauthorizedClient,
200
201    /// The resource owner or authorization server denied the request.
202    AccessDenied,
203
204    /// The authorization server does not support obtaining an authorization
205    /// code using this method.
206    UnsupportedResponseType,
207
208    /// The requested scope is invalid, unknown, or malformed.
209    InvalidScope,
210
211    /// The authorization server encountered an unexpected error.
212    ServerError,
213
214    /// The authorization server is currently unable to handle the request due
215    /// to a temporary overloading or maintenance of the server.
216    TemporarilyUnavailable,
217
218    #[doc(hidden)]
219    _Custom(PrivOwnedStr),
220}
221
222impl ErrorResponseType for AuthorizationCodeErrorResponseType {}
223
224/// All errors that can occur when revoking an OAuth 2.0 token.
225#[derive(Debug, thiserror::Error)]
226#[non_exhaustive]
227pub enum OAuthTokenRevocationError {
228    /// The revocation endpoint URL is insecure.
229    #[error(transparent)]
230    Url(ConfigurationError),
231
232    /// An error occurred interacting with the OAuth 2.0 authorization server
233    /// while revoking the token.
234    #[error("failed to revoke token: {0}")]
235    Revoke(OAuthRequestError<RevocationErrorResponseType>),
236}
237
238/// All errors that can occur when registering a client with an OAuth 2.0
239/// authorization server.
240#[derive(Debug, thiserror::Error)]
241#[non_exhaustive]
242pub enum OAuthClientRegistrationError {
243    /// The authorization server doesn't support dynamic client registration.
244    ///
245    /// The server probably offers another way to register clients.
246    #[error("dynamic client registration is not supported")]
247    NotSupported,
248
249    /// Serialization of the client metadata failed.
250    #[error("failed to serialize client metadata: {0}")]
251    IntoJson(serde_json::Error),
252
253    /// An error occurred when making a request to the OAuth 2.0 authorization
254    /// server.
255    #[error(transparent)]
256    OAuth(#[from] OAuthRequestError<ClientRegistrationErrorResponseType>),
257
258    /// Deserialization of the registration response failed.
259    #[error("failed to deserialize registration response: {0}")]
260    FromJson(serde_json::Error),
261
262    /// Failed to access or store the registration in the store.
263    #[cfg(not(target_arch = "wasm32"))]
264    #[error("failed to use registration store: {0}")]
265    Store(#[from] OAuthRegistrationStoreError),
266}
267
268/// Error response returned by server after requesting an authorization code.
269///
270/// The variant of this enum are defined in [Section 3.2.2 of RFC 7591].
271///
272/// [Section 3.2.2 of RFC 7591]: https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.2
273#[derive(Clone, StringEnum, PartialEqAsRefStr, Eq)]
274#[ruma_enum(rename_all = "snake_case")]
275#[non_exhaustive]
276pub enum ClientRegistrationErrorResponseType {
277    /// The value of one or more redirection URIs is invalid.
278    InvalidRedirectUri,
279
280    /// The value of one of the client metadata fields is invalid and the server
281    /// has rejected this request.
282    InvalidClientMetadata,
283
284    /// The software statement presented is invalid.
285    InvalidSoftwareStatement,
286
287    /// The software statement presented is not approved for use by this
288    /// authorization server.
289    UnapprovedSoftwareStatement,
290
291    #[doc(hidden)]
292    _Custom(PrivOwnedStr),
293}
294
295impl ErrorResponseType for ClientRegistrationErrorResponseType {}