don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(auth/service_auth): use `OffsetDateTime` for timestamps

Serialize and deserialize `iat` and `exp` fields in service-auth claims
as `OffsetDateTime`s.

Signed-off-by: tjh <x@tjh.dev>

tjh 24a9c915 57322576

+34 -25
+5 -4
crates/gordian-auth/src/service_auth.rs
··· 2 2 use gordian_types::Nsid; 3 3 use serde::Deserialize; 4 4 use serde::Serialize; 5 + use time::OffsetDateTime; 5 6 6 7 use crate::jwt; 7 8 use crate::jwt::Algorithm; ··· 63 62 pub audience: DidBuf, 64 63 65 64 /// Token creation time as a UNIX timestamp. 66 - #[serde(rename = "iat")] 67 - pub issued_at: i64, 65 + #[serde(rename = "iat", with = "time::serde::timestamp")] 66 + pub issued_at: OffsetDateTime, 68 67 69 68 /// Token expiration time as a UNIX timestamp. 70 - #[serde(rename = "exp")] 71 - pub expires_at: i64, 69 + #[serde(rename = "exp", with = "time::serde::timestamp")] 70 + pub expires_at: OffsetDateTime, 72 71 73 72 /// Lexicon method NSID. 74 73 // The specification claims this is optional. We require it.
+3 -2
crates/gordian-cred/src/commands/git_credential.rs
··· 3 3 use std::borrow::Cow; 4 4 use std::collections::HashSet; 5 5 use std::path::Path; 6 + use std::time::Duration; 6 7 7 8 use data_encoding::BASE32HEX_NOPAD; 8 9 use data_encoding::BASE64URL_NOPAD; ··· 177 176 // // We found a key, construct our JWT claims. 178 177 let issuer = account_did.clone(); 179 178 let audience = format!("did:web:{knot}").parse::<DidBuf>().or_raise(err)?; 180 - let issued_at = OffsetDateTime::now_utc().unix_timestamp(); 181 - let expires_at = issued_at + 45; 179 + let issued_at = OffsetDateTime::now_utc(); 180 + let expires_at = issued_at + Duration::from_secs(60); 182 181 let jti: [u8; 16] = rand::random(); 183 182 let jti: Box<str> = BASE32HEX_NOPAD.encode(&jti).to_ascii_lowercase().into(); 184 183
+2 -2
crates/gordian-knot/src/model.rs
··· 105 105 fn get_unexpired_claims<'a: 'b, 'b>( 106 106 &'a self, 107 107 jti: &'b str, 108 - now: i64, 108 + now: OffsetDateTime, 109 109 ) -> BoxFuture<'b, Result<Option<service_auth::Claims>, AuthorizationClaimsStoreError>> { 110 110 self.inner.get_unexpired_claims(jti, now) 111 111 } ··· 113 113 fn store_claims( 114 114 &self, 115 115 claims: service_auth::Claims, 116 - now: i64, 116 + now: OffsetDateTime, 117 117 ) -> BoxFuture<'_, Result<(), AuthorizationClaimsStoreError>> { 118 118 self.inner.store_claims(claims, now) 119 119 }
+2 -2
crates/gordian-knot/src/model/knot_state.rs
··· 484 484 fn get_unexpired_claims<'a: 'b, 'b>( 485 485 &'a self, 486 486 jti: &'b str, 487 - now: i64, 487 + now: OffsetDateTime, 488 488 ) -> BoxFuture<'b, Result<Option<service_auth::Claims>, AuthorizationClaimsStoreError>> { 489 489 async move { 490 490 let claims = self.database().get_claims(jti, now).await.ok().flatten(); ··· 507 507 fn store_claims( 508 508 &self, 509 509 claims: service_auth::Claims, 510 - now: i64, 510 + now: OffsetDateTime, 511 511 ) -> BoxFuture<'_, Result<(), AuthorizationClaimsStoreError>> { 512 512 async move { 513 513 self.database()
+1 -1
crates/gordian-knot/src/public/git/authorization.rs
··· 38 38 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 39 39 let knot = Knot::from_ref(state); 40 40 let resolver = Resolver::from_ref(state); 41 - let now = OffsetDateTime::now_utc().unix_timestamp(); 41 + let now = OffsetDateTime::now_utc(); 42 42 43 43 let credential = extract_token(parts, AUTHORIZATION, "bearer").ok_or( 44 44 Error::unauthorized(&knot, "inter-service authorization required"),
+13 -7
crates/gordian-knot/src/services/authorization.rs
··· 25 25 fn get_unexpired_claims<'a: 'b, 'b>( 26 26 &'a self, 27 27 jti: &'b str, 28 - now: i64, 28 + now: OffsetDateTime, 29 29 ) -> BoxFuture<'b, Result<Option<T>, AuthorizationClaimsStoreError>>; 30 30 31 31 fn store_claims( 32 32 &self, 33 33 claims: T, 34 - now: i64, 34 + now: OffsetDateTime, 35 35 ) -> BoxFuture<'_, Result<(), AuthorizationClaimsStoreError>>; 36 36 } 37 37 ··· 54 54 pub trait Verification: fmt::Debug + Send { 55 55 const LEXICON_METHOD: &'static Nsid; 56 56 57 - fn verify_iat(now: i64, claims: &Claims) -> Result<i64, VerificationError> { 57 + fn verify_iat( 58 + now: OffsetDateTime, 59 + claims: &Claims, 60 + ) -> Result<OffsetDateTime, VerificationError> { 58 61 match claims.issued_at { 59 62 iat if iat <= now => Ok(iat), 60 63 _ => Err(VerificationError::UseBeforeIssue), 61 64 } 62 65 } 63 66 64 - fn verify_exp(now: i64, claims: &Claims) -> Result<i64, VerificationError> { 67 + fn verify_exp( 68 + now: OffsetDateTime, 69 + claims: &Claims, 70 + ) -> Result<OffsetDateTime, VerificationError> { 65 71 match claims.expires_at { 66 72 exp if exp > now => Ok(exp), 67 73 _ => Err(VerificationError::UseAfterExpiry), ··· 95 89 96 90 fn verify_unique( 97 91 store: &dyn AuthorizationClaimsStore<Claims>, 98 - now: i64, 92 + now: OffsetDateTime, 99 93 claims: &Claims, 100 94 ) -> impl Future<Output = Result<(), VerificationError>> + Send { 101 95 async move { ··· 109 103 110 104 fn verify( 111 105 store: &dyn AuthorizationClaimsStore<Claims>, 112 - now: i64, 106 + now: OffsetDateTime, 113 107 audience: &gordian_types::Did, 114 108 claims: &Claims, 115 109 ) -> impl Future<Output = Result<(), VerificationError>> + Send { ··· 156 150 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 157 151 let knot = Knot::from_ref(state); 158 152 let resolver = Resolver::from_ref(state); 159 - let now = OffsetDateTime::now_utc().unix_timestamp(); 153 + let now = OffsetDateTime::now_utc(); 160 154 161 155 let credential = extract_token(parts, AUTHORIZATION, "bearer") 162 156 .ok_or(errors::Unauthorized("inter-service authorization required"))?;
+2 -2
crates/gordian-knot/src/services/database.rs
··· 429 429 pub async fn store_claims( 430 430 &self, 431 431 claims: service_auth::Claims, 432 - now: i64, 432 + now: OffsetDateTime, 433 433 ) -> Result<(), DataStoreError> { 434 434 let mut transaction = self.db.begin().await?; 435 435 ··· 454 454 pub async fn get_claims( 455 455 &self, 456 456 id: &str, 457 - now: i64, 457 + now: OffsetDateTime, 458 458 ) -> Result<Option<service_auth::Claims>, DataStoreError> { 459 459 let claims = sqlx::query!( 460 460 r#"SELECT claims as "claims: Value" FROM claim WHERE id = ? AND json_extract(claims, '$.exp') >= ?"#,
+6 -5
crates/gordian-knot/src/tests.rs
··· 103 103 } 104 104 105 105 mod sh_tangled_repo_create { 106 + use std::time::Duration; 107 + 106 108 use axum::http::HeaderValue; 107 109 use axum::http::Method; 108 110 use axum::http::Response; ··· 130 128 let mut claims = Claims { 131 129 issuer: iss.into(), 132 130 audience: aud.into(), 133 - issued_at: OffsetDateTime::now_utc().unix_timestamp(), 134 - expires_at: OffsetDateTime::now_utc().unix_timestamp() + 10, 131 + issued_at: OffsetDateTime::now_utc(), 132 + expires_at: OffsetDateTime::now_utc() + Duration::from_secs(10), 135 133 method: lxm.into_boxed(), 136 134 identifier: jti.into(), 137 135 }; ··· 406 404 let rkey = Tid::from_datetime(OffsetDateTime::now_utc(), 0).to_string(); 407 405 assert_eq!( 408 406 create_repo_with(&knot, pds, did, &rkey, "test-repo", None, |claims| { 409 - // 410 - claims.issued_at = OffsetDateTime::now_utc().unix_timestamp() + 60; 407 + claims.issued_at = OffsetDateTime::now_utc() + Duration::from_secs(60); 411 408 }) 412 409 .await 413 410 .status(), ··· 436 435 assert_eq!( 437 436 create_repo_with(&knot, pds, did, &rkey, "test-repo", None, |claims| { 438 437 // 439 - claims.expires_at = OffsetDateTime::now_utc().unix_timestamp() - 1; 438 + claims.expires_at = OffsetDateTime::now_utc() - Duration::from_secs(1); 440 439 }) 441 440 .await 442 441 .status(),