wiki(ingest): concepts/verdaccio-token-lifecycle — restart trap + JWT fix
Постмортем утреннего фикса verdaccio: ephemeral secret (нет `secret:` в конфиге) → инвалидация всех токенов при каждом рестарте; max_users:-1 + pnpm login → 409; web-UI login как обход; JWT config fix (пинуем secret, api.jwt 900d). Recipe рефреша для yarn classic vs berry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
114
.wiki/concepts/verdaccio-token-lifecycle.md
Normal file
114
.wiki/concepts/verdaccio-token-lifecycle.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
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).
|
||||||
|
|
||||||
|
## Связанные страницы
|
||||||
|
|
||||||
|
- [verdaccio-prune-semantics](verdaccio-prune-semantics.md) — storage layout и GC
|
||||||
|
- [vds-kzntsv](../entities/vds-kzntsv.md) — хост где крутится инстанс
|
||||||
@@ -50,6 +50,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
|
|||||||
- [vbox-windows-stability-tuning](concepts/vbox-windows-stability-tuning.md) — VirtualBox + Windows-гость — нюансы стабильности cross-hypervisor миграции
|
- [vbox-windows-stability-tuning](concepts/vbox-windows-stability-tuning.md) — VirtualBox + Windows-гость — нюансы стабильности cross-hypervisor миграции
|
||||||
- [vds-kzntsv-dhcp-outage-2026-05-28](concepts/vds-kzntsv-dhcp-outage-2026-05-28.md) — DHCP outage 2026-05-28 на vds-kzntsv — диагностика + recovery runbook (link UP но без IPv4 → статика + тикет хостеру)
|
- [vds-kzntsv-dhcp-outage-2026-05-28](concepts/vds-kzntsv-dhcp-outage-2026-05-28.md) — DHCP outage 2026-05-28 на vds-kzntsv — диагностика + recovery runbook (link UP но без IPv4 → статика + тикет хостеру)
|
||||||
- [verdaccio-prune-semantics](concepts/verdaccio-prune-semantics.md) — Verdaccio prune semantics — proxied vs locally-published
|
- [verdaccio-prune-semantics](concepts/verdaccio-prune-semantics.md) — Verdaccio prune semantics — proxied vs locally-published
|
||||||
|
- [verdaccio-token-lifecycle](concepts/verdaccio-token-lifecycle.md) — restart trap (ephemeral secret) + max_users:-1 pnpm 409 + JWT fix + yarn clients
|
||||||
- [winacme-iis-owin-catchall-http01](concepts/winacme-iis-owin-catchall-http01.md) — win-acme HTTP-01 авто-renewal LE на IIS под OWIN-catch-all CMS — challenge в отдельном No-Managed-Code приложении + патч шаблона; заменяет ручной PFX-импорт
|
- [winacme-iis-owin-catchall-http01](concepts/winacme-iis-owin-catchall-http01.md) — win-acme HTTP-01 авто-renewal LE на IIS под OWIN-catch-all CMS — challenge в отдельном No-Managed-Code приложении + патч шаблона; заменяет ручной PFX-импорт
|
||||||
- [wd40efax-smr-cascade](concepts/wd40efax-smr-cascade.md) — WD40EFAX SMR Cascade — root cause NAS failure 2026-05-18
|
- [wd40efax-smr-cascade](concepts/wd40efax-smr-cascade.md) — WD40EFAX SMR Cascade — root cause NAS failure 2026-05-18
|
||||||
- [windows-server-2025-core-bootstrap](concepts/windows-server-2025-core-bootstrap.md) — Win Server 2025 Core (RUVDS) — bootstrap для IIS-хоста: default-blockers + transfer-методов матрица
|
- [windows-server-2025-core-bootstrap](concepts/windows-server-2025-core-bootstrap.md) — Win Server 2025 Core (RUVDS) — bootstrap для IIS-хоста: default-blockers + transfer-методов матрица
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Append-only log of wiki operations (ingests, promotions, lints, migrations).
|
Append-only log of wiki operations (ingests, promotions, lints, migrations).
|
||||||
|
|
||||||
|
## [2026-06-11] ingest | verdaccio-token-lifecycle — restart trap (ephemeral secret → 401), max_users:-1 + pnpm 409 постмортем, JWT config fix, yarn classic vs berry token refresh recipe.
|
||||||
## [2026-06-11] update | MSSQL backup gap closed — `/opt/stacks/backup/scripts/run.sh` расширен: 5 prod DBs через `docker exec mssql sqlcmd BACKUP DATABASE ... WITH COPY_ONLY, INIT` (Express, без COMPRESSION); .bak в bind-mount → mv в DUMP_DIR → rsync kreknin. Обновлён [[concepts/mssql-on-vds]] (TODO→реализовано).
|
## [2026-06-11] update | MSSQL backup gap closed — `/opt/stacks/backup/scripts/run.sh` расширен: 5 prod DBs через `docker exec mssql sqlcmd BACKUP DATABASE ... WITH COPY_ONLY, INIT` (Express, без COMPRESSION); .bak в bind-mount → mv в DUMP_DIR → rsync kreknin. Обновлён [[concepts/mssql-on-vds]] (TODO→реализовано).
|
||||||
|
|
||||||
## [2026-06-11] lint | 10 issues found, 8 fixed inline
|
## [2026-06-11] lint | 10 issues found, 8 fixed inline
|
||||||
|
|||||||
Reference in New Issue
Block a user