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