check-response.js 3.2 KB

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