this repo has no description
1use serde::{Deserialize, Serialize}; 2use chrono::{Utc, Duration}; 3use k256::ecdsa::{SigningKey, VerifyingKey, signature::Signer, signature::Verifier, Signature}; 4use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 5use anyhow::{Context, Result, anyhow}; 6 7#[derive(Debug, Serialize, Deserialize)] 8pub struct Claims { 9 pub iss: String, 10 pub sub: String, 11 pub aud: String, 12 pub exp: usize, 13 pub iat: usize, 14 #[serde(skip_serializing_if = "Option::is_none")] 15 pub scope: Option<String>, 16 #[serde(skip_serializing_if = "Option::is_none")] 17 pub lxm: Option<String>, 18 pub jti: String, 19} 20 21#[derive(Debug, Serialize, Deserialize)] 22struct Header { 23 alg: String, 24 typ: String, 25} 26 27#[derive(Debug, Serialize, Deserialize)] 28struct UnsafeClaims { 29 iss: String, 30 sub: Option<String>, 31} 32 33// fancy boy TokenData equivalent for compatibility/structure 34pub struct TokenData<T> { 35 pub claims: T, 36} 37 38pub fn get_did_from_token(token: &str) -> Result<String, String> { 39 let parts: Vec<&str> = token.split('.').collect(); 40 if parts.len() != 3 { 41 return Err("Invalid token format".to_string()); 42 } 43 44 let payload_bytes = URL_SAFE_NO_PAD.decode(parts[1]) 45 .map_err(|e| format!("Base64 decode failed: {}", e))?; 46 47 let claims: UnsafeClaims = serde_json::from_slice(&payload_bytes) 48 .map_err(|e| format!("JSON decode failed: {}", e))?; 49 50 Ok(claims.sub.unwrap_or(claims.iss)) 51} 52 53pub fn create_access_token(did: &str, key_bytes: &[u8]) -> Result<String, anyhow::Error> { 54 create_signed_token(did, "access", key_bytes, Duration::minutes(15)) 55} 56 57pub fn create_refresh_token(did: &str, key_bytes: &[u8]) -> Result<String, anyhow::Error> { 58 create_signed_token(did, "refresh", key_bytes, Duration::days(7)) 59} 60 61pub fn create_service_token(did: &str, aud: &str, lxm: &str, key_bytes: &[u8]) -> Result<String, anyhow::Error> { 62 let signing_key = SigningKey::from_slice(key_bytes)?; 63 64 let expiration = Utc::now() 65 .checked_add_signed(Duration::seconds(60)) 66 .expect("valid timestamp") 67 .timestamp(); 68 69 let claims = Claims { 70 iss: did.to_owned(), 71 sub: did.to_owned(), 72 aud: aud.to_owned(), 73 exp: expiration as usize, 74 iat: Utc::now().timestamp() as usize, 75 scope: None, 76 lxm: Some(lxm.to_string()), 77 jti: uuid::Uuid::new_v4().to_string(), 78 }; 79 80 sign_claims(claims, &signing_key) 81} 82 83fn create_signed_token(did: &str, scope: &str, key_bytes: &[u8], duration: Duration) -> Result<String, anyhow::Error> { 84 let signing_key = SigningKey::from_slice(key_bytes)?; 85 86 let expiration = Utc::now() 87 .checked_add_signed(duration) 88 .expect("valid timestamp") 89 .timestamp(); 90 91 let claims = Claims { 92 iss: did.to_owned(), 93 sub: did.to_owned(), 94 aud: format!("did:web:{}", std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string())), 95 exp: expiration as usize, 96 iat: Utc::now().timestamp() as usize, 97 scope: Some(scope.to_string()), 98 lxm: None, 99 jti: uuid::Uuid::new_v4().to_string(), 100 }; 101 102 sign_claims(claims, &signing_key) 103} 104 105fn sign_claims(claims: Claims, key: &SigningKey) -> Result<String, anyhow::Error> { 106 let header = Header { 107 alg: "ES256K".to_string(), 108 typ: "JWT".to_string(), 109 }; 110 111 let header_json = serde_json::to_string(&header)?; 112 let claims_json = serde_json::to_string(&claims)?; 113 114 let header_b64 = URL_SAFE_NO_PAD.encode(header_json); 115 let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json); 116 117 let message = format!("{}.{}", header_b64, claims_b64); 118 let signature: Signature = key.sign(message.as_bytes()); 119 let signature_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes()); 120 121 Ok(format!("{}.{}", message, signature_b64)) 122} 123 124pub fn verify_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>, anyhow::Error> { 125 let parts: Vec<&str> = token.split('.').collect(); 126 if parts.len() != 3 { 127 return Err(anyhow!("Invalid token format")); 128 } 129 130 let header_b64 = parts[0]; 131 let claims_b64 = parts[1]; 132 let signature_b64 = parts[2]; 133 134 let signature_bytes = URL_SAFE_NO_PAD.decode(signature_b64) 135 .context("Base64 decode of signature failed")?; 136 let signature = Signature::from_slice(&signature_bytes) 137 .map_err(|e| anyhow!("Invalid signature format: {}", e))?; 138 139 let signing_key = SigningKey::from_slice(key_bytes)?; 140 let verifying_key = VerifyingKey::from(&signing_key); 141 142 let message = format!("{}.{}", header_b64, claims_b64); 143 verifying_key.verify(message.as_bytes(), &signature) 144 .map_err(|e| anyhow!("Signature verification failed: {}", e))?; 145 146 let claims_bytes = URL_SAFE_NO_PAD.decode(claims_b64) 147 .context("Base64 decode of claims failed")?; 148 let claims: Claims = serde_json::from_slice(&claims_bytes) 149 .context("JSON decode of claims failed")?; 150 151 let now = Utc::now().timestamp() as usize; 152 if claims.exp < now { 153 return Err(anyhow!("Token expired")); 154 } 155 156 Ok(TokenData { claims }) 157}