this repo has no description
1use super::{Claims, Header}; 2use anyhow::Result; 3use base64::Engine as _; 4use base64::engine::general_purpose::URL_SAFE_NO_PAD; 5use chrono::{DateTime, Duration, Utc}; 6use hmac::{Hmac, Mac}; 7use k256::ecdsa::{Signature, SigningKey, signature::Signer}; 8use sha2::Sha256; 9use uuid; 10type HmacSha256 = Hmac<Sha256>; 11pub const TOKEN_TYPE_ACCESS: &str = "at+jwt"; 12pub const TOKEN_TYPE_REFRESH: &str = "refresh+jwt"; 13pub const TOKEN_TYPE_SERVICE: &str = "jwt"; 14pub const SCOPE_ACCESS: &str = "com.atproto.access"; 15pub const SCOPE_REFRESH: &str = "com.atproto.refresh"; 16pub const SCOPE_APP_PASS: &str = "com.atproto.appPass"; 17pub const SCOPE_APP_PASS_PRIVILEGED: &str = "com.atproto.appPassPrivileged"; 18pub struct TokenWithMetadata { 19 pub token: String, 20 pub jti: String, 21 pub expires_at: DateTime<Utc>, 22} 23pub fn create_access_token(did: &str, key_bytes: &[u8]) -> Result<String> { 24 Ok(create_access_token_with_metadata(did, key_bytes)?.token) 25} 26pub fn create_refresh_token(did: &str, key_bytes: &[u8]) -> Result<String> { 27 Ok(create_refresh_token_with_metadata(did, key_bytes)?.token) 28} 29pub fn create_access_token_with_metadata(did: &str, key_bytes: &[u8]) -> Result<TokenWithMetadata> { 30 create_signed_token_with_metadata(did, SCOPE_ACCESS, TOKEN_TYPE_ACCESS, key_bytes, Duration::minutes(120)) 31} 32pub fn create_refresh_token_with_metadata(did: &str, key_bytes: &[u8]) -> Result<TokenWithMetadata> { 33 create_signed_token_with_metadata(did, SCOPE_REFRESH, TOKEN_TYPE_REFRESH, key_bytes, Duration::days(90)) 34} 35pub fn create_service_token(did: &str, aud: &str, lxm: &str, key_bytes: &[u8]) -> Result<String> { 36 let signing_key = SigningKey::from_slice(key_bytes)?; 37 let expiration = Utc::now() 38 .checked_add_signed(Duration::seconds(60)) 39 .expect("valid timestamp") 40 .timestamp(); 41 let claims = Claims { 42 iss: did.to_owned(), 43 sub: did.to_owned(), 44 aud: aud.to_owned(), 45 exp: expiration as usize, 46 iat: Utc::now().timestamp() as usize, 47 scope: None, 48 lxm: Some(lxm.to_string()), 49 jti: uuid::Uuid::new_v4().to_string(), 50 }; 51 sign_claims(claims, &signing_key) 52} 53fn create_signed_token_with_metadata( 54 did: &str, 55 scope: &str, 56 typ: &str, 57 key_bytes: &[u8], 58 duration: Duration, 59) -> Result<TokenWithMetadata> { 60 let signing_key = SigningKey::from_slice(key_bytes)?; 61 let expires_at = Utc::now() 62 .checked_add_signed(duration) 63 .expect("valid timestamp"); 64 let expiration = expires_at.timestamp(); 65 let jti = uuid::Uuid::new_v4().to_string(); 66 let claims = Claims { 67 iss: did.to_owned(), 68 sub: did.to_owned(), 69 aud: format!( 70 "did:web:{}", 71 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()) 72 ), 73 exp: expiration as usize, 74 iat: Utc::now().timestamp() as usize, 75 scope: Some(scope.to_string()), 76 lxm: None, 77 jti: jti.clone(), 78 }; 79 let token = sign_claims_with_type(claims, &signing_key, typ)?; 80 Ok(TokenWithMetadata { 81 token, 82 jti, 83 expires_at, 84 }) 85} 86fn sign_claims(claims: Claims, key: &SigningKey) -> Result<String> { 87 sign_claims_with_type(claims, key, TOKEN_TYPE_SERVICE) 88} 89fn sign_claims_with_type(claims: Claims, key: &SigningKey, typ: &str) -> Result<String> { 90 let header = Header { 91 alg: "ES256K".to_string(), 92 typ: typ.to_string(), 93 }; 94 let header_json = serde_json::to_string(&header)?; 95 let claims_json = serde_json::to_string(&claims)?; 96 let header_b64 = URL_SAFE_NO_PAD.encode(header_json); 97 let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json); 98 let message = format!("{}.{}", header_b64, claims_b64); 99 let signature: Signature = key.sign(message.as_bytes()); 100 let signature_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes()); 101 Ok(format!("{}.{}", message, signature_b64)) 102} 103pub fn create_access_token_hs256(did: &str, secret: &[u8]) -> Result<String> { 104 Ok(create_access_token_hs256_with_metadata(did, secret)?.token) 105} 106pub fn create_refresh_token_hs256(did: &str, secret: &[u8]) -> Result<String> { 107 Ok(create_refresh_token_hs256_with_metadata(did, secret)?.token) 108} 109pub fn create_access_token_hs256_with_metadata(did: &str, secret: &[u8]) -> Result<TokenWithMetadata> { 110 create_hs256_token_with_metadata(did, SCOPE_ACCESS, TOKEN_TYPE_ACCESS, secret, Duration::minutes(120)) 111} 112pub fn create_refresh_token_hs256_with_metadata(did: &str, secret: &[u8]) -> Result<TokenWithMetadata> { 113 create_hs256_token_with_metadata(did, SCOPE_REFRESH, TOKEN_TYPE_REFRESH, secret, Duration::days(90)) 114} 115pub fn create_service_token_hs256(did: &str, aud: &str, lxm: &str, secret: &[u8]) -> Result<String> { 116 let expiration = Utc::now() 117 .checked_add_signed(Duration::seconds(60)) 118 .expect("valid timestamp") 119 .timestamp(); 120 let claims = Claims { 121 iss: did.to_owned(), 122 sub: did.to_owned(), 123 aud: aud.to_owned(), 124 exp: expiration as usize, 125 iat: Utc::now().timestamp() as usize, 126 scope: None, 127 lxm: Some(lxm.to_string()), 128 jti: uuid::Uuid::new_v4().to_string(), 129 }; 130 sign_claims_hs256(claims, TOKEN_TYPE_SERVICE, secret) 131} 132fn create_hs256_token_with_metadata( 133 did: &str, 134 scope: &str, 135 typ: &str, 136 secret: &[u8], 137 duration: Duration, 138) -> Result<TokenWithMetadata> { 139 let expires_at = Utc::now() 140 .checked_add_signed(duration) 141 .expect("valid timestamp"); 142 let expiration = expires_at.timestamp(); 143 let jti = uuid::Uuid::new_v4().to_string(); 144 let claims = Claims { 145 iss: did.to_owned(), 146 sub: did.to_owned(), 147 aud: format!( 148 "did:web:{}", 149 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()) 150 ), 151 exp: expiration as usize, 152 iat: Utc::now().timestamp() as usize, 153 scope: Some(scope.to_string()), 154 lxm: None, 155 jti: jti.clone(), 156 }; 157 let token = sign_claims_hs256(claims, typ, secret)?; 158 Ok(TokenWithMetadata { 159 token, 160 jti, 161 expires_at, 162 }) 163} 164fn sign_claims_hs256(claims: Claims, typ: &str, secret: &[u8]) -> Result<String> { 165 let header = Header { 166 alg: "HS256".to_string(), 167 typ: typ.to_string(), 168 }; 169 let header_json = serde_json::to_string(&header)?; 170 let claims_json = serde_json::to_string(&claims)?; 171 let header_b64 = URL_SAFE_NO_PAD.encode(header_json); 172 let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json); 173 let message = format!("{}.{}", header_b64, claims_b64); 174 let mut mac = HmacSha256::new_from_slice(secret) 175 .map_err(|e| anyhow::anyhow!("Invalid secret length: {}", e))?; 176 mac.update(message.as_bytes()); 177 let signature = mac.finalize().into_bytes(); 178 let signature_b64 = URL_SAFE_NO_PAD.encode(signature); 179 Ok(format!("{}.{}", message, signature_b64)) 180}