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( 37 did, 38 SCOPE_ACCESS, 39 TOKEN_TYPE_ACCESS, 40 key_bytes, 41 Duration::minutes(15), 42 ) 43} 44 45pub fn create_refresh_token_with_metadata( 46 did: &str, 47 key_bytes: &[u8], 48) -> Result<TokenWithMetadata> { 49 create_signed_token_with_metadata( 50 did, 51 SCOPE_REFRESH, 52 TOKEN_TYPE_REFRESH, 53 key_bytes, 54 Duration::days(14), 55 ) 56} 57 58pub fn create_service_token(did: &str, aud: &str, lxm: &str, key_bytes: &[u8]) -> Result<String> { 59 let signing_key = SigningKey::from_slice(key_bytes)?; 60 61 let expiration = Utc::now() 62 .checked_add_signed(Duration::seconds(60)) 63 .expect("valid timestamp") 64 .timestamp(); 65 66 let claims = Claims { 67 iss: did.to_owned(), 68 sub: did.to_owned(), 69 aud: aud.to_owned(), 70 exp: expiration as usize, 71 iat: Utc::now().timestamp() as usize, 72 scope: None, 73 lxm: Some(lxm.to_string()), 74 jti: uuid::Uuid::new_v4().to_string(), 75 }; 76 77 sign_claims(claims, &signing_key) 78} 79 80fn create_signed_token_with_metadata( 81 did: &str, 82 scope: &str, 83 typ: &str, 84 key_bytes: &[u8], 85 duration: Duration, 86) -> Result<TokenWithMetadata> { 87 let signing_key = SigningKey::from_slice(key_bytes)?; 88 89 let expires_at = Utc::now() 90 .checked_add_signed(duration) 91 .expect("valid timestamp"); 92 93 let expiration = expires_at.timestamp(); 94 let jti = uuid::Uuid::new_v4().to_string(); 95 96 let claims = Claims { 97 iss: did.to_owned(), 98 sub: did.to_owned(), 99 aud: format!( 100 "did:web:{}", 101 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()) 102 ), 103 exp: expiration as usize, 104 iat: Utc::now().timestamp() as usize, 105 scope: Some(scope.to_string()), 106 lxm: None, 107 jti: jti.clone(), 108 }; 109 110 let token = sign_claims_with_type(claims, &signing_key, typ)?; 111 112 Ok(TokenWithMetadata { 113 token, 114 jti, 115 expires_at, 116 }) 117} 118 119fn sign_claims(claims: Claims, key: &SigningKey) -> Result<String> { 120 sign_claims_with_type(claims, key, TOKEN_TYPE_SERVICE) 121} 122 123fn sign_claims_with_type(claims: Claims, key: &SigningKey, typ: &str) -> Result<String> { 124 let header = Header { 125 alg: "ES256K".to_string(), 126 typ: typ.to_string(), 127 }; 128 129 let header_json = serde_json::to_string(&header)?; 130 let claims_json = serde_json::to_string(&claims)?; 131 132 let header_b64 = URL_SAFE_NO_PAD.encode(header_json); 133 let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json); 134 135 let message = format!("{}.{}", header_b64, claims_b64); 136 let signature: Signature = key.sign(message.as_bytes()); 137 let signature_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes()); 138 139 Ok(format!("{}.{}", message, signature_b64)) 140} 141 142pub fn create_access_token_hs256(did: &str, secret: &[u8]) -> Result<String> { 143 Ok(create_access_token_hs256_with_metadata(did, secret)?.token) 144} 145 146pub fn create_refresh_token_hs256(did: &str, secret: &[u8]) -> Result<String> { 147 Ok(create_refresh_token_hs256_with_metadata(did, secret)?.token) 148} 149 150pub fn create_access_token_hs256_with_metadata( 151 did: &str, 152 secret: &[u8], 153) -> Result<TokenWithMetadata> { 154 create_hs256_token_with_metadata( 155 did, 156 SCOPE_ACCESS, 157 TOKEN_TYPE_ACCESS, 158 secret, 159 Duration::minutes(15), 160 ) 161} 162 163pub fn create_refresh_token_hs256_with_metadata( 164 did: &str, 165 secret: &[u8], 166) -> Result<TokenWithMetadata> { 167 create_hs256_token_with_metadata( 168 did, 169 SCOPE_REFRESH, 170 TOKEN_TYPE_REFRESH, 171 secret, 172 Duration::days(14), 173 ) 174} 175 176pub fn create_service_token_hs256( 177 did: &str, 178 aud: &str, 179 lxm: &str, 180 secret: &[u8], 181) -> Result<String> { 182 let expiration = Utc::now() 183 .checked_add_signed(Duration::seconds(60)) 184 .expect("valid timestamp") 185 .timestamp(); 186 187 let claims = Claims { 188 iss: did.to_owned(), 189 sub: did.to_owned(), 190 aud: aud.to_owned(), 191 exp: expiration as usize, 192 iat: Utc::now().timestamp() as usize, 193 scope: None, 194 lxm: Some(lxm.to_string()), 195 jti: uuid::Uuid::new_v4().to_string(), 196 }; 197 198 sign_claims_hs256(claims, TOKEN_TYPE_SERVICE, secret) 199} 200 201fn create_hs256_token_with_metadata( 202 did: &str, 203 scope: &str, 204 typ: &str, 205 secret: &[u8], 206 duration: Duration, 207) -> Result<TokenWithMetadata> { 208 let expires_at = Utc::now() 209 .checked_add_signed(duration) 210 .expect("valid timestamp"); 211 212 let expiration = expires_at.timestamp(); 213 let jti = uuid::Uuid::new_v4().to_string(); 214 215 let claims = Claims { 216 iss: did.to_owned(), 217 sub: did.to_owned(), 218 aud: format!( 219 "did:web:{}", 220 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()) 221 ), 222 exp: expiration as usize, 223 iat: Utc::now().timestamp() as usize, 224 scope: Some(scope.to_string()), 225 lxm: None, 226 jti: jti.clone(), 227 }; 228 229 let token = sign_claims_hs256(claims, typ, secret)?; 230 231 Ok(TokenWithMetadata { 232 token, 233 jti, 234 expires_at, 235 }) 236} 237 238fn sign_claims_hs256(claims: Claims, typ: &str, secret: &[u8]) -> Result<String> { 239 let header = Header { 240 alg: "HS256".to_string(), 241 typ: typ.to_string(), 242 }; 243 244 let header_json = serde_json::to_string(&header)?; 245 let claims_json = serde_json::to_string(&claims)?; 246 247 let header_b64 = URL_SAFE_NO_PAD.encode(header_json); 248 let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json); 249 250 let message = format!("{}.{}", header_b64, claims_b64); 251 252 let mut mac = HmacSha256::new_from_slice(secret) 253 .map_err(|e| anyhow::anyhow!("Invalid secret length: {}", e))?; 254 mac.update(message.as_bytes()); 255 256 let signature = mac.finalize().into_bytes(); 257 let signature_b64 = URL_SAFE_NO_PAD.encode(signature); 258 259 Ok(format!("{}.{}", message, signature_b64)) 260}