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