feat(registry): add whoami, profile, and token CRUD endpoints#12011
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (3)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
🧰 Additional context used📓 Path-based instructions (1)registry/crates/**/*.rs📄 CodeRabbit inference engine (registry/AGENTS.md)
Files:
🔇 Additional comments (2)
📝 WalkthroughWalkthroughAdds TokenStore lookup/list/revoke APIs with optional SQLite deletion, centralizes ISO millisecond timestamp formatting, wires new npm-compatible auth/token HTTP routes and handlers with cache-control, and adds acceptance tests covering auth flows and persistence. ChangesToken Management Endpoints
Sequence DiagramsequenceDiagram
participant Client
participant Server
participant TokenStore
participant SQLite
Client->>Server: GET /-/whoami (bearer token)
Server->>TokenStore: find_by_key(token_hash)
TokenStore-->>Server: Option<TokenRecord>
Server->>Client: 200 {username} or 401
Client->>Server: GET /-/npm/v1/tokens (bearer token)
Server->>TokenStore: list_for_user(username)
TokenStore-->>Server: Vec<(key, TokenRecord)>
Server->>Client: 200 {objects: [token_response_object], urls: {}}
Client->>Server: DELETE /-/npm/v1/tokens/token/{key}
Server->>TokenStore: revoke_by_key(key)
TokenStore->>SQLite: delete_token(token_hash) (if persistent)
SQLite-->>TokenStore: deletion result
TokenStore-->>Server: Option<TokenRecord>
Server->>Client: 200 (revoked) or 404/403/401
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review Summary by QodoAdd whoami, profile, and token CRUD endpoints with SQLite persistence
WalkthroughsDescription• Add five npm-CLI-compatible auth endpoints: whoami, profile, token listing, and token revocation • Implement token CRUD operations with SQLite persistence for revocation across restarts • Add ownership-gated access control (401 anonymous, 403 cross-user, 404 unknown) • Extract ISO-8601 timestamp formatting for consistent token and packument timestamps • Add 16 comprehensive acceptance tests covering success paths, boundaries, and persistence Diagramflowchart LR
A["HTTP Requests"] -->|GET /-/whoami| B["serve_whoami"]
A -->|GET /-/npm/v1/user| C["serve_profile"]
A -->|GET /-/npm/v1/tokens| D["list_tokens"]
A -->|DELETE /-/npm/v1/tokens/token/:key| E["revoke_token_by_key"]
A -->|DELETE /-/user/token/:tok| F["logout"]
B --> G["TokenStore"]
C --> G
D --> G
E --> G
F --> G
G -->|find_by_key| H["In-Memory Cache"]
G -->|list_for_user| H
G -->|revoke_by_key| I["SQLite DB"]
G -->|revoke_by_raw| I
J["iso_from_unix_millis"] -->|Format timestamps| K["Token Response Objects"]
File Changes1. registry/crates/pnpm-registry/src/auth.rs
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
registry/crates/pnpm-registry/src/publish.rs (1)
184-188:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse
div_euclidfor the day component.
rem_euclid()already normalizes the time-of-day, butmillis / 86_400_000still truncates toward zero. That makes pre-epoch inputs format the wrong calendar date (iso_from_unix_millis(-1)becomes1970-01-01T23:59:59.999Z).Proposed fix
- let (days, ms_in_day) = (millis / 86_400_000, millis.rem_euclid(86_400_000)); + let (days, ms_in_day) = (millis.div_euclid(86_400_000), millis.rem_euclid(86_400_000));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@registry/crates/pnpm-registry/src/publish.rs` around lines 184 - 188, The calculation in iso_from_unix_millis uses normal division for the day count which truncates toward zero for negative millis and yields wrong dates before epoch; replace the expression computing days (currently using millis / 86_400_000) with millis.div_euclid(86_400_000) so the day component uses Euclidean division while keeping ms_in_day = millis.rem_euclid(86_400_000) as-is; update the tuple binding (days, ms_in_day) accordingly in the iso_from_unix_millis function.
🧹 Nitpick comments (2)
registry/crates/pnpm-registry/tests/auth_user_endpoints.rs (1)
227-238: ⚡ Quick winConsider removing the unused
appparameter.The
appparameter is not used by this pure hash-comparison function. While the comment mentions keeping it "for symmetry with the other helpers," most helper functions in this file (such asbody_bytes,body_json, and the request builders) do not take anappparameter. Removing it would simplify the signature without affecting functionality.♻️ Proposed simplification
-async fn uses_token(app: &axum::Router, raw_token: &str, candidate_key: &str) -> bool { +async fn uses_token(raw_token: &str, candidate_key: &str) -> bool { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(raw_token.as_bytes()); let digest = hasher.finalize(); let mut hex = String::with_capacity(64); for byte in digest.iter() { hex.push_str(&format!("{byte:02x}")); } - let _ = app; // app is unused but kept for symmetry with the other helpers hex == candidate_key }And update the call site at line 203:
- let target_key = if uses_token(&app, &victim_token, alice_key).await { + let target_key = if uses_token(&victim_token, alice_key).await {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@registry/crates/pnpm-registry/tests/auth_user_endpoints.rs` around lines 227 - 238, Remove the unused app parameter from the uses_token function signature and its helper implementation (function uses_token), make it take (raw_token: &str, candidate_key: &str) only, and update all call sites that pass an app (e.g., the call near line ~203 referenced in the comment) to call the new two-argument signature; keep the SHA256 hashing and comparison logic unchanged.registry/crates/pnpm-registry/src/server.rs (1)
645-663: Redact the logout path in request/access logs.This endpoint puts a live bearer token in the URL. Make sure any HTTP/access logging records the matched route or a redacted path, otherwise logout requests will write credentials into logs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@registry/crates/pnpm-registry/src/server.rs` around lines 645 - 663, The logout handler leaks the raw bearer token in the URL (raw_token) which can end up in request/access logs; update the request logging middleware or this handler so that any log entry for the logout route records the matched route or a redacted path instead of the full request URI. Specifically, detect requests handled by the logout function (or requests containing the raw_token) and replace the path with a constant like "/-/logout/{redacted}" (or set a sanitized route attribute on the request/context that your logger reads) before any access logging occurs, ensuring state.inner.auth.tokens.lookup(raw_token) and revoke_by_raw(raw_token) still receive the original raw_token while logs only contain the redacted path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@registry/crates/pnpm-registry/src/auth.rs`:
- Around line 339-355: The revoke_by_key implementation removes the token from
inner.tokens before doing the blocking SQLite delete, causing a divergence if
Connection::open() or delete_token() fails; change the flow to keep memory and
DB in sync by either (A) performing the persistent delete first inside the
tokio::task::spawn_blocking block (using self.persist.clone() and the key) and
only removing from inner.tokens after that succeeds, or (B) if you prefer
minimal change, keep the current removal but on any error from the
spawn_blocking call reacquire the TokenStore mutex and re-insert the removed
TokenRecord back into inner.tokens (use the previously captured record variable)
so the in-memory map is restored; reference revoke_by_key, inner.tokens,
persist, Connection::open, and delete_token when making the change.
In `@registry/crates/pnpm-registry/src/server.rs`:
- Around line 553-695: All responses from the auth endpoint group (serve_whoami,
serve_profile, list_tokens, revoke_token_by_key, logout and any branch-returning
helpers they call) must be marked non-cacheable and Vary by Authorization; add
Cache-Control: private, no-store and Vary: Authorization to every Response these
handlers emit, including 401/403/404/error branches. Implement this by adding a
small helper (e.g. add_no_cache_headers(resp: Response) -> Response) that
inserts header::CACHE_CONTROL = "private, no-store" and header::VARY =
"Authorization" and use it to wrap the Response returned from json_response(),
error_response() usages, not_found() branches and the success responses in
serve_whoami, serve_profile, list_tokens, revoke_token_by_key, and logout (or
alternatively change json_response to always set these headers for the auth
endpoints), ensuring no code path in those functions returns a Response without
those headers.
---
Outside diff comments:
In `@registry/crates/pnpm-registry/src/publish.rs`:
- Around line 184-188: The calculation in iso_from_unix_millis uses normal
division for the day count which truncates toward zero for negative millis and
yields wrong dates before epoch; replace the expression computing days
(currently using millis / 86_400_000) with millis.div_euclid(86_400_000) so the
day component uses Euclidean division while keeping ms_in_day =
millis.rem_euclid(86_400_000) as-is; update the tuple binding (days, ms_in_day)
accordingly in the iso_from_unix_millis function.
---
Nitpick comments:
In `@registry/crates/pnpm-registry/src/server.rs`:
- Around line 645-663: The logout handler leaks the raw bearer token in the URL
(raw_token) which can end up in request/access logs; update the request logging
middleware or this handler so that any log entry for the logout route records
the matched route or a redacted path instead of the full request URI.
Specifically, detect requests handled by the logout function (or requests
containing the raw_token) and replace the path with a constant like
"/-/logout/{redacted}" (or set a sanitized route attribute on the
request/context that your logger reads) before any access logging occurs,
ensuring state.inner.auth.tokens.lookup(raw_token) and revoke_by_raw(raw_token)
still receive the original raw_token while logs only contain the redacted path.
In `@registry/crates/pnpm-registry/tests/auth_user_endpoints.rs`:
- Around line 227-238: Remove the unused app parameter from the uses_token
function signature and its helper implementation (function uses_token), make it
take (raw_token: &str, candidate_key: &str) only, and update all call sites that
pass an app (e.g., the call near line ~203 referenced in the comment) to call
the new two-argument signature; keep the SHA256 hashing and comparison logic
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ffcbafbf-d134-4a4c-b1e2-df1ea4e21472
📒 Files selected for processing (4)
registry/crates/pnpm-registry/src/auth.rsregistry/crates/pnpm-registry/src/publish.rsregistry/crates/pnpm-registry/src/server.rsregistry/crates/pnpm-registry/tests/auth_user_endpoints.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
registry/crates/**/*.rs
📄 CodeRabbit inference engine (registry/AGENTS.md)
Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, error handling, and test layout
Files:
registry/crates/pnpm-registry/src/publish.rsregistry/crates/pnpm-registry/tests/auth_user_endpoints.rsregistry/crates/pnpm-registry/src/server.rsregistry/crates/pnpm-registry/src/auth.rs
🔇 Additional comments (10)
registry/crates/pnpm-registry/tests/auth_user_endpoints.rs (10)
1-37: LGTM!
39-77: LGTM!
79-92: LGTM!
94-123: LGTM!
125-150: LGTM!
152-185: LGTM!
187-220: LGTM!
240-282: LGTM!
284-337: LGTM!
339-366: LGTM!
…eable * `TokenStore::revoke_by_key` now writes the SQLite `DELETE` *before* mutating the in-memory map. If the disk write fails the caller sees a 5xx and the token stays valid in both views — the old ordering could revoke in memory but resurrect on restart. * All five auth endpoints (whoami, profile, token list/revoke, logout) now emit `Cache-Control: private, no-store` and `Vary: Authorization` on every branch — success, 401, 403, 404 alike — so a shared HTTP cache can't latch onto one caller's identity and replay it to another. Both addresses CodeRabbit review findings on #12011.
Wires the five remaining auth surfaces from #11973 onto pnpr's verdaccio-compatible HTTP layer: * GET /-/whoami — `{username}` for the bearer caller, 401 anonymous. * GET /-/npm/v1/user — profile shape that `npm profile get` parses. * GET /-/npm/v1/tokens — `{objects, urls}` listing only the caller's tokens; `key` is the SHA-256 hex digest, `token` is the 6-char preview that surfaces when the raw value isn't recoverable. * DELETE /-/npm/v1/tokens/token/:key — `npm token revoke`. Ownership -gated: 401 anonymous, 403 cross-user, 404 unknown key. * DELETE /-/user/token/:tok — `npm logout`. Looks up by raw token, revokes by hash; same gating as the listing-side revoke. TokenStore gains `find_by_key`, `list_for_user`, `revoke_by_key`, and `revoke_by_raw`; revocations write through to the SQLite store so a restart can't resurrect a logged-out token.
…eable * `TokenStore::revoke_by_key` now writes the SQLite `DELETE` *before* mutating the in-memory map. If the disk write fails the caller sees a 5xx and the token stays valid in both views — the old ordering could revoke in memory but resurrect on restart. * All five auth endpoints (whoami, profile, token list/revoke, logout) now emit `Cache-Control: private, no-store` and `Vary: Authorization` on every branch — success, 401, 403, 404 alike — so a shared HTTP cache can't latch onto one caller's identity and replay it to another. Both addresses CodeRabbit review findings on #12011.
Rename single-letter closure params (`|v|` → `|value|`) and add trailing commas to the multi-line `assert_eq!` invocations so the nightly Dylint job stops failing.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12011 +/- ##
==========================================
+ Coverage 88.21% 88.26% +0.05%
==========================================
Files 228 228
Lines 28777 28941 +164
==========================================
+ Hits 25385 25545 +160
- Misses 3392 3396 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Wires the five remaining auth surfaces from the pnpr parity tracking issue — everything under Auth & user endpoints except the pluggable-auth decision.
GET /-/whoami—{ username }for the bearer caller, 401 anonymous.GET /-/npm/v1/user— profile shape (name,tfa,email,cidr_whitelist,fullname) thatnpm profile get's table renderer parses cleanly.GET /-/npm/v1/tokens—{ objects, urls }listing only the caller's tokens.keyis the SHA-256 hex digest of the raw token (matches whatnpm token revokesends back);tokenis the 6-char preview surfaced when the original value isn't recoverable, which is what verdaccio does for the same reason.DELETE /-/npm/v1/tokens/token/:key—npm token revoke. Ownership-gated: 401 anonymous, 403 cross-user, 404 unknown key. Persists through the SQLite store so a restart can't resurrect a revoked token.DELETE /-/user/token/:tok—npm logout. Looks up by raw token, revokes by hash; same gating as the listing-side revoke.TokenStoregainsfind_by_key,list_for_user,revoke_by_key, andrevoke_by_raw;publish::now_isois split so token timestamps render in the same ISO-8601 shape astime.modified.Test plan
cargo test -p pnpm-registry— 140 passed (16 new acceptance tests intests/auth_user_endpoints.rscovering success, ownership boundaries, 401/403/404, and persistence of revocation across a restart).cargo clippy --all-targets --deny warningsclean.cargo fmt --checkclean.cargo check --workspace --all-targetsclean.Closes the corresponding boxes under Auth & user endpoints in #11973 (everything except the pluggable-auth decision, which is left out per the issue's "decision required" note).
Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
New Features
Tests