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}; 18 19#[derive(Deserialize)] 20pub struct CreateAccountInput { 21 pub handle: String, 22 pub email: String, 23 pub password: String, 24 #[serde(rename = "inviteCode")] 25 pub invite_code: Option<String>, 26 pub did: Option<String>, 27} 28 29#[derive(Serialize)] 30#[serde(rename_all = "camelCase")] 31pub struct CreateAccountOutput { 32 pub access_jwt: String, 33 pub refresh_jwt: String, 34 pub handle: String, 35 pub did: String, 36} 37 38pub async fn create_account( 39 State(state): State<AppState>, 40 Json(input): Json<CreateAccountInput>, 41) -> Response { 42 info!("create_account hit: {}", input.handle); 43 if input.handle.contains('!') || input.handle.contains('@') { 44 return ( 45 StatusCode::BAD_REQUEST, 46 Json( 47 json!({"error": "InvalidHandle", "message": "Handle contains invalid characters"}), 48 ), 49 ) 50 .into_response(); 51 } 52 53 let did = if let Some(d) = &input.did { 54 if d.trim().is_empty() { 55 format!("did:plc:{}", uuid::Uuid::new_v4()) 56 } else { 57 let hostname = 58 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 59 if let Err(e) = verify_did_web(d, &hostname, &input.handle).await { 60 return ( 61 StatusCode::BAD_REQUEST, 62 Json(json!({"error": "InvalidDid", "message": e})), 63 ) 64 .into_response(); 65 } 66 d.clone() 67 } 68 } else { 69 format!("did:plc:{}", uuid::Uuid::new_v4()) 70 }; 71 72 let mut tx = match state.db.begin().await { 73 Ok(tx) => tx, 74 Err(e) => { 75 error!("Error starting transaction: {:?}", e); 76 return ( 77 StatusCode::INTERNAL_SERVER_ERROR, 78 Json(json!({"error": "InternalError"})), 79 ) 80 .into_response(); 81 } 82 }; 83 84 let exists_query = sqlx::query!("SELECT 1 as one FROM users WHERE handle = $1", input.handle) 85 .fetch_optional(&mut *tx) 86 .await; 87 88 match exists_query { 89 Ok(Some(_)) => { 90 return ( 91 StatusCode::BAD_REQUEST, 92 Json(json!({"error": "HandleTaken", "message": "Handle already taken"})), 93 ) 94 .into_response(); 95 } 96 Err(e) => { 97 error!("Error checking handle: {:?}", e); 98 return ( 99 StatusCode::INTERNAL_SERVER_ERROR, 100 Json(json!({"error": "InternalError"})), 101 ) 102 .into_response(); 103 } 104 Ok(None) => {} 105 } 106 107 if let Some(code) = &input.invite_code { 108 let invite_query = 109 sqlx::query!("SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE", code) 110 .fetch_optional(&mut *tx) 111 .await; 112 113 match invite_query { 114 Ok(Some(row)) => { 115 if row.available_uses <= 0 { 116 return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidInviteCode", "message": "Invite code exhausted"}))).into_response(); 117 } 118 119 let update_invite = sqlx::query!( 120 "UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1", 121 code 122 ) 123 .execute(&mut *tx) 124 .await; 125 126 if let Err(e) = update_invite { 127 error!("Error updating invite code: {:?}", e); 128 return ( 129 StatusCode::INTERNAL_SERVER_ERROR, 130 Json(json!({"error": "InternalError"})), 131 ) 132 .into_response(); 133 } 134 } 135 Ok(None) => { 136 return ( 137 StatusCode::BAD_REQUEST, 138 Json(json!({"error": "InvalidInviteCode", "message": "Invite code not found"})), 139 ) 140 .into_response(); 141 } 142 Err(e) => { 143 error!("Error checking invite code: {:?}", e); 144 return ( 145 StatusCode::INTERNAL_SERVER_ERROR, 146 Json(json!({"error": "InternalError"})), 147 ) 148 .into_response(); 149 } 150 } 151 } 152 153 let password_hash = match hash(&input.password, DEFAULT_COST) { 154 Ok(h) => h, 155 Err(e) => { 156 error!("Error hashing password: {:?}", e); 157 return ( 158 StatusCode::INTERNAL_SERVER_ERROR, 159 Json(json!({"error": "InternalError"})), 160 ) 161 .into_response(); 162 } 163 }; 164 165 let user_insert = sqlx::query!( 166 "INSERT INTO users (handle, email, did, password_hash) VALUES ($1, $2, $3, $4) RETURNING id", 167 input.handle, 168 input.email, 169 did, 170 password_hash 171 ) 172 .fetch_one(&mut *tx) 173 .await; 174 175 let user_id = match user_insert { 176 Ok(row) => row.id, 177 Err(e) => { 178 error!("Error inserting user: {:?}", e); 179 // TODO: Check for unique constraint violation on email/did specifically 180 return ( 181 StatusCode::INTERNAL_SERVER_ERROR, 182 Json(json!({"error": "InternalError"})), 183 ) 184 .into_response(); 185 } 186 }; 187 188 let secret_key = SecretKey::random(&mut OsRng); 189 let secret_key_bytes = secret_key.to_bytes(); 190 191 let key_insert = sqlx::query!("INSERT INTO user_keys (user_id, key_bytes) VALUES ($1, $2)", user_id, &secret_key_bytes[..]) 192 .execute(&mut *tx) 193 .await; 194 195 if let Err(e) = key_insert { 196 error!("Error inserting user key: {:?}", e); 197 return ( 198 StatusCode::INTERNAL_SERVER_ERROR, 199 Json(json!({"error": "InternalError"})), 200 ) 201 .into_response(); 202 } 203 204 let mst = Mst::new(Arc::new(state.block_store.clone())); 205 let mst_root = match mst.persist().await { 206 Ok(c) => c, 207 Err(e) => { 208 error!("Error persisting MST: {:?}", e); 209 return ( 210 StatusCode::INTERNAL_SERVER_ERROR, 211 Json(json!({"error": "InternalError"})), 212 ) 213 .into_response(); 214 } 215 }; 216 217 let did_obj = match Did::new(&did) { 218 Ok(d) => d, 219 Err(_) => { 220 return ( 221 StatusCode::INTERNAL_SERVER_ERROR, 222 Json(json!({"error": "InternalError", "message": "Invalid DID"})), 223 ) 224 .into_response(); 225 } 226 }; 227 228 let rev = Tid::now(LimitedU32::MIN); 229 230 let commit = Commit::new_unsigned(did_obj, mst_root, rev, None); 231 232 let commit_bytes = match commit.to_cbor() { 233 Ok(b) => b, 234 Err(e) => { 235 error!("Error serializing genesis commit: {:?}", e); 236 return ( 237 StatusCode::INTERNAL_SERVER_ERROR, 238 Json(json!({"error": "InternalError"})), 239 ) 240 .into_response(); 241 } 242 }; 243 244 let commit_cid = match state.block_store.put(&commit_bytes).await { 245 Ok(c) => c, 246 Err(e) => { 247 error!("Error saving genesis commit: {:?}", e); 248 return ( 249 StatusCode::INTERNAL_SERVER_ERROR, 250 Json(json!({"error": "InternalError"})), 251 ) 252 .into_response(); 253 } 254 }; 255 256 let commit_cid_str = commit_cid.to_string(); 257 let repo_insert = sqlx::query!("INSERT INTO repos (user_id, repo_root_cid) VALUES ($1, $2)", user_id, commit_cid_str) 258 .execute(&mut *tx) 259 .await; 260 261 if let Err(e) = repo_insert { 262 error!("Error initializing repo: {:?}", e); 263 return ( 264 StatusCode::INTERNAL_SERVER_ERROR, 265 Json(json!({"error": "InternalError"})), 266 ) 267 .into_response(); 268 } 269 270 if let Some(code) = &input.invite_code { 271 let use_insert = 272 sqlx::query!("INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)", code, user_id) 273 .execute(&mut *tx) 274 .await; 275 276 if let Err(e) = use_insert { 277 error!("Error recording invite usage: {:?}", e); 278 return ( 279 StatusCode::INTERNAL_SERVER_ERROR, 280 Json(json!({"error": "InternalError"})), 281 ) 282 .into_response(); 283 } 284 } 285 286 let access_jwt = crate::auth::create_access_token(&did, &secret_key_bytes[..]).map_err(|e| { 287 error!("Error creating access token: {:?}", e); 288 ( 289 StatusCode::INTERNAL_SERVER_ERROR, 290 Json(json!({"error": "InternalError"})), 291 ) 292 .into_response() 293 }); 294 let access_jwt = match access_jwt { 295 Ok(t) => t, 296 Err(r) => return r, 297 }; 298 299 let refresh_jwt = crate::auth::create_refresh_token(&did, &secret_key_bytes[..]).map_err(|e| { 300 error!("Error creating refresh token: {:?}", e); 301 ( 302 StatusCode::INTERNAL_SERVER_ERROR, 303 Json(json!({"error": "InternalError"})), 304 ) 305 .into_response() 306 }); 307 let refresh_jwt = match refresh_jwt { 308 Ok(t) => t, 309 Err(r) => return r, 310 }; 311 312 let session_insert = 313 sqlx::query!("INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)", access_jwt, refresh_jwt, did) 314 .execute(&mut *tx) 315 .await; 316 317 if let Err(e) = session_insert { 318 error!("Error inserting session: {:?}", e); 319 return ( 320 StatusCode::INTERNAL_SERVER_ERROR, 321 Json(json!({"error": "InternalError"})), 322 ) 323 .into_response(); 324 } 325 326 if let Err(e) = tx.commit().await { 327 error!("Error committing transaction: {:?}", e); 328 return ( 329 StatusCode::INTERNAL_SERVER_ERROR, 330 Json(json!({"error": "InternalError"})), 331 ) 332 .into_response(); 333 } 334 335 ( 336 StatusCode::OK, 337 Json(CreateAccountOutput { 338 access_jwt, 339 refresh_jwt, 340 handle: input.handle, 341 did, 342 }), 343 ) 344 .into_response() 345}