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 {}