diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6de223a4..56b656b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: include: - build: pinned os: ubuntu-20.04 - rust: 1.67.0 + rust: 1.73.0 - build: stable os: ubuntu-20.04 rust: stable diff --git a/Cargo.toml b/Cargo.toml index e3059967..f355a8dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ include = [ "README.md", "CHANGELOG.md", ] -rust-version = "1.67.0" +rust-version = "1.73.0" [dependencies] serde_json = "1.0" diff --git a/src/validation.rs b/src/validation.rs index e0d64a7c..54a6cf3a 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -39,6 +39,13 @@ pub struct Validation { /// /// Defaults to `60`. pub leeway: u64, + /// Reject a token some time (in seconds) before the `exp` to prevent + /// expiration in transit over the network. + /// + /// The value is the inverse of `leeway`, subtracting from the validation time. + /// + /// Defaults to `0`. + pub reject_tokens_expiring_in_less_than: u64, /// Whether to validate the `exp` field. /// /// It will return an error if the time in the `exp` field is past. @@ -94,6 +101,7 @@ impl Validation { required_spec_claims: required_claims, algorithms: vec![alg], leeway: 60, + reject_tokens_expiring_in_less_than: 0, validate_exp: true, validate_nbf: false, @@ -246,7 +254,8 @@ pub(crate) fn validate(claims: ClaimsForValidation, options: &Validation) -> Res if options.validate_exp || options.validate_nbf { let now = get_current_timestamp(); - if matches!(claims.exp, TryParse::Parsed(exp) if options.validate_exp && exp < now - options.leeway) + if matches!(claims.exp, TryParse::Parsed(exp) if options.validate_exp + && exp - options.reject_tokens_expiring_in_less_than < now - options.leeway ) { return Err(new_error(ErrorKind::ExpiredSignature)); } @@ -366,6 +375,17 @@ mod tests { assert!(res.is_ok()); } + #[test] + #[wasm_bindgen_test] + fn exp_in_future_but_in_rejection_period_fails() { + let claims = json!({ "exp": get_current_timestamp() + 500 }); + let mut validation = Validation::new(Algorithm::HS256); + validation.leeway = 0; + validation.reject_tokens_expiring_in_less_than = 501; + let res = validate(deserialize_claims(&claims), &validation); + assert!(res.is_err()); + } + #[test] #[wasm_bindgen_test] fn exp_float_in_future_ok() { @@ -374,6 +394,17 @@ mod tests { assert!(res.is_ok()); } + #[test] + #[wasm_bindgen_test] + fn exp_float_in_future_but_in_rejection_period_fails() { + let claims = json!({ "exp": (get_current_timestamp() as f64) + 500.123 }); + let mut validation = Validation::new(Algorithm::HS256); + validation.leeway = 0; + validation.reject_tokens_expiring_in_less_than = 501; + let res = validate(deserialize_claims(&claims), &validation); + assert!(res.is_err()); + } + #[test] #[wasm_bindgen_test] fn exp_in_past_fails() {