this repo has no description
1use crate::api::error::ApiError; 2use crate::api::repo::record::utils::create_signed_commit; 3use crate::auth::BearerAuth; 4use crate::delegation::{self, DelegationActionType}; 5use crate::oauth::db as oauth_db; 6use crate::state::{AppState, RateLimitKind}; 7use crate::types::{Did, Handle}; 8use crate::util::extract_client_ip; 9use axum::{ 10 Json, 11 extract::{Query, State}, 12 http::{HeaderMap, StatusCode}, 13 response::{IntoResponse, Response}, 14}; 15use jacquard::types::{integer::LimitedU32, string::Tid}; 16use jacquard_repo::{mst::Mst, storage::BlockStore}; 17use serde::{Deserialize, Serialize}; 18use serde_json::json; 19use std::sync::Arc; 20use tracing::{error, info, warn}; 21 22#[derive(Debug, Serialize)] 23#[serde(rename_all = "camelCase")] 24pub struct ControllerInfo { 25 pub did: Did, 26 pub handle: Handle, 27 pub granted_scopes: String, 28 pub granted_at: chrono::DateTime<chrono::Utc>, 29 pub is_active: bool, 30} 31 32#[derive(Debug, Serialize)] 33pub struct ListControllersResponse { 34 pub controllers: Vec<ControllerInfo>, 35} 36 37pub async fn list_controllers(State(state): State<AppState>, auth: BearerAuth) -> Response { 38 let controllers = match delegation::get_delegations_for_account(&state.db, &auth.0.did).await { 39 Ok(c) => c, 40 Err(e) => { 41 tracing::error!("Failed to list controllers: {:?}", e); 42 return ApiError::InternalError(Some("Failed to list controllers".into())).into_response(); 43 } 44 }; 45 46 Json(ListControllersResponse { 47 controllers: controllers 48 .into_iter() 49 .map(|c| ControllerInfo { 50 did: c.did.into(), 51 handle: c.handle, 52 granted_scopes: c.granted_scopes, 53 granted_at: c.granted_at, 54 is_active: c.is_active, 55 }) 56 .collect(), 57 }) 58 .into_response() 59} 60 61#[derive(Debug, Deserialize)] 62pub struct AddControllerInput { 63 pub controller_did: Did, 64 pub granted_scopes: String, 65} 66 67pub async fn add_controller( 68 State(state): State<AppState>, 69 auth: BearerAuth, 70 Json(input): Json<AddControllerInput>, 71) -> Response { 72 if let Err(e) = delegation::scopes::validate_delegation_scopes(&input.granted_scopes) { 73 return ApiError::InvalidScopes(e).into_response(); 74 } 75 76 let controller_exists: bool = sqlx::query_scalar!( 77 r#"SELECT EXISTS(SELECT 1 FROM users WHERE did = $1) as "exists!""#, 78 input.controller_did.as_str() 79 ) 80 .fetch_one(&state.db) 81 .await 82 .unwrap_or(false); 83 84 if !controller_exists { 85 return ApiError::ControllerNotFound.into_response(); 86 } 87 88 match delegation::controls_any_accounts(&state.db, &auth.0.did).await { 89 Ok(true) => { 90 return ApiError::InvalidDelegation( 91 "Cannot add controllers to an account that controls other accounts".into(), 92 ) 93 .into_response(); 94 } 95 Err(e) => { 96 tracing::error!("Failed to check delegation status: {:?}", e); 97 return ApiError::InternalError(Some("Failed to verify delegation status".into())) 98 .into_response(); 99 } 100 Ok(false) => {} 101 } 102 103 match delegation::has_any_controllers(&state.db, &input.controller_did).await { 104 Ok(true) => { 105 return ApiError::InvalidDelegation( 106 "Cannot add a controlled account as a controller".into(), 107 ) 108 .into_response(); 109 } 110 Err(e) => { 111 tracing::error!("Failed to check controller status: {:?}", e); 112 return ApiError::InternalError(Some("Failed to verify controller status".into())) 113 .into_response(); 114 } 115 Ok(false) => {} 116 } 117 118 match delegation::create_delegation( 119 &state.db, 120 &auth.0.did, 121 &input.controller_did, 122 &input.granted_scopes, 123 &auth.0.did, 124 ) 125 .await 126 { 127 Ok(_) => { 128 let _ = delegation::log_delegation_action( 129 &state.db, 130 &auth.0.did, 131 &auth.0.did, 132 Some(&input.controller_did), 133 DelegationActionType::GrantCreated, 134 Some(serde_json::json!({ 135 "granted_scopes": input.granted_scopes 136 })), 137 None, 138 None, 139 ) 140 .await; 141 142 ( 143 StatusCode::OK, 144 Json(serde_json::json!({ 145 "success": true 146 })), 147 ) 148 .into_response() 149 } 150 Err(e) => { 151 tracing::error!("Failed to add controller: {:?}", e); 152 ApiError::InternalError(Some("Failed to add controller".into())).into_response() 153 } 154 } 155} 156 157#[derive(Debug, Deserialize)] 158pub struct RemoveControllerInput { 159 pub controller_did: Did, 160} 161 162pub async fn remove_controller( 163 State(state): State<AppState>, 164 auth: BearerAuth, 165 Json(input): Json<RemoveControllerInput>, 166) -> Response { 167 match delegation::revoke_delegation(&state.db, &auth.0.did, &input.controller_did, &auth.0.did) 168 .await 169 { 170 Ok(true) => { 171 let revoked_app_passwords = sqlx::query_scalar!( 172 r#"DELETE FROM app_passwords 173 WHERE user_id = (SELECT id FROM users WHERE did = $1) 174 AND created_by_controller_did = $2 175 RETURNING id"#, 176 &auth.0.did, 177 input.controller_did.as_str() 178 ) 179 .fetch_all(&state.db) 180 .await 181 .map(|r| r.len()) 182 .unwrap_or(0); 183 184 let revoked_oauth_tokens = oauth_db::revoke_tokens_for_controller( 185 &state.db, 186 &auth.0.did, 187 &input.controller_did, 188 ) 189 .await 190 .unwrap_or(0); 191 192 let _ = delegation::log_delegation_action( 193 &state.db, 194 &auth.0.did, 195 &auth.0.did, 196 Some(&input.controller_did), 197 DelegationActionType::GrantRevoked, 198 Some(serde_json::json!({ 199 "revoked_app_passwords": revoked_app_passwords, 200 "revoked_oauth_tokens": revoked_oauth_tokens 201 })), 202 None, 203 None, 204 ) 205 .await; 206 207 ( 208 StatusCode::OK, 209 Json(serde_json::json!({ 210 "success": true 211 })), 212 ) 213 .into_response() 214 } 215 Ok(false) => ApiError::DelegationNotFound.into_response(), 216 Err(e) => { 217 tracing::error!("Failed to remove controller: {:?}", e); 218 ApiError::InternalError(Some("Failed to remove controller".into())).into_response() 219 } 220 } 221} 222 223#[derive(Debug, Deserialize)] 224pub struct UpdateControllerScopesInput { 225 pub controller_did: Did, 226 pub granted_scopes: String, 227} 228 229pub async fn update_controller_scopes( 230 State(state): State<AppState>, 231 auth: BearerAuth, 232 Json(input): Json<UpdateControllerScopesInput>, 233) -> Response { 234 if let Err(e) = delegation::scopes::validate_delegation_scopes(&input.granted_scopes) { 235 return ApiError::InvalidScopes(e).into_response(); 236 } 237 238 match delegation::update_delegation_scopes( 239 &state.db, 240 &auth.0.did, 241 &input.controller_did, 242 &input.granted_scopes, 243 ) 244 .await 245 { 246 Ok(true) => { 247 let _ = delegation::log_delegation_action( 248 &state.db, 249 &auth.0.did, 250 &auth.0.did, 251 Some(&input.controller_did), 252 DelegationActionType::ScopesModified, 253 Some(serde_json::json!({ 254 "new_scopes": input.granted_scopes 255 })), 256 None, 257 None, 258 ) 259 .await; 260 261 ( 262 StatusCode::OK, 263 Json(serde_json::json!({ 264 "success": true 265 })), 266 ) 267 .into_response() 268 } 269 Ok(false) => ApiError::DelegationNotFound.into_response(), 270 Err(e) => { 271 tracing::error!("Failed to update controller scopes: {:?}", e); 272 ApiError::InternalError(Some("Failed to update controller scopes".into())).into_response() 273 } 274 } 275} 276 277#[derive(Debug, Serialize)] 278#[serde(rename_all = "camelCase")] 279pub struct DelegatedAccountInfo { 280 pub did: Did, 281 pub handle: Handle, 282 pub granted_scopes: String, 283 pub granted_at: chrono::DateTime<chrono::Utc>, 284} 285 286#[derive(Debug, Serialize)] 287pub struct ListControlledAccountsResponse { 288 pub accounts: Vec<DelegatedAccountInfo>, 289} 290 291pub async fn list_controlled_accounts(State(state): State<AppState>, auth: BearerAuth) -> Response { 292 let accounts = match delegation::get_accounts_controlled_by(&state.db, &auth.0.did).await { 293 Ok(a) => a, 294 Err(e) => { 295 tracing::error!("Failed to list controlled accounts: {:?}", e); 296 return ApiError::InternalError(Some("Failed to list controlled accounts".into())) 297 .into_response(); 298 } 299 }; 300 301 Json(ListControlledAccountsResponse { 302 accounts: accounts 303 .into_iter() 304 .map(|a| DelegatedAccountInfo { 305 did: a.did.into(), 306 handle: a.handle, 307 granted_scopes: a.granted_scopes, 308 granted_at: a.granted_at, 309 }) 310 .collect(), 311 }) 312 .into_response() 313} 314 315#[derive(Debug, Deserialize)] 316pub struct AuditLogParams { 317 #[serde(default = "default_limit")] 318 pub limit: i64, 319 #[serde(default)] 320 pub offset: i64, 321} 322 323fn default_limit() -> i64 { 324 50 325} 326 327#[derive(Debug, Serialize)] 328#[serde(rename_all = "camelCase")] 329pub struct AuditLogEntry { 330 pub id: String, 331 pub delegated_did: Did, 332 pub actor_did: Did, 333 pub controller_did: Option<Did>, 334 pub action_type: String, 335 pub action_details: Option<serde_json::Value>, 336 pub created_at: chrono::DateTime<chrono::Utc>, 337} 338 339#[derive(Debug, Serialize)] 340pub struct GetAuditLogResponse { 341 pub entries: Vec<AuditLogEntry>, 342 pub total: i64, 343} 344 345pub async fn get_audit_log( 346 State(state): State<AppState>, 347 auth: BearerAuth, 348 Query(params): Query<AuditLogParams>, 349) -> Response { 350 let limit = params.limit.clamp(1, 100); 351 let offset = params.offset.max(0); 352 353 let entries = 354 match delegation::audit::get_audit_log_for_account(&state.db, &auth.0.did, limit, offset) 355 .await 356 { 357 Ok(e) => e, 358 Err(e) => { 359 tracing::error!("Failed to get audit log: {:?}", e); 360 return ApiError::InternalError(Some("Failed to get audit log".into())).into_response(); 361 } 362 }; 363 364 let total = delegation::audit::count_audit_log_entries(&state.db, &auth.0.did) 365 .await 366 .unwrap_or_default(); 367 368 Json(GetAuditLogResponse { 369 entries: entries 370 .into_iter() 371 .map(|e| AuditLogEntry { 372 id: e.id.to_string(), 373 delegated_did: e.delegated_did.into(), 374 actor_did: e.actor_did.into(), 375 controller_did: e.controller_did.map(Into::into), 376 action_type: format!("{:?}", e.action_type), 377 action_details: e.action_details, 378 created_at: e.created_at, 379 }) 380 .collect(), 381 total, 382 }) 383 .into_response() 384} 385 386#[derive(Debug, Serialize)] 387pub struct ScopePresetInfo { 388 pub name: &'static str, 389 pub label: &'static str, 390 pub description: &'static str, 391 pub scopes: &'static str, 392} 393 394#[derive(Debug, Serialize)] 395pub struct GetScopePresetsResponse { 396 pub presets: Vec<ScopePresetInfo>, 397} 398 399pub async fn get_scope_presets() -> Response { 400 Json(GetScopePresetsResponse { 401 presets: delegation::SCOPE_PRESETS 402 .iter() 403 .map(|p| ScopePresetInfo { 404 name: p.name, 405 label: p.label, 406 description: p.description, 407 scopes: p.scopes, 408 }) 409 .collect(), 410 }) 411 .into_response() 412} 413 414#[derive(Debug, Deserialize)] 415#[serde(rename_all = "camelCase")] 416pub struct CreateDelegatedAccountInput { 417 pub handle: String, 418 pub email: Option<String>, 419 pub controller_scopes: String, 420 pub invite_code: Option<String>, 421} 422 423#[derive(Debug, Serialize)] 424#[serde(rename_all = "camelCase")] 425pub struct CreateDelegatedAccountResponse { 426 pub did: Did, 427 pub handle: Handle, 428} 429 430pub async fn create_delegated_account( 431 State(state): State<AppState>, 432 headers: HeaderMap, 433 auth: BearerAuth, 434 Json(input): Json<CreateDelegatedAccountInput>, 435) -> Response { 436 let client_ip = extract_client_ip(&headers); 437 if !state 438 .check_rate_limit(RateLimitKind::AccountCreation, &client_ip) 439 .await 440 { 441 warn!(ip = %client_ip, "Delegated account creation rate limit exceeded"); 442 return ApiError::RateLimitExceeded(Some( 443 "Too many account creation attempts. Please try again later.".into(), 444 )) 445 .into_response(); 446 } 447 448 if let Err(e) = delegation::scopes::validate_delegation_scopes(&input.controller_scopes) { 449 return ApiError::InvalidScopes(e).into_response(); 450 } 451 452 match delegation::has_any_controllers(&state.db, &auth.0.did).await { 453 Ok(true) => { 454 return ApiError::InvalidDelegation( 455 "Cannot create delegated accounts from a controlled account".into(), 456 ) 457 .into_response(); 458 } 459 Err(e) => { 460 tracing::error!("Failed to check controller status: {:?}", e); 461 return ApiError::InternalError(Some("Failed to verify controller status".into())) 462 .into_response(); 463 } 464 Ok(false) => {} 465 } 466 467 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 468 let pds_suffix = format!(".{}", hostname); 469 470 let handle = if !input.handle.contains('.') || input.handle.ends_with(&pds_suffix) { 471 let handle_to_validate = if input.handle.ends_with(&pds_suffix) { 472 input 473 .handle 474 .strip_suffix(&pds_suffix) 475 .unwrap_or(&input.handle) 476 } else { 477 &input.handle 478 }; 479 match crate::api::validation::validate_short_handle(handle_to_validate) { 480 Ok(h) => format!("{}.{}", h, hostname), 481 Err(e) => { 482 return ApiError::InvalidRequest(e.to_string()).into_response(); 483 } 484 } 485 } else { 486 input.handle.to_lowercase() 487 }; 488 489 let email = input 490 .email 491 .as_ref() 492 .map(|e| e.trim().to_string()) 493 .filter(|e| !e.is_empty()); 494 if let Some(ref email) = email 495 && !crate::api::validation::is_valid_email(email) 496 { 497 return ApiError::InvalidEmail.into_response(); 498 } 499 500 if let Some(ref code) = input.invite_code { 501 let valid = sqlx::query_scalar!( 502 "SELECT available_uses > 0 AND NOT disabled FROM invite_codes WHERE code = $1", 503 code 504 ) 505 .fetch_optional(&state.db) 506 .await 507 .ok() 508 .flatten() 509 .unwrap_or(Some(false)); 510 511 if valid != Some(true) { 512 return ApiError::InvalidInviteCode.into_response(); 513 } 514 } else { 515 let invite_required = std::env::var("INVITE_CODE_REQUIRED") 516 .map(|v| v == "true" || v == "1") 517 .unwrap_or(false); 518 if invite_required { 519 return ApiError::InviteCodeRequired.into_response(); 520 } 521 } 522 523 use k256::ecdsa::SigningKey; 524 use rand::rngs::OsRng; 525 526 let pds_endpoint = format!("https://{}", hostname); 527 let secret_key = k256::SecretKey::random(&mut OsRng); 528 let secret_key_bytes = secret_key.to_bytes().to_vec(); 529 530 let signing_key = match SigningKey::from_slice(&secret_key_bytes) { 531 Ok(k) => k, 532 Err(e) => { 533 error!("Error creating signing key: {:?}", e); 534 return ApiError::InternalError(None).into_response(); 535 } 536 }; 537 538 let rotation_key = std::env::var("PLC_ROTATION_KEY") 539 .unwrap_or_else(|_| crate::plc::signing_key_to_did_key(&signing_key)); 540 541 let genesis_result = match crate::plc::create_genesis_operation( 542 &signing_key, 543 &rotation_key, 544 &handle, 545 &pds_endpoint, 546 ) { 547 Ok(r) => r, 548 Err(e) => { 549 error!("Error creating PLC genesis operation: {:?}", e); 550 return ApiError::InternalError(Some("Failed to create PLC operation".into())) 551 .into_response(); 552 } 553 }; 554 555 let plc_client = crate::plc::PlcClient::with_cache(None, Some(state.cache.clone())); 556 if let Err(e) = plc_client 557 .send_operation(&genesis_result.did, &genesis_result.signed_operation) 558 .await 559 { 560 error!("Failed to submit PLC genesis operation: {:?}", e); 561 return ApiError::UpstreamErrorMsg(format!( 562 "Failed to register DID with PLC directory: {}", 563 e 564 )) 565 .into_response(); 566 } 567 568 let did = genesis_result.did; 569 info!(did = %did, handle = %handle, controller = %&auth.0.did, "Created DID for delegated account"); 570 571 let mut tx = match state.db.begin().await { 572 Ok(tx) => tx, 573 Err(e) => { 574 error!("Error starting transaction: {:?}", e); 575 return ApiError::InternalError(None).into_response(); 576 } 577 }; 578 579 let user_insert: Result<(uuid::Uuid,), _> = sqlx::query_as( 580 r#"INSERT INTO users ( 581 handle, email, did, password_hash, password_required, 582 account_type, preferred_comms_channel 583 ) VALUES ($1, $2, $3, NULL, FALSE, 'delegated'::account_type, 'email'::comms_channel) RETURNING id"#, 584 ) 585 .bind(&handle) 586 .bind(&email) 587 .bind(&did) 588 .fetch_one(&mut *tx) 589 .await; 590 591 let user_id = match user_insert { 592 Ok((id,)) => id, 593 Err(e) => { 594 if let Some(db_err) = e.as_database_error() 595 && db_err.code().as_deref() == Some("23505") 596 { 597 let constraint = db_err.constraint().unwrap_or(""); 598 if constraint.contains("handle") { 599 return ApiError::HandleNotAvailable(None).into_response(); 600 } else if constraint.contains("email") { 601 return ApiError::EmailTaken.into_response(); 602 } 603 } 604 error!("Error inserting user: {:?}", e); 605 return ApiError::InternalError(None).into_response(); 606 } 607 }; 608 609 let encrypted_key_bytes = match crate::config::encrypt_key(&secret_key_bytes) { 610 Ok(bytes) => bytes, 611 Err(e) => { 612 error!("Error encrypting signing key: {:?}", e); 613 return ApiError::InternalError(None).into_response(); 614 } 615 }; 616 617 if let Err(e) = sqlx::query!( 618 "INSERT INTO user_keys (user_id, key_bytes, encryption_version, encrypted_at) VALUES ($1, $2, $3, NOW())", 619 user_id, 620 &encrypted_key_bytes[..], 621 crate::config::ENCRYPTION_VERSION 622 ) 623 .execute(&mut *tx) 624 .await 625 { 626 error!("Error inserting user key: {:?}", e); 627 return ApiError::InternalError(None).into_response(); 628 } 629 630 if let Err(e) = sqlx::query!( 631 r#"INSERT INTO account_delegations (delegated_did, controller_did, granted_scopes, granted_by) 632 VALUES ($1, $2, $3, $4)"#, 633 did, 634 &auth.0.did, 635 input.controller_scopes, 636 &auth.0.did 637 ) 638 .execute(&mut *tx) 639 .await 640 { 641 error!("Error creating initial delegation: {:?}", e); 642 return ApiError::InternalError(None).into_response(); 643 } 644 645 let mst = Mst::new(Arc::new(state.block_store.clone())); 646 let mst_root = match mst.persist().await { 647 Ok(c) => c, 648 Err(e) => { 649 error!("Error persisting MST: {:?}", e); 650 return ApiError::InternalError(None).into_response(); 651 } 652 }; 653 let rev = Tid::now(LimitedU32::MIN); 654 let (commit_bytes, _sig) = 655 match create_signed_commit(&did, mst_root, rev.as_ref(), None, &signing_key) { 656 Ok(result) => result, 657 Err(e) => { 658 error!("Error creating genesis commit: {:?}", e); 659 return ApiError::InternalError(None).into_response(); 660 } 661 }; 662 let commit_cid: cid::Cid = match state.block_store.put(&commit_bytes).await { 663 Ok(c) => c, 664 Err(e) => { 665 error!("Error saving genesis commit: {:?}", e); 666 return ApiError::InternalError(None).into_response(); 667 } 668 }; 669 let commit_cid_str = commit_cid.to_string(); 670 let rev_str = rev.as_ref().to_string(); 671 if let Err(e) = sqlx::query!( 672 "INSERT INTO repos (user_id, repo_root_cid, repo_rev) VALUES ($1, $2, $3)", 673 user_id, 674 commit_cid_str, 675 rev_str 676 ) 677 .execute(&mut *tx) 678 .await 679 { 680 error!("Error inserting repo: {:?}", e); 681 return ApiError::InternalError(None).into_response(); 682 } 683 let genesis_block_cids = vec![mst_root.to_bytes(), commit_cid.to_bytes()]; 684 if let Err(e) = sqlx::query!( 685 r#" 686 INSERT INTO user_blocks (user_id, block_cid) 687 SELECT $1, block_cid FROM UNNEST($2::bytea[]) AS t(block_cid) 688 ON CONFLICT (user_id, block_cid) DO NOTHING 689 "#, 690 user_id, 691 &genesis_block_cids 692 ) 693 .execute(&mut *tx) 694 .await 695 { 696 error!("Error inserting user_blocks: {:?}", e); 697 return ApiError::InternalError(None).into_response(); 698 } 699 700 if let Some(ref code) = input.invite_code { 701 let _ = sqlx::query!( 702 "UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1", 703 code 704 ) 705 .execute(&mut *tx) 706 .await; 707 708 let _ = sqlx::query!( 709 "INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)", 710 code, 711 user_id 712 ) 713 .execute(&mut *tx) 714 .await; 715 } 716 717 if let Err(e) = tx.commit().await { 718 error!("Error committing transaction: {:?}", e); 719 return ApiError::InternalError(None).into_response(); 720 } 721 722 if let Err(e) = 723 crate::api::repo::record::sequence_identity_event(&state, &did, Some(&handle)).await 724 { 725 warn!("Failed to sequence identity event for {}: {}", did, e); 726 } 727 if let Err(e) = crate::api::repo::record::sequence_account_event(&state, &did, true, None).await 728 { 729 warn!("Failed to sequence account event for {}: {}", did, e); 730 } 731 732 let profile_record = json!({ 733 "$type": "app.bsky.actor.profile", 734 "displayName": handle 735 }); 736 if let Err(e) = crate::api::repo::record::create_record_internal( 737 &state, 738 &did, 739 "app.bsky.actor.profile", 740 "self", 741 &profile_record, 742 ) 743 .await 744 { 745 warn!("Failed to create default profile for {}: {}", did, e); 746 } 747 748 let _ = delegation::log_delegation_action( 749 &state.db, 750 &did, 751 &auth.0.did, 752 Some(&auth.0.did), 753 DelegationActionType::GrantCreated, 754 Some(json!({ 755 "account_created": true, 756 "granted_scopes": input.controller_scopes 757 })), 758 None, 759 None, 760 ) 761 .await; 762 763 info!(did = %did, handle = %handle, controller = %&auth.0.did, "Delegated account created"); 764 765 Json(CreateDelegatedAccountResponse { did: did.into(), handle: handle.into() }).into_response() 766}