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