mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-13 03:50:32 +00:00
feat: upstream proxy retry + Maven proxy-only (#56)
* docs: add DCO, governance model, roles, vulnerability credit policy * security: migrate token hashing from SHA256 to Argon2id - Replace unsalted SHA256 with Argon2id (salted) for API token hashing - Fix TOCTOU race: replace exists()+read() with read()+match on error - Set chmod 600 on token files and 700 on token storage directory - Auto-migrate legacy SHA256 tokens to Argon2id on first verification - Add regression tests: argon2 format, legacy migration, file permissions * feat: add retry with timeout for upstream proxy, mark Maven proxy-only - Add shared proxy_fetch() and proxy_fetch_text() with 1 retry on 5xx/timeout - Replace duplicated fetch_from_proxy in maven.rs, npm.rs, pypi.rs - Mark Maven as proxy-only in README (no full repository manager support) - Existing timeout config (30s maven/npm/pypi, 60s docker) preserved - 4xx errors fail immediately without retry
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::activity_log::{ActionType, ActivityEntry};
|
||||
use crate::audit::AuditEntry;
|
||||
use crate::config::basic_auth_header;
|
||||
use crate::registry::proxy_fetch;
|
||||
use crate::AppState;
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
@@ -16,7 +16,6 @@ use axum::{
|
||||
use base64::Engine;
|
||||
use sha2::Digest;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
@@ -140,7 +139,7 @@ async fn handle_request(State(state): State<Arc<AppState>>, Path(path): Path<Str
|
||||
if let Some(proxy_url) = &state.config.npm.proxy {
|
||||
let url = format!("{}/{}", proxy_url.trim_end_matches('/'), path);
|
||||
|
||||
if let Ok(data) = fetch_from_proxy(
|
||||
if let Ok(data) = proxy_fetch(
|
||||
&state.http_client,
|
||||
&url,
|
||||
state.config.npm.proxy_timeout,
|
||||
@@ -208,7 +207,7 @@ async fn refetch_metadata(state: &Arc<AppState>, path: &str, key: &str) -> Optio
|
||||
let proxy_url = state.config.npm.proxy.as_ref()?;
|
||||
let url = format!("{}/{}", proxy_url.trim_end_matches('/'), path);
|
||||
|
||||
let data = fetch_from_proxy(
|
||||
let data = proxy_fetch(
|
||||
&state.http_client,
|
||||
&url,
|
||||
state.config.npm.proxy_timeout,
|
||||
@@ -419,25 +418,6 @@ async fn handle_publish(
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
async fn fetch_from_proxy(
|
||||
client: &reqwest::Client,
|
||||
url: &str,
|
||||
timeout_secs: u64,
|
||||
auth: Option<&str>,
|
||||
) -> Result<Vec<u8>, ()> {
|
||||
let mut request = client.get(url).timeout(Duration::from_secs(timeout_secs));
|
||||
if let Some(credentials) = auth {
|
||||
request = request.header("Authorization", basic_auth_header(credentials));
|
||||
}
|
||||
let response = request.send().await.map_err(|_| ())?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
response.bytes().await.map(|b| b.to_vec()).map_err(|_| ())
|
||||
}
|
||||
|
||||
fn with_content_type(
|
||||
is_tarball: bool,
|
||||
data: Bytes,
|
||||
|
||||
Reference in New Issue
Block a user