use std::fmt;
#[derive(Debug, Clone)]
pub enum JwtError {
SecretMissing,
Decode(JwtDecodeError),
TokenRequired,
Claims(JwtClaimsError),
}
#[derive(Debug, Clone)]
pub enum JwtDecodeError {
EmptyAuthHeader,
UnexpectedParts(usize),
KeyError(String),
BadAlgorithm(String),
BadCrypto,
UnsupportedTokenType,
}
#[derive(Debug, Clone)]
pub enum JwtClaimsError {
Expired,
NotYetValid,
IssuedAtFuture,
NotInAudience,
ParsingFailed,
}
impl fmt::Display for JwtError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JwtError::SecretMissing => write!(f, "Server lacks JWT secret"),
JwtError::Decode(e) => write!(f, "{e}"),
JwtError::TokenRequired => write!(f, "Anonymous access is disabled"),
JwtError::Claims(e) => write!(f, "{e}"),
}
}
}
impl fmt::Display for JwtDecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JwtDecodeError::EmptyAuthHeader => {
write!(f, "Empty JWT is sent in Authorization header")
}
JwtDecodeError::UnexpectedParts(n) => {
write!(f, "Expected 3 parts in JWT; got {n}")
}
JwtDecodeError::KeyError(_) => {
write!(f, "No suitable key or wrong key type")
}
JwtDecodeError::BadAlgorithm(_) => {
write!(f, "Wrong or unsupported encoding algorithm")
}
JwtDecodeError::BadCrypto => {
write!(f, "JWT cryptographic operation failed")
}
JwtDecodeError::UnsupportedTokenType => {
write!(f, "Unsupported token type")
}
}
}
}
impl fmt::Display for JwtClaimsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JwtClaimsError::Expired => write!(f, "JWT expired"),
JwtClaimsError::NotYetValid => write!(f, "JWT not yet valid"),
JwtClaimsError::IssuedAtFuture => write!(f, "JWT issued at future"),
JwtClaimsError::NotInAudience => write!(f, "JWT not in audience"),
JwtClaimsError::ParsingFailed => write!(f, "Parsing claims failed"),
}
}
}
impl std::error::Error for JwtError {}
impl std::error::Error for JwtDecodeError {}
impl std::error::Error for JwtClaimsError {}
impl From<JwtDecodeError> for JwtError {
fn from(e: JwtDecodeError) -> Self {
JwtError::Decode(e)
}
}
impl From<JwtClaimsError> for JwtError {
fn from(e: JwtClaimsError) -> Self {
JwtError::Claims(e)
}
}
impl JwtError {
pub fn code(&self) -> &'static str {
match self {
JwtError::SecretMissing => "DBRST300",
JwtError::Decode(_) => "DBRST301",
JwtError::TokenRequired => "DBRST302",
JwtError::Claims(_) => "DBRST303",
}
}
pub fn status(&self) -> http::StatusCode {
match self {
JwtError::SecretMissing => http::StatusCode::INTERNAL_SERVER_ERROR,
JwtError::Decode(_) => http::StatusCode::UNAUTHORIZED,
JwtError::TokenRequired => http::StatusCode::UNAUTHORIZED,
JwtError::Claims(_) => http::StatusCode::UNAUTHORIZED,
}
}
pub fn details(&self) -> Option<String> {
match self {
JwtError::Decode(JwtDecodeError::KeyError(d)) => Some(d.clone()),
JwtError::Decode(JwtDecodeError::BadAlgorithm(d)) => Some(d.clone()),
_ => None,
}
}
pub fn www_authenticate(&self) -> Option<String> {
match self {
JwtError::TokenRequired => Some("Bearer".to_string()),
JwtError::Decode(e) => {
let msg = e.to_string();
Some(format!(
"Bearer error=\"invalid_token\", error_description=\"{msg}\""
))
}
JwtError::Claims(e) => {
let msg = e.to_string();
Some(format!(
"Bearer error=\"invalid_token\", error_description=\"{msg}\""
))
}
JwtError::SecretMissing => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_codes() {
assert_eq!(JwtError::SecretMissing.code(), "DBRST300");
assert_eq!(
JwtError::Decode(JwtDecodeError::EmptyAuthHeader).code(),
"DBRST301"
);
assert_eq!(JwtError::TokenRequired.code(), "DBRST302");
assert_eq!(JwtError::Claims(JwtClaimsError::Expired).code(), "DBRST303");
}
#[test]
fn test_error_status() {
assert_eq!(
JwtError::SecretMissing.status(),
http::StatusCode::INTERNAL_SERVER_ERROR
);
assert_eq!(
JwtError::Decode(JwtDecodeError::BadCrypto).status(),
http::StatusCode::UNAUTHORIZED
);
assert_eq!(
JwtError::TokenRequired.status(),
http::StatusCode::UNAUTHORIZED
);
assert_eq!(
JwtError::Claims(JwtClaimsError::NotInAudience).status(),
http::StatusCode::UNAUTHORIZED
);
}
#[test]
fn test_www_authenticate_headers() {
let hdr = JwtError::TokenRequired.www_authenticate().unwrap();
assert_eq!(hdr, "Bearer");
let hdr = JwtError::Decode(JwtDecodeError::BadCrypto)
.www_authenticate()
.unwrap();
assert!(hdr.contains("invalid_token"));
assert!(hdr.contains("cryptographic"));
let hdr = JwtError::Claims(JwtClaimsError::Expired)
.www_authenticate()
.unwrap();
assert!(hdr.contains("expired"));
assert!(JwtError::SecretMissing.www_authenticate().is_none());
}
#[test]
fn test_display_messages() {
assert_eq!(
JwtError::SecretMissing.to_string(),
"Server lacks JWT secret"
);
assert_eq!(
JwtError::TokenRequired.to_string(),
"Anonymous access is disabled"
);
assert_eq!(
JwtDecodeError::UnexpectedParts(2).to_string(),
"Expected 3 parts in JWT; got 2"
);
assert_eq!(JwtClaimsError::Expired.to_string(), "JWT expired");
}
#[test]
fn test_details() {
let err = JwtError::Decode(JwtDecodeError::KeyError(
"None of the keys was able to decode the JWT".to_string(),
));
assert!(err.details().unwrap().contains("keys"));
assert!(
JwtError::Claims(JwtClaimsError::Expired)
.details()
.is_none()
);
}
}