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 k256::ecdsa::{Signature, SigningKey, signature::Signer}; 7use uuid; 8 9pub const TOKEN_TYPE_ACCESS: &str = "at+jwt"; 10pub const TOKEN_TYPE_REFRESH: &str = "refresh+jwt"; 11pub const TOKEN_TYPE_SERVICE: &str = "jwt"; 12 13pub const SCOPE_ACCESS: &str = "com.atproto.access"; 14pub const SCOPE_REFRESH: &str = "com.atproto.refresh"; 15pub const SCOPE_APP_PASS: &str = "com.atproto.appPass"; 16pub const SCOPE_APP_PASS_PRIVILEGED: &str = "com.atproto.appPassPrivileged"; 17 18pub struct TokenWithMetadata { 19 pub token: String, 20 pub jti: String, 21 pub expires_at: DateTime<Utc>, 22} 23 24pub fn create_access_token(did: &str, key_bytes: &[u8]) -> Result<String> { 25 Ok(create_access_token_with_metadata(did, key_bytes)?.token) 26} 27 28pub fn create_refresh_token(did: &str, key_bytes: &[u8]) -> Result<String> { 29 Ok(create_refresh_token_with_metadata(did, key_bytes)?.token) 30} 31 32pub fn create_access_token_with_metadata(did: &str, key_bytes: &[u8]) -> Result<TokenWithMetadata> { 33 create_signed_token_with_metadata(did, SCOPE_ACCESS, TOKEN_TYPE_ACCESS, key_bytes, Duration::minutes(120)) 34} 35 36pub fn create_refresh_token_with_metadata(did: &str, key_bytes: &[u8]) -> Result<TokenWithMetadata> { 37 create_signed_token_with_metadata(did, SCOPE_REFRESH, TOKEN_TYPE_REFRESH, key_bytes, Duration::days(90)) 38} 39 40pub fn create_service_token(did: &str, aud: &str, lxm: &str, key_bytes: &[u8]) -> Result<String> { 41 let signing_key = SigningKey::from_slice(key_bytes)?; 42 43 let expiration = Utc::now() 44 .checked_add_signed(Duration::seconds(60)) 45 .expect("valid timestamp") 46 .timestamp(); 47 48 let claims = Claims { 49 iss: did.to_owned(), 50 sub: did.to_owned(), 51 aud: aud.to_owned(), 52 exp: expiration as usize, 53 iat: Utc::now().timestamp() as usize, 54 scope: None, 55 lxm: Some(lxm.to_string()), 56 jti: uuid::Uuid::new_v4().to_string(), 57 }; 58 59 sign_claims(claims, &signing_key) 60} 61 62fn create_signed_token_with_metadata( 63 did: &str, 64 scope: &str, 65 typ: &str, 66 key_bytes: &[u8], 67 duration: Duration, 68) -> Result<TokenWithMetadata> { 69 let signing_key = SigningKey::from_slice(key_bytes)?; 70 71 let expires_at = Utc::now() 72 .checked_add_signed(duration) 73 .expect("valid timestamp"); 74 let expiration = expires_at.timestamp(); 75 let jti = uuid::Uuid::new_v4().to_string(); 76 77 let claims = Claims { 78 iss: did.to_owned(), 79 sub: did.to_owned(), 80 aud: format!( 81 "did:web:{}", 82 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()) 83 ), 84 exp: expiration as usize, 85 iat: Utc::now().timestamp() as usize, 86 scope: Some(scope.to_string()), 87 lxm: None, 88 jti: jti.clone(), 89 }; 90 91 let token = sign_claims_with_type(claims, &signing_key, typ)?; 92 Ok(TokenWithMetadata { 93 token, 94 jti, 95 expires_at, 96 }) 97} 98 99fn sign_claims(claims: Claims, key: &SigningKey) -> Result<String> { 100 sign_claims_with_type(claims, key, TOKEN_TYPE_SERVICE) 101} 102 103fn sign_claims_with_type(claims: Claims, key: &SigningKey, typ: &str) -> Result<String> { 104 let header = Header { 105 alg: "ES256K".to_string(), 106 typ: typ.to_string(), 107 }; 108 109 let header_json = serde_json::to_string(&header)?; 110 let claims_json = serde_json::to_string(&claims)?; 111 112 let header_b64 = URL_SAFE_NO_PAD.encode(header_json); 113 let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json); 114 115 let message = format!("{}.{}", header_b64, claims_b64); 116 let signature: Signature = key.sign(message.as_bytes()); 117 let signature_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes()); 118 119 Ok(format!("{}.{}", message, signature_b64)) 120}