backend/cookie/
validation.rs

1use std::sync::Arc;
2
3use axum::{
4    Json,
5    body::Body,
6    extract::State,
7    http::{Request, StatusCode, header},
8    middleware::Next,
9    response::IntoResponse,
10};
11use axum_extra::extract::CookieJar;
12use jsonwebtoken::DecodingKey;
13use serde_json::json;
14
15use crate::{AppState, cookie::jwt::decode_token, handlers::auth::filter_user, models::User};
16
17/// Axum middleware to validate a JWT token present in cookies or Authorization header.
18///
19/// This function extracts a JWT from the request (either from the `token` cookie or
20/// the `Authorization: Bearer` header), decodes and validates it using [`decode_token`](`crate::cookie::jwt::decode_token`)).
21/// If valid, it fetches the corresponding [`User`] from the database and inserts a
22/// [`FilteredUser`](crate::models::FilteredUser)
23/// (converted via [`filter_user`](`crate::handlers::auth::filter_user`)) into the request extensions for subsequent handlers to use.
24///
25/// If the token is missing, invalid, or the user is not found, it returns an
26/// appropriate error response (401 Unauthorized).
27///
28/// # Arguments
29/// - `cookies`: The `CookieJar` from the request, used to extract the `token` cookie.
30/// - `State(data)`: Application state containing `AppState` for database access and `token_secret`.
31/// - `mut request`: The incoming HTTP request, which will have user data injected into its extensions.
32/// - `next`: The next middleware or handler in the chain.
33///
34/// # Returns
35/// - `Ok(impl IntoResponse)`: If validation succeeds, the request proceeds to the next handler.
36/// - `Err((StatusCode, Json<serde_json::Value>))`: An error response if validation fails.
37pub async fn validate_token(
38    cookies: CookieJar,
39    State(data): State<Arc<AppState>>,
40    mut request: Request<Body>,
41    next: Next,
42) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
43    let token = cookies
44        .get("token")
45        .map(|cookie| cookie.value().to_string())
46        .or_else(|| {
47            request
48                .headers()
49                .get(header::AUTHORIZATION)
50                .and_then(|header| header.to_str().ok())
51                .and_then(|value| {
52                    if value.starts_with("Bearer ") {
53                        Some(value[7..].to_owned())
54                    } else {
55                        None
56                    }
57                })
58        });
59
60    let token = token.ok_or_else(|| {
61        let error = json!({
62            "status": "error",
63            "message": "Please provide a valid token"
64        });
65        (StatusCode::UNAUTHORIZED, Json(error))
66    })?;
67
68    let claims = decode_token(
69        token,
70        &DecodingKey::from_secret(data.env.token_secret.as_ref()),
71    )
72    .map_err(|(status, json_err)| {
73        let error = json!({
74            "status": json_err.status,
75            "message": json_err.message
76        });
77        (status, Json(error))
78    })?;
79
80    let uuid = (&claims.sub).parse::<i16>().map_err(|_| {
81        let error = json!({
82            "status": "error",
83            "message": "Invalid user id"
84        });
85        (StatusCode::UNAUTHORIZED, Json(error))
86    })?;
87
88    let user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#)
89        .bind(uuid)
90        .fetch_optional(&data.db)
91        .await
92        .map_err(|e| {
93            let error = json!({
94                "status": "error",
95                "message": format!("Database error: {}", e)
96            });
97            (StatusCode::INTERNAL_SERVER_ERROR, Json(error))
98        })?;
99
100    let user = user.ok_or_else(|| {
101        let error = json!({
102            "status": "error",
103            "message": "Invalid user"
104        });
105        (StatusCode::UNAUTHORIZED, Json(error))
106    })?;
107
108    request.extensions_mut().insert(filter_user(&user));
109    Ok(next.run(request).await)
110}
111
112/// Axum middleware to validate JWT token and ensure the authenticated user has admin privileges.
113///
114/// This middleware first performs all checks of [`validate_token`]: extracting, decoding,
115/// and validating the JWT via [`decode_token`](`crate::cookie::jwt::decode_token`), and fetching the associated [`User`] from the database.
116/// Additionally, it verifies that the fetched user has `is_admin` set to `true`. Returns a [`FilteredUser`](crate::models::FilteredUser)
117/// (converted via [`filter_user`](`crate::handlers::auth::filter_user`)) in the request extensions if both authentication and admin status are valid.
118///
119/// If the user is not authenticated or not an administrator, it returns an
120/// appropriate error response (401 Unauthorized or 403 Forbidden).
121///
122/// # Arguments
123/// - `cookies`: The `CookieJar` from the request.
124/// - `State(data)`: Application state containing `AppState`.
125/// - `mut request`: The incoming HTTP request, which will have admin user data injected.
126/// - `next`: The next middleware or handler in the chain.
127///
128/// # Returns
129/// - `Ok(impl IntoResponse)`: If validation and admin check succeed, the request proceeds.
130/// - `Err((StatusCode, Json<serde_json::Value>))`: An error response if validation fails
131///   or the user is not an admin.
132pub async fn validate_admin(
133    cookies: CookieJar,
134    State(data): State<Arc<AppState>>,
135    mut request: Request<Body>,
136    next: Next,
137) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
138    let token = cookies
139        .get("token")
140        .map(|cookie| cookie.value().to_string())
141        .or_else(|| {
142            request
143                .headers()
144                .get(header::AUTHORIZATION)
145                .and_then(|header| header.to_str().ok())
146                .and_then(|value| {
147                    if value.starts_with("Bearer ") {
148                        Some(value[7..].to_owned())
149                    } else {
150                        None
151                    }
152                })
153        });
154
155    let token = token.ok_or_else(|| {
156        let error = json!({
157            "status": "error",
158            "message": "Please provide a valid token"
159        });
160        (StatusCode::UNAUTHORIZED, Json(error))
161    })?;
162
163    let claims = decode_token(
164        token,
165        &DecodingKey::from_secret(data.env.token_secret.as_ref()),
166    )
167    .map_err(|(status, json_err)| {
168        let error = json!({
169            "status": json_err.status,
170            "message": json_err.message
171        });
172        (status, Json(error))
173    })?;
174
175    let uuid = (&claims.sub).parse::<i16>().map_err(|_| {
176        let error = json!({
177            "status": "error",
178            "message": "Invalid user id"
179        });
180        (StatusCode::UNAUTHORIZED, Json(error))
181    })?;
182
183    let user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#)
184        .bind(uuid)
185        .fetch_optional(&data.db)
186        .await
187        .map_err(|e| {
188            let error = json!({
189                "status": "error",
190                "message": format!("Database error: {}", e)
191            });
192            (StatusCode::INTERNAL_SERVER_ERROR, Json(error))
193        })?;
194
195    let user = user.ok_or_else(|| {
196        let error = json!({
197            "status": "error",
198            "message": "Invalid user"
199        });
200        (StatusCode::UNAUTHORIZED, Json(error))
201    })?;
202
203    if !user.is_admin {
204        let error = json!({
205            "status": "error",
206            "message": "Admin access required"
207        });
208        return Err((StatusCode::FORBIDDEN, Json(error)));
209    }
210
211    request.extensions_mut().insert(filter_user(&user));
212    Ok(next.run(request).await)
213}