matrix_sdk/authentication/oidc/
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 [`Oidc`](super::Oidc) API.
16
17pub use mas_oidc_client::error::*;
18use matrix_sdk_base::deserialized_responses::PrivOwnedStr;
19use oauth2::ErrorResponseType;
20pub use oauth2::{
21    basic::{
22        BasicErrorResponse, BasicErrorResponseType, BasicRequestTokenError,
23        BasicRevocationErrorResponse,
24    },
25    ConfigurationError, HttpClientError, RequestTokenError, RevocationErrorResponseType,
26    StandardErrorResponse,
27};
28use ruma::serde::{PartialEqAsRefStr, StringEnum};
29
30pub use super::cross_process::CrossProcessRefreshLockError;
31
32/// An error when trying to parse the query of a redirect URI.
33#[derive(Debug, Clone, thiserror::Error)]
34#[non_exhaustive]
35pub enum RedirectUriQueryParseError {
36    /// There is no query part in the URI.
37    #[error("No query in URI")]
38    MissingQuery,
39
40    /// Deserialization failed.
41    #[error("Query is not using one of the defined formats")]
42    UnknownFormat,
43}
44
45/// All errors that can occur when using the OpenID Connect API.
46#[derive(Debug, thiserror::Error)]
47#[non_exhaustive]
48pub enum OidcError {
49    /// An error occurred when interacting with the provider.
50    #[error(transparent)]
51    Oidc(Error),
52
53    /// An error occurred when discovering the authorization server's issuer.
54    #[error("authorization server discovery failed: {0}")]
55    Discovery(#[from] OauthDiscoveryError),
56
57    /// The OpenID Connect Provider doesn't support dynamic client registration.
58    ///
59    /// The provider probably offers another way to register clients.
60    #[error("no dynamic registration support")]
61    NoRegistrationSupport,
62
63    /// The client has not registered while the operation requires it.
64    #[error("client not registered")]
65    NotRegistered,
66
67    /// The device ID was not returned by the homeserver after login.
68    #[error("missing device ID in response")]
69    MissingDeviceId,
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(BasicRequestTokenError<HttpClientError<reqwest::Error>>),
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 parsing a URL.
89    #[error(transparent)]
90    Url(url::ParseError),
91
92    /// An error occurred caused by the cross-process locks.
93    #[error(transparent)]
94    LockError(#[from] CrossProcessRefreshLockError),
95
96    /// An unknown error occurred.
97    #[error("unknown error")]
98    UnknownError(#[source] Box<dyn std::error::Error + Send + Sync>),
99}
100
101impl<E> From<E> for OidcError
102where
103    E: Into<Error>,
104{
105    fn from(value: E) -> Self {
106        Self::Oidc(value.into())
107    }
108}
109
110/// All errors that can occur when discovering the OAuth 2.0 server metadata.
111#[derive(Debug, thiserror::Error)]
112#[non_exhaustive]
113pub enum OauthDiscoveryError {
114    /// OAuth 2.0 is not supported by the homeserver.
115    #[error("OAuth 2.0 is not supported by the homeserver")]
116    NotSupported,
117
118    /// An error occurred when making a request to the homeserver.
119    #[error(transparent)]
120    Http(#[from] crate::HttpError),
121
122    /// The server metadata is invalid.
123    #[error(transparent)]
124    Json(#[from] serde_json::Error),
125
126    /// An error occurred when making a request to the OpenID Connect provider.
127    #[error(transparent)]
128    Oidc(#[from] DiscoveryError),
129}
130
131impl OauthDiscoveryError {
132    /// Whether this error occurred because OAuth 2.0 is not supported by the
133    /// homeserver.
134    pub fn is_not_supported(&self) -> bool {
135        matches!(self, Self::NotSupported)
136    }
137}
138
139/// All errors that can occur when using the Authorization Code grant with the
140/// OAuth 2.0 API.
141#[derive(Debug, thiserror::Error)]
142#[non_exhaustive]
143pub enum OauthAuthorizationCodeError {
144    /// The query of the redirect URI doesn't have the expected format.
145    #[error(transparent)]
146    RedirectUri(#[from] RedirectUriQueryParseError),
147
148    /// The user cancelled the authorization in the web UI.
149    #[error("authorization cancelled by the user")]
150    Cancelled,
151
152    /// An error occurred when getting the authorization from the user in the
153    /// web UI.
154    #[error("authorization failed: {0}")]
155    Authorization(StandardErrorResponse<AuthorizationCodeErrorResponseType>),
156
157    /// The state used to complete authorization doesn't match any of the
158    /// ongoing authorizations.
159    #[error("authorization state value is unexpected")]
160    InvalidState,
161
162    /// An error occurred interacting with the OAuth 2.0 authorization server
163    /// while exchanging the authorization code for an access token.
164    #[error("failed to request token: {0}")]
165    RequestToken(BasicRequestTokenError<HttpClientError<reqwest::Error>>),
166}
167
168impl From<StandardErrorResponse<AuthorizationCodeErrorResponseType>>
169    for OauthAuthorizationCodeError
170{
171    fn from(value: StandardErrorResponse<AuthorizationCodeErrorResponseType>) -> Self {
172        if *value.error() == AuthorizationCodeErrorResponseType::AccessDenied {
173            // The user cancelled the login in the web view.
174            Self::Cancelled
175        } else {
176            Self::Authorization(value)
177        }
178    }
179}
180
181/// Error response returned by server after requesting an authorization code.
182///
183/// The fields in this structure are defined in [Section 4.1.2.1 of RFC 6749].
184///
185/// [Section 4.1.2.1 of RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
186#[derive(Clone, StringEnum, PartialEqAsRefStr, Eq)]
187#[ruma_enum(rename_all = "snake_case")]
188#[non_exhaustive]
189pub enum AuthorizationCodeErrorResponseType {
190    /// The request is invalid.
191    ///
192    /// It is missing a required parameter, includes an invalid parameter value,
193    /// includes a parameter more than once, or is otherwise malformed.
194    InvalidRequest,
195
196    /// The client is not authorized to request an authorization code using this
197    /// method.
198    UnauthorizedClient,
199
200    /// The resource owner or authorization server denied the request.
201    AccessDenied,
202
203    /// The authorization server does not support obtaining an authorization
204    /// code using this method.
205    UnsupportedResponseType,
206
207    /// The requested scope is invalid, unknown, or malformed.
208    InvalidScope,
209
210    /// The authorization server encountered an unexpected error.
211    ServerError,
212
213    /// The authorization server is currently unable to handle the request due
214    /// to a temporary overloading or maintenance of the server.
215    TemporarilyUnavailable,
216
217    #[doc(hidden)]
218    _Custom(PrivOwnedStr),
219}
220
221impl ErrorResponseType for AuthorizationCodeErrorResponseType {}
222
223/// All errors that can occur when revoking an OAuth 2.0 token.
224#[derive(Debug, thiserror::Error)]
225#[non_exhaustive]
226pub enum OauthTokenRevocationError {
227    /// Revocation is not supported by the OAuth 2.0 authorization server.
228    #[error("token revocation is not supported")]
229    NotSupported,
230
231    /// The revocation endpoint URL is insecure.
232    #[error(transparent)]
233    Url(ConfigurationError),
234
235    /// An error occurred interacting with the OAuth 2.0 authorization server
236    /// while revoking the token.
237    #[error("failed to revoke token: {0}")]
238    Revoke(RequestTokenError<HttpClientError<reqwest::Error>, BasicRevocationErrorResponse>),
239}