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