Feat: Implement basic web authentication and UI

This commit introduces the foundation for web-based authentication and
user interface. It includes:

- **Session Management**: Securely handling user sessions using
  cryptographically generated tokens, cookies with appropriate security
  flags, and server-side storage.
- **Login/Logout Functionality**: Endpoints for users to log in with
  their credentials and log out, clearing their session.
- **User Interface**: Basic templates for the login page, a list of
  cases, and a detailed view of a single case, allowing users to
  navigate and view their data.
- **Authorization**: An `AuthenticatedWebUser` extractor to ensure only
  logged-in users can access protected web routes.
- **Configuration**: Added a `cookie_secure` option to the configuration
  to control the `Secure` flag on session cookies, useful for local
  development.
- **Dependencies**: Added necessary dependencies for password hashing,
  time handling, and TOML file manipulation.
- **Helper Binary**: Included a `hash-password` binary to simplify
  generating bcrypt hashes for user passwords.
This commit is contained in:
2026-04-14 15:09:23 +02:00
parent 61b5c8e125
commit d489716c9b
24 changed files with 930 additions and 16 deletions
+11
View File
@@ -7,15 +7,26 @@ pub enum AppError {
BadRequest(String),
NotFound(String),
Internal(String),
/// 302 redirect. Used by web auth to send unauthenticated users to login.
Redirect(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
if let Self::Redirect(url) = &self {
return Response::builder()
.status(StatusCode::FOUND)
.header(axum::http::header::LOCATION, url)
.body(axum::body::Body::empty())
.unwrap();
}
let (status, message) = match &self {
Self::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".to_owned()),
Self::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
Self::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
Self::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
Self::Redirect(_) => unreachable!(),
};
let body = axum::Json(json!({ "error": message }));