@vanishcode/verdaccio-admin-users
v0.1.0
Published
Verdaccio middleware plugin: admin UI for managing htpasswd users and packages access rules
Downloads
113
Maintainers
Readme
verdaccio-admin-users
A Verdaccio middleware plugin that adds an admin web UI for managing htpasswd users and packages: access rules — without hand-editing htpasswd or config.yaml.
Features
- List, add, delete, and reset-password htpasswd users.
- View and edit the
packages:access rules block ofconfig.yaml. - React + Vite SPA mounted at
/-/admin/. - Reuses Verdaccio's web-UI JWT for auth; gates admin actions by an explicit allow-list and/or group membership.
- Built-in defenses: per-IP rate limit, optional IP allow-list, password length policy, audit log of every write action.
Install
pnpm add verdaccio-admin-usersIn config.yaml:
auth:
htpasswd:
file: ./htpasswd
max_users: -1 # IMPORTANT: disables `npm adduser` self-signup;
# new users can ONLY be created via the admin UI.
middlewares:
admin-users:
enabled: true
admin_group: admin # optional; default "admin"
admins: [alice] # explicit admin allow-list (recommended)
# ─── Security knobs (all optional; defaults shown) ───
min_password_length: 8 # admin-set passwords must be at least this long
rate_limit:
window_ms: 900000 # 15 minutes
max: 100 # max requests per IP per window
# rate_limit: false # set to false to disable rate limiting entirely
# allowed_ips: # if set, only these IPs may reach /-/admin/*
# - 127.0.0.1
# - 10.0.0.42Then visit http://localhost:4873/-/admin/. The SPA reads the JWT that the main UI stores in localStorage, so log in via the main UI first.
Endpoints
All under /-/admin/api/. Require an authenticated request from a user listed in admins or carrying the admin_group group.
| Method | Path | Body |
| ------ | ----------------------------- | -------------------------- |
| GET | /me | — |
| GET | /users | — |
| POST | /users | { name, password } |
| DELETE | /users/:name | — |
| POST | /users/:name/reset-password | { password } |
| GET | /packages | — |
| PUT | /packages | full new packages: block |
| GET | /uplinks | — |
Security model
| Layer (in order requests are checked) | What it does |
| ------------------------------------- | ------------ |
| allowed_ips allow-list (optional) | Reject 403 if the client IP isn't whitelisted |
| rate_limit per-IP throttle | 429 once a single IP exceeds the window quota — applies to all /-/admin/* traffic, authenticated or not |
| Verdaccio web JWT validation | 401 if no/expired/invalid token (reuses webUIJWTmiddleware) |
| requireAdmin allow-list / group | 403 if the authenticated user isn't an admin |
| min_password_length policy | 400 on add/reset if the new password is too short |
| Username length cap (64 chars) | 400 on add/delete/reset if the username is longer |
| Password length cap (72 UTF-8 bytes) | 400 on add/reset — bcrypt silently truncates past 72 bytes, so anything longer is misleading |
| bcrypt_rounds clamp (8–14) | Out-of-range values in config are coerced and a warning is logged at startup |
| Audit log | Every add/delete/reset/save emits a warn log line with actor, ip, action, target, outcome — successes AND failures (invalid input, conflict, not found, error) |
Recommended setup
- Disable
npm adduserself-signup by settingauth.htpasswd.max_users: -1. Otherwise an attacker can register their own account regardless of what this plugin does. Once disabled, the only way to create a new user is via the admin UI. - Run Verdaccio behind a TLS reverse proxy (nginx/caddy/Traefik). The Bearer JWT travels in plaintext — TLS is non-negotiable for any non-localhost setup. Set
server.trustProxysoreq.ipreflects the real client IP for the rate limiter andallowed_ips. - Don't bind to
0.0.0.0if you only need localhost.listen: 127.0.0.1:4873is fine for a developer-only setup. Otherwise rely on a firewall / VPC security group to restrict the network. - Pin a real
secret:inconfig.yaml(a long random string). Otherwise Verdaccio regenerates one on each restart and invalidates all tokens — and worse, may default to predictable values in misconfigured deployments. - Set
userRateLimit:at the top level ofconfig.yamlto throttle/-/verdaccio/sec/login, which is outside this plugin and is the natural target for password brute-forcing:userRateLimit: windowMs: 900000 # 15 min max: 20 - Watch the audit log. Every admin write emits a line like:
Ship those lines to whatever you use for security monitoring. Anomalies look like high-ratewarn --- admin-users audit: alice from 10.0.0.5 did add_user (target=bob)add_user/reset_passwordactions, or activity from unfamiliar IPs. - Treat the admin's password like a secret. Use a password manager and a long random string. The bcrypt cost factor is 10 by default — fine, but any leaked password file with a weak password will be cracked offline quickly.
What this plugin does not protect against
- A stolen JWT being replayed before its
expruns out (default Verdaccio JWT lifetime). Mitigate via short JWT expiry, TLS, and not pasting tokens into untrusted contexts. - XSS in the main Verdaccio UI exfiltrating
localStorage.token. The token is reused by this SPA, so any XSS in either UI is a full takeover. Don't install untrusted theme plugins. - Disk-level access to
htpasswd(the bcrypt hashes) orconfig.yaml(your packages rules). Use OS-level file permissions and run Verdaccio under a dedicated unprivileged user. - Plugin-config tampering. Only
packages:is rewritten by the plugin. Other sections must still be edited by hand and the operator should treat config.yaml as a sensitive file.
Caveats
- Editing the packages block via the UI rewrites
config.yamlwithjs-yaml. Comments are not preserved. Aconfig.yaml.bakbackup is created before the first write. - The plugin only manipulates the
packages:section. All other config (auth, middlewares, web, etc.) stays manual. - htpasswd-authenticated users have no native group concept beyond
[username, $authenticated, $all]. Use theadmins:allow-list rather thanadmin_group:unless you have a custom auth plugin that returns groups. - Newly-created users are hashed with bcrypt regardless of the algorithm configured for the htpasswd auth plugin.
License
MIT
