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