diff options
author | Myles Borins <myles.borins@gmail.com> | 2017-10-26 22:35:25 -0400 |
---|---|---|
committer | Myles Borins <myles.borins@gmail.com> | 2017-10-29 21:32:15 -0400 |
commit | ace4fe566fc3af4876c7458f983feeb5eae3df26 (patch) | |
tree | 458d847e9bd56199cd0d8b34cec126c7410fb6ca /deps/npm/lib/profile.js | |
parent | 64168eb9b43e30e4c0b986c9b29c41be63e85df6 (diff) | |
download | node-new-ace4fe566fc3af4876c7458f983feeb5eae3df26.tar.gz |
deps: update npm to 5.5.1
Closes: https://github.com/nodejs/node/pull/16280
PR-URL: https://github.com/nodejs/node/pull/16509
Fixes: https://github.com/nodejs/node/issues/14161
Reviewed-By: Daijiro Wachi <daijiro.wachi@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Diffstat (limited to 'deps/npm/lib/profile.js')
-rw-r--r-- | deps/npm/lib/profile.js | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/deps/npm/lib/profile.js b/deps/npm/lib/profile.js new file mode 100644 index 0000000000..4238e14276 --- /dev/null +++ b/deps/npm/lib/profile.js @@ -0,0 +1,296 @@ +'use strict' +const profile = require('npm-profile') +const npm = require('./npm.js') +const log = require('npmlog') +const output = require('./utils/output.js') +const qw = require('qw') +const Table = require('cli-table2') +const ansistyles = require('ansistyles') +const Bluebird = require('bluebird') +const readUserInfo = require('./utils/read-user-info.js') +const qrcodeTerminal = require('qrcode-terminal') +const url = require('url') +const queryString = require('query-string') +const pulseTillDone = require('./utils/pulse-till-done.js') + +module.exports = profileCmd + +profileCmd.usage = + 'npm profile enable-2fa [auth-only|auth-and-writes]\n' + + 'npm profile disable-2fa\n' + + 'npm profile get [<key>]\n' + + 'npm profile set <key> <value>' + +profileCmd.subcommands = qw`enable-2fa disable-2fa get set` + +profileCmd.completion = function (opts, cb) { + var argv = opts.conf.argv.remain + switch (argv[2]) { + case 'enable-2fa': + case 'enable-tfa': + if (argv.length === 3) { + return cb(null, qw`auth-and-writes auth-only`) + } else { + return cb(null, []) + } + case 'disable-2fa': + case 'disable-tfa': + case 'get': + case 'set': + return cb(null, []) + default: + return cb(new Error(argv[2] + ' not recognized')) + } +} + +function withCb (prom, cb) { + prom.then((value) => cb(null, value), cb) +} + +function profileCmd (args, cb) { + if (args.length === 0) return cb(new Error(profileCmd.usage)) + log.gauge.show('profile') + switch (args[0]) { + case 'enable-2fa': + case 'enable-tfa': + case 'enable2fa': + case 'enabletfa': + withCb(enable2fa(args.slice(1)), cb) + break + case 'disable-2fa': + case 'disable-tfa': + case 'disable2fa': + case 'disabletfa': + withCb(disable2fa(), cb) + break + case 'get': + withCb(get(args.slice(1)), cb) + break + case 'set': + withCb(set(args.slice(1)), cb) + break + default: + cb(new Error('Unknown profile command: ' + args[0])) + } +} + +function config () { + const conf = { + json: npm.config.get('json'), + parseable: npm.config.get('parseable'), + registry: npm.config.get('registry'), + otp: npm.config.get('otp') + } + conf.auth = npm.config.getCredentialsByURI(conf.registry) + if (conf.otp) conf.auth.otp = conf.otp + return conf +} + +const knownProfileKeys = qw` + name email ${'two factor auth'} fullname homepage + freenode twitter github created updated` + +function get (args) { + const tfa = 'two factor auth' + const conf = config() + return pulseTillDone.withPromise(profile.get(conf)).then((info) => { + if (!info.cidr_whitelist) delete info.cidr_whitelist + if (conf.json) { + output(JSON.stringify(info, null, 2)) + return + } + const cleaned = {} + knownProfileKeys.forEach((k) => { cleaned[k] = info[k] || '' }) + Object.keys(info).filter((k) => !(k in cleaned)).forEach((k) => { cleaned[k] = info[k] || '' }) + delete cleaned.tfa + delete cleaned.email_verified + cleaned['email'] += info.email_verified ? ' (verified)' : '(unverified)' + if (info.tfa && !info.tfa.pending) { + cleaned[tfa] = info.tfa.mode + } else { + cleaned[tfa] = 'disabled' + } + if (args.length) { + const values = args // comma or space separated ↓ + .join(',').split(/,/).map((arg) => arg.trim()).filter((arg) => arg !== '') + .map((arg) => cleaned[arg]) + .join('\t') + output(values) + } else { + if (conf.parseable) { + Object.keys(info).forEach((key) => { + if (key === 'tfa') { + output(`${key}\t${cleaned[tfa]}`) + } else { + output(`${key}\t${info[key]}`) + } + }) + return + } else { + const table = new Table() + Object.keys(cleaned).forEach((k) => table.push({[ansistyles.bright(k)]: cleaned[k]})) + output(table.toString()) + } + } + }) +} + +const writableProfileKeys = qw` + email password fullname homepage freenode twitter github` + +function set (args) { + const conf = config() + const prop = (args[0] || '').toLowerCase().trim() + let value = args.length > 1 ? args.slice(1).join(' ') : null + if (prop !== 'password' && value === null) { + return Promise.reject(Error('npm profile set <prop> <value>')) + } + if (prop === 'password' && value !== null) { + return Promise.reject(Error( + 'npm profile set password\n' + + 'Do not include your current or new passwords on the command line.')) + } + if (writableProfileKeys.indexOf(prop) === -1) { + return Promise.reject(Error(`"${prop}" is not a property we can set. Valid properties are: ` + writableProfileKeys.join(', '))) + } + return Bluebird.try(() => { + if (prop !== 'password') return + return readUserInfo.password('Current password: ').then((current) => { + return readPasswords().then((newpassword) => { + value = {old: current, new: newpassword} + }) + }) + function readPasswords () { + return readUserInfo.password('New password: ').then((password1) => { + return readUserInfo.password(' Again: ').then((password2) => { + if (password1 !== password2) { + log.warn('profile', 'Passwords do not match, please try again.') + return readPasswords() + } + return password1 + }) + }) + } + }).then(() => { + // FIXME: Work around to not clear everything other than what we're setting + return pulseTillDone.withPromise(profile.get(conf).then((user) => { + const newUser = {} + writableProfileKeys.forEach((k) => { newUser[k] = user[k] }) + newUser[prop] = value + return profile.set(newUser, conf).catch((err) => { + if (err.code !== 'EOTP') throw err + return readUserInfo.otp('Enter OTP: ').then((otp) => { + conf.auth.otp = otp + return profile.set(newUser, conf) + }) + }).then((result) => { + if (conf.json) { + output(JSON.stringify({[prop]: result[prop]}, null, 2)) + } else if (conf.parseable) { + output(prop + '\t' + result[prop]) + } else { + output('Set', prop, 'to', result[prop]) + } + }) + })) + }) +} + +function enable2fa (args) { + if (args.length > 1) { + return Promise.reject(new Error('npm profile enable-2fa [auth-and-writes|auth-only]')) + } + const mode = args[0] || 'auth-and-writes' + if (mode !== 'auth-only' && mode !== 'auth-and-writes') { + return Promise.reject(new Error(`Invalid two factor authentication mode "${mode}".\n` + + 'Valid modes are:\n' + + ' auth-only - Require two-factor authentication only when logging in\n' + + ' auth-and-writes - Require two-factor authentication when logging in AND when publishing')) + } + const conf = config() + if (conf.json || conf.parseable) { + return Promise.reject(new Error( + 'Enabling two-factor authentication is an interactive opperation and ' + + (conf.json ? 'JSON' : 'parseable') + 'output mode is not available')) + } + log.notice('profile', 'Enabling two factor authentication for ' + mode) + const info = { + tfa: { + mode: mode + } + } + return readUserInfo.password().then((password) => { + info.tfa.password = password + log.info('profile', 'Determine if tfa is pending') + return pulseTillDone.withPromise(profile.get(conf)).then((info) => { + if (!info.tfa) return + if (info.tfa.pending) { + log.info('profile', 'Resetting two-factor authentication') + return pulseTillDone.withPromise(profile.set({tfa: {password, mode: 'disable'}}, conf)) + } else { + if (conf.auth.otp) return + return readUserInfo.otp('Enter OTP: ').then((otp) => { + conf.auth.otp = otp + }) + } + }) + }).then(() => { + log.info('profile', 'Setting two factor authentication to ' + mode) + return pulseTillDone.withPromise(profile.set(info, conf)) + }).then((challenge) => { + if (challenge.tfa === null) { + output('Two factor authentication mode changed to: ' + mode) + return + } + if (typeof challenge.tfa !== 'string' || !/^otpauth:[/][/]/.test(challenge.tfa)) { + throw new Error('Unknown error enabling two-factor authentication. Expected otpauth URL, got: ' + challenge.tfa) + } + const otpauth = url.parse(challenge.tfa) + const opts = queryString.parse(otpauth.query) + return qrcode(challenge.tfa).then((code) => { + output('Scan into your authenticator app:\n' + code + '\n Or enter code:', opts.secret) + }).then((code) => { + return readUserInfo.otp('And an OTP code from your authenticator: ') + }).then((otp1) => { + log.info('profile', 'Finalizing two factor authentication') + return profile.set({tfa: [otp1]}, conf) + }).then((result) => { + output('TFA successfully enabled. Below are your recovery codes, please print these out.') + output('You will need these to recover access to your account if you lose your authentication device.') + result.tfa.forEach((c) => output('\t' + c)) + }) + }) +} + +function disable2fa (args) { + const conf = config() + return pulseTillDone.withPromise(profile.get(conf)).then((info) => { + if (!info.tfa || info.tfa.pending) { + output('Two factor authentication not enabled.') + return + } + return readUserInfo.password().then((password) => { + return Bluebird.try(() => { + if (conf.auth.otp) return + return readUserInfo.otp('Enter one-time password from your authenticator: ').then((otp) => { + conf.auth.otp = otp + }) + }).then(() => { + log.info('profile', 'disabling tfa') + return pulseTillDone.withPromise(profile.set({tfa: {password: password, mode: 'disable'}}, conf)).then(() => { + if (conf.json) { + output(JSON.stringify({tfa: false}, null, 2)) + } else if (conf.parseable) { + output('tfa\tfalse') + } else { + output('Two factor authentication disabled.') + } + }) + }) + }) + }) +} + +function qrcode (url) { + return new Promise((resolve) => qrcodeTerminal.generate(url, resolve)) +} |