Something went wrong. Try again.
Our Personal Data Server from scratch!
Something went wrong. Try again.
7.9 kB · 248 lines
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249use axum::{Json, extract::State};use serde::{Deserialize, Serialize};use serde_json::json;use tranquil_pds::api::ApiError;use tranquil_pds::api::error::DbResultExt;use tranquil_pds::auth::{Active, Auth};use tranquil_pds::state::AppState;
#[derive(Debug, Clone, Serialize, Deserialize)]#[serde(rename_all = "camelCase")]pub struct VerificationMethod { pub id: String, #[serde(rename = "type")] pub method_type: String, pub public_key_multibase: String,}
#[derive(Deserialize)]#[serde(rename_all = "camelCase")]pub struct UpdateDidDocumentInput { pub verification_methods: Option<Vec<VerificationMethod>>, pub also_known_as: Option<Vec<String>>, pub service_endpoint: Option<String>,}
#[derive(Serialize)]#[serde(rename_all = "camelCase")]pub struct UpdateDidDocumentOutput { pub success: bool, pub did_document: serde_json::Value,}
pub async fn update_did_document( State(state): State<AppState>, auth: Auth<Active>, Json(input): Json<UpdateDidDocumentInput>,) -> Result<Json<UpdateDidDocumentOutput>, ApiError> { if !auth.did.starts_with("did:web:") { return Err(ApiError::InvalidRequest( "DID document updates are only available for did:web accounts".into(), )); }
let user = state .repos .user .get_user_for_did_doc(&auth.did) .await .log_db_err("getting user")? .ok_or(ApiError::AccountNotFound)?;
if let Some(ref methods) = input.verification_methods { if methods.is_empty() { return Err(ApiError::InvalidRequest( "verification_methods cannot be empty".into(), )); } let validation_error = methods.iter().find_map(|method| { if method.id.is_empty() { Some("verification method id is required") } else if method.method_type != "Multikey" { Some("verification method type must be 'Multikey'") } else if !method.public_key_multibase.starts_with('z') { Some("publicKeyMultibase must start with 'z' (base58btc)") } else if method.public_key_multibase.len() < 40 { Some("publicKeyMultibase appears too short for a valid key") } else { None } }); if let Some(err) = validation_error { return Err(ApiError::InvalidRequest(err.into())); } }
if let Some(ref handles) = input.also_known_as && handles.iter().any(|h| !h.starts_with("at://")) { return Err(ApiError::InvalidRequest( "alsoKnownAs entries must be at:// URIs".into(), )); }
if let Some(ref endpoint) = input.service_endpoint { let endpoint = endpoint.trim(); if !endpoint.starts_with("https://") { return Err(ApiError::InvalidRequest( "serviceEndpoint must start with https://".into(), )); } }
let verification_methods_json = input .verification_methods .as_ref() .map(|v| serde_json::to_value(v).unwrap_or_default());
let also_known_as: Option<Vec<String>> = input.also_known_as.clone();
state .repos .user .upsert_did_web_overrides(user.id, verification_methods_json, also_known_as) .await .log_db_err("upserting did_web_overrides")?;
if let Some(ref endpoint) = input.service_endpoint { let endpoint_clean = endpoint.trim().trim_end_matches('/'); state .repos .user .update_migrated_to_pds(&auth.did, endpoint_clean) .await .log_db_err("updating service endpoint")?; }
let did_doc = build_did_document(&state, &auth.did).await;
tracing::info!("Updated DID document for {}", &auth.did);
Ok(Json(UpdateDidDocumentOutput { success: true, did_document: did_doc, }))}
pub async fn get_did_document( State(state): State<AppState>, auth: Auth<Active>,) -> Result<Json<serde_json::Value>, ApiError> { if !auth.did.starts_with("did:web:") { return Err(ApiError::InvalidRequest( "This endpoint is only available for did:web accounts".into(), )); }
let did_doc = build_did_document(&state, &auth.did).await;
Ok(Json(serde_json::json!({ "didDocument": did_doc })))}
async fn build_did_document(state: &AppState, did: &tranquil_pds::types::Did) -> serde_json::Value { let hostname = &tranquil_config::get().server.hostname;
let user = match state.repos.user.get_user_for_did_doc_build(did).await { Ok(Some(row)) => row, _ => { return json!({ "error": "User not found" }); } };
let overrides = state .repos .user .get_did_web_overrides(user.id) .await .ok() .flatten();
let service_endpoint = user .migrated_to_pds .unwrap_or_else(|| format!("https://{}", hostname));
if let Some((ovr, parsed)) = overrides.as_ref().and_then(|ovr| { serde_json::from_value::<Vec<VerificationMethod>>(ovr.verification_methods.clone()) .ok() .filter(|p| !p.is_empty()) .map(|p| (ovr, p)) }) { let also_known_as = if !ovr.also_known_as.is_empty() { ovr.also_known_as.clone() } else { vec![format!("at://{}", user.handle)] }; return json!({ "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1", "https://w3id.org/security/suites/secp256k1-2019/v1" ], "id": did, "alsoKnownAs": also_known_as, "verificationMethod": parsed.iter().map(|m| json!({ "id": format!("{}{}", did, if m.id.starts_with('#') { m.id.clone() } else { format!("#{}", m.id) }), "type": m.method_type, "controller": did, "publicKeyMultibase": m.public_key_multibase })).collect::<Vec<_>>(), "service": [{ "id": "#atproto_pds", "type": tranquil_pds::plc::ServiceType::Pds.as_str(), "serviceEndpoint": service_endpoint }] }); }
let key_info = state .repos .user .get_user_key_by_id(user.id) .await .ok() .flatten();
let public_key_multibase = match key_info { Some(info) => { match tranquil_pds::config::decrypt_key(&info.key_bytes, info.encryption_version) { Ok(key_bytes) => crate::identity::did::get_public_key_multibase(&key_bytes) .unwrap_or_else(|_| "error".to_string()), Err(_) => "error".to_string(), } } None => "error".to_string(), };
let also_known_as = if let Some(ref ovr) = overrides { if !ovr.also_known_as.is_empty() { ovr.also_known_as.clone() } else { vec![format!("at://{}", user.handle)] } } else { vec![format!("at://{}", user.handle)] };
json!({ "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/multikey/v1", "https://w3id.org/security/suites/secp256k1-2019/v1" ], "id": did, "alsoKnownAs": also_known_as, "verificationMethod": [{ "id": format!("{}#atproto", did), "type": "Multikey", "controller": did, "publicKeyMultibase": public_key_multibase }], "service": [{ "id": "#atproto_pds", "type": tranquil_pds::plc::ServiceType::Pds.as_str(), "serviceEndpoint": service_endpoint }] })}