Files
admin/.wiki/concepts/verdaccio-token-lifecycle.md
vitya eb21b282d8 wiki(ingest): concepts/yarn-npm-minimal-age-gate — YN0016 на свежих версиях
Yarn >=4.16 npmMinimalAgeGate (default 1d) карантинит версии <24ч на
КЛИЕНТЕ — независимый от auth gate #2 (после 401). Серверного карантина
в verdaccio нет; первичная гипотеза опровергнута пруфом из yarn.js.
Глобальный config (per-scope не работает), fix npmMinimalAgeGate:0.
Backlink из verdaccio-token-lifecycle + index/log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 18:21:41 +03:00

4.8 KiB
Raw Blame History

title, type, tags, sources, updated
title type tags sources updated
Verdaccio token lifecycle — restart trap + JWT fix concept
verdaccio
npm
pnpm
yarn
auth
jwt
gotcha
postmortem
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():

$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:

//verdaccio.kzntsv.site/:_authToken=eyJ...

Конфиг-фикс (2026-06-11)

Добавлено в /opt/stacks/verdaccio/config/config.yaml на vds-kzntsv:

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 в корне проекта:

//verdaccio.kzntsv.site/:_authToken=eyJ...
@snollajs:registry=https://verdaccio.kzntsv.site/

Yarn classic читает тот же ~/.npmrc — update токена в одном файле покрывает всех.

yarn berry (2.x+)

Не читает ~/.npmrc. Конфиг в .yarnrc.yml:

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).

Связанные страницы