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