From 3731031708bfafb15910cb1176ccf3f0dfc39259 Mon Sep 17 00:00:00 2001 From: Myles Borins Date: Fri, 10 Feb 2023 15:39:11 -0500 Subject: [PATCH 1/2] fix: gracefully fallback from auth-type=web Originally `auth-type=web` would gracefully fallback to `auth-type=legacy` if a registry had not yet implemented web. This brings back that behavior. --- lib/utils/auth.js | 54 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/lib/utils/auth.js b/lib/utils/auth.js index 8b9125a1c3ef0..abd6cb88777b2 100644 --- a/lib/utils/auth.js +++ b/lib/utils/auth.js @@ -4,6 +4,26 @@ const openUrlPrompt = require('../utils/open-url-prompt.js') const read = require('../utils/read-user-info.js') const otplease = require('../utils/otplease.js') +async function loginCouch (npm, creds, opts) { + const username = await read.username('Username:', creds.username) + const password = await read.password('Password:', creds.password) + return await otplease(npm, opts, (reqOpts) => + profile.loginCouch(username, password, reqOpts) + ) +} + +async function addUserCouch (npm, creds, opts) { + const username = await read.username('Username:', creds.username) + const password = await read.password('Password:', creds.password) + const email = await read.email('Email: (this IS public) ', creds.email) + // npm registry quirk: If you "add" an existing user with their current + // password, it's effectively a login, and if that account has otp you'll + // be prompted for it. + return await otplease(npm, opts, (reqOpts) => + profile.adduserCouch(username, email, password, opts) + ) +} + const adduser = async (npm, { creds, ...opts }) => { const authType = npm.config.get('auth-type') let res @@ -16,17 +36,16 @@ const adduser = async (npm, { creds, ...opts }) => { 'Press ENTER to open in the browser...', emitter ) - }, opts) + }, opts).catch(er => { + if (er.message === 'Web login not supported' && er.code === 'ENYI') { + log.verbose('web add user not supported, trying couch') + return addUserCouch(npm, creds, opts) + } else { + throw er + } + }) } else { - const username = await read.username('Username:', creds.username) - const password = await read.password('Password:', creds.password) - const email = await read.email('Email: (this IS public) ', creds.email) - // npm registry quirk: If you "add" an existing user with their current - // password, it's effectively a login, and if that account has otp you'll - // be prompted for it. - res = await otplease(npm, opts, (reqOpts) => - profile.adduserCouch(username, email, password, opts) - ) + res = await addUserCouch(npm, creds, opts) } // We don't know the username if it was a web login, all we can reliably log is scope and registry @@ -52,13 +71,16 @@ const login = async (npm, { creds, ...opts }) => { 'Press ENTER to open in the browser...', emitter ) - }, opts) + }, opts).catch(er => { + if (er.message === 'Web login not supported' && er.code === 'ENYI') { + log.verbose('web login not supported, trying couch') + return loginCouch(npm, creds, opts) + } else { + throw er + } + }) } else { - const username = await read.username('Username:', creds.username) - const password = await read.password('Password:', creds.password) - res = await otplease(npm, opts, (reqOpts) => - profile.loginCouch(username, password, reqOpts) - ) + res = await loginCouch(npm, creds, opts) } // We don't know the username if it was a web login, all we can reliably log is scope and registry From 29226afa8c00385fb7983906abcf1c049b05983d Mon Sep 17 00:00:00 2001 From: Gar Date: Mon, 13 Feb 2023 14:55:20 -0800 Subject: [PATCH 2/2] fixup! fix: gracefully fallback from auth-type=web --- lib/utils/auth.js | 100 +++++++++++++++++------------------ test/lib/commands/adduser.js | 44 +++++++++++++++ test/lib/commands/login.js | 40 ++++++++++++++ 3 files changed, 134 insertions(+), 50 deletions(-) diff --git a/lib/utils/auth.js b/lib/utils/auth.js index abd6cb88777b2..729ce32c2a7a8 100644 --- a/lib/utils/auth.js +++ b/lib/utils/auth.js @@ -4,48 +4,40 @@ const openUrlPrompt = require('../utils/open-url-prompt.js') const read = require('../utils/read-user-info.js') const otplease = require('../utils/otplease.js') -async function loginCouch (npm, creds, opts) { - const username = await read.username('Username:', creds.username) - const password = await read.password('Password:', creds.password) - return await otplease(npm, opts, (reqOpts) => - profile.loginCouch(username, password, reqOpts) - ) -} - -async function addUserCouch (npm, creds, opts) { - const username = await read.username('Username:', creds.username) - const password = await read.password('Password:', creds.password) - const email = await read.email('Email: (this IS public) ', creds.email) - // npm registry quirk: If you "add" an existing user with their current - // password, it's effectively a login, and if that account has otp you'll - // be prompted for it. - return await otplease(npm, opts, (reqOpts) => - profile.adduserCouch(username, email, password, opts) - ) -} - const adduser = async (npm, { creds, ...opts }) => { const authType = npm.config.get('auth-type') let res if (authType === 'web') { - res = await profile.adduserWeb((url, emitter) => { - openUrlPrompt( - npm, - url, - 'Create your account at', - 'Press ENTER to open in the browser...', - emitter - ) - }, opts).catch(er => { - if (er.message === 'Web login not supported' && er.code === 'ENYI') { + try { + res = await profile.adduserWeb((url, emitter) => { + openUrlPrompt( + npm, + url, + 'Create your account at', + 'Press ENTER to open in the browser...', + emitter + ) + }, opts) + } catch (err) { + if (err.code === 'ENYI') { log.verbose('web add user not supported, trying couch') - return addUserCouch(npm, creds, opts) } else { - throw er + throw err } - }) - } else { - res = await addUserCouch(npm, creds, opts) + } + } + + // auth type !== web or ENYI error w/ web adduser + if (!res) { + const username = await read.username('Username:', creds.username) + const password = await read.password('Password:', creds.password) + const email = await read.email('Email: (this IS public) ', creds.email) + // npm registry quirk: If you "add" an existing user with their current + // password, it's effectively a login, and if that account has otp you'll + // be prompted for it. + res = await otplease(npm, opts, (reqOpts) => + profile.adduserCouch(username, email, password, opts) + ) } // We don't know the username if it was a web login, all we can reliably log is scope and registry @@ -63,24 +55,32 @@ const login = async (npm, { creds, ...opts }) => { const authType = npm.config.get('auth-type') let res if (authType === 'web') { - res = await profile.loginWeb((url, emitter) => { - openUrlPrompt( - npm, - url, - 'Login at', - 'Press ENTER to open in the browser...', - emitter - ) - }, opts).catch(er => { - if (er.message === 'Web login not supported' && er.code === 'ENYI') { + try { + res = await profile.loginWeb((url, emitter) => { + openUrlPrompt( + npm, + url, + 'Login at', + 'Press ENTER to open in the browser...', + emitter + ) + }, opts) + } catch (err) { + if (err.code === 'ENYI') { log.verbose('web login not supported, trying couch') - return loginCouch(npm, creds, opts) } else { - throw er + throw err } - }) - } else { - res = await loginCouch(npm, creds, opts) + } + } + + // auth type !== web or ENYI error w/ web login + if (!res) { + const username = await read.username('Username:', creds.username) + const password = await read.password('Password:', creds.password) + res = await otplease(npm, opts, (reqOpts) => + profile.loginCouch(username, password, reqOpts) + ) } // We don't know the username if it was a web login, all we can reliably log is scope and registry diff --git a/test/lib/commands/adduser.js b/test/lib/commands/adduser.js index 90ed10df70274..ddfbb945b2fcf 100644 --- a/test/lib/commands/adduser.js +++ b/test/lib/commands/adduser.js @@ -174,5 +174,49 @@ t.test('web', t => { '//registry.npmjs.org/:_authToken': 'npm_test-token', }) }) + + t.test('server error', async t => { + const { npm } = await loadMockNpm(t, { + config: { 'auth-type': 'web' }, + }) + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + }) + registry.nock.post(registry.fullPath('/-/v1/login')) + .reply(503, {}) + await t.rejects( + npm.exec('adduser', []), + { message: /503/ } + ) + }) + + t.test('fallback', async t => { + const stdin = new stream.PassThrough() + stdin.write('test-user\n') + stdin.write('test-password\n') + stdin.write('test-email@npmjs.org\n') + mockGlobals(t, { + 'process.stdin': stdin, + 'process.stdout': new stream.PassThrough(), // to quiet readline + }, { replace: true }) + const { npm } = await loadMockNpm(t, { + config: { 'auth-type': 'web' }, + }) + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + }) + registry.nock.post(registry.fullPath('/-/v1/login')) + .reply(404, {}) + registry.couchadduser({ + username: 'test-user', + password: 'test-password', + email: 'test-email@npmjs.org', + token: 'npm_test-token', + }) + await npm.exec('adduser', []) + t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token') + }) t.end() }) diff --git a/test/lib/commands/login.js b/test/lib/commands/login.js index ae7e3ffab0f02..63666670712ef 100644 --- a/test/lib/commands/login.js +++ b/test/lib/commands/login.js @@ -139,5 +139,45 @@ t.test('web', t => { '//registry.npmjs.org/:_authToken': 'npm_test-token', }) }) + t.test('server error', async t => { + const { npm } = await loadMockNpm(t, { + config: { 'auth-type': 'web' }, + }) + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + }) + registry.nock.post(registry.fullPath('/-/v1/login')) + .reply(503, {}) + await t.rejects( + npm.exec('login', []), + { message: /503/ } + ) + }) + t.test('fallback', async t => { + const stdin = new stream.PassThrough() + stdin.write('test-user\n') + stdin.write('test-password\n') + mockGlobals(t, { + 'process.stdin': stdin, + 'process.stdout': new stream.PassThrough(), // to quiet readline + }, { replace: true }) + const { npm } = await loadMockNpm(t, { + config: { 'auth-type': 'web' }, + }) + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + }) + registry.nock.post(registry.fullPath('/-/v1/login')) + .reply(404, {}) + registry.couchlogin({ + username: 'test-user', + password: 'test-password', + token: 'npm_test-token', + }) + await npm.exec('login', []) + t.same(npm.config.get('//registry.npmjs.org/:_authToken'), 'npm_test-token') + }) t.end() })