this repo has no description
1use axum::{ 2 extract::State, 3 Json, 4 response::{IntoResponse, Response}, 5 http::StatusCode, 6}; 7use serde::{Deserialize, Serialize}; 8use serde_json::json; 9use crate::state::AppState; 10use sqlx::Row; 11use bcrypt::{hash, verify, DEFAULT_COST}; 12use tracing::{info, error, warn}; 13use jacquard_repo::{mst::Mst, commit::Commit, storage::BlockStore}; 14use jacquard::types::{string::Tid, did::Did, integer::LimitedU32}; 15use std::sync::Arc; 16use k256::SecretKey; 17use rand::rngs::OsRng; 18 19pub async fn describe_server() -> impl IntoResponse { 20 let domains_str = std::env::var("AVAILABLE_USER_DOMAINS").unwrap_or_else(|_| "example.com".to_string()); 21 let domains: Vec<&str> = domains_str.split(',').map(|s| s.trim()).collect(); 22 23 Json(json!({ 24 "availableUserDomains": domains 25 })) 26} 27 28pub async fn health(State(state): State<AppState>) -> impl IntoResponse { 29 match sqlx::query("SELECT 1").execute(&state.db).await { 30 Ok(_) => (StatusCode::OK, "OK"), 31 Err(e) => { 32 error!("Health check failed: {:?}", e); 33 (StatusCode::SERVICE_UNAVAILABLE, "Service Unavailable") 34 } 35 } 36} 37 38#[derive(Deserialize)] 39pub struct CreateAccountInput { 40 pub handle: String, 41 pub email: String, 42 pub password: String, 43 #[serde(rename = "inviteCode")] 44 pub invite_code: Option<String>, 45} 46 47#[derive(Serialize)] 48#[serde(rename_all = "camelCase")] 49pub struct CreateAccountOutput { 50 pub access_jwt: String, 51 pub refresh_jwt: String, 52 pub handle: String, 53 pub did: String, 54} 55 56pub async fn create_account( 57 State(state): State<AppState>, 58 Json(input): Json<CreateAccountInput>, 59) -> Response { 60 info!("create_account hit: {}", input.handle); 61 if input.handle.contains('!') || input.handle.contains('@') { 62 return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidHandle", "message": "Handle contains invalid characters"}))).into_response(); 63 } 64 65 let mut tx = match state.db.begin().await { 66 Ok(tx) => tx, 67 Err(e) => { 68 error!("Error starting transaction: {:?}", e); 69 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 70 } 71 }; 72 73 let exists_query = sqlx::query("SELECT 1 FROM users WHERE handle = $1") 74 .bind(&input.handle) 75 .fetch_optional(&mut *tx) 76 .await; 77 78 match exists_query { 79 Ok(Some(_)) => return (StatusCode::BAD_REQUEST, Json(json!({"error": "HandleTaken", "message": "Handle already taken"}))).into_response(), 80 Err(e) => { 81 error!("Error checking handle: {:?}", e); 82 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 83 } 84 Ok(None) => {} 85 } 86 87 if let Some(code) = &input.invite_code { 88 let invite_query = sqlx::query("SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE") 89 .bind(code) 90 .fetch_optional(&mut *tx) 91 .await; 92 93 match invite_query { 94 Ok(Some(row)) => { 95 let uses: i32 = row.get("available_uses"); 96 if uses <= 0 { 97 return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidInviteCode", "message": "Invite code exhausted"}))).into_response(); 98 } 99 100 let update_invite = sqlx::query("UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1") 101 .bind(code) 102 .execute(&mut *tx) 103 .await; 104 105 if let Err(e) = update_invite { 106 error!("Error updating invite code: {:?}", e); 107 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 108 } 109 }, 110 Ok(None) => return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidInviteCode", "message": "Invite code not found"}))).into_response(), 111 Err(e) => { 112 error!("Error checking invite code: {:?}", e); 113 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 114 } 115 } 116 } 117 118 let did = format!("did:plc:{}", uuid::Uuid::new_v4()); 119 120 let password_hash = match hash(&input.password, DEFAULT_COST) { 121 Ok(h) => h, 122 Err(e) => { 123 error!("Error hashing password: {:?}", e); 124 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 125 } 126 }; 127 128 let user_insert = sqlx::query("INSERT INTO users (handle, email, did, password_hash) VALUES ($1, $2, $3, $4) RETURNING id") 129 .bind(&input.handle) 130 .bind(&input.email) 131 .bind(&did) 132 .bind(&password_hash) 133 .fetch_one(&mut *tx) 134 .await; 135 136 let user_id: uuid::Uuid = match user_insert { 137 Ok(row) => row.get("id"), 138 Err(e) => { 139 error!("Error inserting user: {:?}", e); 140 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 141 } 142 }; 143 144 let secret_key = SecretKey::random(&mut OsRng); 145 let secret_key_bytes = secret_key.to_bytes(); 146 147 let key_insert = sqlx::query("INSERT INTO user_keys (user_id, key_bytes) VALUES ($1, $2)") 148 .bind(user_id) 149 .bind(&secret_key_bytes[..]) 150 .execute(&mut *tx) 151 .await; 152 153 if let Err(e) = key_insert { 154 error!("Error inserting user key: {:?}", e); 155 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 156 } 157 158 let store = Arc::new(state.block_store.clone()); 159 let mst = Mst::new(store.clone()); 160 let mst_root = match mst.root().await { 161 Ok(c) => c, 162 Err(e) => { 163 error!("Error creating MST root: {:?}", e); 164 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 165 } 166 }; 167 168 let did_obj = match Did::new(&did) { 169 Ok(d) => d, 170 Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Invalid DID"}))).into_response(), 171 }; 172 173 let rev = Tid::now(LimitedU32::MIN); 174 175 let commit = Commit::new_unsigned( 176 did_obj, 177 mst_root, 178 rev, 179 None 180 ); 181 182 let commit_bytes = match commit.to_cbor() { 183 Ok(b) => b, 184 Err(e) => { 185 error!("Error serializing genesis commit: {:?}", e); 186 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 187 } 188 }; 189 190 let commit_cid = match state.block_store.put(&commit_bytes).await { 191 Ok(c) => c, 192 Err(e) => { 193 error!("Error saving genesis commit: {:?}", e); 194 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 195 } 196 }; 197 198 let repo_insert = sqlx::query("INSERT INTO repos (user_id, repo_root_cid) VALUES ($1, $2)") 199 .bind(user_id) 200 .bind(commit_cid.to_string()) 201 .execute(&mut *tx) 202 .await; 203 204 if let Err(e) = repo_insert { 205 error!("Error initializing repo: {:?}", e); 206 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 207 } 208 209 if let Some(code) = &input.invite_code { 210 let use_insert = sqlx::query("INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)") 211 .bind(code) 212 .bind(user_id) 213 .execute(&mut *tx) 214 .await; 215 216 if let Err(e) = use_insert { 217 error!("Error recording invite usage: {:?}", e); 218 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 219 } 220 } 221 222 let access_jwt = crate::auth::create_access_token(&did, &secret_key_bytes[..]).map_err(|e| { 223 error!("Error creating access token: {:?}", e); 224 (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response() 225 }); 226 let access_jwt = match access_jwt { 227 Ok(t) => t, 228 Err(r) => return r, 229 }; 230 231 let refresh_jwt = crate::auth::create_refresh_token(&did, &secret_key_bytes[..]).map_err(|e| { 232 error!("Error creating refresh token: {:?}", e); 233 (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response() 234 }); 235 let refresh_jwt = match refresh_jwt { 236 Ok(t) => t, 237 Err(r) => return r, 238 }; 239 240 let session_insert = sqlx::query("INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)") 241 .bind(&access_jwt) 242 .bind(&refresh_jwt) 243 .bind(&did) 244 .execute(&mut *tx) 245 .await; 246 247 if let Err(e) = session_insert { 248 error!("Error inserting session: {:?}", e); 249 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 250 } 251 252 if let Err(e) = tx.commit().await { 253 error!("Error committing transaction: {:?}", e); 254 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 255 } 256 257 (StatusCode::OK, Json(CreateAccountOutput { 258 access_jwt, 259 refresh_jwt, 260 handle: input.handle, 261 did, 262 })).into_response() 263} 264 265#[derive(Deserialize)] 266pub struct CreateSessionInput { 267 pub identifier: String, 268 pub password: String, 269} 270 271#[derive(Serialize)] 272#[serde(rename_all = "camelCase")] 273pub struct CreateSessionOutput { 274 pub access_jwt: String, 275 pub refresh_jwt: String, 276 pub handle: String, 277 pub did: String, 278} 279 280pub async fn create_session( 281 State(state): State<AppState>, 282 Json(input): Json<CreateSessionInput>, 283) -> Response { 284 info!("create_session: identifier='{}'", input.identifier); 285 286 let user_row = sqlx::query("SELECT u.did, u.handle, u.password_hash, k.key_bytes FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1") 287 .bind(&input.identifier) 288 .fetch_optional(&state.db) 289 .await; 290 291 match user_row { 292 Ok(Some(row)) => { 293 let stored_hash: String = row.get("password_hash"); 294 295 if verify(&input.password, &stored_hash).unwrap_or(false) { 296 let did: String = row.get("did"); 297 let handle: String = row.get("handle"); 298 let key_bytes: Vec<u8> = row.get("key_bytes"); 299 300 let access_jwt = match crate::auth::create_access_token(&did, &key_bytes) { 301 Ok(t) => t, 302 Err(e) => { 303 error!("Failed to create access token: {:?}", e); 304 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 305 } 306 }; 307 308 let refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) { 309 Ok(t) => t, 310 Err(e) => { 311 error!("Failed to create refresh token: {:?}", e); 312 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 313 } 314 }; 315 316 let session_insert = sqlx::query("INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)") 317 .bind(&access_jwt) 318 .bind(&refresh_jwt) 319 .bind(&did) 320 .execute(&state.db) 321 .await; 322 323 match session_insert { 324 Ok(_) => { 325 return (StatusCode::OK, Json(CreateSessionOutput { 326 access_jwt, 327 refresh_jwt, 328 handle, 329 did, 330 })).into_response(); 331 }, 332 Err(e) => { 333 error!("Failed to insert session: {:?}", e); 334 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 335 } 336 } 337 } else { 338 warn!("Password verification failed for identifier: {}", input.identifier); 339 } 340 }, 341 Ok(None) => { 342 warn!("User not found for identifier: {}", input.identifier); 343 }, 344 Err(e) => { 345 error!("Database error fetching user: {:?}", e); 346 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 347 } 348 } 349 350 (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid identifier or password"}))).into_response() 351} 352 353pub async fn get_session( 354 State(state): State<AppState>, 355 headers: axum::http::HeaderMap, 356) -> Response { 357 let auth_header = headers.get("Authorization"); 358 if auth_header.is_none() { 359 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationRequired"}))).into_response(); 360 } 361 362 let token = auth_header.unwrap().to_str().unwrap_or("").replace("Bearer ", ""); 363 364 let result = sqlx::query( 365 r#" 366 SELECT u.handle, u.did, u.email, k.key_bytes 367 FROM sessions s 368 JOIN users u ON s.did = u.did 369 JOIN user_keys k ON u.id = k.user_id 370 WHERE s.access_jwt = $1 371 "# 372 ) 373 .bind(&token) 374 .fetch_optional(&state.db) 375 .await; 376 377 match result { 378 Ok(Some(row)) => { 379 let handle: String = row.get("handle"); 380 let did: String = row.get("did"); 381 let email: String = row.get("email"); 382 let key_bytes: Vec<u8> = row.get("key_bytes"); 383 384 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 385 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"}))).into_response(); 386 } 387 388 return (StatusCode::OK, Json(json!({ 389 "handle": handle, 390 "did": did, 391 "email": email, 392 "didDoc": {} 393 }))).into_response(); 394 }, 395 Ok(None) => { 396 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed"}))).into_response(); 397 }, 398 Err(e) => { 399 error!("Database error in get_session: {:?}", e); 400 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 401 } 402 } 403} 404 405pub async fn delete_session( 406 State(state): State<AppState>, 407 headers: axum::http::HeaderMap, 408) -> Response { 409 let auth_header = headers.get("Authorization"); 410 if auth_header.is_none() { 411 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationRequired"}))).into_response(); 412 } 413 414 let token = auth_header.unwrap().to_str().unwrap_or("").replace("Bearer ", ""); 415 416 let result = sqlx::query("DELETE FROM sessions WHERE access_jwt = $1") 417 .bind(token) 418 .execute(&state.db) 419 .await; 420 421 match result { 422 Ok(res) => { 423 if res.rows_affected() > 0 { 424 return (StatusCode::OK, Json(json!({}))).into_response(); 425 } 426 }, 427 Err(e) => { 428 error!("Database error in delete_session: {:?}", e); 429 } 430 } 431 432 (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed"}))).into_response() 433} 434 435pub async fn refresh_session( 436 State(state): State<AppState>, 437 headers: axum::http::HeaderMap, 438) -> Response { 439 let auth_header = headers.get("Authorization"); 440 if auth_header.is_none() { 441 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationRequired"}))).into_response(); 442 } 443 444 let refresh_token = auth_header.unwrap().to_str().unwrap_or("").replace("Bearer ", ""); 445 446 let session = sqlx::query( 447 "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.refresh_jwt = $1" 448 ) 449 .bind(&refresh_token) 450 .fetch_optional(&state.db) 451 .await; 452 453 match session { 454 Ok(Some(session_row)) => { 455 let did: String = session_row.get("did"); 456 let key_bytes: Vec<u8> = session_row.get("key_bytes"); 457 458 if let Err(_) = crate::auth::verify_token(&refresh_token, &key_bytes) { 459 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token signature"}))).into_response(); 460 } 461 462 let new_access_jwt = match crate::auth::create_access_token(&did, &key_bytes) { 463 Ok(t) => t, 464 Err(e) => { 465 error!("Failed to create access token: {:?}", e); 466 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 467 } 468 }; 469 let new_refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) { 470 Ok(t) => t, 471 Err(e) => { 472 error!("Failed to create refresh token: {:?}", e); 473 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 474 } 475 }; 476 477 let update = sqlx::query("UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3") 478 .bind(&new_access_jwt) 479 .bind(&new_refresh_jwt) 480 .bind(&refresh_token) 481 .execute(&state.db) 482 .await; 483 484 match update { 485 Ok(_) => { 486 let user = sqlx::query("SELECT handle FROM users WHERE did = $1") 487 .bind(&did) 488 .fetch_optional(&state.db) 489 .await; 490 491 match user { 492 Ok(Some(u)) => { 493 let handle: String = u.get("handle"); 494 return (StatusCode::OK, Json(json!({ 495 "accessJwt": new_access_jwt, 496 "refreshJwt": new_refresh_jwt, 497 "handle": handle, 498 "did": did 499 }))).into_response(); 500 }, 501 Ok(None) => { 502 error!("User not found for existing session: {}", did); 503 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 504 }, 505 Err(e) => { 506 error!("Database error fetching user: {:?}", e); 507 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 508 } 509 } 510 }, 511 Err(e) => { 512 error!("Database error updating session: {:?}", e); 513 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 514 } 515 } 516 }, 517 Ok(None) => { 518 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"}))).into_response(); 519 }, 520 Err(e) => { 521 error!("Database error fetching session: {:?}", e); 522 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response(); 523 } 524 } 525}