• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3const errors = require('./errors.js')
4const { Response } = require('minipass-fetch')
5const defaultOpts = require('./default-opts.js')
6const log = require('proc-log')
7const cleanUrl = require('./clean-url.js')
8
9/* eslint-disable-next-line max-len */
10const moreInfoUrl = 'https://github.com/npm/cli/wiki/No-auth-for-URI,-but-auth-present-for-scoped-registry'
11const checkResponse =
12  async ({ method, uri, res, startTime, auth, opts }) => {
13    opts = { ...defaultOpts, ...opts }
14    if (res.headers.has('npm-notice') && !res.headers.has('x-local-cache')) {
15      log.notice('', res.headers.get('npm-notice'))
16    }
17
18    if (res.status >= 400) {
19      logRequest(method, res, startTime)
20      if (auth && auth.scopeAuthKey && !auth.token && !auth.auth) {
21      // we didn't have auth for THIS request, but we do have auth for
22      // requests to the registry indicated by the spec's scope value.
23      // Warn the user.
24        log.warn('registry', `No auth for URI, but auth present for scoped registry.
25
26URI: ${uri}
27Scoped Registry Key: ${auth.scopeAuthKey}
28
29More info here: ${moreInfoUrl}`)
30      }
31      return checkErrors(method, res, startTime, opts)
32    } else {
33      res.body.on('end', () => logRequest(method, res, startTime, opts))
34      if (opts.ignoreBody) {
35        res.body.resume()
36        return new Response(null, res)
37      }
38      return res
39    }
40  }
41module.exports = checkResponse
42
43function logRequest (method, res, startTime) {
44  const elapsedTime = Date.now() - startTime
45  const attempt = res.headers.get('x-fetch-attempts')
46  const attemptStr = attempt && attempt > 1 ? ` attempt #${attempt}` : ''
47  const cacheStatus = res.headers.get('x-local-cache-status')
48  const cacheStr = cacheStatus ? ` (cache ${cacheStatus})` : ''
49  const urlStr = cleanUrl(res.url)
50
51  log.http(
52    'fetch',
53    `${method.toUpperCase()} ${res.status} ${urlStr} ${elapsedTime}ms${attemptStr}${cacheStr}`
54  )
55}
56
57function checkErrors (method, res, startTime, opts) {
58  return res.buffer()
59    .catch(() => null)
60    .then(body => {
61      let parsed = body
62      try {
63        parsed = JSON.parse(body.toString('utf8'))
64      } catch {
65        // ignore errors
66      }
67      if (res.status === 401 && res.headers.get('www-authenticate')) {
68        const auth = res.headers.get('www-authenticate')
69          .split(/,\s*/)
70          .map(s => s.toLowerCase())
71        if (auth.indexOf('ipaddress') !== -1) {
72          throw new errors.HttpErrorAuthIPAddress(
73            method, res, parsed, opts.spec
74          )
75        } else if (auth.indexOf('otp') !== -1) {
76          throw new errors.HttpErrorAuthOTP(
77            method, res, parsed, opts.spec
78          )
79        } else {
80          throw new errors.HttpErrorAuthUnknown(
81            method, res, parsed, opts.spec
82          )
83        }
84      } else if (
85        res.status === 401 &&
86        body != null &&
87        /one-time pass/.test(body.toString('utf8'))
88      ) {
89        // Heuristic for malformed OTP responses that don't include the
90        // www-authenticate header.
91        throw new errors.HttpErrorAuthOTP(
92          method, res, parsed, opts.spec
93        )
94      } else {
95        throw new errors.HttpErrorGeneral(
96          method, res, parsed, opts.spec
97        )
98      }
99    })
100}
101