diff options
Diffstat (limited to 'deps/npm/node_modules/npm-profile/index.js')
-rw-r--r-- | deps/npm/node_modules/npm-profile/index.js | 389 |
1 files changed, 137 insertions, 252 deletions
diff --git a/deps/npm/node_modules/npm-profile/index.js b/deps/npm/node_modules/npm-profile/index.js index 023ddae40c..b7f753fb4d 100644 --- a/deps/npm/node_modules/npm-profile/index.js +++ b/deps/npm/node_modules/npm-profile/index.js @@ -1,8 +1,10 @@ 'use strict' -const fetch = require('make-fetch-happen').defaults({retry: false}) -const validate = require('aproba') -const url = require('url') + +const fetch = require('npm-registry-fetch') +const {HttpErrorBase} = require('npm-registry-fetch/errors.js') const os = require('os') +const pudding = require('figgy-pudding') +const validate = require('aproba') exports.adduserCouch = adduserCouch exports.loginCouch = loginCouch @@ -16,99 +18,83 @@ exports.listTokens = listTokens exports.removeToken = removeToken exports.createToken = createToken +const ProfileConfig = pudding({ + creds: {}, + hostname: {}, + otp: {} +}) + // try loginWeb, catch the "not supported" message and fall back to couch -function login (opener, prompter, conf) { +function login (opener, prompter, opts) { validate('FFO', arguments) - return loginWeb(opener, conf).catch(er => { + opts = ProfileConfig(opts) + return loginWeb(opener, opts).catch(er => { if (er instanceof WebLoginNotSupported) { process.emit('log', 'verbose', 'web login not supported, trying couch') - return prompter(conf.creds) - .then(data => loginCouch(data.username, data.password, conf)) + return prompter(opts.creds) + .then(data => loginCouch(data.username, data.password, opts)) } else { throw er } }) } -function adduser (opener, prompter, conf) { +function adduser (opener, prompter, opts) { validate('FFO', arguments) - return adduserWeb(opener, conf).catch(er => { + opts = ProfileConfig(opts) + return adduserWeb(opener, opts).catch(er => { if (er instanceof WebLoginNotSupported) { process.emit('log', 'verbose', 'web adduser not supported, trying couch') - return prompter(conf.creds) - .then(data => adduserCouch(data.username, data.email, data.password, conf)) + return prompter(opts.creds) + .then(data => adduserCouch(data.username, data.email, data.password, opts)) } else { throw er } }) } -function adduserWeb (opener, conf) { +function adduserWeb (opener, opts) { validate('FO', arguments) const body = { create: true } process.emit('log', 'verbose', 'web adduser', 'before first POST') - return webAuth(opener, conf, body) + return webAuth(opener, opts, body) } -function loginWeb (opener, conf) { +function loginWeb (opener, opts) { validate('FO', arguments) process.emit('log', 'verbose', 'web login', 'before first POST') - return webAuth(opener, conf, {}) + return webAuth(opener, opts, {}) } -function webAuth (opener, conf, body) { - if (!conf.opts) conf.opts = {} - const target = url.resolve(conf.registry, '-/v1/login') - body.hostname = conf.hostname || os.hostname() - return fetchJSON({ - target: target, +function webAuth (opener, opts, body) { + opts = ProfileConfig(opts) + body.hostname = opts.hostname || os.hostname() + const target = '/-/v1/login' + return fetch(target, opts.concat({ method: 'POST', - body: body, - opts: conf.opts, - saveResponse: true - }).then(result => { - const res = result[0] - const content = result[1] + body + })).then(res => { + return Promise.all([res, res.json()]) + }).then(([res, content]) => { + const {doneUrl, loginUrl} = content process.emit('log', 'verbose', 'web auth', 'got response', content) - const doneUrl = content.doneUrl - const loginUrl = content.loginUrl - if (typeof doneUrl !== 'string' || - typeof loginUrl !== 'string' || - !doneUrl || !loginUrl) { - throw new WebLoginInvalidResponse('POST', target, res, content) + if ( + typeof doneUrl !== 'string' || + typeof loginUrl !== 'string' || + !doneUrl || + !loginUrl + ) { + throw new WebLoginInvalidResponse('POST', res, content) } + return content + }).then(({doneUrl, loginUrl}) => { process.emit('log', 'verbose', 'web auth', 'opening url pair') - const doneConf = { - target: doneUrl, - method: 'GET', - opts: conf.opts, - saveResponse: true - } - return opener(loginUrl).then(() => fetchJSON(doneConf)).then(onDone) - function onDone (result) { - const res = result[0] - const content = result[1] - if (res.status === 200) { - if (!content.token) { - throw new WebLoginInvalidResponse('GET', doneUrl, res, content) - } else { - return content - } - } else if (res.status === 202) { - const retry = +res.headers.get('retry-after') - if (retry > 0) { - return new Promise(resolve => setTimeout(resolve, 1000 * retry)) - .then(() => fetchJSON(doneConf)).then(onDone) - } else { - return fetchJSON(doneConf).then(onDone) - } - } else { - throw new WebLoginInvalidResponse('GET', doneUrl, res, content) - } - } + return opener(loginUrl).then( + () => webAuthCheckLogin(doneUrl, opts.concat({cache: false})) + ) }).catch(er => { if ((er.statusCode >= 400 && er.statusCode <= 499) || er.statusCode === 500) { - throw new WebLoginNotSupported('POST', target, { + throw new WebLoginNotSupported('POST', { status: er.statusCode, headers: { raw: () => er.headers } }, er.body) @@ -118,10 +104,33 @@ function webAuth (opener, conf, body) { }) } -function adduserCouch (username, email, password, conf) { +function webAuthCheckLogin (doneUrl, opts) { + return fetch(doneUrl, opts).then(res => { + return Promise.all([res, res.json()]) + }).then(([res, content]) => { + if (res.status === 200) { + if (!content.token) { + throw new WebLoginInvalidResponse('GET', res, content) + } else { + return content + } + } else if (res.status === 202) { + const retry = +res.headers.get('retry-after') * 1000 + if (retry > 0) { + return sleep(retry).then(() => webAuthCheckLogin(doneUrl, opts)) + } else { + return webAuthCheckLogin(doneUrl, opts) + } + } else { + throw new WebLoginInvalidResponse('GET', res, content) + } + }) +} + +function adduserCouch (username, email, password, opts) { validate('SSSO', arguments) - if (!conf.opts) conf.opts = {} - const userobj = { + opts = ProfileConfig(opts) + const body = { _id: 'org.couchdb.user:' + username, name: username, password: password, @@ -131,23 +140,25 @@ function adduserCouch (username, email, password, conf) { date: new Date().toISOString() } const logObj = {} - Object.keys(userobj).forEach(k => { - logObj[k] = k === 'password' ? 'XXXXX' : userobj[k] + Object.keys(body).forEach(k => { + logObj[k] = k === 'password' ? 'XXXXX' : body[k] }) process.emit('log', 'verbose', 'adduser', 'before first PUT', logObj) - const target = url.resolve(conf.registry, '-/user/org.couchdb.user:' + encodeURIComponent(username)) - - return fetchJSON({target: target, method: 'PUT', body: userobj, opts: conf.opts}) - .then(result => { - result.username = username - return result - }) + const target = '/-/user/org.couchdb.user:' + encodeURIComponent(username) + return fetch.json(target, opts.concat({ + method: 'PUT', + body + })).then(result => { + result.username = username + return result + }) } -function loginCouch (username, password, conf) { +function loginCouch (username, password, opts) { validate('SSO', arguments) - const userobj = { + opts = ProfileConfig(opts) + const body = { _id: 'org.couchdb.user:' + username, name: username, password: password, @@ -156,36 +167,38 @@ function loginCouch (username, password, conf) { date: new Date().toISOString() } const logObj = {} - Object.keys(userobj).forEach(k => { - logObj[k] = k === 'password' ? 'XXXXX' : userobj[k] + Object.keys(body).forEach(k => { + logObj[k] = k === 'password' ? 'XXXXX' : body[k] }) process.emit('log', 'verbose', 'login', 'before first PUT', logObj) - const target = url.resolve(conf.registry, '-/user/org.couchdb.user:' + encodeURIComponent(username)) - return fetchJSON(Object.assign({method: 'PUT', target: target, body: userobj}, conf)).catch(err => { + const target = '-/user/org.couchdb.user:' + encodeURIComponent(username) + return fetch.json(target, opts.concat({ + method: 'PUT', + body + })).catch(err => { if (err.code === 'E400') { err.message = `There is no user with the username "${username}".` throw err } if (err.code !== 'E409') throw err - return fetchJSON(Object.assign({method: 'GET', target: target + '?write=true'}, conf)).then(result => { + return fetch.json(target, opts.concat({ + query: {write: true} + })).then(result => { Object.keys(result).forEach(function (k) { - if (!userobj[k] || k === 'roles') { - userobj[k] = result[k] + if (!body[k] || k === 'roles') { + body[k] = result[k] } }) - const req = { + return fetch.json(`${target}/-rev/${body._rev}`, opts.concat({ method: 'PUT', - target: target + '/-rev/' + userobj._rev, - body: userobj, - auth: { - basic: { - username: username, - password: password - } + body, + forceAuth: { + username, + password: Buffer.from(password, 'utf8').toString('base64'), + otp: opts.otp } - } - return fetchJSON(Object.assign({}, conf, req)) + })) }) }).then(result => { result.username = username @@ -193,29 +206,31 @@ function loginCouch (username, password, conf) { }) } -function get (conf) { +function get (opts) { validate('O', arguments) - const target = url.resolve(conf.registry, '-/npm/v1/user') - return fetchJSON(Object.assign({target: target}, conf)) + return fetch.json('/-/npm/v1/user', opts) } -function set (profile, conf) { +function set (profile, opts) { validate('OO', arguments) - const target = url.resolve(conf.registry, '-/npm/v1/user') Object.keys(profile).forEach(key => { // profile keys can't be empty strings, but they CAN be null if (profile[key] === '') profile[key] = null }) - return fetchJSON(Object.assign({target: target, method: 'POST', body: profile}, conf)) + return fetch.json('/-/npm/v1/user', ProfileConfig(opts, { + method: 'POST', + body: profile + })) } -function listTokens (conf) { +function listTokens (opts) { validate('O', arguments) + opts = ProfileConfig(opts) - return untilLastPage(`-/npm/v1/tokens`) + return untilLastPage('/-/npm/v1/tokens') function untilLastPage (href, objects) { - return fetchJSON(Object.assign({target: url.resolve(conf.registry, href)}, conf)).then(result => { + return fetch.json(href, opts).then(result => { objects = objects ? objects.concat(result.objects) : result.objects if (result.urls.next) { return untilLastPage(result.urls.next, objects) @@ -226,174 +241,44 @@ function listTokens (conf) { } } -function removeToken (tokenKey, conf) { +function removeToken (tokenKey, opts) { validate('SO', arguments) - const target = url.resolve(conf.registry, `-/npm/v1/tokens/token/${tokenKey}`) - return fetchJSON(Object.assign({target: target, method: 'DELETE'}, conf)) + const target = `/-/npm/v1/tokens/token/${tokenKey}` + return fetch(target, ProfileConfig(opts, { + method: 'DELETE', + ignoreBody: true + })).then(() => null) } -function createToken (password, readonly, cidrs, conf) { +function createToken (password, readonly, cidrs, opts) { validate('SBAO', arguments) - const target = url.resolve(conf.registry, '-/npm/v1/tokens') - const props = { - password: password, - readonly: readonly, - cidr_whitelist: cidrs - } - return fetchJSON(Object.assign({target: target, method: 'POST', body: props}, conf)) -} - -function FetchError (err, method, target) { - err.method = method - err.href = target - return err -} - -class HttpErrorBase extends Error { - constructor (method, target, res, body) { - super() - this.headers = res.headers.raw() - this.statusCode = res.status - this.code = 'E' + res.status - this.method = method - this.target = target - this.body = body - this.pkgid = packageName(target) - } -} - -class HttpErrorGeneral extends HttpErrorBase { - constructor (method, target, res, body) { - super(method, target, res, body) - if (body && body.error) { - this.message = `Registry returned ${this.statusCode} for ${this.method} on ${this.target}: ${body.error}` - } else { - this.message = `Registry returned ${this.statusCode} for ${this.method} on ${this.target}` + return fetch.json('/-/npm/v1/tokens', ProfileConfig(opts, { + method: 'POST', + body: { + password: password, + readonly: readonly, + cidr_whitelist: cidrs } - Error.captureStackTrace(this, HttpErrorGeneral) - } + })) } class WebLoginInvalidResponse extends HttpErrorBase { - constructor (method, target, res, body) { - super(method, target, res, body) + constructor (method, res, body) { + super(method, res, body) this.message = 'Invalid response from web login endpoint' Error.captureStackTrace(this, WebLoginInvalidResponse) } } class WebLoginNotSupported extends HttpErrorBase { - constructor (method, target, res, body) { - super(method, target, res, body) + constructor (method, res, body) { + super(method, res, body) this.message = 'Web login not supported' this.code = 'ENYI' Error.captureStackTrace(this, WebLoginNotSupported) } } -class HttpErrorAuthOTP extends HttpErrorBase { - constructor (method, target, res, body) { - super(method, target, res, body) - this.message = 'OTP required for authentication' - this.code = 'EOTP' - Error.captureStackTrace(this, HttpErrorAuthOTP) - } -} - -class HttpErrorAuthIPAddress extends HttpErrorBase { - constructor (method, target, res, body) { - super(method, target, res, body) - this.message = 'Login is not allowed from your IP address' - this.code = 'EAUTHIP' - Error.captureStackTrace(this, HttpErrorAuthIPAddress) - } -} - -class HttpErrorAuthUnknown extends HttpErrorBase { - constructor (method, target, res, body) { - super(method, target, res, body) - this.message = 'Unable to authenticate, need: ' + res.headers.get('www-authenticate') - this.code = 'EAUTHUNKNOWN' - Error.captureStackTrace(this, HttpErrorAuthUnknown) - } -} - -function authHeaders (auth) { - const headers = {} - if (!auth) return headers - if (auth.otp) headers['npm-otp'] = auth.otp - if (auth.token) { - headers['Authorization'] = 'Bearer ' + auth.token - } else if (auth.basic) { - const basic = auth.basic.username + ':' + auth.basic.password - headers['Authorization'] = 'Basic ' + Buffer.from(basic).toString('base64') - } - return headers -} - -function fetchJSON (conf) { - const fetchOpts = { - method: conf.method, - headers: Object.assign({}, conf.headers || (conf.auth && authHeaders(conf.auth)) || {}) - } - if (conf.body != null) { - fetchOpts.headers['Content-Type'] = 'application/json' - fetchOpts.body = JSON.stringify(conf.body) - } - process.emit('log', 'http', 'request', '→', conf.method || 'GET', conf.target) - return fetch.defaults(conf.opts || {})(conf.target, fetchOpts).catch(err => { - throw new FetchError(err, conf.method, conf.target) - }).then(res => { - if (res.headers.has('npm-notice')) { - process.emit('warn', 'notice', res.headers.get('npm-notice')) - } - if (res.headers.get('content-type') === 'application/json') { - return res.json().then(content => [res, content]) - } else { - return res.buffer().then(content => { - try { - return [res, JSON.parse(content)] - } catch (_) { - return [res, content] - } - }) - } - }).then(result => { - const res = result[0] - const content = result[1] - const retVal = conf.saveResponse ? result : content - process.emit('log', 'http', res.status, `← ${res.statusText} (${conf.target})`) - if (res.status === 401 && res.headers.get('www-authenticate')) { - const auth = res.headers.get('www-authenticate').split(/,\s*/).map(s => s.toLowerCase()) - if (auth.indexOf('ipaddress') !== -1) { - throw new HttpErrorAuthIPAddress(conf.method, conf.target, res, content) - } else if (auth.indexOf('otp') !== -1) { - throw new HttpErrorAuthOTP(conf.method, conf.target, res, content) - } else { - throw new HttpErrorAuthUnknown(conf.method, conf.target, res, content) - } - } else if (res.status < 200 || res.status >= 300) { - throw new HttpErrorGeneral(conf.method, conf.target, res, content) - } else { - return retVal - } - }) -} - -function packageName (href) { - try { - let basePath = url.parse(href).pathname.substr(1) - if (!basePath.match(/^-/)) { - basePath = basePath.split('/') - var index = basePath.indexOf('_rewrite') - if (index === -1) { - index = basePath.length - 1 - } else { - index++ - } - return decodeURIComponent(basePath[index]) - } - } catch (_) { - // this is ok - } +function sleep (ms) { + return new Promise((resolve, reject) => setTimeout(resolve, ms)) } |