feat(pm): add private npm registry auth#2758
Conversation
The old auto-update used in-memory global state (AtomicBool) that reset on every process start, so updates could never actually trigger. The background version check was also killed by runtime shutdown before completing. New design: - Read cached version from ~/.utoo/remote-version.json on startup - If cache is fresh and version differs, try update immediately - If cache is missing/expired, background fetch + update if needed - 24h cooldown after failed attempts to avoid nagging - UTOO_FORCE_UPDATE=1|true to bypass dev/CI guards for testing - Suppress install output during auto-update, show colored status Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…wnload Support Bearer token authentication for private npm registries. Token is resolved from NPM_TOKEN / NODE_AUTH_TOKEN env vars or ~/.utoo/config.toml (via `utoo login`). - ruborist: make HTTP client injectable (`set_http_client`) so pm can configure it with auth headers — ruborist itself stays auth-unaware - pm: build pre-configured HTTP client with Bearer default header and inject into ruborist at startup - downloader: add per-request Bearer auth with hostname guard to prevent token leakage to third-party CDNs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request refactors the auto-update mechanism to include a 24-hour retry cooldown and introduces private registry authentication support by injecting Bearer tokens into HTTP clients. Key changes include moving the update check to a non-blocking background task and adding a global authentication token resolver. Feedback focuses on improving the robustness of host extraction for token attachment, handling potential token parsing failures, and addressing thread-safety issues in tests that modify environment variables.
| // Set CI environment variable | ||
| #[test] | ||
| fn test_skip_ci_environment() { | ||
| unsafe { std::env::set_var("CI", "1") }; |
There was a problem hiding this comment.
| fn extract_host(url: &str) -> &str { | ||
| let without_scheme = url | ||
| .strip_prefix("https://") | ||
| .or_else(|| url.strip_prefix("http://")) | ||
| .unwrap_or(url); | ||
| without_scheme.split('/').next().unwrap_or(without_scheme) | ||
| } |
There was a problem hiding this comment.
The manual string parsing for host extraction is fragile and doesn't handle authentication info in the URL (e.g., https://user:pass@host). Since the url crate is already a dependency, it's recommended to use it for robust host extraction, or at least improve the string manipulation to handle the auth part.
| fn extract_host(url: &str) -> &str { | |
| let without_scheme = url | |
| .strip_prefix("https://") | |
| .or_else(|| url.strip_prefix("http://")) | |
| .unwrap_or(url); | |
| without_scheme.split('/').next().unwrap_or(without_scheme) | |
| } | |
| fn extract_host(url: &str) -> &str { | |
| let without_scheme = url | |
| .strip_prefix("https://") | |
| .or_else(|| url.strip_prefix("http://")) | |
| .unwrap_or(url); | |
| let host_part = without_scheme.split('/').next().unwrap_or(without_scheme); | |
| host_part.rsplit('@').next().unwrap_or(host_part) | |
| } |
| if let Ok(val) = format!("Bearer {token}").parse() { | ||
| headers.insert(AUTHORIZATION, val); | ||
| } |
There was a problem hiding this comment.
If the token contains characters that are invalid for an HTTP header value (like newlines), parse() will fail silently, and the client will be configured without authentication. This can lead to confusing 401 errors later. Consider logging a warning or returning early if the token is invalid.
let Ok(val) = format!("Bearer {token}").parse() else {
tracing::warn!("Failed to parse auth token for registry {}", registry);
return;
};
headers.insert(AUTHORIZATION, val);- extract_host: handle user:pass@host URLs by stripping auth prefix - init_auth_token: warn and return early on invalid token header value Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
NPM_TOKEN/NODE_AUTH_TOKENenv vars or~/.utoo/config.toml(utoo login)set_http_client) — stays auth-unawareDesign
set_http_client, otherwise auto-builtAuthorization: Bearerdefault header, injects into ruboristTest plan
NPM_TOKEN=<token> ut installwith a private scoped packageutoo login+ut installreads token from~/.utoo/config.toml🤖 Generated with Claude Code