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