Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(publish): Support 2FA during publish (WIP)
TODO: - unit tests (direct + high-level) - figure out how to make integration tests work again refs #1091
- Loading branch information
Showing
19 changed files
with
278 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
commands/publish/lib/__mocks__/get-two-factor-auth-required.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"use strict"; | ||
|
||
// to mock user modules, you _must_ call `jest.mock('./path/to/module')` | ||
module.exports = jest.fn(() => Promise.resolve(false)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"use strict"; | ||
|
||
// to mock user modules, you _must_ call `jest.mock('./path/to/module')` | ||
module.exports = jest.fn(() => Promise.resolve("MOCK_OTP")); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"use strict"; | ||
|
||
// to mock user modules, you _must_ call `jest.mock('./path/to/module')` | ||
module.exports = jest.fn(() => Promise.resolve()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"use strict"; | ||
|
||
// to mock user modules, you _must_ call `jest.mock('./path/to/module')` | ||
module.exports = jest.fn(() => Promise.resolve()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
"use strict"; | ||
|
||
const log = require("npmlog"); | ||
const childProcess = require("@lerna/child-process"); | ||
const getExecOpts = require("@lerna/get-npm-exec-opts"); | ||
const ValidationError = require("@lerna/validation-error"); | ||
|
||
module.exports = getTwoFactorAuthRequired; | ||
|
||
function getTwoFactorAuthRequired(location, { registry }) { | ||
log.silly("getTwoFactorAuthRequired"); | ||
|
||
const args = [ | ||
"profile", | ||
"get", | ||
// next parameter is _not_ a typo... | ||
"two-factor auth", | ||
// immediate feedback from request errors, not excruciatingly slow retries | ||
// @see https://docs.npmjs.com/misc/config#fetch-retries | ||
"--fetch-retries=0", | ||
// including http requests makes raw logging easier to debug for end users | ||
"--loglevel=http", | ||
]; | ||
const opts = getExecOpts({ location }, registry); | ||
|
||
// we do not need special log handling | ||
delete opts.pkg; | ||
|
||
return childProcess.exec("npm", args, opts).then( | ||
result => result.stdout === "auth-and-writes", | ||
({ stderr }) => { | ||
// Log the error cleanly to stderr, it already has npmlog decorations | ||
log.pause(); | ||
console.error(stderr); // eslint-disable-line no-console | ||
log.resume(); | ||
|
||
throw new ValidationError("ETWOFACTOR", "Unable to obtain two-factor auth mode"); | ||
} | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
"use strict"; | ||
|
||
const PromptUtilities = require("@lerna/prompt"); | ||
|
||
module.exports = promptOneTimePassword; | ||
|
||
function promptOneTimePassword() { | ||
// Logic taken from npm internals: https://git.io/fNoMe | ||
return PromptUtilities.input("Enter OTP", { | ||
filter: otp => otp.replace(/\s+/g, ""), | ||
validate: otp => | ||
(otp && /^[\d ]+$|^[A-Fa-f0-9]{64,64}$/.test(otp)) || | ||
"Must be a valid one-time-password. " + | ||
"See https://docs.npmjs.com/getting-started/using-two-factor-authentication", | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
"use strict"; | ||
|
||
const log = require("npmlog"); | ||
const childProcess = require("@lerna/child-process"); | ||
const getExecOpts = require("@lerna/get-npm-exec-opts"); | ||
const ValidationError = require("@lerna/validation-error"); | ||
|
||
module.exports = verifyNpmPackageAccess; | ||
|
||
function verifyNpmPackageAccess(packages, location, { registry }) { | ||
log.silly("verifyNpmPackageAccess"); | ||
|
||
const args = [ | ||
"access", | ||
"ls-packages", | ||
// immediate feedback from request errors, not excruciatingly slow retries | ||
// @see https://docs.npmjs.com/misc/config#fetch-retries | ||
"--fetch-retries=0", | ||
// including http requests makes raw logging easier to debug for end users | ||
"--loglevel=http", | ||
]; | ||
const opts = getExecOpts({ location }, registry); | ||
|
||
// we do not need special log handling | ||
delete opts.pkg; | ||
|
||
return childProcess.exec("npm", args, opts).then( | ||
result => { | ||
const permission = JSON.parse(result.stdout); | ||
|
||
for (const pkg of packages) { | ||
if (permission[pkg.name] !== "read-write") { | ||
throw new ValidationError( | ||
"EACCESS", | ||
"You do not have write permission required to publish %j", | ||
pkg.name | ||
); | ||
} | ||
} | ||
}, | ||
// only catch npm error, not validation error above | ||
({ stderr }) => { | ||
// pass if registry does not support ls-packages endpoint | ||
if (/E500/.test(stderr) && /ECONNREFUSED/.test(stderr)) { | ||
// most likely a private registry (npm Enterprise, verdaccio, etc) | ||
log.warn( | ||
"EREGISTRY", | ||
"Registry %j does not support `npm access ls-packages`, skipping permission checks...", | ||
registry | ||
); | ||
|
||
// don't log redundant errors | ||
return; | ||
} | ||
|
||
if (/ENEEDAUTH/.test(stderr)) { | ||
throw new ValidationError( | ||
"ENEEDAUTH", | ||
"You must be logged in to publish packages. Use `npm login` and try again." | ||
); | ||
} | ||
|
||
// Log the error cleanly to stderr, it already has npmlog decorations | ||
log.pause(); | ||
console.error(stderr); // eslint-disable-line no-console | ||
log.resume(); | ||
|
||
throw new ValidationError("EWHOAMI", "Authentication error. Use `npm whoami` to troubleshoot."); | ||
} | ||
); | ||
} |
Oops, something went wrong.