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