use std::sync::Arc; use axum::{ Json, body::Body, extract::State, http::{Request, StatusCode, header}, middleware::Next, response::IntoResponse, }; use axum_extra::extract::CookieJar; use jsonwebtoken::DecodingKey; use serde_json::json; use crate::{AppState, cookie::jwt::decode_token, handlers::auth::filter_user, models::User}; /// Axum middleware to validate a JWT token present in cookies or Authorization header. /// /// This function extracts a JWT from the request (either from the `token` cookie or /// the `Authorization: Bearer` header), decodes and validates it using [`decode_token`](`crate::cookie::jwt::decode_token`)). /// If valid, it fetches the corresponding [`User`] from the database and inserts a /// [`FilteredUser`](crate::models::FilteredUser) /// (converted via [`filter_user`](`crate::handlers::auth::filter_user`)) into the request extensions for subsequent handlers to use. /// /// If the token is missing, invalid, or the user is not found, it returns an /// appropriate error response (401 Unauthorized). /// /// # Arguments /// - `cookies`: The `CookieJar` from the request, used to extract the `token` cookie. /// - `State(data)`: Application state containing `AppState` for database access and `token_secret`. /// - `mut request`: The incoming HTTP request, which will have user data injected into its extensions. /// - `next`: The next middleware or handler in the chain. /// /// # Returns /// - `Ok(impl IntoResponse)`: If validation succeeds, the request proceeds to the next handler. /// - `Err((StatusCode, Json))`: An error response if validation fails. pub async fn validate_token( cookies: CookieJar, State(data): State>, mut request: Request, next: Next, ) -> Result)> { let token = cookies .get("token") .map(|cookie| cookie.value().to_string()) .or_else(|| { request .headers() .get(header::AUTHORIZATION) .and_then(|header| header.to_str().ok()) .and_then(|value| { if value.starts_with("Bearer ") { Some(value[7..].to_owned()) } else { None } }) }); let token = token.ok_or_else(|| { let error = json!({ "status": "error", "message": "Please provide a valid token" }); (StatusCode::UNAUTHORIZED, Json(error)) })?; let claims = decode_token( token, &DecodingKey::from_secret(data.env.token_secret.as_ref()), ) .map_err(|(status, json_err)| { let error = json!({ "status": json_err.status, "message": json_err.message }); (status, Json(error)) })?; let uuid = (&claims.sub).parse::().map_err(|_| { let error = json!({ "status": "error", "message": "Invalid user id" }); (StatusCode::UNAUTHORIZED, Json(error)) })?; let user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#) .bind(uuid) .fetch_optional(&data.db) .await .map_err(|e| { let error = json!({ "status": "error", "message": format!("Database error: {}", e) }); (StatusCode::INTERNAL_SERVER_ERROR, Json(error)) })?; let user = user.ok_or_else(|| { let error = json!({ "status": "error", "message": "Invalid user" }); (StatusCode::UNAUTHORIZED, Json(error)) })?; request.extensions_mut().insert(filter_user(&user)); Ok(next.run(request).await) } /// Axum middleware to validate JWT token and ensure the authenticated user has admin privileges. /// /// This middleware first performs all checks of [`validate_token`]: extracting, decoding, /// and validating the JWT via [`decode_token`](`crate::cookie::jwt::decode_token`), and fetching the associated [`User`] from the database. /// Additionally, it verifies that the fetched user has `is_admin` set to `true`. Returns a [`FilteredUser`](crate::models::FilteredUser) /// (converted via [`filter_user`](`crate::handlers::auth::filter_user`)) in the request extensions if both authentication and admin status are valid. /// /// If the user is not authenticated or not an administrator, it returns an /// appropriate error response (401 Unauthorized or 403 Forbidden). /// /// # Arguments /// - `cookies`: The `CookieJar` from the request. /// - `State(data)`: Application state containing `AppState`. /// - `mut request`: The incoming HTTP request, which will have admin user data injected. /// - `next`: The next middleware or handler in the chain. /// /// # Returns /// - `Ok(impl IntoResponse)`: If validation and admin check succeed, the request proceeds. /// - `Err((StatusCode, Json))`: An error response if validation fails /// or the user is not an admin. pub async fn validate_admin( cookies: CookieJar, State(data): State>, mut request: Request, next: Next, ) -> Result)> { let token = cookies .get("token") .map(|cookie| cookie.value().to_string()) .or_else(|| { request .headers() .get(header::AUTHORIZATION) .and_then(|header| header.to_str().ok()) .and_then(|value| { if value.starts_with("Bearer ") { Some(value[7..].to_owned()) } else { None } }) }); let token = token.ok_or_else(|| { let error = json!({ "status": "error", "message": "Please provide a valid token" }); (StatusCode::UNAUTHORIZED, Json(error)) })?; let claims = decode_token( token, &DecodingKey::from_secret(data.env.token_secret.as_ref()), ) .map_err(|(status, json_err)| { let error = json!({ "status": json_err.status, "message": json_err.message }); (status, Json(error)) })?; let uuid = (&claims.sub).parse::().map_err(|_| { let error = json!({ "status": "error", "message": "Invalid user id" }); (StatusCode::UNAUTHORIZED, Json(error)) })?; let user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#) .bind(uuid) .fetch_optional(&data.db) .await .map_err(|e| { let error = json!({ "status": "error", "message": format!("Database error: {}", e) }); (StatusCode::INTERNAL_SERVER_ERROR, Json(error)) })?; let user = user.ok_or_else(|| { let error = json!({ "status": "error", "message": "Invalid user" }); (StatusCode::UNAUTHORIZED, Json(error)) })?; if !user.is_admin { let error = json!({ "status": "error", "message": "Admin access required" }); return Err((StatusCode::FORBIDDEN, Json(error))); } request.extensions_mut().insert(filter_user(&user)); Ok(next.run(request).await) }