this repo has no description
1use serde::{Deserialize, Serialize};
2use chrono::{Utc, Duration};
3use k256::ecdsa::{SigningKey, VerifyingKey, signature::Signer, signature::Verifier, Signature};
4use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
5use anyhow::{Context, Result, anyhow};
6
7#[derive(Debug, Serialize, Deserialize)]
8pub struct Claims {
9 pub iss: String,
10 pub sub: String,
11 pub aud: String,
12 pub exp: usize,
13 pub iat: usize,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub scope: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub lxm: Option<String>,
18 pub jti: String,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22struct Header {
23 alg: String,
24 typ: String,
25}
26
27#[derive(Debug, Serialize, Deserialize)]
28struct UnsafeClaims {
29 iss: String,
30 sub: Option<String>,
31}
32
33// fancy boy TokenData equivalent for compatibility/structure
34pub struct TokenData<T> {
35 pub claims: T,
36}
37
38pub fn get_did_from_token(token: &str) -> Result<String, String> {
39 let parts: Vec<&str> = token.split('.').collect();
40 if parts.len() != 3 {
41 return Err("Invalid token format".to_string());
42 }
43
44 let payload_bytes = URL_SAFE_NO_PAD.decode(parts[1])
45 .map_err(|e| format!("Base64 decode failed: {}", e))?;
46
47 let claims: UnsafeClaims = serde_json::from_slice(&payload_bytes)
48 .map_err(|e| format!("JSON decode failed: {}", e))?;
49
50 Ok(claims.sub.unwrap_or(claims.iss))
51}
52
53pub fn create_access_token(did: &str, key_bytes: &[u8]) -> Result<String, anyhow::Error> {
54 create_signed_token(did, "access", key_bytes, Duration::minutes(15))
55}
56
57pub fn create_refresh_token(did: &str, key_bytes: &[u8]) -> Result<String, anyhow::Error> {
58 create_signed_token(did, "refresh", key_bytes, Duration::days(7))
59}
60
61pub fn create_service_token(did: &str, aud: &str, lxm: &str, key_bytes: &[u8]) -> Result<String, anyhow::Error> {
62 let signing_key = SigningKey::from_slice(key_bytes)?;
63
64 let expiration = Utc::now()
65 .checked_add_signed(Duration::seconds(60))
66 .expect("valid timestamp")
67 .timestamp();
68
69 let claims = Claims {
70 iss: did.to_owned(),
71 sub: did.to_owned(),
72 aud: aud.to_owned(),
73 exp: expiration as usize,
74 iat: Utc::now().timestamp() as usize,
75 scope: None,
76 lxm: Some(lxm.to_string()),
77 jti: uuid::Uuid::new_v4().to_string(),
78 };
79
80 sign_claims(claims, &signing_key)
81}
82
83fn create_signed_token(did: &str, scope: &str, key_bytes: &[u8], duration: Duration) -> Result<String, anyhow::Error> {
84 let signing_key = SigningKey::from_slice(key_bytes)?;
85
86 let expiration = Utc::now()
87 .checked_add_signed(duration)
88 .expect("valid timestamp")
89 .timestamp();
90
91 let claims = Claims {
92 iss: did.to_owned(),
93 sub: did.to_owned(),
94 aud: format!("did:web:{}", std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string())),
95 exp: expiration as usize,
96 iat: Utc::now().timestamp() as usize,
97 scope: Some(scope.to_string()),
98 lxm: None,
99 jti: uuid::Uuid::new_v4().to_string(),
100 };
101
102 sign_claims(claims, &signing_key)
103}
104
105fn sign_claims(claims: Claims, key: &SigningKey) -> Result<String, anyhow::Error> {
106 let header = Header {
107 alg: "ES256K".to_string(),
108 typ: "JWT".to_string(),
109 };
110
111 let header_json = serde_json::to_string(&header)?;
112 let claims_json = serde_json::to_string(&claims)?;
113
114 let header_b64 = URL_SAFE_NO_PAD.encode(header_json);
115 let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json);
116
117 let message = format!("{}.{}", header_b64, claims_b64);
118 let signature: Signature = key.sign(message.as_bytes());
119 let signature_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes());
120
121 Ok(format!("{}.{}", message, signature_b64))
122}
123
124pub fn verify_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>, anyhow::Error> {
125 let parts: Vec<&str> = token.split('.').collect();
126 if parts.len() != 3 {
127 return Err(anyhow!("Invalid token format"));
128 }
129
130 let header_b64 = parts[0];
131 let claims_b64 = parts[1];
132 let signature_b64 = parts[2];
133
134 let signature_bytes = URL_SAFE_NO_PAD.decode(signature_b64)
135 .context("Base64 decode of signature failed")?;
136 let signature = Signature::from_slice(&signature_bytes)
137 .map_err(|e| anyhow!("Invalid signature format: {}", e))?;
138
139 let signing_key = SigningKey::from_slice(key_bytes)?;
140 let verifying_key = VerifyingKey::from(&signing_key);
141
142 let message = format!("{}.{}", header_b64, claims_b64);
143 verifying_key.verify(message.as_bytes(), &signature)
144 .map_err(|e| anyhow!("Signature verification failed: {}", e))?;
145
146 let claims_bytes = URL_SAFE_NO_PAD.decode(claims_b64)
147 .context("Base64 decode of claims failed")?;
148 let claims: Claims = serde_json::from_slice(&claims_bytes)
149 .context("JSON decode of claims failed")?;
150
151 let now = Utc::now().timestamp() as usize;
152 if claims.exp < now {
153 return Err(anyhow!("Token expired"));
154 }
155
156 Ok(TokenData { claims })
157}