Repository: libsql/libsql
File: libsql-server/src/auth/user_auth_strategies/jwt.rs (Rust)

Code before:
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        Option::<i64>::deserialize(deserializer)?
            .map(|x| {
                DateTime::from_timestamp(x, 0).ok_or_else(|| D::Error::custom("invalid exp claim"))
            })
            .transpose()
    }
}

fn validate_any_jwt(
    jwt_keys: &Vec<jsonwebtoken::DecodingKey>,
    jwt: &str,
) -> Result<Authenticated, AuthError> {
    for (index, jwt_key) in jwt_keys.iter().enumerate() {
        let result = validate_jwt(&jwt_key, jwt);
        if result.is_ok() || index == jwt_keys.len() - 1 {
            return result;
        }
    }

    Err(AuthError::Other)
}

fn validate_jwt(
    jwt_key: &jsonwebtoken::DecodingKey,
    jwt: &str,
) -> Result<Authenticated, AuthError> {
    use jsonwebtoken::errors::ErrorKind;

    let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::EdDSA);
    validation.required_spec_claims.remove("exp");

    match jsonwebtoken::decode::<Token>(jwt, jwt_key, &validation).map(|t| t.claims) {
        Ok(Token { id, a, p, .. }) => {
            if p.is_some() {
                Ok(Authenticated::Authorized(p.unwrap_or_default().into()))
            } else {
                Ok(Authenticated::Legacy(LegacyAuth {
                    namespace: id,
                    perm: a.unwrap_or(Permission::Write),
                }))
            }
        }
        Err(error) => Err(match error.kind() {
            ErrorKind::InvalidToken
            | ErrorKind::InvalidSignature

Review comment (bug):
in case validation fails, we might be interested in individual errors for each jwt
I suggest that we collect all errors an fold them into a single one, something like https://stackoverflow.com/a/61171459

Code after:
                DateTime::from_timestamp(x, 0).ok_or_else(|| D::Error::custom("invalid exp claim"))
            })
            .transpose()
    }
}

fn validate_any_jwt(
    jwt_keys: &Vec<jsonwebtoken::DecodingKey>,
    jwt: &str,
) -> Result<Authenticated, AuthError> {
    for jwt_key in jwt_keys.iter() {
        match validate_jwt(&jwt_key, jwt) {
            Ok(result) => return Ok(result),
            Err(AuthError::JwtInvalid) => continue, // Try another key
            Err(other) => return Err(other), // For anything else, return the error immediately
        }
    }
    Err(AuthError::JwtInvalid)
}

fn validate_jwt(
    jwt_key: &jsonwebtoken::DecodingKey,
    jwt: &str,
) -> Result<Authenticated, AuthError> {
    use jsonwebtoken::errors::ErrorKind;

    let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::EdDSA);
    validation.required_spec_claims.remove("exp");

    match jsonwebtoken::decode::<Token>(jwt, jwt_key, &validation).map(|t| t.claims) {
        Ok(Token { id, a, p, .. }) => {
            if p.is_some() {
                Ok(Authenticated::Authorized(p.unwrap_or_default().into()))
            } else {
                Ok(Authenticated::Legacy(LegacyAuth {
                    namespace: id,
                    perm: a.unwrap_or(Permission::Write),
                }))
            }
        }
        Err(error) => Err(match error.kind() {
            ErrorKind::InvalidToken
            | ErrorKind::InvalidSignature
            | ErrorKind::InvalidAlgorithm
            | ErrorKind::Base64(_)
            | ErrorKind::Json(_)
            | ErrorKind::Utf8(_) => AuthError::JwtInvalid,
            ErrorKind::ExpiredSignature => AuthError::JwtExpired,
            ErrorKind::ImmatureSignature => AuthError::JwtImmature,
            _ => AuthError::Other,
        }),