mas_oidc_client/
error.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2022-2024 Kévin Commaille.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7//! The error types used in this crate.
8
9use async_trait::async_trait;
10use mas_jose::{
11    claims::ClaimError,
12    jwa::InvalidAlgorithm,
13    jwt::{JwtDecodeError, JwtSignatureError, NoKeyWorked},
14};
15use oauth2_types::{oidc::ProviderMetadataVerificationError, pkce::CodeChallengeError};
16use serde::Deserialize;
17use thiserror::Error;
18
19/// All possible errors when using this crate.
20#[derive(Debug, Error)]
21#[error(transparent)]
22pub enum Error {
23    /// An error occurred fetching provider metadata.
24    Discovery(#[from] DiscoveryError),
25
26    /// An error occurred fetching the provider JWKS.
27    Jwks(#[from] JwksError),
28
29    /// An error occurred building the authorization URL.
30    Authorization(#[from] AuthorizationError),
31
32    /// An error occurred exchanging an authorization code for an access token.
33    TokenAuthorizationCode(#[from] TokenAuthorizationCodeError),
34
35    /// An error occurred requesting an access token with client credentials.
36    TokenClientCredentials(#[from] TokenRequestError),
37
38    /// An error occurred refreshing an access token.
39    TokenRefresh(#[from] TokenRefreshError),
40
41    /// An error occurred requesting user info.
42    UserInfo(#[from] UserInfoError),
43}
44
45/// All possible errors when fetching provider metadata.
46#[derive(Debug, Error)]
47#[error("Fetching provider metadata failed")]
48pub enum DiscoveryError {
49    /// An error occurred building the request's URL.
50    IntoUrl(#[from] url::ParseError),
51
52    /// The server returned an HTTP error status code.
53    Http(#[from] reqwest::Error),
54
55    /// An error occurred validating the metadata.
56    Validation(#[from] ProviderMetadataVerificationError),
57
58    /// The provider doesn't have an issuer set, which is required if discovery
59    /// is enabled.
60    #[error("Provider doesn't have an issuer set")]
61    MissingIssuer,
62
63    /// Discovery is disabled for this provider.
64    #[error("Discovery is disabled for this provider")]
65    Disabled,
66}
67
68/// All possible errors when authorizing the client.
69#[derive(Debug, Error)]
70#[error("Building the authorization URL failed")]
71pub enum AuthorizationError {
72    /// An error occurred constructing the PKCE code challenge.
73    Pkce(#[from] CodeChallengeError),
74
75    /// An error occurred serializing the request.
76    UrlEncoded(#[from] serde_urlencoded::ser::Error),
77}
78
79/// All possible errors when requesting an access token.
80#[derive(Debug, Error)]
81#[error("Request to the token endpoint failed")]
82pub enum TokenRequestError {
83    /// The HTTP client returned an error.
84    Http(#[from] reqwest::Error),
85
86    /// The server returned an error
87    OAuth2(#[from] OAuth2Error),
88
89    /// Error while injecting the client credentials into the request.
90    Credentials(#[from] CredentialsError),
91}
92
93/// All possible errors when exchanging a code for an access token.
94#[derive(Debug, Error)]
95pub enum TokenAuthorizationCodeError {
96    /// An error occurred requesting the access token.
97    #[error(transparent)]
98    Token(#[from] TokenRequestError),
99
100    /// An error occurred validating the ID Token.
101    #[error("Verifying the 'id_token' returned by the provider failed")]
102    IdToken(#[from] IdTokenError),
103}
104
105/// All possible errors when refreshing an access token.
106#[derive(Debug, Error)]
107pub enum TokenRefreshError {
108    /// An error occurred requesting the access token.
109    #[error(transparent)]
110    Token(#[from] TokenRequestError),
111
112    /// An error occurred validating the ID Token.
113    #[error("Verifying the 'id_token' returned by the provider failed")]
114    IdToken(#[from] IdTokenError),
115}
116
117/// All possible errors when requesting user info.
118#[derive(Debug, Error)]
119pub enum UserInfoError {
120    /// The content-type header is missing from the response.
121    #[error("missing response content-type")]
122    MissingResponseContentType,
123
124    /// The content-type is not valid.
125    #[error("invalid response content-type")]
126    InvalidResponseContentTypeValue,
127
128    /// The content-type is not the one that was expected.
129    #[error("unexpected response content-type {got:?}, expected {expected:?}")]
130    UnexpectedResponseContentType {
131        /// The expected content-type.
132        expected: String,
133        /// The returned content-type.
134        got: String,
135    },
136
137    /// An error occurred verifying the Id Token.
138    #[error("Verifying the 'id_token' returned by the provider failed")]
139    IdToken(#[from] IdTokenError),
140
141    /// An error occurred sending the request.
142    #[error(transparent)]
143    Http(#[from] reqwest::Error),
144
145    /// The server returned an error
146    #[error(transparent)]
147    OAuth2(#[from] OAuth2Error),
148}
149
150/// All possible errors when requesting a JWKS.
151#[derive(Debug, Error)]
152#[error("Failed to fetch JWKS")]
153pub enum JwksError {
154    /// An error occurred sending the request.
155    Http(#[from] reqwest::Error),
156}
157
158/// All possible errors when verifying a JWT.
159#[derive(Debug, Error)]
160pub enum JwtVerificationError {
161    /// An error occured decoding the JWT.
162    #[error(transparent)]
163    JwtDecode(#[from] JwtDecodeError),
164
165    /// No key worked for verifying the JWT's signature.
166    #[error(transparent)]
167    JwtSignature(#[from] NoKeyWorked),
168
169    /// An error occurred extracting a claim.
170    #[error(transparent)]
171    Claim(#[from] ClaimError),
172
173    /// The algorithm used for signing the JWT is not the one that was
174    /// requested.
175    #[error("wrong signature alg")]
176    WrongSignatureAlg,
177}
178
179/// All possible errors when verifying an ID token.
180#[derive(Debug, Error)]
181pub enum IdTokenError {
182    /// No ID Token was found in the response although one was expected.
183    #[error("ID token is missing")]
184    MissingIdToken,
185
186    /// The ID Token from the latest Authorization was not provided although
187    /// this request expects to be verified against one.
188    #[error("Authorization ID token is missing")]
189    MissingAuthIdToken,
190
191    #[error(transparent)]
192    /// An error occurred validating the ID Token's signature and basic claims.
193    Jwt(#[from] JwtVerificationError),
194
195    #[error(transparent)]
196    /// An error occurred extracting a claim.
197    Claim(#[from] ClaimError),
198
199    /// The subject identifier returned by the issuer is not the same as the one
200    /// we got before.
201    #[error("wrong subject identifier")]
202    WrongSubjectIdentifier,
203
204    /// The authentication time returned by the issuer is not the same as the
205    /// one we got before.
206    #[error("wrong authentication time")]
207    WrongAuthTime,
208}
209
210/// All errors that can occur when adding client credentials to the request.
211#[derive(Debug, Error)]
212pub enum CredentialsError {
213    /// Trying to use an unsupported authentication method.
214    #[error("unsupported authentication method")]
215    UnsupportedMethod,
216
217    /// When authenticationg with `private_key_jwt`, no private key was found
218    /// for the given algorithm.
219    #[error("no private key was found for the given algorithm")]
220    NoPrivateKeyFound,
221
222    /// The signing algorithm is invalid for this authentication method.
223    #[error("invalid algorithm: {0}")]
224    InvalidSigningAlgorithm(#[from] InvalidAlgorithm),
225
226    /// An error occurred when building the claims of the JWT.
227    #[error(transparent)]
228    JwtClaims(#[from] ClaimError),
229
230    /// The key found cannot be used with the algorithm.
231    #[error("Wrong algorithm for key")]
232    JwtWrongAlgorithm,
233
234    /// An error occurred when signing the JWT.
235    #[error(transparent)]
236    JwtSignature(#[from] JwtSignatureError),
237}
238
239#[derive(Debug, Deserialize)]
240struct OAuth2ErrorResponse {
241    error: String,
242    error_description: Option<String>,
243    error_uri: Option<String>,
244}
245
246impl std::fmt::Display for OAuth2ErrorResponse {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        write!(f, "{:?}", self.error)?;
249
250        if let Some(error_uri) = &self.error_uri {
251            write!(f, " (See {error_uri})")?;
252        }
253
254        if let Some(error_description) = &self.error_description {
255            write!(f, ": {error_description}")?;
256        }
257
258        Ok(())
259    }
260}
261
262/// An error returned by the OAuth 2.0 provider
263#[derive(Debug, Error)]
264pub struct OAuth2Error {
265    error: Option<OAuth2ErrorResponse>,
266
267    #[source]
268    inner: reqwest::Error,
269}
270
271impl std::fmt::Display for OAuth2Error {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        if let Some(error) = &self.error {
274            write!(
275                f,
276                "Request to the provider failed with the following error: {error}"
277            )
278        } else {
279            write!(f, "Request to the provider failed")
280        }
281    }
282}
283
284impl From<reqwest::Error> for OAuth2Error {
285    fn from(inner: reqwest::Error) -> Self {
286        Self { error: None, inner }
287    }
288}
289
290/// An extension trait to deal with error responses from the OAuth 2.0 provider
291#[async_trait]
292pub(crate) trait ResponseExt {
293    async fn error_from_oauth2_error_response(self) -> Result<Self, OAuth2Error>
294    where
295        Self: Sized;
296}
297
298#[async_trait]
299impl ResponseExt for reqwest::Response {
300    async fn error_from_oauth2_error_response(self) -> Result<Self, OAuth2Error> {
301        let Err(inner) = self.error_for_status_ref() else {
302            return Ok(self);
303        };
304
305        let error: OAuth2ErrorResponse = self.json().await?;
306
307        Err(OAuth2Error {
308            error: Some(error),
309            inner,
310        })
311    }
312}