Skip to main content

allowthem_server/
error.rs

1use axum::http::StatusCode;
2use axum::response::{IntoResponse, Response};
3use serde_json::json;
4
5use allowthem_core::AuthError;
6
7/// Error type for authentication extractor failures.
8///
9/// Implements `IntoResponse` to produce appropriate HTTP error responses.
10/// Used as the `Rejection` type for [`AuthUser`](crate::AuthUser).
11#[derive(Debug)]
12pub enum AuthExtractError {
13    /// No valid session. Covers: missing cookie, invalid token, expired
14    /// session, orphaned session (user deleted), or inactive user.
15    Unauthenticated,
16    /// Database or internal error during extraction.
17    Internal(AuthError),
18}
19
20impl IntoResponse for AuthExtractError {
21    fn into_response(self) -> Response {
22        match self {
23            Self::Unauthenticated => (
24                StatusCode::UNAUTHORIZED,
25                axum::Json(json!({"error": "unauthenticated"})),
26            )
27                .into_response(),
28            Self::Internal(err) => {
29                tracing::error!("auth extraction error: {err}");
30                (
31                    StatusCode::INTERNAL_SERVER_ERROR,
32                    axum::Json(json!({"error": "internal error"})),
33                )
34                    .into_response()
35            }
36        }
37    }
38}
39
40/// Rejection type for [`BrowserAuthUser`](crate::BrowserAuthUser).
41///
42/// Redirects unauthenticated browser requests to the login page with a
43/// `?next=` parameter preserving the originally requested path.
44#[derive(Debug)]
45pub struct BrowserAuthRedirect(pub(crate) String);
46
47impl BrowserAuthRedirect {
48    pub fn new(path: &str) -> Self {
49        Self(format!("/login?next={path}"))
50    }
51}
52
53impl IntoResponse for BrowserAuthRedirect {
54    fn into_response(self) -> Response {
55        (
56            StatusCode::SEE_OTHER,
57            [(axum::http::header::LOCATION, self.0)],
58        )
59            .into_response()
60    }
61}
62
63/// 403 Forbidden response for authenticated non-admin users.
64///
65/// Returns a minimal HTML body. No template needed — this is a guard page,
66/// not a user-facing feature.
67pub struct BrowserAdminForbidden;
68
69impl IntoResponse for BrowserAdminForbidden {
70    fn into_response(self) -> Response {
71        (
72            StatusCode::FORBIDDEN,
73            axum::response::Html("<h1>403 Forbidden</h1><p>Admin access required.</p>"),
74        )
75            .into_response()
76    }
77}