this repo has no description
1use crate::state::AppState; 2use axum::{ 3 Json, 4 extract::{Query, State}, 5 http::StatusCode, 6 response::{IntoResponse, Response}, 7}; 8use bcrypt::verify; 9use serde::{Deserialize, Serialize}; 10use serde_json::json; 11use tracing::{error, info, warn}; 12 13#[derive(Deserialize)] 14pub struct GetServiceAuthParams { 15 pub aud: String, 16 pub lxm: Option<String>, 17 pub exp: Option<i64>, 18} 19 20#[derive(Serialize)] 21pub struct GetServiceAuthOutput { 22 pub token: String, 23} 24 25pub async fn get_service_auth( 26 State(state): State<AppState>, 27 headers: axum::http::HeaderMap, 28 Query(params): Query<GetServiceAuthParams>, 29) -> Response { 30 let token = match crate::auth::extract_bearer_token_from_header( 31 headers.get("Authorization").and_then(|h| h.to_str().ok()) 32 ) { 33 Some(t) => t, 34 None => { 35 return ( 36 StatusCode::UNAUTHORIZED, 37 Json(json!({"error": "AuthenticationRequired"})), 38 ) 39 .into_response(); 40 } 41 }; 42 43 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await; 44 let (did, key_bytes) = match auth_result { 45 Ok(user) => { 46 let kb = match user.key_bytes { 47 Some(kb) => kb, 48 None => { 49 return ( 50 StatusCode::UNAUTHORIZED, 51 Json(json!({"error": "AuthenticationFailed", "message": "OAuth tokens cannot create service auth"})), 52 ) 53 .into_response(); 54 } 55 }; 56 (user.did, kb) 57 } 58 Err(e) => { 59 return ( 60 StatusCode::UNAUTHORIZED, 61 Json(json!({"error": e})), 62 ) 63 .into_response(); 64 } 65 }; 66 67 let lxm = params.lxm.as_deref().unwrap_or("*"); 68 69 let service_token = match crate::auth::create_service_token(&did, &params.aud, lxm, &key_bytes) 70 { 71 Ok(t) => t, 72 Err(e) => { 73 error!("Failed to create service token: {:?}", e); 74 return ( 75 StatusCode::INTERNAL_SERVER_ERROR, 76 Json(json!({"error": "InternalError"})), 77 ) 78 .into_response(); 79 } 80 }; 81 82 (StatusCode::OK, Json(GetServiceAuthOutput { token: service_token })).into_response() 83} 84 85#[derive(Deserialize)] 86pub struct CreateSessionInput { 87 pub identifier: String, 88 pub password: String, 89} 90 91#[derive(Serialize)] 92#[serde(rename_all = "camelCase")] 93pub struct CreateSessionOutput { 94 pub access_jwt: String, 95 pub refresh_jwt: String, 96 pub handle: String, 97 pub did: String, 98} 99 100pub async fn create_session( 101 State(state): State<AppState>, 102 Json(input): Json<CreateSessionInput>, 103) -> Response { 104 info!("create_session: identifier='{}'", input.identifier); 105 106 let user_row = sqlx::query!( 107 "SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes, k.encryption_version FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1", 108 input.identifier 109 ) 110 .fetch_optional(&state.db) 111 .await; 112 113 match user_row { 114 Ok(Some(row)) => { 115 let user_id = row.id; 116 let stored_hash = &row.password_hash; 117 let did = &row.did; 118 let handle = &row.handle; 119 let key_bytes = match crate::config::decrypt_key(&row.key_bytes, row.encryption_version) { 120 Ok(k) => k, 121 Err(e) => { 122 error!("Failed to decrypt user key: {:?}", e); 123 return ( 124 StatusCode::INTERNAL_SERVER_ERROR, 125 Json(json!({"error": "InternalError"})), 126 ) 127 .into_response(); 128 } 129 }; 130 131 let password_valid = if verify(&input.password, stored_hash).unwrap_or(false) { 132 true 133 } else { 134 let app_pass_rows = sqlx::query!("SELECT password_hash FROM app_passwords WHERE user_id = $1", user_id) 135 .fetch_all(&state.db) 136 .await 137 .unwrap_or_default(); 138 139 app_pass_rows.iter().any(|row| { 140 verify(&input.password, &row.password_hash).unwrap_or(false) 141 }) 142 }; 143 144 if password_valid { 145 let access_meta = match crate::auth::create_access_token_with_metadata(did, &key_bytes) { 146 Ok(m) => m, 147 Err(e) => { 148 error!("Failed to create access token: {:?}", e); 149 return ( 150 StatusCode::INTERNAL_SERVER_ERROR, 151 Json(json!({"error": "InternalError"})), 152 ) 153 .into_response(); 154 } 155 }; 156 157 let refresh_meta = match crate::auth::create_refresh_token_with_metadata(did, &key_bytes) { 158 Ok(m) => m, 159 Err(e) => { 160 error!("Failed to create refresh token: {:?}", e); 161 return ( 162 StatusCode::INTERNAL_SERVER_ERROR, 163 Json(json!({"error": "InternalError"})), 164 ) 165 .into_response(); 166 } 167 }; 168 169 let session_insert = sqlx::query!( 170 "INSERT INTO session_tokens (did, access_jti, refresh_jti, access_expires_at, refresh_expires_at) VALUES ($1, $2, $3, $4, $5)", 171 did, 172 access_meta.jti, 173 refresh_meta.jti, 174 access_meta.expires_at, 175 refresh_meta.expires_at 176 ) 177 .execute(&state.db) 178 .await; 179 180 match session_insert { 181 Ok(_) => { 182 return ( 183 StatusCode::OK, 184 Json(CreateSessionOutput { 185 access_jwt: access_meta.token, 186 refresh_jwt: refresh_meta.token, 187 handle: handle.clone(), 188 did: did.clone(), 189 }), 190 ) 191 .into_response(); 192 } 193 Err(e) => { 194 error!("Failed to insert session: {:?}", e); 195 return ( 196 StatusCode::INTERNAL_SERVER_ERROR, 197 Json(json!({"error": "InternalError"})), 198 ) 199 .into_response(); 200 } 201 } 202 } else { 203 warn!( 204 "Password verification failed for identifier: {}", 205 input.identifier 206 ); 207 } 208 } 209 Ok(None) => { 210 warn!("User not found for identifier: {}", input.identifier); 211 } 212 Err(e) => { 213 error!("Database error fetching user: {:?}", e); 214 return ( 215 StatusCode::INTERNAL_SERVER_ERROR, 216 Json(json!({"error": "InternalError"})), 217 ) 218 .into_response(); 219 } 220 } 221 222 ( 223 StatusCode::UNAUTHORIZED, 224 Json(json!({"error": "AuthenticationFailed", "message": "Invalid identifier or password"})), 225 ) 226 .into_response() 227} 228 229pub async fn get_session( 230 State(state): State<AppState>, 231 headers: axum::http::HeaderMap, 232) -> Response { 233 let token = match crate::auth::extract_bearer_token_from_header( 234 headers.get("Authorization").and_then(|h| h.to_str().ok()) 235 ) { 236 Some(t) => t, 237 None => { 238 return ( 239 StatusCode::UNAUTHORIZED, 240 Json(json!({"error": "AuthenticationRequired", "message": "Invalid Authorization header format"})), 241 ) 242 .into_response(); 243 } 244 }; 245 246 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await; 247 let did = match auth_result { 248 Ok(user) => user.did, 249 Err(e) => { 250 return ( 251 StatusCode::UNAUTHORIZED, 252 Json(json!({"error": e})), 253 ) 254 .into_response(); 255 } 256 }; 257 258 let user = sqlx::query!( 259 "SELECT handle, email FROM users WHERE did = $1", 260 did 261 ) 262 .fetch_optional(&state.db) 263 .await; 264 265 match user { 266 Ok(Some(row)) => { 267 return ( 268 StatusCode::OK, 269 Json(json!({ 270 "handle": row.handle, 271 "did": did, 272 "email": row.email, 273 "didDoc": {} 274 })), 275 ) 276 .into_response(); 277 } 278 Ok(None) => { 279 return ( 280 StatusCode::UNAUTHORIZED, 281 Json(json!({"error": "AuthenticationFailed"})), 282 ) 283 .into_response(); 284 } 285 Err(e) => { 286 error!("Database error in get_session: {:?}", e); 287 return ( 288 StatusCode::INTERNAL_SERVER_ERROR, 289 Json(json!({"error": "InternalError"})), 290 ) 291 .into_response(); 292 } 293 } 294} 295 296pub async fn delete_session( 297 State(state): State<AppState>, 298 headers: axum::http::HeaderMap, 299) -> Response { 300 let token = match crate::auth::extract_bearer_token_from_header( 301 headers.get("Authorization").and_then(|h| h.to_str().ok()) 302 ) { 303 Some(t) => t, 304 None => { 305 return ( 306 StatusCode::UNAUTHORIZED, 307 Json(json!({"error": "AuthenticationRequired"})), 308 ) 309 .into_response(); 310 } 311 }; 312 313 let jti = match crate::auth::get_did_from_token(&token) { 314 Ok(_) => { 315 let parts: Vec<&str> = token.split('.').collect(); 316 if parts.len() != 3 { 317 return ( 318 StatusCode::UNAUTHORIZED, 319 Json(json!({"error": "AuthenticationFailed"})), 320 ) 321 .into_response(); 322 } 323 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 324 let claims_json = match URL_SAFE_NO_PAD.decode(parts[1]) { 325 Ok(bytes) => bytes, 326 Err(_) => { 327 return ( 328 StatusCode::UNAUTHORIZED, 329 Json(json!({"error": "AuthenticationFailed"})), 330 ) 331 .into_response(); 332 } 333 }; 334 let claims: serde_json::Value = match serde_json::from_slice(&claims_json) { 335 Ok(c) => c, 336 Err(_) => { 337 return ( 338 StatusCode::UNAUTHORIZED, 339 Json(json!({"error": "AuthenticationFailed"})), 340 ) 341 .into_response(); 342 } 343 }; 344 match claims.get("jti").and_then(|j| j.as_str()) { 345 Some(jti) => jti.to_string(), 346 None => { 347 return ( 348 StatusCode::UNAUTHORIZED, 349 Json(json!({"error": "AuthenticationFailed"})), 350 ) 351 .into_response(); 352 } 353 } 354 } 355 Err(_) => { 356 return ( 357 StatusCode::UNAUTHORIZED, 358 Json(json!({"error": "AuthenticationFailed"})), 359 ) 360 .into_response(); 361 } 362 }; 363 364 let result = sqlx::query!("DELETE FROM session_tokens WHERE access_jti = $1", jti) 365 .execute(&state.db) 366 .await; 367 368 match result { 369 Ok(res) => { 370 if res.rows_affected() > 0 { 371 return (StatusCode::OK, Json(json!({}))).into_response(); 372 } 373 } 374 Err(e) => { 375 error!("Database error in delete_session: {:?}", e); 376 } 377 } 378 379 ( 380 StatusCode::UNAUTHORIZED, 381 Json(json!({"error": "AuthenticationFailed"})), 382 ) 383 .into_response() 384} 385 386pub async fn refresh_session( 387 State(state): State<AppState>, 388 headers: axum::http::HeaderMap, 389) -> Response { 390 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 391 392 let refresh_token = match crate::auth::extract_bearer_token_from_header( 393 headers.get("Authorization").and_then(|h| h.to_str().ok()) 394 ) { 395 Some(t) => t, 396 None => { 397 return ( 398 StatusCode::UNAUTHORIZED, 399 Json(json!({"error": "AuthenticationRequired"})), 400 ) 401 .into_response(); 402 } 403 }; 404 405 let refresh_jti = { 406 let parts: Vec<&str> = refresh_token.split('.').collect(); 407 if parts.len() != 3 { 408 return ( 409 StatusCode::UNAUTHORIZED, 410 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token format"})), 411 ) 412 .into_response(); 413 } 414 let claims_bytes = match URL_SAFE_NO_PAD.decode(parts[1]) { 415 Ok(b) => b, 416 Err(_) => { 417 return ( 418 StatusCode::UNAUTHORIZED, 419 Json(json!({"error": "AuthenticationFailed"})), 420 ) 421 .into_response(); 422 } 423 }; 424 let claims: serde_json::Value = match serde_json::from_slice(&claims_bytes) { 425 Ok(c) => c, 426 Err(_) => { 427 return ( 428 StatusCode::UNAUTHORIZED, 429 Json(json!({"error": "AuthenticationFailed"})), 430 ) 431 .into_response(); 432 } 433 }; 434 match claims.get("jti").and_then(|j| j.as_str()) { 435 Some(jti) => jti.to_string(), 436 None => { 437 return ( 438 StatusCode::UNAUTHORIZED, 439 Json(json!({"error": "AuthenticationFailed"})), 440 ) 441 .into_response(); 442 } 443 } 444 }; 445 446 let reuse_check = sqlx::query_scalar!( 447 "SELECT session_id FROM used_refresh_tokens WHERE refresh_jti = $1", 448 refresh_jti 449 ) 450 .fetch_optional(&state.db) 451 .await; 452 453 if let Ok(Some(session_id)) = reuse_check { 454 warn!("Refresh token reuse detected! Revoking token family for session_id: {}", session_id); 455 let _ = sqlx::query!("DELETE FROM session_tokens WHERE id = $1", session_id) 456 .execute(&state.db) 457 .await; 458 return ( 459 StatusCode::UNAUTHORIZED, 460 Json(json!({"error": "ExpiredToken", "message": "Refresh token has been revoked due to suspected compromise"})), 461 ) 462 .into_response(); 463 } 464 465 let session = sqlx::query!( 466 r#"SELECT st.id, st.did, k.key_bytes, k.encryption_version 467 FROM session_tokens st 468 JOIN users u ON st.did = u.did 469 JOIN user_keys k ON u.id = k.user_id 470 WHERE st.refresh_jti = $1 AND st.refresh_expires_at > NOW()"#, 471 refresh_jti 472 ) 473 .fetch_optional(&state.db) 474 .await; 475 476 match session { 477 Ok(Some(session_row)) => { 478 let session_id = session_row.id; 479 let did = &session_row.did; 480 let key_bytes = match crate::config::decrypt_key(&session_row.key_bytes, session_row.encryption_version) { 481 Ok(k) => k, 482 Err(e) => { 483 error!("Failed to decrypt user key: {:?}", e); 484 return ( 485 StatusCode::INTERNAL_SERVER_ERROR, 486 Json(json!({"error": "InternalError"})), 487 ) 488 .into_response(); 489 } 490 }; 491 492 if let Err(_) = crate::auth::verify_refresh_token(&refresh_token, &key_bytes) { 493 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"}))).into_response(); 494 } 495 496 let new_access_meta = match crate::auth::create_access_token_with_metadata(did, &key_bytes) { 497 Ok(m) => m, 498 Err(e) => { 499 error!("Failed to create access token: {:?}", e); 500 return ( 501 StatusCode::INTERNAL_SERVER_ERROR, 502 Json(json!({"error": "InternalError"})), 503 ) 504 .into_response(); 505 } 506 }; 507 let new_refresh_meta = match crate::auth::create_refresh_token_with_metadata(did, &key_bytes) { 508 Ok(m) => m, 509 Err(e) => { 510 error!("Failed to create refresh token: {:?}", e); 511 return ( 512 StatusCode::INTERNAL_SERVER_ERROR, 513 Json(json!({"error": "InternalError"})), 514 ) 515 .into_response(); 516 } 517 }; 518 519 let mut tx = match state.db.begin().await { 520 Ok(tx) => tx, 521 Err(e) => { 522 error!("Failed to begin transaction: {:?}", e); 523 return ( 524 StatusCode::INTERNAL_SERVER_ERROR, 525 Json(json!({"error": "InternalError"})), 526 ) 527 .into_response(); 528 } 529 }; 530 531 if let Err(e) = sqlx::query!( 532 "INSERT INTO used_refresh_tokens (refresh_jti, session_id) VALUES ($1, $2)", 533 refresh_jti, 534 session_id 535 ) 536 .execute(&mut *tx) 537 .await 538 { 539 error!("Failed to record used refresh token: {:?}", e); 540 return ( 541 StatusCode::INTERNAL_SERVER_ERROR, 542 Json(json!({"error": "InternalError"})), 543 ) 544 .into_response(); 545 } 546 547 if let Err(e) = sqlx::query!( 548 "UPDATE session_tokens SET access_jti = $1, refresh_jti = $2, access_expires_at = $3, refresh_expires_at = $4, updated_at = NOW() WHERE id = $5", 549 new_access_meta.jti, 550 new_refresh_meta.jti, 551 new_access_meta.expires_at, 552 new_refresh_meta.expires_at, 553 session_id 554 ) 555 .execute(&mut *tx) 556 .await 557 { 558 error!("Database error updating session: {:?}", e); 559 return ( 560 StatusCode::INTERNAL_SERVER_ERROR, 561 Json(json!({"error": "InternalError"})), 562 ) 563 .into_response(); 564 } 565 566 if let Err(e) = tx.commit().await { 567 error!("Failed to commit transaction: {:?}", e); 568 return ( 569 StatusCode::INTERNAL_SERVER_ERROR, 570 Json(json!({"error": "InternalError"})), 571 ) 572 .into_response(); 573 } 574 575 let user = sqlx::query!("SELECT handle FROM users WHERE did = $1", did) 576 .fetch_optional(&state.db) 577 .await; 578 579 match user { 580 Ok(Some(u)) => { 581 return ( 582 StatusCode::OK, 583 Json(json!({ 584 "accessJwt": new_access_meta.token, 585 "refreshJwt": new_refresh_meta.token, 586 "handle": u.handle, 587 "did": did 588 })), 589 ) 590 .into_response(); 591 } 592 Ok(None) => { 593 error!("User not found for existing session: {}", did); 594 return ( 595 StatusCode::INTERNAL_SERVER_ERROR, 596 Json(json!({"error": "InternalError"})), 597 ) 598 .into_response(); 599 } 600 Err(e) => { 601 error!("Database error fetching user: {:?}", e); 602 return ( 603 StatusCode::INTERNAL_SERVER_ERROR, 604 Json(json!({"error": "InternalError"})), 605 ) 606 .into_response(); 607 } 608 } 609 } 610 Ok(None) => { 611 return ( 612 StatusCode::UNAUTHORIZED, 613 Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"})), 614 ) 615 .into_response(); 616 } 617 Err(e) => { 618 error!("Database error fetching session: {:?}", e); 619 return ( 620 StatusCode::INTERNAL_SERVER_ERROR, 621 Json(json!({"error": "InternalError"})), 622 ) 623 .into_response(); 624 } 625 } 626}