small_jwt 1.1.0

Simple and small JWT libary
Documentation
//! Decode function home.

use crate::{Error, Result, utils, Header};

/// Decodes token into <P>.
///
/// # Usage
/// ```
/// #[derive(Debug, serde::Deserialize)]
/// pub struct Claims {
///     pub user: String,
/// }
///
/// let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlcm5hbWUifQ.8N2JWyOsRDi3XCvJugsjaarlRMZbJvRyGAMFlFmsDi4";
/// let key = b"test";
/// let claims = small_jwt::decode::<Claims>(token, key);
/// ```
pub fn decode<P>(
    token: &str,
    secret_key: &[u8],
) -> Result<P>
where
    P: serde::de::DeserializeOwned,
{
    let parts: Vec<&str> = token.split('.').collect();

    if parts.len() < 3 {
        return Err(Error::InvalidTokenLength);
    }

    let header = utils::from_json_b64::<Header>(parts[0])?;
    let payload = utils::from_json_b64::<P>(parts[1])?;
    let signature = parts[2];

    let unsigned_jwt = format!("{}.{}", parts[0], parts[1]);
    let expected_signature = header.alg.sign_fn()(unsigned_jwt.as_bytes(), secret_key)?;
    let expected_signature_encoded = utils::to_b64(&expected_signature);

    if signature != expected_signature_encoded {
        return Err(Error::InvalidTokenSignature);
    }

    Ok(payload)
}

#[cfg(test)]
mod tests {
    use super::*;

    // INFO:
    // - For token testing: https://jwt.io/

    #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct Claims {
        pub user: String,
        pub age: u16,
        pub is_admin: bool,
    }

    const SECRET_KEY: &[u8] = b"super_secret_key";

    #[test]
    fn decode_success() {
        let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlcm5hbWUiLCJhZ2UiOjEyOCwiaXNfYWRtaW4iOnRydWV9.3dTbG3tFs2QBcxkxmpxnRzPdCi-fpe9YzoLWX-lBBl0";
        let decoded = decode::<Claims>(token, SECRET_KEY);

        assert!(decoded.is_ok(), "Should be ok");
        assert_eq!(Claims {user: String::from("username"), age: 128, is_admin: true }, decoded.unwrap());
    }

    #[test]
    fn decode_fail_token_length() {
        // token without signature
        let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlcm5hbWUiLCJhZ2UiOjEyOCwiaXNfYWRtaW4iOnRydWV9";
        let decoded = decode::<Claims>(token, SECRET_KEY);

        match decoded {
            Err(err) => match err {
                crate::error::Error::InvalidTokenLength => (),
                _ => panic!("Decoded isn't a InvalidTokenLength"),
            },
            _ => panic!("Decoded isn't a error"),
        }
    }

    #[test]
    fn decode_fail_token_signature() {
        let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlcm5hbWUiLCJhZ2UiOjEyOCwiaXNfYWRtaW4iOnRydWV9.aR3g11ENGu8Bc5GfF5kNFbFVwEgmzyt-wl1w_gIPv9E";
        let decoded = decode::<Claims>(token, SECRET_KEY);

        match decoded {
            Err(err) => match err {
                crate::error::Error::InvalidTokenSignature => (),
                _ => panic!("Decoded isn't a InvalidTokenSignature"),
            },
            _ => panic!("Decoded isn't a error"),
        }
    }
}