mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-04 00:47:01 +01:00
The login endpoint answered guesses as fast as they arrived, against a single static password. Wrong attempts now back off — 1s, 2s, 4s, up to 30s — and a correct one clears the count. It is a single counter rather than a table keyed by address: there is only one account, so an attacker gains nothing by rotating source IPs, and there is no per-client map to grow. The cap is there so that someone hammering the login delays the owner by half a minute rather than locking them out. Wrong credentials now answer 401 rather than 400. The server also warns at startup if DIARY_PASSWORD is still the value from .env.example.
50 lines
1.4 KiB
Rust
50 lines
1.4 KiB
Rust
use axum::{
|
|
http::StatusCode,
|
|
response::{IntoResponse, Response},
|
|
Json,
|
|
};
|
|
use serde_json::json;
|
|
|
|
#[derive(Debug)]
|
|
pub enum AppError {
|
|
Unauthorized,
|
|
NotFound,
|
|
BadRequest(String),
|
|
/// The caller must wait this many seconds before trying again.
|
|
TooManyRequests(i64),
|
|
Internal(anyhow::Error),
|
|
}
|
|
|
|
pub type AppResult<T> = Result<T, AppError>;
|
|
|
|
impl IntoResponse for AppError {
|
|
fn into_response(self) -> Response {
|
|
let (status, message) = match self {
|
|
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized".to_string()),
|
|
AppError::NotFound => (StatusCode::NOT_FOUND, "not found".to_string()),
|
|
AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m),
|
|
AppError::TooManyRequests(wait) => (
|
|
StatusCode::TOO_MANY_REQUESTS,
|
|
format!("too many attempts — wait {wait}s"),
|
|
),
|
|
AppError::Internal(e) => {
|
|
tracing::error!("internal error: {e:#}");
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
"internal error".to_string(),
|
|
)
|
|
}
|
|
};
|
|
(status, Json(json!({ "error": message }))).into_response()
|
|
}
|
|
}
|
|
|
|
impl<E> From<E> for AppError
|
|
where
|
|
E: Into<anyhow::Error>,
|
|
{
|
|
fn from(err: E) -> Self {
|
|
AppError::Internal(err.into())
|
|
}
|
|
}
|