Keats / jsonwebtoken

JWT lib in rust
MIT License
1.69k stars 271 forks source link

implement serde Serialize and Deserialize on Error #192

Closed gbaranski closed 3 years ago

gbaranski commented 3 years ago

hi, what do you think about that?

Keats commented 3 years ago

I can see Serialize if you want to log it to json logs but Deserialize?

gbaranski commented 3 years ago

I can see Serialize if you want to log it to json logs but Deserialize?

yes, deserialize the error returned by the server to allow making some specific action for each token error variant.

gbaranski commented 3 years ago

nevermind, I'll just implement my own custom error with Serialize/Deserialize trait implemented.

#[derive(Debug, thiserror::Error, Serialize, Deserialize)]
#[serde(tag = "kind", content = "message")]
pub enum Error {
    /// When a token doesn't have a valid JWT shape
    InvalidToken,

    /// When the signature doesn't match
    InvalidSignature,

    /// When the secret given is not a valid ECDSA key
    InvalidEcdsaKey,

    /// When the secret given is not a valid RSA key
    InvalidRsaKey,

    /// When the algorithm from string doesn't match the one passed to `from_str`
    InvalidAlgorithmName,

    /// When a key is provided with an invalid format
    InvalidKeyFormat,

    // Validation errors
    /// When a token’s `exp` claim indicates that it has expired
    ExpiredSignature,

    /// When a token’s `iss` claim does not match the expected issuer
    InvalidIssuer,

    /// When a token’s `aud` claim does not match one of the expected audience values
    InvalidAudience,

    /// When a token’s `aud` claim does not match one of the expected audience values
    InvalidSubject,
    /// When a token’s nbf claim represents a time in the future
    ImmatureSignature,
    /// When the algorithm in the header doesn't match the one passed to `decode` or the encoding/decoding key
    /// used doesn't match the alg requested
    InvalidAlgorithm,

    // 3rd party errors
    /// An error happened when decoding some base64 text
    Base64(String),
    /// An error happened while serializing/deserializing JSON
    Json(String),
    /// Some of the text was invalid UTF-8
    Utf8(String),
    /// Something unspecified went wrong with crypto
    Crypto(String),

    Other,
}

impl From<jsonwebtoken::errors::ErrorKind> for Error {
    fn from(val: jsonwebtoken::errors::ErrorKind) -> Self {
        use jsonwebtoken::errors::ErrorKind as JWTErrorKind;

        match val {
            JWTErrorKind::InvalidToken => Self::InvalidToken,
            JWTErrorKind::InvalidSignature => Self::InvalidSignature,
            JWTErrorKind::InvalidEcdsaKey => Self::InvalidEcdsaKey,
            JWTErrorKind::InvalidRsaKey => Self::InvalidRsaKey,
            JWTErrorKind::InvalidAlgorithmName => Self::InvalidAlgorithmName,
            JWTErrorKind::InvalidKeyFormat => Self::InvalidKeyFormat,
            JWTErrorKind::ExpiredSignature => Self::ExpiredSignature,
            JWTErrorKind::InvalidIssuer => Self::InvalidIssuer,
            JWTErrorKind::InvalidAudience => Self::InvalidAudience,
            JWTErrorKind::InvalidSubject => Self::InvalidSubject,
            JWTErrorKind::ImmatureSignature => Self::ImmatureSignature,
            JWTErrorKind::InvalidAlgorithm => Self::InvalidAlgorithm,
            JWTErrorKind::Base64(err) => Self::Base64(err.to_string()),
            JWTErrorKind::Json(err) => Self::Json(err.to_string()),
            JWTErrorKind::Utf8(err) => Self::Utf8(err.to_string()),
            JWTErrorKind::Crypto(err) => Self::Crypto(err.to_string()),
            JWTErrorKind::__Nonexhaustive => unreachable!(),
        }
    }
}

impl From<jsonwebtoken::errors::Error> for Error {
    fn from(val: jsonwebtoken::errors::Error) -> Self {
        val.into_kind().into()
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match *self {
            Self::InvalidToken
            | Self::InvalidSignature
            | Self::InvalidEcdsaKey
            | Self::InvalidRsaKey
            | Self::ExpiredSignature
            | Self::InvalidIssuer
            | Self::InvalidAudience
            | Self::InvalidSubject
            | Self::ImmatureSignature
            | Self::InvalidAlgorithm
            | Self::InvalidKeyFormat
            | Self::InvalidAlgorithmName => write!(f, "{:?}", self),
            Self::Json(ref err) => write!(f, "JSON error: {}", err),
            Self::Utf8(ref err) => write!(f, "UTF-8 error: {}", err),
            Self::Crypto(ref err) => write!(f, "Crypto error: {}", err),
            Self::Base64(ref err) => write!(f, "Base64 error: {}", err),
            Self::Other => write!(f, "Unknown error"),
        }
    }
}

#[cfg(feature = "actix")]
impl Error {
    fn status_code(&self) -> actix_web::http::StatusCode {
        use actix_web::http::StatusCode;
        use Error::*;

        match self {
            InvalidToken | InvalidSignature | ExpiredSignature | InvalidSubject | InvalidIssuer
            | InvalidAudience | ImmatureSignature | InvalidAlgorithm | Base64(_) | Json(_)
            | Utf8(_) | Crypto(_) | Other => StatusCode::UNAUTHORIZED,

            InvalidKeyFormat | InvalidEcdsaKey | InvalidRsaKey | InvalidAlgorithmName => {
                StatusCode::INTERNAL_SERVER_ERROR
            }
        }
    }
}