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