this repo has no description
1use super::did::verify_did_web; 2use crate::state::AppState; 3use axum::{ 4 Json, 5 extract::State, 6 http::StatusCode, 7 response::{IntoResponse, Response}, 8}; 9use bcrypt::{DEFAULT_COST, hash}; 10use jacquard::types::{did::Did, integer::LimitedU32, string::Tid}; 11use jacquard_repo::{commit::Commit, mst::Mst, storage::BlockStore}; 12use k256::SecretKey; 13use rand::rngs::OsRng; 14use serde::{Deserialize, Serialize}; 15use serde_json::json; 16use std::sync::Arc; 17use tracing::{error, info, warn}; 18 19#[derive(Deserialize)] 20#[serde(rename_all = "camelCase")] 21pub struct CreateAccountInput { 22 pub handle: String, 23 pub email: String, 24 pub password: String, 25 pub invite_code: Option<String>, 26 pub did: Option<String>, 27 pub signing_key: Option<String>, 28} 29 30#[derive(Serialize)] 31#[serde(rename_all = "camelCase")] 32pub struct CreateAccountOutput { 33 pub access_jwt: String, 34 pub refresh_jwt: String, 35 pub handle: String, 36 pub did: String, 37} 38 39pub async fn create_account( 40 State(state): State<AppState>, 41 Json(input): Json<CreateAccountInput>, 42) -> Response { 43 info!("create_account hit: {}", input.handle); 44 if input.handle.contains('!') || input.handle.contains('@') { 45 return ( 46 StatusCode::BAD_REQUEST, 47 Json( 48 json!({"error": "InvalidHandle", "message": "Handle contains invalid characters"}), 49 ), 50 ) 51 .into_response(); 52 } 53 54 let did = if let Some(d) = &input.did { 55 if d.trim().is_empty() { 56 format!("did:plc:{}", uuid::Uuid::new_v4()) 57 } else { 58 let hostname = 59 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 60 if let Err(e) = verify_did_web(d, &hostname, &input.handle).await { 61 return ( 62 StatusCode::BAD_REQUEST, 63 Json(json!({"error": "InvalidDid", "message": e})), 64 ) 65 .into_response(); 66 } 67 d.clone() 68 } 69 } else { 70 format!("did:plc:{}", uuid::Uuid::new_v4()) 71 }; 72 73 let mut tx = match state.db.begin().await { 74 Ok(tx) => tx, 75 Err(e) => { 76 error!("Error starting transaction: {:?}", e); 77 return ( 78 StatusCode::INTERNAL_SERVER_ERROR, 79 Json(json!({"error": "InternalError"})), 80 ) 81 .into_response(); 82 } 83 }; 84 85 let exists_query = sqlx::query!("SELECT 1 as one FROM users WHERE handle = $1", input.handle) 86 .fetch_optional(&mut *tx) 87 .await; 88 89 match exists_query { 90 Ok(Some(_)) => { 91 return ( 92 StatusCode::BAD_REQUEST, 93 Json(json!({"error": "HandleTaken", "message": "Handle already taken"})), 94 ) 95 .into_response(); 96 } 97 Err(e) => { 98 error!("Error checking handle: {:?}", e); 99 return ( 100 StatusCode::INTERNAL_SERVER_ERROR, 101 Json(json!({"error": "InternalError"})), 102 ) 103 .into_response(); 104 } 105 Ok(None) => {} 106 } 107 108 if let Some(code) = &input.invite_code { 109 let invite_query = 110 sqlx::query!("SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE", code) 111 .fetch_optional(&mut *tx) 112 .await; 113 114 match invite_query { 115 Ok(Some(row)) => { 116 if row.available_uses <= 0 { 117 return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidInviteCode", "message": "Invite code exhausted"}))).into_response(); 118 } 119 120 let update_invite = sqlx::query!( 121 "UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1", 122 code 123 ) 124 .execute(&mut *tx) 125 .await; 126 127 if let Err(e) = update_invite { 128 error!("Error updating invite code: {:?}", e); 129 return ( 130 StatusCode::INTERNAL_SERVER_ERROR, 131 Json(json!({"error": "InternalError"})), 132 ) 133 .into_response(); 134 } 135 } 136 Ok(None) => { 137 return ( 138 StatusCode::BAD_REQUEST, 139 Json(json!({"error": "InvalidInviteCode", "message": "Invite code not found"})), 140 ) 141 .into_response(); 142 } 143 Err(e) => { 144 error!("Error checking invite code: {:?}", e); 145 return ( 146 StatusCode::INTERNAL_SERVER_ERROR, 147 Json(json!({"error": "InternalError"})), 148 ) 149 .into_response(); 150 } 151 } 152 } 153 154 let password_hash = match hash(&input.password, DEFAULT_COST) { 155 Ok(h) => h, 156 Err(e) => { 157 error!("Error hashing password: {:?}", e); 158 return ( 159 StatusCode::INTERNAL_SERVER_ERROR, 160 Json(json!({"error": "InternalError"})), 161 ) 162 .into_response(); 163 } 164 }; 165 166 let user_insert = sqlx::query!( 167 "INSERT INTO users (handle, email, did, password_hash) VALUES ($1, $2, $3, $4) RETURNING id", 168 input.handle, 169 input.email, 170 did, 171 password_hash 172 ) 173 .fetch_one(&mut *tx) 174 .await; 175 176 let user_id = match user_insert { 177 Ok(row) => row.id, 178 Err(e) => { 179 error!("Error inserting user: {:?}", e); 180 // TODO: Check for unique constraint violation on email/did specifically 181 return ( 182 StatusCode::INTERNAL_SERVER_ERROR, 183 Json(json!({"error": "InternalError"})), 184 ) 185 .into_response(); 186 } 187 }; 188 189 let (secret_key_bytes, reserved_key_id): (Vec<u8>, Option<uuid::Uuid>) = 190 if let Some(signing_key_did) = &input.signing_key { 191 let reserved = sqlx::query!( 192 r#" 193 SELECT id, private_key_bytes 194 FROM reserved_signing_keys 195 WHERE public_key_did_key = $1 196 AND used_at IS NULL 197 AND expires_at > NOW() 198 FOR UPDATE 199 "#, 200 signing_key_did 201 ) 202 .fetch_optional(&mut *tx) 203 .await; 204 205 match reserved { 206 Ok(Some(row)) => (row.private_key_bytes, Some(row.id)), 207 Ok(None) => { 208 return ( 209 StatusCode::BAD_REQUEST, 210 Json(json!({ 211 "error": "InvalidSigningKey", 212 "message": "Signing key not found, already used, or expired" 213 })), 214 ) 215 .into_response(); 216 } 217 Err(e) => { 218 error!("Error looking up reserved signing key: {:?}", e); 219 return ( 220 StatusCode::INTERNAL_SERVER_ERROR, 221 Json(json!({"error": "InternalError"})), 222 ) 223 .into_response(); 224 } 225 } 226 } else { 227 let secret_key = SecretKey::random(&mut OsRng); 228 (secret_key.to_bytes().to_vec(), None) 229 }; 230 231 let encrypted_key_bytes = match crate::config::encrypt_key(&secret_key_bytes) { 232 Ok(enc) => enc, 233 Err(e) => { 234 error!("Error encrypting user key: {:?}", e); 235 return ( 236 StatusCode::INTERNAL_SERVER_ERROR, 237 Json(json!({"error": "InternalError"})), 238 ) 239 .into_response(); 240 } 241 }; 242 243 let key_insert = sqlx::query!( 244 "INSERT INTO user_keys (user_id, key_bytes, encryption_version, encrypted_at) VALUES ($1, $2, $3, NOW())", 245 user_id, 246 &encrypted_key_bytes[..], 247 crate::config::ENCRYPTION_VERSION 248 ) 249 .execute(&mut *tx) 250 .await; 251 252 if let Err(e) = key_insert { 253 error!("Error inserting user key: {:?}", e); 254 return ( 255 StatusCode::INTERNAL_SERVER_ERROR, 256 Json(json!({"error": "InternalError"})), 257 ) 258 .into_response(); 259 } 260 261 if let Some(key_id) = reserved_key_id { 262 let mark_used = sqlx::query!( 263 "UPDATE reserved_signing_keys SET used_at = NOW() WHERE id = $1", 264 key_id 265 ) 266 .execute(&mut *tx) 267 .await; 268 269 if let Err(e) = mark_used { 270 error!("Error marking reserved key as used: {:?}", e); 271 return ( 272 StatusCode::INTERNAL_SERVER_ERROR, 273 Json(json!({"error": "InternalError"})), 274 ) 275 .into_response(); 276 } 277 } 278 279 let mst = Mst::new(Arc::new(state.block_store.clone())); 280 let mst_root = match mst.persist().await { 281 Ok(c) => c, 282 Err(e) => { 283 error!("Error persisting MST: {:?}", e); 284 return ( 285 StatusCode::INTERNAL_SERVER_ERROR, 286 Json(json!({"error": "InternalError"})), 287 ) 288 .into_response(); 289 } 290 }; 291 292 let did_obj = match Did::new(&did) { 293 Ok(d) => d, 294 Err(_) => { 295 return ( 296 StatusCode::INTERNAL_SERVER_ERROR, 297 Json(json!({"error": "InternalError", "message": "Invalid DID"})), 298 ) 299 .into_response(); 300 } 301 }; 302 303 let rev = Tid::now(LimitedU32::MIN); 304 305 let commit = Commit::new_unsigned(did_obj, mst_root, rev, None); 306 307 let commit_bytes = match commit.to_cbor() { 308 Ok(b) => b, 309 Err(e) => { 310 error!("Error serializing genesis commit: {:?}", e); 311 return ( 312 StatusCode::INTERNAL_SERVER_ERROR, 313 Json(json!({"error": "InternalError"})), 314 ) 315 .into_response(); 316 } 317 }; 318 319 let commit_cid = match state.block_store.put(&commit_bytes).await { 320 Ok(c) => c, 321 Err(e) => { 322 error!("Error saving genesis commit: {:?}", e); 323 return ( 324 StatusCode::INTERNAL_SERVER_ERROR, 325 Json(json!({"error": "InternalError"})), 326 ) 327 .into_response(); 328 } 329 }; 330 331 let commit_cid_str = commit_cid.to_string(); 332 let repo_insert = sqlx::query!("INSERT INTO repos (user_id, repo_root_cid) VALUES ($1, $2)", user_id, commit_cid_str) 333 .execute(&mut *tx) 334 .await; 335 336 if let Err(e) = repo_insert { 337 error!("Error initializing repo: {:?}", e); 338 return ( 339 StatusCode::INTERNAL_SERVER_ERROR, 340 Json(json!({"error": "InternalError"})), 341 ) 342 .into_response(); 343 } 344 345 if let Some(code) = &input.invite_code { 346 let use_insert = 347 sqlx::query!("INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)", code, user_id) 348 .execute(&mut *tx) 349 .await; 350 351 if let Err(e) = use_insert { 352 error!("Error recording invite usage: {:?}", e); 353 return ( 354 StatusCode::INTERNAL_SERVER_ERROR, 355 Json(json!({"error": "InternalError"})), 356 ) 357 .into_response(); 358 } 359 } 360 361 let access_meta = crate::auth::create_access_token_with_metadata(&did, &secret_key_bytes[..]).map_err(|e| { 362 error!("Error creating access token: {:?}", e); 363 ( 364 StatusCode::INTERNAL_SERVER_ERROR, 365 Json(json!({"error": "InternalError"})), 366 ) 367 .into_response() 368 }); 369 let access_meta = match access_meta { 370 Ok(m) => m, 371 Err(r) => return r, 372 }; 373 374 let refresh_meta = crate::auth::create_refresh_token_with_metadata(&did, &secret_key_bytes[..]).map_err(|e| { 375 error!("Error creating refresh token: {:?}", e); 376 ( 377 StatusCode::INTERNAL_SERVER_ERROR, 378 Json(json!({"error": "InternalError"})), 379 ) 380 .into_response() 381 }); 382 let refresh_meta = match refresh_meta { 383 Ok(m) => m, 384 Err(r) => return r, 385 }; 386 387 let session_insert = 388 sqlx::query!( 389 "INSERT INTO session_tokens (did, access_jti, refresh_jti, access_expires_at, refresh_expires_at) VALUES ($1, $2, $3, $4, $5)", 390 did, 391 access_meta.jti, 392 refresh_meta.jti, 393 access_meta.expires_at, 394 refresh_meta.expires_at 395 ) 396 .execute(&mut *tx) 397 .await; 398 399 if let Err(e) = session_insert { 400 error!("Error inserting session: {:?}", e); 401 return ( 402 StatusCode::INTERNAL_SERVER_ERROR, 403 Json(json!({"error": "InternalError"})), 404 ) 405 .into_response(); 406 } 407 408 if let Err(e) = tx.commit().await { 409 error!("Error committing transaction: {:?}", e); 410 return ( 411 StatusCode::INTERNAL_SERVER_ERROR, 412 Json(json!({"error": "InternalError"})), 413 ) 414 .into_response(); 415 } 416 417 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 418 if let Err(e) = crate::notifications::enqueue_welcome_email( 419 &state.db, 420 user_id, 421 &input.email, 422 &input.handle, 423 &hostname, 424 ) 425 .await 426 { 427 warn!("Failed to enqueue welcome email: {:?}", e); 428 } 429 430 ( 431 StatusCode::OK, 432 Json(CreateAccountOutput { 433 access_jwt: access_meta.token, 434 refresh_jwt: refresh_meta.token, 435 handle: input.handle, 436 did, 437 }), 438 ) 439 .into_response() 440}