mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-08-17 23:06:05 +00:00
Format and split big files into smaller ones
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod token;
|
||||
pub mod verify;
|
||||
|
||||
pub use token::{create_access_token, create_refresh_token, create_service_token};
|
||||
pub use verify::{get_did_from_token, verify_token};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub iss: String,
|
||||
pub sub: String,
|
||||
pub aud: String,
|
||||
pub exp: usize,
|
||||
pub iat: usize,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub scope: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lxm: Option<String>,
|
||||
pub jti: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Header {
|
||||
pub alg: String,
|
||||
pub typ: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UnsafeClaims {
|
||||
pub iss: String,
|
||||
pub sub: Option<String>,
|
||||
}
|
||||
|
||||
// fancy boy TokenData equivalent for compatibility/structure
|
||||
pub struct TokenData<T> {
|
||||
pub claims: T,
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use super::{Claims, Header};
|
||||
use anyhow::Result;
|
||||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
use chrono::{Duration, Utc};
|
||||
use k256::ecdsa::{Signature, SigningKey, signature::Signer};
|
||||
use uuid;
|
||||
|
||||
pub fn create_access_token(did: &str, key_bytes: &[u8]) -> Result<String> {
|
||||
create_signed_token(did, "access", key_bytes, Duration::minutes(15))
|
||||
}
|
||||
|
||||
pub fn create_refresh_token(did: &str, key_bytes: &[u8]) -> Result<String> {
|
||||
create_signed_token(did, "refresh", key_bytes, Duration::days(7))
|
||||
}
|
||||
|
||||
pub fn create_service_token(did: &str, aud: &str, lxm: &str, key_bytes: &[u8]) -> Result<String> {
|
||||
let signing_key = SigningKey::from_slice(key_bytes)?;
|
||||
|
||||
let expiration = Utc::now()
|
||||
.checked_add_signed(Duration::seconds(60))
|
||||
.expect("valid timestamp")
|
||||
.timestamp();
|
||||
|
||||
let claims = Claims {
|
||||
iss: did.to_owned(),
|
||||
sub: did.to_owned(),
|
||||
aud: aud.to_owned(),
|
||||
exp: expiration as usize,
|
||||
iat: Utc::now().timestamp() as usize,
|
||||
scope: None,
|
||||
lxm: Some(lxm.to_string()),
|
||||
jti: uuid::Uuid::new_v4().to_string(),
|
||||
};
|
||||
|
||||
sign_claims(claims, &signing_key)
|
||||
}
|
||||
|
||||
fn create_signed_token(
|
||||
did: &str,
|
||||
scope: &str,
|
||||
key_bytes: &[u8],
|
||||
duration: Duration,
|
||||
) -> Result<String> {
|
||||
let signing_key = SigningKey::from_slice(key_bytes)?;
|
||||
|
||||
let expiration = Utc::now()
|
||||
.checked_add_signed(duration)
|
||||
.expect("valid timestamp")
|
||||
.timestamp();
|
||||
|
||||
let claims = Claims {
|
||||
iss: did.to_owned(),
|
||||
sub: did.to_owned(),
|
||||
aud: format!(
|
||||
"did:web:{}",
|
||||
std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string())
|
||||
),
|
||||
exp: expiration as usize,
|
||||
iat: Utc::now().timestamp() as usize,
|
||||
scope: Some(scope.to_string()),
|
||||
lxm: None,
|
||||
jti: uuid::Uuid::new_v4().to_string(),
|
||||
};
|
||||
|
||||
sign_claims(claims, &signing_key)
|
||||
}
|
||||
|
||||
fn sign_claims(claims: Claims, key: &SigningKey) -> Result<String> {
|
||||
let header = Header {
|
||||
alg: "ES256K".to_string(),
|
||||
typ: "JWT".to_string(),
|
||||
};
|
||||
|
||||
let header_json = serde_json::to_string(&header)?;
|
||||
let claims_json = serde_json::to_string(&claims)?;
|
||||
|
||||
let header_b64 = URL_SAFE_NO_PAD.encode(header_json);
|
||||
let claims_b64 = URL_SAFE_NO_PAD.encode(claims_json);
|
||||
|
||||
let message = format!("{}.{}", header_b64, claims_b64);
|
||||
let signature: Signature = key.sign(message.as_bytes());
|
||||
let signature_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes());
|
||||
|
||||
Ok(format!("{}.{}", message, signature_b64))
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use super::{Claims, TokenData, UnsafeClaims};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
use chrono::Utc;
|
||||
use k256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Verifier};
|
||||
|
||||
pub fn get_did_from_token(token: &str) -> Result<String, String> {
|
||||
let parts: Vec<&str> = token.split('.').collect();
|
||||
if parts.len() != 3 {
|
||||
return Err("Invalid token format".to_string());
|
||||
}
|
||||
|
||||
let payload_bytes = URL_SAFE_NO_PAD
|
||||
.decode(parts[1])
|
||||
.map_err(|e| format!("Base64 decode failed: {}", e))?;
|
||||
|
||||
let claims: UnsafeClaims =
|
||||
serde_json::from_slice(&payload_bytes).map_err(|e| format!("JSON decode failed: {}", e))?;
|
||||
|
||||
Ok(claims.sub.unwrap_or(claims.iss))
|
||||
}
|
||||
|
||||
pub fn verify_token(token: &str, key_bytes: &[u8]) -> Result<TokenData<Claims>> {
|
||||
let parts: Vec<&str> = token.split('.').collect();
|
||||
if parts.len() != 3 {
|
||||
return Err(anyhow!("Invalid token format"));
|
||||
}
|
||||
|
||||
let header_b64 = parts[0];
|
||||
let claims_b64 = parts[1];
|
||||
let signature_b64 = parts[2];
|
||||
|
||||
let signature_bytes = URL_SAFE_NO_PAD
|
||||
.decode(signature_b64)
|
||||
.context("Base64 decode of signature failed")?;
|
||||
let signature = Signature::from_slice(&signature_bytes)
|
||||
.map_err(|e| anyhow!("Invalid signature format: {}", e))?;
|
||||
|
||||
let signing_key = SigningKey::from_slice(key_bytes)?;
|
||||
let verifying_key = VerifyingKey::from(&signing_key);
|
||||
|
||||
let message = format!("{}.{}", header_b64, claims_b64);
|
||||
verifying_key
|
||||
.verify(message.as_bytes(), &signature)
|
||||
.map_err(|e| anyhow!("Signature verification failed: {}", e))?;
|
||||
|
||||
let claims_bytes = URL_SAFE_NO_PAD
|
||||
.decode(claims_b64)
|
||||
.context("Base64 decode of claims failed")?;
|
||||
let claims: Claims =
|
||||
serde_json::from_slice(&claims_bytes).context("JSON decode of claims failed")?;
|
||||
|
||||
let now = Utc::now().timestamp() as usize;
|
||||
if claims.exp < now {
|
||||
return Err(anyhow!("Token expired"));
|
||||
}
|
||||
|
||||
Ok(TokenData { claims })
|
||||
}
|
||||
Reference in New Issue
Block a user