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

Code before:
    where
        S: Serializer,
    {
        match date {
            Some(date) => serializer.serialize_i64(date.timestamp()),
            None => serializer.serialize_none(),
        }
    }

    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,

Review comment (suggestion):
Should this be limited to two keys so that no iteration needs to be done and encapsulate the concept some how. 

Pseudo Code:
```
validate_jwt(&primary_jwt_key, jwt) || validate_jwt(&secondary_jwt_key, jwt);
```

Code after:

    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 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