Files
pnpm/network/fetch/src
Zoltan Kochan b1fa2d5979 fix(dist-tag): support npm's web-based 2FA flow (#11998)
* fix(dist-tag): open the browser for npm web 2FA when --otp is absent

Without `--otp`, `pnpm dist-tag add` (and `rm`) failed against
npmjs.org with `[ERR_PNPM_UNAUTHORIZED] You must be logged in to set
dist-tag … "You must provide a one-time pass. Upgrade your client to
npm@latest in order to use 2FA."` — the browser never opened. The
fallback "upgrade your client" message is what npmjs.org returns when
the client doesn't announce `npm-auth-type: web`; without that header
the server skips the web challenge and tells the user to install a
newer npm. `--otp=<6-digit code>` already worked because the OTP went
out in `npm-otp` directly.

Send `npm-auth-type: web` on dist-tag writes when no `--otp` is
given, surface 401 responses carrying `authUrl`/`doneUrl` (or the
legacy "one-time pass" text) as `SyntheticOtpError`, and wrap the
call in the existing `withOtpHandling` helper (already used by
`pnpm publish`), which opens the browser, polls the done URL, and
retries with the resulting token as `npm-otp` while keeping
`npm-auth-type: web` in place.

Drive-by cleanup in `@pnpm/network.fetch`: the abbreviated-metadata
`Accept` header is no longer attached to non-GET requests, matching
`npm-registry-fetch`'s behavior.

* fix(dist-tag): default authType to 'web'; inherit network config in OTP context

Two review fixes:

- `setDistTag` documented `authType` as defaulting to `'web'` but only
  sent the `npm-auth-type` header when the field was explicitly passed.
  Always send the header, defaulting to `'web'`.

- `OTP_CONTEXT` used a module-level `createFetchFromRegistry({})`, so
  the `withOtpHandling` doneUrl poll ignored the command's proxy / TLS
  / `configByUri` config. Build the OTP context per call from the
  command's `opts` instead.

Also rename `toOtpOrUnauthorizedError` → `parseAuthError` and drop the
spurious `async` (the body string is already awaited at the call site).
2026-05-28 12:24:29 +02:00
..