Update config and dependencies for auth
Refactor configuration loading to separate user credentials from server settings. Update dependencies to their latest compatible versions to leverage new features and security patches. Key changes include: - Moving user credentials (`api_key`, `web_password`, `role`) from `.env` to a dedicated `users.toml` file for better manageability and security. - Introducing an `AuthenticatedUser` struct and extractor for API key-based authentication. - Updating `axum` and related crates to leverage Rust edition 2024 improvements. - Adjusting configuration values and tests to reflect these changes.
This commit is contained in:
Generated
+17
-71
@@ -61,17 +61,6 @@ dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -86,13 +75,13 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.9"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
@@ -105,8 +94,7 @@ dependencies = [
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
@@ -120,19 +108,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
@@ -141,26 +127,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.9.6"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
|
||||
checksum = "fef252edff26ddba56bbcdf2ee3307b8129acb86f5749b68990c168a6fcc9c76"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"multer",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -349,7 +333,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"toml",
|
||||
"tower",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
@@ -906,9 +890,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -949,23 +933,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multer"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"http",
|
||||
"httparse",
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.18"
|
||||
@@ -1256,7 +1223,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower",
|
||||
"tower-http 0.6.8",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -1502,12 +1469,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
@@ -1773,23 +1734,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.8"
|
||||
@@ -1801,11 +1745,13 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7"
|
||||
axum = "0.8"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
reqwest = { version = "0.12", features = ["json", "multipart"] }
|
||||
askama = "0.12"
|
||||
@@ -16,10 +16,10 @@ uuid = { version = "1", features = ["v4"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
toml = "0.8"
|
||||
tower-http = { version = "0.5", features = ["limit", "trace"] }
|
||||
tower-http = { version = "0.6", features = ["limit", "trace"] }
|
||||
rand = "0.8"
|
||||
bcrypt = "0.15"
|
||||
axum-extra = { version = "0.9", features = ["cookie"] }
|
||||
axum-extra = { version = "0.12", features = ["cookie"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tower = { version = "0.5", features = ["util"] }
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::FromRequestParts;
|
||||
use axum::http::request::Parts;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::AppError;
|
||||
|
||||
/// Extracted from the X-API-Key header on every authenticated request.
|
||||
pub struct AuthenticatedUser {
|
||||
pub slug: String,
|
||||
pub role: String,
|
||||
pub data_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl FromRequestParts<Arc<Config>> for AuthenticatedUser {
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
state: &Arc<Config>,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
let api_key = parts
|
||||
.headers
|
||||
.get("X-API-Key")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
let slug = state
|
||||
.api_keys
|
||||
.get(api_key)
|
||||
.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
let user = state
|
||||
.users
|
||||
.iter()
|
||||
.find(|u| u.slug == *slug)
|
||||
.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
let data_dir = state.data_path.join(slug);
|
||||
tokio::fs::create_dir_all(data_dir.join("open")).await?;
|
||||
tokio::fs::create_dir_all(data_dir.join("done")).await?;
|
||||
|
||||
Ok(Self {
|
||||
slug: slug.clone(),
|
||||
role: user.role.clone(),
|
||||
data_dir,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use axum::Json;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::auth::AuthenticatedUser;
|
||||
use crate::error::AppError;
|
||||
|
||||
pub async fn handle_whoami(user: AuthenticatedUser) -> Result<Json<Value>, AppError> {
|
||||
Ok(Json(json!({
|
||||
"slug": user.slug,
|
||||
"role": user.role,
|
||||
"data_dir": user.data_dir.to_string_lossy(),
|
||||
})))
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
mod debug;
|
||||
mod health;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub fn api_router() -> Router<Arc<Config>> {
|
||||
Router::new().route("/api/health", axum::routing::get(health::handle_health))
|
||||
Router::new()
|
||||
.route("/api/health", get(health::handle_health))
|
||||
.route("/api/debug/whoami", get(debug::handle_whoami))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use doctate_server::config::{Config, User};
|
||||
|
||||
fn test_config() -> Arc<Config> {
|
||||
Arc::new(Config {
|
||||
server_port: 3000,
|
||||
data_path: "/tmp/doctate-test".into(),
|
||||
log_level: "info".into(),
|
||||
log_path: "/tmp/doctate-test/logs".into(),
|
||||
log_max_days: 90,
|
||||
users: vec![User {
|
||||
slug: "dr_test".into(),
|
||||
api_key: "test-key-123".into(),
|
||||
web_password: "unused".into(),
|
||||
role: "doctor".into(),
|
||||
}],
|
||||
api_keys: HashMap::from([("test-key-123".into(), "dr_test".into())]),
|
||||
retention_audio_days: 30,
|
||||
retention_transcript_days: 30,
|
||||
retention_document_days: 0,
|
||||
whisper_url: "http://localhost:10300".into(),
|
||||
whisper_timeout_seconds: 120,
|
||||
ollama_url: "http://localhost:11434".into(),
|
||||
ollama_model: "gemma3:4b".into(),
|
||||
ollama_keep_alive: 0,
|
||||
llm_url: String::new(),
|
||||
llm_api_key: String::new(),
|
||||
llm_model: String::new(),
|
||||
llm_temperature: 0.0,
|
||||
session_timeout_hours: 8,
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn valid_api_key_returns_user() {
|
||||
let app = doctate_server::create_router(test_config());
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/debug/whoami")
|
||||
.header("X-API-Key", "test-key-123")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.unwrap();
|
||||
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
assert_eq!(json["slug"], "dr_test");
|
||||
assert_eq!(json["role"], "doctor");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_api_key_returns_401() {
|
||||
let app = doctate_server::create_router(test_config());
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/debug/whoami")
|
||||
.header("X-API-Key", "wrong-key")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn missing_api_key_returns_401() {
|
||||
let app = doctate_server::create_router(test_config());
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/debug/whoami")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
Reference in New Issue
Block a user