exercises

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 }