auth.rs (4056B)
1 use axum::{ 2 extract::State, 3 http::{header, StatusCode}, 4 response::IntoResponse, 5 Json, 6 }; 7 use serde::Serialize; 8 use tracing::instrument; 9 10 use crate::{ 11 auth, 12 models::{LoginRequest, LoginResponse, RegisterRequest, UserResponse}, 13 AppState, 14 }; 15 16 #[derive(Serialize)] 17 pub struct ErrorResponse { 18 pub error: String, 19 } 20 21 #[instrument(name = "register", skip(state, req), fields(user.email = %req.email))] 22 pub async fn register( 23 State(state): State<AppState>, 24 Json(req): Json<RegisterRequest>, 25 ) -> impl IntoResponse { 26 match auth::register_user(state.db.pool(), req).await { 27 Ok(user) => { 28 let response = UserResponse::from(user); 29 (StatusCode::CREATED, Json(response)).into_response() 30 } 31 Err(e) => ( 32 StatusCode::BAD_REQUEST, 33 Json(ErrorResponse { 34 error: e.to_string(), 35 }), 36 ) 37 .into_response(), 38 } 39 } 40 41 #[instrument(name = "login", skip(state, req), fields(user.email = %req.email))] 42 pub async fn login( 43 State(state): State<AppState>, 44 Json(req): Json<LoginRequest>, 45 ) -> impl IntoResponse { 46 match auth::login_user(state.db.pool(), req).await { 47 Ok((user, session)) => { 48 let response = LoginResponse { 49 token: session.session_token.clone(), 50 user: UserResponse::from(user), 51 expires_at: session.expires_at, 52 }; 53 54 ( 55 StatusCode::OK, 56 [( 57 header::SET_COOKIE, 58 format!( 59 "session_token={}; HttpOnly; SameSite=Lax; Path=/; Max-Age={}", 60 session.session_token, 61 7 * 24 * 60 * 60 62 ), 63 )], 64 Json(response), 65 ) 66 .into_response() 67 } 68 Err(e) => ( 69 StatusCode::UNAUTHORIZED, 70 Json(ErrorResponse { 71 error: e.to_string(), 72 }), 73 ) 74 .into_response(), 75 } 76 } 77 78 #[instrument(name = "logout", skip(state, headers))] 79 pub async fn logout( 80 State(state): State<AppState>, 81 headers: axum::http::HeaderMap, 82 ) -> impl IntoResponse { 83 // Extract session token from cookie or Authorization header 84 let token = extract_token(&headers); 85 86 if let Some(token) = token { 87 match auth::logout_user(state.db.pool(), &token).await { 88 Ok(_) => ( 89 StatusCode::OK, 90 [( 91 header::SET_COOKIE, 92 "session_token=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0", 93 )], 94 Json(serde_json::json!({ "message": "Logged out successfully" })), 95 ) 96 .into_response(), 97 Err(e) => ( 98 StatusCode::INTERNAL_SERVER_ERROR, 99 Json(ErrorResponse { 100 error: e.to_string(), 101 }), 102 ) 103 .into_response(), 104 } 105 } else { 106 ( 107 StatusCode::UNAUTHORIZED, 108 Json(ErrorResponse { 109 error: "No session token provided".to_string(), 110 }), 111 ) 112 .into_response() 113 } 114 } 115 116 fn extract_token(headers: &axum::http::HeaderMap) -> Option<String> { 117 // Try to get from Authorization header 118 if let Some(auth_header) = headers.get(header::AUTHORIZATION) { 119 if let Ok(auth_str) = auth_header.to_str() { 120 if auth_str.starts_with("Bearer ") { 121 return Some(auth_str[7..].to_string()); 122 } 123 } 124 } 125 126 // Try to get from Cookie header 127 if let Some(cookie_header) = headers.get(header::COOKIE) { 128 if let Ok(cookie_str) = cookie_header.to_str() { 129 for cookie in cookie_str.split(';') { 130 let cookie = cookie.trim(); 131 if cookie.starts_with("session_token=") { 132 return Some(cookie[14..].to_string()); 133 } 134 } 135 } 136 } 137 138 None 139 }