v0.8.0: dep prune, memtest introspection fix, concurrent uploads, x-api-key
Dependency cleanup (ponytail audit): - Drop 14 unused dependency declarations across 7 crates; quick-xml and x509-parser leave the tree entirely (SAML cert/XML work is handled by bergshamra + roxmltree). Fixes: - introspect: drop the over-broad "microsoft" UTF-16 bulk-scan marker that mislabeled Secure-Boot-signed non-Windows bootables (memtest86, signed BSDs, firmware tools) as Windows — the string lives in their MS-signed EFI loader's FAT long-filename entries. INTROSPECT_REV 3 -> 4 re-probes existing local ISOs on startup so the bogus label clears on upgrade. - upload: begin_upload now reclaims an abandoned <id>.partial instead of rejecting the re-upload with "already uploading". Robust against browser refresh, tab close, and dropped connections (the chunked protocol can't resume a dead session anyway). Features: - Storage upload: multi-file + concurrent. Each dropped/selected .iso gets its own progress row and uploads independently; a single page-leave guard plus a pagehide keepalive-abort replace the old shared singletons. - Operator API key (x-api-key): a persisted key authenticates /api/* exactly like an operator session, for Postman/scripts. New core ApiKeyStore (generated on first run, regenerable), accepted in require_auth alongside the session cookie, surfaced in Settings -> Advanced with copy + regenerate and a usage reference. GET /api/api-key + POST /api/api-key/regenerate. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
27703c437a
commit
1c262a6d61
@@ -99,6 +99,7 @@ async fn build_state() -> (AppState, tempfile::TempDir) {
|
||||
let admin = openpxe_core::AdminStore::load_or_default(dir.path());
|
||||
let sso = openpxe_core::SsoStore::load_or_default(dir.path());
|
||||
let notify = openpxe_core::NotifyStore::load_or_default(dir.path());
|
||||
let api_key = openpxe_core::ApiKeyStore::load_or_init(dir.path());
|
||||
let sessions = openpxe_http_api::auth::SessionStore::default();
|
||||
let metrics = Metrics::new();
|
||||
let state = AppState {
|
||||
@@ -114,6 +115,7 @@ async fn build_state() -> (AppState, tempfile::TempDir) {
|
||||
pxe_bg_cache: openpxe_http_api::state::PxeBgCache::default(),
|
||||
admin,
|
||||
sessions,
|
||||
api_key,
|
||||
sso,
|
||||
saml: openpxe_http_api::saml_routes::SamlRuntime::default(),
|
||||
notify,
|
||||
@@ -135,6 +137,60 @@ async fn build_state() -> (AppState, tempfile::TempDir) {
|
||||
(state, dir)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn api_key_authenticates_gated_endpoints() {
|
||||
// v0.8.0: the x-api-key header authenticates /api/* like an operator
|
||||
// session. The middleware only enforces once an admin is configured
|
||||
// (before that everything is open), so bootstrap one first.
|
||||
let (state, _dir) = build_state().await;
|
||||
state
|
||||
.admin
|
||||
.bootstrap("admin", "correct-horse-battery-staple")
|
||||
.unwrap();
|
||||
let key = state.api_key.current();
|
||||
let app = build_router(state);
|
||||
|
||||
// No credentials → 401.
|
||||
let res = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/isos")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED, "no auth must 401");
|
||||
|
||||
// Wrong key → 401.
|
||||
let res = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/isos")
|
||||
.header("x-api-key", "not-the-key")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED, "wrong key must 401");
|
||||
|
||||
// Correct key → 200 (operator-equivalent access).
|
||||
let res = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/isos")
|
||||
.header("x-api-key", key)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(res.status(), StatusCode::OK, "valid key must authenticate");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_and_ready_endpoints() {
|
||||
let (state, _dir) = build_state().await;
|
||||
|
||||
Reference in New Issue
Block a user