Skip to content

pnpr: persist users and tokens to disk #11974

Description

@zkochan

Part of #11973.

Problem

registry/crates/pnpm-registry/src/auth.rs keeps both stores entirely in memory:

  • UserStore: username → plaintext password in a HashMap guarded by a Mutex. The module doc even calls this out: "no need for password hashing, token expiration, or on-disk persistence — everything lives in an in-memory store".
  • TokenStore: token → username, same shape.

Every restart wipes both. That's fine for @pnpm/registry-mock (the only consumer today) but it makes pnpr unusable as a hosted registry — operators cannot keep user accounts or tokens across process restarts, container redeploys, or crashes. This is the single biggest blocker on the parity tracking issue.

Scope

  • Persist users to an htpasswd file (path from config: auth.htpasswd.file, already in the verdaccio-shaped YAML).
  • Hash passwords on write (bcrypt — verdaccio uses bcrypt; matches the existing htpasswd toolchain).
  • Load the htpasswd file on startup; create it on first user registration if absent.
  • Persist tokens to a sibling file (or sqlite — open to either, but a flat JSONL keeps the dependency budget low). Token records: token_hash, username, created_at, last_used_at, readonly, cidr_whitelist.
  • Store hashes of tokens, not raw tokens, so a leaked file doesn't grant access.
  • Enforce auth.htpasswd.max_users (config field already documented in the bundled example).
  • Honor auth.htpasswd.max_users: -1 → registration disabled.
  • Crash-safe writes: write to *.tmp then rename, same pattern as the tarball store.

Out of scope (separate issues)

  • Token revocation endpoint (DELETE /-/npm/v1/tokens/token/:tok) — straightforward once persistence lands, but it's a distinct API surface.
  • Token expiration / TTL.
  • Pluggable auth backends (LDAP, OAuth).

Acceptance

  • Integration test: boot pnpr → adduser → restart pnpr with the same storage dir → existing token still authenticates, existing username can log back in with the same password.
  • Integration test: corrupt the htpasswd file → server returns a parse diagnostic on startup, not a silent empty user list.
  • htpasswd file produced by pnpr can be read by htpasswd -v (cross-tool compatibility).

Written by an agent (Claude Code, claude-opus-4-7).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions