···174174 );175175176176 // // We found a key, construct our JWT claims.177177- let iss = account_did.clone();178178- let aud = format!("did:web:{knot}").parse::<DidBuf>().or_raise(err)?;179179- let iat = OffsetDateTime::now_utc().unix_timestamp();180180- let exp = iat + 45;177177+ let issuer = account_did.clone();178178+ let audience = format!("did:web:{knot}").parse::<DidBuf>().or_raise(err)?;179179+ let issued_at = OffsetDateTime::now_utc().unix_timestamp();180180+ let expires_at = issued_at + 45;181181 let jti: [u8; 16] = rand::random();182182 let jti: Box<str> = BASE32HEX_NOPAD.encode(&jti).to_ascii_lowercase().into();183183184184- let claims = gordian_auth::jwt::Claims {185185- iss,186186- aud,187187- iat,188188- exp,189189- lxm: Some(190190- "sh.tangled.repo.gitReceivePack"191191- .try_into()192192- .expect("nsid should be valid"),193193- ),194194- jti: jti.clone(),184184+ let claims = gordian_auth::service_auth::Claims {185185+ issuer,186186+ audience,187187+ issued_at,188188+ expires_at,189189+ method: "sh.tangled.repo.gitReceivePack"190190+ .try_into()191191+ .expect("sh.tangled.repo.gitReceivePack is a valid NSID"),192192+ identifier: jti.clone(),195193 };196194197195 let header = match &agent_identity.key_data() {
+1-4
crates/gordian-knot/src/extract/if_none_match.rs
···6969 /// assert!(!EntityTag::weak("1").strong_eq(&EntityTag::strong("1")));7070 /// assert!(EntityTag::strong("1").strong_eq(&EntityTag::strong("1")));7171 /// ```7272- ///7372 pub fn strong_eq(&self, other: &Self) -> bool {7473 !self.weak && !other.weak && self.value == other.value7574 }···8485 /// assert!(EntityTag::weak("1").weak_eq(&EntityTag::strong("1")));8586 /// assert!(EntityTag::strong("1").weak_eq(&EntityTag::strong("1")));8687 /// ```8787- ///8888 pub fn weak_eq(&self, other: &Self) -> bool {8989 self.value == other.value9090 }···270272 use axum::response::IntoResponse;271273 use tower::ServiceExt as _;272274275275+ use super::IfNoneMatch;273276 use crate::extract::if_none_match::EntityTag;274277 use crate::extract::if_none_match::StrongEq;275278 use crate::extract::if_none_match::WeakEq as _;276276-277277- use super::IfNoneMatch;278279279280 async fn weak_0815(if_none_match: IfNoneMatch) -> impl IntoResponse {280281 if if_none_match.match_weak("0815") {
···44use axum::http::request::Parts;55use gordian_auth::IntoVerificationKey;66use gordian_auth::OpenSshKey;77-use gordian_auth::jwt::Claims;88-use gordian_auth::jwt::Token;99-use gordian_auth::jwt::decode;77+use gordian_auth::service_auth::Claims;88+use gordian_auth::service_auth::Token;109use gordian_identity::Resolver;1110use gordian_types::Nsid;1211use time::OffsetDateTime;···6768 // the verification methods into public keys.68696970 let (resolved_did, doc) = resolver7070- .resolve(unverified_claims.iss.as_str())7171+ .resolve(unverified_claims.issuer.as_str())7172 .await7273 .map_err(|error| Error::forbidden(&knot, error.to_string()))?;73747474- assert_eq!(unverified_claims.iss, resolved_did);7575+ assert_eq!(unverified_claims.issuer, resolved_did);75767677 let verification_keys = doc7778 .verification_method···8182 // Try to decode and verify the JWT using any one of the verification keys8283 // we have for the DID.8384 for verification_key in verification_keys {8484- if let Ok(token) = decode::<Claims>(credential, &verification_key) {8585+ if let Ok(token) = Token::decode(credential, &verification_key) {8586 // Store the JWT so it cannot be re-used within the claim period.8687 knot.store_claims(token.claims.clone(), now).await?;8788 return Ok(Self(token.claims));···9293 // with claimed issuer.9394 let public_keys = knot9495 .database()9595- .public_keys_for_did(&unverified_claims.iss)9696+ .public_keys_for_did(&unverified_claims.issuer)9697 .await9798 .unwrap_or_default()9899 .into_iter()···101102 // Try to decode and verify the JWT using any one of the public keys102103 // we have for the DID.103104 for verification_key in public_keys {104104- if let Ok(token) = decode::<Claims>(credential, &verification_key) {105105+ if let Ok(token) = Token::decode(credential, &verification_key) {105106 // Store the JWT so it cannot be re-used within the claim period.106107 knot.store_claims(token.claims.clone(), now).await?;107108 return Ok(Self(token.claims));
···4040 let policy = RepositoryCreatePolicy;4141 let can_create = policy4242 .evaluate_access(4343- &claims.iss.as_ref(),4343+ &claims.issuer.as_ref(),4444 &Action::RepositoryCreate,4545 &knot,4646 &knot,···5050 if !matches!(can_create, Granted) {5151 return Err(errors::Forbidden(format!(5252 "'{}' does not have permission to create repositories on this knot",5353- claims.iss5353+ claims.issuer5454 )))?;5555 }5656···5858 let response = atrepo::fetch_record_bytes(5959 knot.resolver(),6060 knot.http(),6161- &claims.iss,6161+ &claims.issuer,6262 "sh.tangled.repo",6363 ¶ms.rkey,6464)6565.await6666-.inspect_err(|error| tracing::error!(?error, did = %claims.iss, rkey = %params.rkey, "unable to fetch record"))6666+.inspect_err(|error| tracing::error!(?error, did = %claims.issuer, rkey = %params.rkey, "unable to fetch record"))6767.map_err(errors::RepoError)?;68686969 let record = serde_json::from_slice::<Record>(&response)
···5353 let policy = RepositoryEditPolicy;5454 let can_create = policy5555 .evaluate_access(5656- &claims.iss.as_ref(),5656+ &claims.issuer.as_ref(),5757 &Action::RepositoryEdit,5858 &RepositoryRef::from(&repo_key),5959 &knot,···6363 if !matches!(can_create, Granted) {6464 return Err(errors::Forbidden(format!(6565 "'{}' does not have permission to modify repositories on this knot",6666- claims.iss6666+ claims.issuer6767 )))?;6868 }6969
+15-17
crates/gordian-knot/src/services/authorization.rs
···77use axum::http::request::Parts;88use futures_util::future::BoxFuture;99use gordian_auth::IntoVerificationKey as _;1010-use gordian_auth::jwt::Claims;1111-use gordian_auth::jwt::Token;1212-use gordian_auth::jwt::decode;1010+use gordian_auth::service_auth::Claims;1111+use gordian_auth::service_auth::Token;1312use gordian_identity::Resolver;1413use gordian_types::Nsid;1514use time::OffsetDateTime;···5556 const LEXICON_METHOD: &'static Nsid;56575758 fn verify_iat(now: i64, claims: &Claims) -> Result<i64, VerificationError> {5858- match claims.iat {5959+ match claims.issued_at {5960 iat if iat <= now => Ok(iat),6061 _ => Err(VerificationError::UseBeforeIssue),6162 }6263 }63646465 fn verify_exp(now: i64, claims: &Claims) -> Result<i64, VerificationError> {6565- match claims.exp {6666+ match claims.expires_at {6667 exp if exp > now => Ok(exp),6768 _ => Err(VerificationError::UseAfterExpiry),6869 }···70717172 /// Verify [`Claims::lxm`] matches the required value.7273 fn verify_lexicon_method(claims: &Claims) -> Result<&'static str, VerificationError> {7373- match claims.lxm.as_deref() {7474- Some(lxm) if lxm == Self::LEXICON_METHOD => Ok(Self::LEXICON_METHOD),7575- _ => Err(VerificationError::LexiconMethod),7474+ if claims.method != Self::LEXICON_METHOD {7575+ return Err(VerificationError::LexiconMethod);7676 }7777+7878+ Ok(Self::LEXICON_METHOD)7779 }78807981 fn verify_audience<'a>(8082 audience: &gordian_types::Did,8183 claims: &'a Claims,8284 ) -> Result<&'a gordian_types::Did, VerificationError> {8383- match claims.aud == audience {8484- true => Ok(&claims.aud),8585+ match claims.audience == audience {8686+ true => Ok(&claims.audience),8587 false => Err(VerificationError::WrongAudience),8688 }8789 }···9393 claims: &Claims,9494 ) -> impl Future<Output = Result<(), VerificationError>> + Send {9595 async move {9696- match store.get_unexpired_claims(&claims.jti, now).await? {9797- Some(stored_claims) if stored_claims.exp < now => Ok(()),9696+ match store.get_unexpired_claims(&claims.identifier, now).await? {9797+ Some(stored_claims) if stored_claims.expires_at < now => Ok(()),9898 None => Ok(()),9999 _ => Err(VerificationError::Reused),100100 }···169169 // the verification methods into public keys.170170171171 let (resolved_did, doc) = resolver172172- .resolve(unverified_claims.iss.as_str())172172+ .resolve(unverified_claims.issuer.as_str())173173 .await174174 .map_err(errors::Forbidden)?;175175176176- assert_eq!(unverified_claims.iss, resolved_did);176176+ assert_eq!(unverified_claims.issuer, resolved_did);177177178178 // @QUESTION Should we check all verification keys, or just the first?179179 let verification_keys = doc···186186 // Try to decode and verify the JWT using any one of the public keys187187 // we have for the DID.188188 for verification_key in verification_keys {189189- if let Ok(token) = decode::<Claims>(credential, &verification_key) {190190- let claims = token.claims;191191-189189+ if let Ok(Token { header: _, claims }) = Token::decode(credential, &verification_key) {192190 // Store the JWT so it cannot be re-used.193191 knot.store_claims(claims.clone(), now)194192 .await
+9-5
crates/gordian-knot/src/services/database.rs
···3344use futures_util::StreamExt;55use futures_util::stream::BoxStream;66-use gordian_auth::jwt;66+use gordian_auth::service_auth;77use gordian_jetstream::Value;88use gordian_lexicon::sh_tangled::PublicKey;99use gordian_lexicon::sh_tangled::knot::Member;···426426 .boxed()427427 }428428429429- pub async fn store_claims(&self, claims: jwt::Claims, now: i64) -> Result<(), DataStoreError> {429429+ pub async fn store_claims(430430+ &self,431431+ claims: service_auth::Claims,432432+ now: i64,433433+ ) -> Result<(), DataStoreError> {430434 let mut transaction = self.db.begin().await?;431435432436 // First delete any expired claims.···441437 .execute(&mut *transaction)442438 .await?;443439444444- let id = &claims.jti;440440+ let id = &claims.identifier;445441 let claims = serde_json::to_value(&claims)?;446442 sqlx::query!("INSERT INTO claim (id, claims) VALUES (?, ?)", id, claims)447443 .execute(&mut *transaction)···455451 &self,456452 id: &str,457453 now: i64,458458- ) -> Result<Option<jwt::Claims>, DataStoreError> {454454+ ) -> Result<Option<service_auth::Claims>, DataStoreError> {459455 let claims = sqlx::query!(460456 r#"SELECT claims as "claims: Value" FROM claim WHERE id = ? AND json_extract(claims, '$.exp') >= ?"#,461457 id, now462458 )463459 .fetch_optional(&self.db)464460 .await?465465- .map(|record| serde_json::from_value::<jwt::Claims>(record.claims))461461+ .map(|record| serde_json::from_value::<service_auth::Claims>(record.claims))466462 .transpose()?;467463468464 Ok(claims)
···99use aws_lc_rs::signature::KeyPair as _;1010use futures_util::FutureExt as _;1111use gordian_auth::jwt;1212+use gordian_auth::service_auth;1213use gordian_identity::DidDocument;1314use gordian_types::DidBuf;1415use gordian_types::Tid;···123122 }124123125124 // Create an inter-service auth header for an account in the fake PDS.126126- pub async fn service_auth(&self, claims: &jwt::Claims) -> String {125125+ pub async fn service_auth(&self, claims: &service_auth::Claims) -> String {127126 use sqlx::Row as _;128127129129- let header = jwt::Header {130130- typ: jwt::Type::Jwt,131131- alg: jwt::Algorithm::ES256K,132132- ..Default::default()133133- };134134-128128+ let header = service_auth::Header::new(jwt::Algorithm::ES256K);135129 let result = sqlx::query("SELECT key FROM identity WHERE did = ?")136136- .bind(claims.iss.as_ref())130130+ .bind(claims.issuer.as_ref())137131 .fetch_one(self.db())138132 .await139133 .unwrap();