Cross-ref the practical auth runbook (projects-wiki concepts/verdaccio-token-usage) from the local postmortem. lifecycle = why, token-usage = how. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
5.2 KiB
Markdown
117 lines
5.2 KiB
Markdown
---
|
||
title: Verdaccio token lifecycle — restart trap + JWT fix
|
||
type: concept
|
||
tags: [verdaccio, npm, pnpm, yarn, auth, jwt, gotcha, postmortem]
|
||
sources: []
|
||
updated: 2026-06-11
|
||
---
|
||
|
||
# Verdaccio token lifecycle — restart trap + JWT fix
|
||
|
||
## Root cause: auto-generated secret
|
||
|
||
Verdaccio без явного `secret:` в конфиге генерирует случайный секрет **при каждом запуске**. Все токены (legacy opaque и JWT) подписаны этим секретом — перезапуск контейнера = мгновенная инвалидация всех выданных токенов у всех клиентов.
|
||
|
||
**Симптом:** `401 Unauthorized` после рестарта VDS/контейнера, несмотря на то что токен в `.npmrc` визуально «есть».
|
||
|
||
**Фикс (применён 2026-06-11):** явный `secret:` в `config.yaml` → токены переживают рестарты.
|
||
|
||
## `max_users: -1` + pnpm login → 409
|
||
|
||
Стандартный workaround «перелогинься» (`pnpm login --registry ...`) не работает при `max_users: -1`.
|
||
|
||
Verdaccio htpasswd plugin v6 проверяет `max_users` **первым** в `adduser()`, до проверки существования пользователя:
|
||
|
||
```
|
||
if (max_users === -1) → return 409 "user registration disabled"
|
||
```
|
||
|
||
`pnpm login` отправляет `PUT /-/user/org.couchdb.user:<name>` — это registration-path. Verdaccio блокирует даже для существующих юзеров, даже если пароль верный.
|
||
|
||
Изменение `max_users` на положительное число решает проблему, но открывает регистрацию всем — нежелательно.
|
||
|
||
## Правильный способ получить токен при `max_users: -1`
|
||
|
||
Использовать **web-UI login endpoint**, который не проходит через `adduser()`:
|
||
|
||
```powershell
|
||
$body = '{"username":"vitya","password":"..."}'
|
||
$r = Invoke-RestMethod -Uri "https://verdaccio.kzntsv.site/-/verdaccio/sec/login" `
|
||
-Method POST -Body $body -ContentType "application/json"
|
||
$r.token # JWT, 7d expiry
|
||
```
|
||
|
||
Полученный токен — в `~/.npmrc`:
|
||
|
||
```ini
|
||
//verdaccio.kzntsv.site/:_authToken=eyJ...
|
||
```
|
||
|
||
## Конфиг-фикс (2026-06-11)
|
||
|
||
Добавлено в `/opt/stacks/verdaccio/config/config.yaml` на [vds-kzntsv](../entities/vds-kzntsv.md):
|
||
|
||
```yaml
|
||
secret: <32-byte-hex-permanent>
|
||
|
||
security:
|
||
api:
|
||
jwt:
|
||
sign:
|
||
expiresIn: 30d
|
||
notBefore: 0
|
||
web:
|
||
sign:
|
||
expiresIn: 7d
|
||
verify: {}
|
||
```
|
||
|
||
До фикса: нет `secret` и нет `security` → legacy tokens, привязанные к ephemeral secret.
|
||
После: web-UI и npm-API используют один JWT формат, подписанный постоянным секретом.
|
||
|
||
> **Примечание:** `security.api.jwt` меняет формат API-токенов с legacy opaque (`5a3X...==`) на JWT (`eyJ...`). Старые legacy-токены в `.npmrc` у всех клиентов перестают работать после смены конфига — нужен refresh.
|
||
|
||
## Срок жизни токенов
|
||
|
||
| Endpoint | Expiry | Используется для |
|
||
|---|---|---|
|
||
| `/-/verdaccio/sec/login` | 7d (web.sign) | Ручной refresh, агенты |
|
||
| npm API (pnpm/yarn/npm login) | 30d (api.jwt.sign) | После решения `max_users` проблемы |
|
||
|
||
## Клиенты: где хранится токен
|
||
|
||
### npm / pnpm / yarn classic (1.x)
|
||
|
||
Читают `~/.npmrc` (user-level) или `.npmrc` в корне проекта:
|
||
|
||
```ini
|
||
//verdaccio.kzntsv.site/:_authToken=eyJ...
|
||
@snollajs:registry=https://verdaccio.kzntsv.site/
|
||
```
|
||
|
||
Yarn classic читает тот же `~/.npmrc` — update токена в одном файле покрывает всех.
|
||
|
||
### yarn berry (2.x+)
|
||
|
||
Не читает `~/.npmrc`. Конфиг в `.yarnrc.yml`:
|
||
|
||
```yaml
|
||
npmRegistries:
|
||
"https://verdaccio.kzntsv.site":
|
||
npmAuthToken: "eyJ..."
|
||
npmAlwaysAuth: true
|
||
|
||
npmScopes:
|
||
snollajs:
|
||
npmRegistryServer: "https://verdaccio.kzntsv.site"
|
||
```
|
||
|
||
Refresh токена = обновить `npmAuthToken` в `.yarnrc.yml` каждого проекта, либо использовать `yarn npm login --scope snollajs` (тоже обходит `max_users: -1` через web UI path).
|
||
|
||
## Связанные страницы
|
||
|
||
- **Общая вики:** `concepts/verdaccio-token-usage` (репо `projects-wiki`) — практический runbook аутентификации: два гейта, конфиг клиента, диагностика 401, stale-parent-process trap, чеклист. Эта страница — детальный postmortem про *почему*; runbook — про *как пользоваться*.
|
||
- [verdaccio-prune-semantics](verdaccio-prune-semantics.md) — storage layout и GC
|
||
- [yarn-npm-minimal-age-gate](yarn-npm-minimal-age-gate.md) — гейт #2: YN0016 на свежих версиях (Yarn ≥4.16), независим от auth
|
||
- [vds-kzntsv](../entities/vds-kzntsv.md) — хост где крутится инстанс
|