jwt_compact/
error.rs

1//! Error handling.
2
3use alloc::string::String;
4#[cfg(feature = "ciborium")]
5use core::convert::Infallible;
6use core::fmt;
7
8#[cfg(feature = "ciborium")]
9pub(crate) type CborDeError<E = anyhow::Error> = ciborium::de::Error<E>;
10#[cfg(feature = "ciborium")]
11pub(crate) type CborSerError<E = Infallible> = ciborium::ser::Error<E>;
12
13/// Errors that may occur during token parsing.
14#[derive(Debug)]
15#[non_exhaustive]
16pub enum ParseError {
17    /// Token has invalid structure.
18    ///
19    /// Valid tokens must consist of 3 base64url-encoded parts (header, claims, and signature)
20    /// separated by periods.
21    InvalidTokenStructure,
22    /// Cannot decode base64.
23    InvalidBase64Encoding,
24    /// Token header cannot be parsed.
25    MalformedHeader(serde_json::Error),
26    /// [Content type][cty] mentioned in the token header is not supported.
27    ///
28    /// Supported content types are JSON (used by default) and CBOR (only if the `ciborium`
29    /// crate feature is enabled, which it is by default).
30    ///
31    /// [cty]: https://tools.ietf.org/html/rfc7515#section-4.1.10
32    UnsupportedContentType(String),
33}
34
35impl fmt::Display for ParseError {
36    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            Self::InvalidTokenStructure => formatter.write_str("invalid token structure"),
39            Self::InvalidBase64Encoding => write!(formatter, "invalid base64 decoding"),
40            Self::MalformedHeader(err) => write!(formatter, "malformed token header: {err}"),
41            Self::UnsupportedContentType(ty) => {
42                write!(formatter, "unsupported content type: {ty}")
43            }
44        }
45    }
46}
47
48impl core::error::Error for ParseError {
49    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
50        match self {
51            Self::MalformedHeader(err) => Some(err),
52            _ => None,
53        }
54    }
55}
56
57/// Errors that can occur during token validation.
58#[derive(Debug)]
59#[non_exhaustive]
60pub enum ValidationError {
61    /// Algorithm mentioned in the token header differs from invoked one.
62    AlgorithmMismatch {
63        /// Expected algorithm name.
64        expected: String,
65        /// Actual algorithm in the token.
66        actual: String,
67    },
68    /// Token signature has invalid byte length.
69    InvalidSignatureLen {
70        /// Expected signature length.
71        expected: usize,
72        /// Actual signature length.
73        actual: usize,
74    },
75    /// Token signature is malformed.
76    MalformedSignature(anyhow::Error),
77    /// Token signature has failed verification.
78    InvalidSignature,
79    /// Token claims cannot be deserialized from JSON.
80    MalformedClaims(serde_json::Error),
81    /// Token claims cannot be deserialized from CBOR.
82    #[cfg(feature = "ciborium")]
83    #[cfg_attr(docsrs, doc(cfg(feature = "ciborium")))]
84    MalformedCborClaims(CborDeError),
85    /// Claim requested during validation is not present in the token.
86    NoClaim(Claim),
87    /// Token has expired.
88    Expired,
89    /// Token is not yet valid as per `nbf` claim.
90    NotMature,
91}
92
93/// Identifier of a claim in `Claims`.
94#[derive(Debug, Clone, PartialEq, Eq)]
95#[non_exhaustive]
96pub enum Claim {
97    /// `exp` claim (expiration time).
98    Expiration,
99    /// `nbf` claim (valid not before).
100    NotBefore,
101}
102
103impl fmt::Display for Claim {
104    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105        formatter.write_str(match self {
106            Self::Expiration => "exp",
107            Self::NotBefore => "nbf",
108        })
109    }
110}
111
112impl fmt::Display for ValidationError {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            Self::AlgorithmMismatch { expected, actual } => write!(
116                formatter,
117                "token algorithm ({actual}) differs from expected ({expected})"
118            ),
119            Self::InvalidSignatureLen { expected, actual } => write!(
120                formatter,
121                "invalid signature length: expected {expected} bytes, got {actual} bytes"
122            ),
123            Self::MalformedSignature(err) => write!(formatter, "malformed token signature: {err}"),
124            Self::InvalidSignature => formatter.write_str("signature has failed verification"),
125            Self::MalformedClaims(err) => write!(formatter, "cannot deserialize claims: {err}"),
126            #[cfg(feature = "ciborium")]
127            Self::MalformedCborClaims(err) => write!(formatter, "cannot deserialize claims: {err}"),
128            Self::NoClaim(claim) => write!(
129                formatter,
130                "claim `{claim}` requested during validation is not present in the token"
131            ),
132            Self::Expired => formatter.write_str("token has expired"),
133            Self::NotMature => formatter.write_str("token is not yet ready"),
134        }
135    }
136}
137
138impl core::error::Error for ValidationError {
139    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
140        match self {
141            Self::MalformedSignature(err) => Some(err.as_ref()),
142            Self::MalformedClaims(err) => Some(err),
143            #[cfg(feature = "ciborium")]
144            Self::MalformedCborClaims(err) => Some(err),
145            _ => None,
146        }
147    }
148}
149
150/// Errors that can occur during token creation.
151#[derive(Debug)]
152#[non_exhaustive]
153pub enum CreationError {
154    /// Token header cannot be serialized.
155    Header(serde_json::Error),
156    /// Token claims cannot be serialized into JSON.
157    Claims(serde_json::Error),
158    /// Token claims cannot be serialized into CBOR.
159    #[cfg(feature = "ciborium")]
160    #[cfg_attr(docsrs, doc(cfg(feature = "ciborium")))]
161    CborClaims(CborSerError),
162}
163
164impl fmt::Display for CreationError {
165    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self {
167            Self::Header(err) => write!(formatter, "cannot serialize header: {err}"),
168            Self::Claims(err) => write!(formatter, "cannot serialize claims: {err}"),
169            #[cfg(feature = "ciborium")]
170            Self::CborClaims(err) => write!(formatter, "cannot serialize claims into CBOR: {err}"),
171        }
172    }
173}
174
175impl core::error::Error for CreationError {
176    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
177        match self {
178            Self::Header(err) | Self::Claims(err) => Some(err),
179            #[cfg(feature = "ciborium")]
180            Self::CborClaims(err) => Some(err),
181        }
182    }
183}