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