Our Personal Data Server from scratch!
0

Configure Feed

Select the types of activity you want to include in your feed.

1use crate::common; 2use axum::{ 3 Json, 4 extract::{Path, Query, State}, 5 http::{HeaderMap, StatusCode}, 6 response::{IntoResponse, Response}, 7}; 8use base64::Engine; 9use k256::SecretKey; 10use k256::elliptic_curve::sec1::ToEncodedPoint; 11use serde::{Deserialize, Serialize}; 12use serde_json::json; 13use tracing::{error, warn}; 14use tranquil_pds::api::error::DbResultExt; 15use tranquil_pds::api::{ApiError, DidResponse, EmptyResponse}; 16use tranquil_pds::auth::{Auth, NotTakendown}; 17use tranquil_pds::plc::signing_key_to_did_key; 18use tranquil_pds::rate_limit::{ 19 HandleUpdateDailyLimit, HandleUpdateLimit, check_user_rate_limit_with_message, 20}; 21use tranquil_pds::state::AppState; 22use tranquil_pds::types::{Did, Handle}; 23use tranquil_pds::util::get_header_str; 24 25#[derive(Debug, Clone, Serialize, Deserialize)] 26#[serde(rename_all = "camelCase")] 27pub struct DidWebVerificationMethod { 28 pub id: String, 29 #[serde(rename = "type")] 30 pub method_type: String, 31 pub public_key_multibase: String, 32} 33 34#[derive(Deserialize)] 35pub struct ResolveHandleParams { 36 pub handle: String, 37} 38 39pub async fn resolve_handle( 40 State(state): State<AppState>, 41 Query(params): Query<ResolveHandleParams>, 42) -> Response { 43 let handle_str = params.handle.trim(); 44 if handle_str.is_empty() { 45 return ApiError::InvalidRequest("handle is required".into()).into_response(); 46 } 47 let handle: Handle = match handle_str.parse() { 48 Ok(h) => h, 49 Err(_) => { 50 return ApiError::InvalidHandle(Some("Invalid handle format".into())).into_response(); 51 } 52 }; 53 let cache_key = tranquil_pds::cache_keys::handle_key(&handle); 54 if let Some(did) = state.cache.get(&cache_key).await { 55 return DidResponse::response(did).into_response(); 56 } 57 let user = state.repos.user.get_by_handle(&handle).await; 58 match user { 59 Ok(Some(row)) => { 60 let _ = state 61 .cache 62 .set(&cache_key, &row.did, std::time::Duration::from_secs(300)) 63 .await; 64 DidResponse::response(row.did).into_response() 65 } 66 Ok(None) => match tranquil_pds::handle::resolve_handle(&handle).await { 67 Ok(did) => { 68 let _ = state 69 .cache 70 .set(&cache_key, &did, std::time::Duration::from_secs(300)) 71 .await; 72 DidResponse::response(did).into_response() 73 } 74 Err(_) => ApiError::HandleNotFound.into_response(), 75 }, 76 Err(e) => { 77 error!("DB error resolving handle: {:?}", e); 78 ApiError::InternalError(None).into_response() 79 } 80 } 81} 82 83#[derive(Debug)] 84pub enum KeyError { 85 InvalidKeyLength, 86 MissingCoordinate, 87} 88 89impl std::fmt::Display for KeyError { 90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 91 match self { 92 Self::InvalidKeyLength => write!(f, "invalid key length"), 93 Self::MissingCoordinate => write!(f, "missing elliptic curve coordinate"), 94 } 95 } 96} 97 98impl std::error::Error for KeyError {} 99 100pub fn get_jwk(key_bytes: &[u8]) -> Result<serde_json::Value, KeyError> { 101 let secret_key = SecretKey::from_slice(key_bytes).map_err(|_| KeyError::InvalidKeyLength)?; 102 let public_key = secret_key.public_key(); 103 let encoded = public_key.to_encoded_point(false); 104 let x = encoded.x().ok_or(KeyError::MissingCoordinate)?; 105 let y = encoded.y().ok_or(KeyError::MissingCoordinate)?; 106 let x_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(x); 107 let y_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(y); 108 Ok(json!({ 109 "kty": "EC", 110 "crv": "secp256k1", 111 "x": x_b64, 112 "y": y_b64 113 })) 114} 115 116pub fn get_public_key_multibase(key_bytes: &[u8]) -> Result<String, KeyError> { 117 let secret_key = SecretKey::from_slice(key_bytes).map_err(|_| KeyError::InvalidKeyLength)?; 118 let public_key = secret_key.public_key(); 119 let compressed = public_key.to_encoded_point(true); 120 let compressed_bytes = compressed.as_bytes(); 121 let mut multicodec_key = vec![0xe7, 0x01]; 122 multicodec_key.extend_from_slice(compressed_bytes); 123 Ok(format!("z{}", bs58::encode(&multicodec_key).into_string())) 124} 125 126pub async fn well_known_did(State(state): State<AppState>, headers: HeaderMap) -> Response { 127 let cfg = tranquil_config::get(); 128 let hostname = &cfg.server.hostname; 129 let hostname_without_port = cfg.server.hostname_without_port(); 130 let host_header = get_header_str(&headers, http::header::HOST).unwrap_or(hostname); 131 let host_without_port = host_header.split(':').next().unwrap_or(host_header); 132 if host_without_port != hostname_without_port { 133 let is_subdomain = cfg 134 .server 135 .available_user_domain_list() 136 .into_iter() 137 .chain(std::iter::once(hostname_without_port.to_string())) 138 .any(|d| host_without_port.ends_with(&format!(".{}", d))); 139 if is_subdomain { 140 return serve_handle_did_doc(&state, host_without_port, hostname).await; 141 } 142 } 143 let did = if hostname.contains(':') { 144 format!("did:web:{}", hostname.replace(':', "%3A")) 145 } else { 146 format!("did:web:{}", hostname) 147 }; 148 Json(json!({ 149 "@context": ["https://www.w3.org/ns/did/v1"], 150 "id": did, 151 "service": [{ 152 "id": "#atproto_pds", 153 "type": tranquil_pds::plc::ServiceType::Pds.as_str(), 154 "serviceEndpoint": format!("https://{}", hostname) 155 }] 156 })) 157 .into_response() 158} 159 160async fn serve_handle_did_doc(state: &AppState, handle: &str, hostname: &str) -> Response { 161 let encoded_handle = handle.replace(':', "%3A"); 162 let expected_did: tranquil_pds::types::Did = match format!("did:web:{}", encoded_handle).parse() 163 { 164 Ok(d) => d, 165 Err(_) => return ApiError::InvalidRequest("Invalid DID format".into()).into_response(), 166 }; 167 let user = match state 168 .repos 169 .user 170 .get_user_for_did_doc_build(&expected_did) 171 .await 172 { 173 Ok(Some(u)) => u, 174 Ok(None) => { 175 return ApiError::NotFoundMsg("User not found".into()).into_response(); 176 } 177 Err(e) => { 178 error!("DB Error: {:?}", e); 179 return ApiError::InternalError(None).into_response(); 180 } 181 }; 182 let (user_id, current_handle, migrated_to_pds) = (user.id, user.handle, user.migrated_to_pds); 183 let did = expected_did; 184 185 let overrides = state 186 .repos 187 .user 188 .get_did_web_overrides(user_id) 189 .await 190 .ok() 191 .flatten(); 192 193 let service_endpoint = migrated_to_pds.unwrap_or_else(|| format!("https://{}", hostname)); 194 195 let verification_methods = 196 build_override_or_key_verification_methods(state, user_id, &did, overrides.as_ref()).await; 197 let verification_methods = match verification_methods { 198 Ok(vm) => vm, 199 Err(resp) => return resp, 200 }; 201 202 let also_known_as = common::resolve_also_known_as(overrides.as_ref(), &current_handle); 203 Json(common::build_did_document( 204 &did, 205 also_known_as, 206 verification_methods, 207 &service_endpoint, 208 )) 209 .into_response() 210} 211 212pub async fn user_did_doc(State(state): State<AppState>, Path(handle): Path<String>) -> Response { 213 let hostname = &tranquil_config::get().server.hostname; 214 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 215 let current_handle: Handle = match format!("{}.{}", handle, hostname_for_handles).parse() { 216 Ok(h) => h, 217 Err(_) => { 218 return ApiError::InvalidHandle(Some("Invalid handle format".into())).into_response(); 219 } 220 }; 221 let user = match state 222 .repos 223 .user 224 .get_did_web_info_by_handle(&current_handle) 225 .await 226 { 227 Ok(Some(u)) => u, 228 Ok(None) => { 229 return ApiError::NotFoundMsg("User not found".into()).into_response(); 230 } 231 Err(e) => { 232 error!("DB Error: {:?}", e); 233 return ApiError::InternalError(None).into_response(); 234 } 235 }; 236 let (user_id, did, migrated_to_pds) = (user.id, user.did, user.migrated_to_pds); 237 if !did.starts_with("did:web:") { 238 return ApiError::NotFoundMsg("User is not did:web".into()).into_response(); 239 } 240 let encoded_hostname = hostname.replace(':', "%3A"); 241 let old_path_format = format!("did:web:{}:u:{}", encoded_hostname, handle); 242 let subdomain_host = format!("{}.{}", handle, hostname_for_handles); 243 let encoded_subdomain = subdomain_host.replace(':', "%3A"); 244 let new_subdomain_format = format!("did:web:{}", encoded_subdomain); 245 if did != old_path_format && did != new_subdomain_format { 246 return ApiError::NotFoundMsg("External did:web - DID document hosted by user".into()) 247 .into_response(); 248 } 249 250 let overrides = state 251 .repos 252 .user 253 .get_did_web_overrides(user_id) 254 .await 255 .ok() 256 .flatten(); 257 258 let service_endpoint = migrated_to_pds.unwrap_or_else(|| format!("https://{}", hostname)); 259 260 let verification_methods = 261 build_override_or_key_verification_methods(&state, user_id, &did, overrides.as_ref()).await; 262 let verification_methods = match verification_methods { 263 Ok(vm) => vm, 264 Err(resp) => return resp, 265 }; 266 267 let also_known_as = common::resolve_also_known_as(overrides.as_ref(), &current_handle); 268 Json(common::build_did_document( 269 &did, 270 also_known_as, 271 verification_methods, 272 &service_endpoint, 273 )) 274 .into_response() 275} 276 277async fn build_override_or_key_verification_methods( 278 state: &AppState, 279 user_id: uuid::Uuid, 280 did: &str, 281 overrides: Option<&tranquil_db_traits::DidWebOverrides>, 282) -> Result<Vec<serde_json::Value>, Response> { 283 if let Some(parsed) = overrides.and_then(|ovr| { 284 serde_json::from_value::<Vec<DidWebVerificationMethod>>(ovr.verification_methods.clone()) 285 .ok() 286 .filter(|p| !p.is_empty()) 287 }) { 288 return Ok(parsed 289 .iter() 290 .map(|m| { 291 json!({ 292 "id": format!("{}{}", did, if m.id.starts_with('#') { m.id.clone() } else { format!("#{}", m.id) }), 293 "type": m.method_type, 294 "controller": did, 295 "publicKeyMultibase": m.public_key_multibase 296 }) 297 }) 298 .collect()); 299 } 300 301 let key_info = match state.repos.user.get_user_key_by_id(user_id).await { 302 Ok(Some(k)) => k, 303 _ => return Err(ApiError::InternalError(None).into_response()), 304 }; 305 let key_bytes = 306 tranquil_pds::config::decrypt_key(&key_info.key_bytes, key_info.encryption_version) 307 .map_err(|_| ApiError::InternalError(None).into_response())?; 308 let public_key_multibase = get_public_key_multibase(&key_bytes).map_err(|e| { 309 tracing::error!("Failed to generate public key multibase: {}", e); 310 ApiError::InternalError(None).into_response() 311 })?; 312 313 Ok(vec![json!({ 314 "id": format!("{}#atproto", did), 315 "type": "Multikey", 316 "controller": did, 317 "publicKeyMultibase": public_key_multibase 318 })]) 319} 320 321#[derive(Debug, thiserror::Error)] 322pub enum DidWebVerifyError { 323 #[error("Invalid did:web format")] 324 InvalidFormat, 325 #[error("Invalid DID path for this PDS. Expected {0}")] 326 InvalidPath(String), 327 #[error( 328 "External did:web requires a pre-reserved signing key. Call com.atproto.server.reserveSigningKey first, configure your DID document with the returned key, then provide the signingKey in createAccount." 329 )] 330 MissingSigningKey, 331 #[error("Failed to fetch DID doc: {0}")] 332 FetchFailed(String), 333 #[error("Invalid DID document: {0}")] 334 InvalidDocument(String), 335 #[error("DID document does not list this PDS ({0}) as AtprotoPersonalDataServer")] 336 PdsNotListed(String), 337 #[error( 338 "DID document verification key does not match reserved signing key. Expected publicKeyMultibase: {0}" 339 )] 340 KeyMismatch(String), 341 #[error("Invalid signing key format")] 342 InvalidSigningKey, 343} 344 345pub async fn verify_did_web( 346 did: &str, 347 hostname: &str, 348 handle: &str, 349 expected_signing_key: Option<&Did>, 350) -> Result<(), DidWebVerifyError> { 351 let hostname_for_handles = hostname.split(':').next().unwrap_or(hostname); 352 let subdomain_host = format!("{}.{}", handle, hostname_for_handles); 353 let encoded_subdomain = subdomain_host.replace(':', "%3A"); 354 let expected_subdomain_did = format!("did:web:{}", encoded_subdomain); 355 if did == expected_subdomain_did { 356 return Ok(()); 357 } 358 let expected_prefix = if hostname.contains(':') { 359 format!("did:web:{}", hostname.replace(':', "%3A")) 360 } else { 361 format!("did:web:{}", hostname) 362 }; 363 if did.starts_with(&expected_prefix) { 364 let suffix = &did[expected_prefix.len()..]; 365 let expected_suffix = format!(":u:{}", handle); 366 return if suffix == expected_suffix { 367 Ok(()) 368 } else { 369 Err(DidWebVerifyError::InvalidPath(expected_suffix)) 370 }; 371 } 372 let expected_signing_key = expected_signing_key.ok_or(DidWebVerifyError::MissingSigningKey)?; 373 let parts: Vec<&str> = did.split(':').collect(); 374 if parts.len() < 3 || parts[0] != "did" || parts[1] != "web" { 375 return Err(DidWebVerifyError::InvalidFormat); 376 } 377 let domain_segment = parts[2]; 378 let domain = domain_segment.replace("%3A", ":"); 379 let scheme = if domain.starts_with("localhost") || domain.starts_with("127.0.0.1") { 380 "http" 381 } else { 382 "https" 383 }; 384 let url = if parts.len() == 3 { 385 format!("{}://{}/.well-known/did.json", scheme, domain) 386 } else { 387 let path = parts[3..].join("/"); 388 format!("{}://{}/{}/did.json", scheme, domain, path) 389 }; 390 let client = tranquil_pds::api::proxy_client::did_resolution_client(); 391 let resp = client 392 .get(&url) 393 .send() 394 .await 395 .map_err(|e| DidWebVerifyError::FetchFailed(e.to_string()))?; 396 if !resp.status().is_success() { 397 return Err(DidWebVerifyError::FetchFailed(format!( 398 "HTTP {}", 399 resp.status() 400 ))); 401 } 402 let doc: serde_json::Value = resp 403 .json() 404 .await 405 .map_err(|e| DidWebVerifyError::InvalidDocument(e.to_string()))?; 406 let services = doc["service"] 407 .as_array() 408 .ok_or(DidWebVerifyError::InvalidDocument( 409 "No services found".to_string(), 410 ))?; 411 let pds_endpoint = format!("https://{}", hostname); 412 let has_valid_service = services.iter().any(|s| { 413 s["type"] == tranquil_pds::plc::ServiceType::Pds.as_str() 414 && s["serviceEndpoint"] == pds_endpoint 415 }); 416 if !has_valid_service { 417 return Err(DidWebVerifyError::PdsNotListed(pds_endpoint)); 418 } 419 let verification_methods = 420 doc["verificationMethod"] 421 .as_array() 422 .ok_or(DidWebVerifyError::InvalidDocument( 423 "No verificationMethod found".to_string(), 424 ))?; 425 let expected_multibase = expected_signing_key 426 .strip_prefix("did:key:") 427 .ok_or(DidWebVerifyError::InvalidSigningKey)?; 428 let has_matching_key = verification_methods.iter().any(|vm| { 429 vm["publicKeyMultibase"] 430 .as_str() 431 .is_some_and(|pk| pk == expected_multibase) 432 }); 433 if !has_matching_key { 434 return Err(DidWebVerifyError::KeyMismatch( 435 expected_multibase.to_string(), 436 )); 437 } 438 Ok(()) 439} 440 441#[derive(serde::Serialize)] 442#[serde(rename_all = "camelCase")] 443pub struct GetRecommendedDidCredentialsOutput { 444 pub rotation_keys: Vec<String>, 445 pub also_known_as: Vec<String>, 446 pub verification_methods: VerificationMethods, 447 pub services: Services, 448} 449 450#[derive(serde::Serialize)] 451#[serde(rename_all = "camelCase")] 452pub struct VerificationMethods { 453 pub atproto: String, 454} 455 456#[derive(serde::Serialize)] 457pub struct Services { 458 pub atproto_pds: AtprotoPds, 459} 460 461#[derive(serde::Serialize)] 462#[serde(rename_all = "camelCase")] 463pub struct AtprotoPds { 464 #[serde(rename = "type")] 465 pub service_type: String, 466 pub endpoint: String, 467} 468 469pub async fn get_recommended_did_credentials( 470 State(state): State<AppState>, 471 auth: Auth<NotTakendown>, 472) -> Result<Json<GetRecommendedDidCredentialsOutput>, ApiError> { 473 let handle = state 474 .repos 475 .user 476 .get_handle_by_did(&auth.did) 477 .await 478 .log_db_err("fetching handle for DID credentials")? 479 .ok_or(ApiError::InternalError(None))?; 480 481 let key_bytes = auth.key_bytes.clone().ok_or_else(|| { 482 ApiError::AuthenticationFailed(Some("OAuth tokens cannot get DID credentials".into())) 483 })?; 484 485 let hostname = &tranquil_config::get().server.hostname; 486 let pds_endpoint = format!("https://{}", hostname); 487 let signing_key = k256::ecdsa::SigningKey::from_slice(&key_bytes) 488 .map_err(|_| ApiError::InternalError(None))?; 489 let did_key = signing_key_to_did_key(&signing_key); 490 let rotation_keys = if auth.did.starts_with("did:web:") { 491 vec![] 492 } else { 493 tranquil_pds::plc::rotation_keys_for( 494 tranquil_config::get().secrets.plc_rotation_key.as_deref(), 495 &signing_key, 496 ) 497 }; 498 Ok(Json(GetRecommendedDidCredentialsOutput { 499 rotation_keys, 500 also_known_as: vec![format!("at://{}", handle)], 501 verification_methods: VerificationMethods { atproto: did_key }, 502 services: Services { 503 atproto_pds: AtprotoPds { 504 service_type: tranquil_pds::plc::ServiceType::Pds.as_str().to_string(), 505 endpoint: pds_endpoint, 506 }, 507 }, 508 })) 509} 510 511#[derive(Deserialize)] 512pub struct UpdateHandleInput { 513 pub handle: String, 514} 515 516pub async fn update_handle( 517 State(state): State<AppState>, 518 auth: Auth<NotTakendown>, 519 Json(input): Json<UpdateHandleInput>, 520) -> Result<Json<EmptyResponse>, ApiError> { 521 tranquil_pds::auth::scope_check::check_identity_scope( 522 &auth.auth_source, 523 auth.scope.as_deref(), 524 tranquil_pds::oauth::scopes::IdentityAttr::Handle, 525 )?; 526 let did = auth.did.clone(); 527 let _rate_limit = check_user_rate_limit_with_message::<HandleUpdateLimit>( 528 &state, 529 &did, 530 "Too many handle updates. Try again later.", 531 ) 532 .await?; 533 let _daily_rate_limit = check_user_rate_limit_with_message::<HandleUpdateDailyLimit>( 534 &state, 535 &did, 536 "Daily handle update limit exceeded.", 537 ) 538 .await?; 539 let user_row = state 540 .repos 541 .user 542 .get_id_and_handle_by_did(&did) 543 .await 544 .log_db_err("fetching user for handle update")? 545 .ok_or(ApiError::InternalError(None))?; 546 let user_id = user_row.id; 547 let current_handle = user_row.handle; 548 let new_handle = input.handle.trim().to_ascii_lowercase(); 549 if new_handle.is_empty() { 550 return Err(ApiError::InvalidRequest("handle is required".into())); 551 } 552 if !new_handle 553 .chars() 554 .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-') 555 { 556 return Err(ApiError::InvalidHandle(Some( 557 "Handle contains invalid characters".into(), 558 ))); 559 } 560 if new_handle.split('.').any(|segment| segment.is_empty()) { 561 return Err(ApiError::InvalidHandle(Some( 562 "Handle contains empty segment".into(), 563 ))); 564 } 565 if new_handle 566 .split('.') 567 .any(|segment| segment.starts_with('-') || segment.ends_with('-')) 568 { 569 return Err(ApiError::InvalidHandle(Some( 570 "Handle segment cannot start or end with hyphen".into(), 571 ))); 572 } 573 if tranquil_pds::moderation::has_explicit_slur(&new_handle) { 574 return Err(ApiError::InvalidHandle(Some( 575 "Inappropriate language in handle".into(), 576 ))); 577 } 578 let handle_domains = tranquil_config::get().server.user_handle_domain_list(); 579 let matched_handle_domain = handle_domains 580 .iter() 581 .filter(|d| new_handle.ends_with(&format!(".{}", d))) 582 .max_by_key(|d| d.len()) 583 .cloned(); 584 let is_domain_itself = handle_domains.iter().any(|d| d == &new_handle); 585 let handle: Handle = if (!new_handle.contains('.') || matched_handle_domain.is_some()) 586 && !is_domain_itself 587 { 588 let (short_part, full_handle) = match &matched_handle_domain { 589 Some(domain) => { 590 let suffix = format!(".{}", domain); 591 let short = new_handle.strip_suffix(&suffix).unwrap_or(&new_handle); 592 (short.to_string(), new_handle.clone()) 593 } 594 None => { 595 let primary = &handle_domains[0]; 596 (new_handle.clone(), format!("{}.{}", new_handle, primary)) 597 } 598 }; 599 if full_handle == current_handle { 600 let handle: Handle = match full_handle.parse() { 601 Ok(h) => h, 602 Err(_) => return Err(ApiError::InvalidHandle(None)), 603 }; 604 if let Err(e) = 605 tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await 606 { 607 warn!("Failed to sequence identity event for handle update: {}", e); 608 } 609 return Ok(Json(EmptyResponse {})); 610 } 611 if short_part.contains('.') { 612 return Err(ApiError::InvalidHandle(Some( 613 "Nested subdomains are not allowed. Use a simple handle without dots.".into(), 614 ))); 615 } 616 if short_part.len() < 3 { 617 return Err(ApiError::InvalidHandle(Some("Handle too short".into()))); 618 } 619 if short_part.len() > 18 { 620 return Err(ApiError::InvalidHandle(Some("Handle too long".into()))); 621 } 622 full_handle 623 .parse() 624 .map_err(|_| ApiError::InvalidHandle(Some("Invalid handle format".into())))? 625 } else { 626 let handle: Handle = new_handle 627 .parse() 628 .map_err(|_| ApiError::InvalidHandle(Some("Invalid handle format".into())))?; 629 if new_handle == current_handle { 630 if let Err(e) = 631 tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await 632 { 633 warn!("Failed to sequence identity event for handle update: {}", e); 634 } 635 return Ok(Json(EmptyResponse {})); 636 } 637 match tranquil_pds::handle::verify_handle_ownership(&handle, &did).await { 638 Ok(()) => {} 639 Err(tranquil_pds::handle::HandleResolutionError::NotFound) => { 640 return Err(ApiError::HandleNotAvailable(None)); 641 } 642 Err(tranquil_pds::handle::HandleResolutionError::DidMismatch { expected, actual }) => { 643 return Err(ApiError::HandleNotAvailable(Some(format!( 644 "Handle points to different DID. Expected {}, got {}", 645 expected, actual 646 )))); 647 } 648 Err(e) => { 649 warn!("Handle verification failed: {}", e); 650 return Err(ApiError::HandleNotAvailable(Some(format!( 651 "Handle verification failed: {}", 652 e 653 )))); 654 } 655 } 656 handle 657 }; 658 let handle_exists = state 659 .repos 660 .user 661 .check_handle_exists(&handle, user_id) 662 .await 663 .log_db_err("checking handle existence")?; 664 if handle_exists { 665 return Err(ApiError::HandleTaken); 666 } 667 state 668 .repos 669 .user 670 .update_handle(user_id, &handle) 671 .await 672 .map_err(|e| { 673 error!("DB error updating handle: {:?}", e); 674 ApiError::InternalError(None) 675 })?; 676 677 if !current_handle.is_empty() { 678 let _ = state 679 .cache 680 .delete(&tranquil_pds::cache_keys::handle_key(&current_handle)) 681 .await; 682 } 683 let _ = state 684 .cache 685 .delete(&tranquil_pds::cache_keys::handle_key(&handle)) 686 .await; 687 if let Err(e) = 688 tranquil_pds::repo_ops::sequence_identity_event(&state, &did, Some(&handle)).await 689 { 690 warn!("Failed to sequence identity event for handle update: {}", e); 691 } 692 if let Err(e) = update_plc_handle(&state, &did, &handle).await { 693 warn!("Failed to update PLC handle: {}", e); 694 } 695 Ok(Json(EmptyResponse {})) 696} 697 698pub async fn update_plc_handle( 699 state: &AppState, 700 did: &tranquil_pds::types::Did, 701 new_handle: &Handle, 702) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 703 if !did.as_str().starts_with("did:plc:") { 704 return Ok(()); 705 } 706 let user_row = match state.repos.user.get_user_with_key_by_did(did).await? { 707 Some(r) => r, 708 None => return Ok(()), 709 }; 710 let key_bytes = 711 tranquil_pds::config::decrypt_key(&user_row.key_bytes, user_row.encryption_version)?; 712 let signing_key = k256::ecdsa::SigningKey::from_slice(&key_bytes)?; 713 let plc_client = state.plc_client(); 714 let last_op = plc_client.get_last_op(did).await?; 715 let new_also_known_as = vec![format!("at://{}", new_handle)]; 716 let update_op = 717 tranquil_pds::plc::create_update_op(&last_op, None, None, Some(new_also_known_as), None)?; 718 let signed_op = tranquil_pds::plc::sign_operation(&update_op, &signing_key)?; 719 plc_client.send_operation(did, &signed_op).await?; 720 Ok(()) 721} 722 723pub async fn well_known_atproto_did(State(state): State<AppState>, headers: HeaderMap) -> Response { 724 let host = match tranquil_pds::util::get_header_str(&headers, http::header::HOST) { 725 Some(h) => h, 726 None => return (StatusCode::BAD_REQUEST, "Missing host header").into_response(), 727 }; 728 let handle_str = host.split(':').next().unwrap_or(host); 729 let handle: Handle = match handle_str.parse() { 730 Ok(h) => h, 731 Err(_) => return (StatusCode::BAD_REQUEST, "Invalid handle format").into_response(), 732 }; 733 let user = state.repos.user.get_by_handle(&handle).await; 734 match user { 735 Ok(Some(row)) => row.did.to_string().into_response(), 736 Ok(None) => (StatusCode::NOT_FOUND, "Handle not found").into_response(), 737 Err(e) => { 738 error!("DB error in well-known atproto-did: {:?}", e); 739 (StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response() 740 } 741 } 742}