From 08a4e007ab18c4d5496a4aeaebea3525b9c0067c Mon Sep 17 00:00:00 2001 From: "npm-cli+bot@github.com" Date: Wed, 20 Jul 2022 22:14:49 +0000 Subject: [PATCH] deps: upgrade npm to 8.15.0 --- deps/npm/docs/content/commands/npm-audit.md | 57 ++++++++++++-- .../docs/content/configuring-npm/folders.md | 2 +- .../content/configuring-npm/package-json.md | 10 +-- deps/npm/docs/content/using-npm/config.md | 8 +- deps/npm/docs/output/commands/npm-audit.html | 47 ++++++++++-- deps/npm/docs/output/commands/npm-ls.html | 2 +- deps/npm/docs/output/commands/npm.html | 2 +- .../docs/output/configuring-npm/folders.html | 2 +- .../output/configuring-npm/package-json.html | 12 +-- deps/npm/docs/output/using-npm/config.html | 8 +- deps/npm/lib/auth/sso.js | 2 +- deps/npm/lib/commands/access.js | 2 +- deps/npm/lib/commands/adduser.js | 2 +- deps/npm/lib/commands/deprecate.js | 2 +- deps/npm/lib/commands/dist-tag.js | 4 +- deps/npm/lib/commands/hook.js | 2 +- deps/npm/lib/commands/org.js | 2 +- deps/npm/lib/commands/owner.js | 2 +- deps/npm/lib/commands/profile.js | 2 +- deps/npm/lib/commands/publish.js | 7 +- deps/npm/lib/commands/team.js | 2 +- deps/npm/lib/commands/token.js | 4 +- deps/npm/lib/commands/unpublish.js | 2 +- deps/npm/lib/utils/config/definitions.js | 7 +- deps/npm/lib/utils/get-identity.js | 4 +- deps/npm/lib/utils/otplease.js | 41 ++++++++-- deps/npm/lib/utils/web-auth.js | 20 +++++ deps/npm/man/man1/npm-audit.1 | 76 +++++++++++++++++-- deps/npm/man/man1/npm-ls.1 | 2 +- deps/npm/man/man1/npm.1 | 2 +- deps/npm/man/man5/folders.5 | 2 +- deps/npm/man/man5/package-json.5 | 10 +-- deps/npm/man/man7/config.7 | 8 +- .../node_modules/@npmcli/config/lib/index.js | 19 ++++- .../node_modules/@npmcli/config/package.json | 6 +- .../make-fetch-happen/lib/cache/entry.js | 1 + .../make-fetch-happen/package.json | 2 +- .../npm-registry-fetch/lib/auth.js | 37 ++++++++- .../npm-registry-fetch/lib/index.js | 4 +- .../npm-registry-fetch/package.json | 2 +- deps/npm/package.json | 10 +-- .../test/lib/commands/publish.js.test.cjs | 6 +- .../lib/utils/config/definitions.js.test.cjs | 8 +- .../lib/utils/config/describe-all.js.test.cjs | 8 +- deps/npm/test/lib/commands/publish.js | 31 +++++++- deps/npm/test/lib/commands/whoami.js | 14 ++++ deps/npm/test/lib/utils/otplease.js | 52 +++++++++++-- deps/npm/test/lib/utils/web-auth.js | 32 ++++++++ 48 files changed, 475 insertions(+), 114 deletions(-) create mode 100644 deps/npm/lib/utils/web-auth.js create mode 100644 deps/npm/test/lib/utils/web-auth.js diff --git a/deps/npm/docs/content/commands/npm-audit.md b/deps/npm/docs/content/commands/npm-audit.md index 206a33f53a688c..48e0a3161e8f2c 100644 --- a/deps/npm/docs/content/commands/npm-audit.md +++ b/deps/npm/docs/content/commands/npm-audit.md @@ -43,14 +43,55 @@ output, it simply changes the command's failure threshold. ### Audit Signatures -This command can also audit the integrity values of the packages in your -tree against any signatures present in the registry they were downloaded -from. npm will attempt to download the keys from `/-/npm/v1/keys` on -each the registry used to download any given package. It will then -check the `dist.signatures` object in the package itself, and verify the -`sig` present there using the `keyid` there, matching it with a key -returned from the registry. The command for this is `npm audit -signatures` +To ensure the integrity of packages you download from the public npm registry, or any registry that supports signatures, you can verify the registry signatures of downloaded packages using the npm CLI. + +Registry signatures can be verified using the following `audit` command: + +```bash +$ npm audit signatures +``` + +The npm CLI supports registry signatures and signing keys provided by any registry if the following conventions are followed: + +1. Signatures are provided in the package's `packument` in each published version within the `dist` object: + +```json +"dist":{ + "..omitted..": "..omitted..", + "signatures": [{ + "keyid": "SHA256:{{SHA256_PUBLIC_KEY}}", + "sig": "a312b9c3cb4a1b693e8ebac5ee1ca9cc01f2661c14391917dcb111517f72370809..." + }] +} +``` + +See this [example](https://registry.npmjs.org/light-cycle/1.4.3) of a signed package from the public npm registry. + +The `sig` is generated using the following template: `${package.name}@${package.version}:${package.dist.integrity}` and the `keyid` has to match one of the public signing keys below. + +2. Public signing keys are provided at `registry-host.tld/-/npm/v1/keys` in the following format: + +``` +{ + "keys": [{ + "expires": null, + "keyid": "SHA256:{{SHA256_PUBLIC_KEY}}", + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "key": "{{B64_PUBLIC_KEY}}" + }] +} +``` + +Keys response: + +- `expires`: null or a simplified extended ISO 8601 format: `YYYY-MM-DDTHH:mm:ss.sssZ` +- `keydid`: sha256 fingerprint of the public key +- `keytype`: only `ecdsa-sha2-nistp256` is currently supported by the npm CLI +- `scheme`: only `ecdsa-sha2-nistp256` is currently supported by the npm CLI +- `key`: base64 encoded public key + +See this example key's response from the public npm registry. ### Audit Endpoints diff --git a/deps/npm/docs/content/configuring-npm/folders.md b/deps/npm/docs/content/configuring-npm/folders.md index 218870765b2625..5bab80ec169c5c 100644 --- a/deps/npm/docs/content/configuring-npm/folders.md +++ b/deps/npm/docs/content/configuring-npm/folders.md @@ -202,7 +202,7 @@ For a graphical breakdown of what is installed where, use `npm ls`. #### Publishing Upon publishing, npm will look in the `node_modules` folder. If any of -the items there are not in the `bundledDependencies` array, then they will +the items there are not in the `bundleDependencies` array, then they will not be included in the package tarball. This allows a package maintainer to install all of their dependencies diff --git a/deps/npm/docs/content/configuring-npm/package-json.md b/deps/npm/docs/content/configuring-npm/package-json.md index 826f10ce825317..f0315d60efef48 100644 --- a/deps/npm/docs/content/configuring-npm/package-json.md +++ b/deps/npm/docs/content/configuring-npm/package-json.md @@ -829,14 +829,14 @@ if the `soy-milk` package is not installed on the host. This allows you to integrate and interact with a variety of host packages without requiring all of them to be installed. -### bundledDependencies +### bundleDependencies This defines an array of package names that will be bundled when publishing the package. In cases where you need to preserve npm packages locally or have them available through a single file download, you can bundle the packages in a -tarball file by specifying the package names in the `bundledDependencies` +tarball file by specifying the package names in the `bundleDependencies` array and executing `npm pack`. For example: @@ -847,7 +847,7 @@ If we define a package.json like this: { "name": "awesome-web-framework", "version": "1.0.0", - "bundledDependencies": [ + "bundleDependencies": [ "renderized", "super-streams" ] @@ -860,9 +860,9 @@ can be installed in a new project by executing `npm install awesome-web-framework-1.0.0.tgz`. Note that the package names do not include any versions, as that information is specified in `dependencies`. -If this is spelled `"bundleDependencies"`, then that is also honored. +If this is spelled `"bundledDependencies"`, then that is also honored. -Alternatively, `"bundledDependencies"` can be defined as a boolean value. A +Alternatively, `"bundleDependencies"` can be defined as a boolean value. A value of `true` will bundle all dependencies, a value of `false` will bundle none. diff --git a/deps/npm/docs/content/using-npm/config.md b/deps/npm/docs/content/using-npm/config.md index ffbf9be055719b..e3e1bd6c73bb3b 100644 --- a/deps/npm/docs/content/using-npm/config.md +++ b/deps/npm/docs/content/using-npm/config.md @@ -357,8 +357,9 @@ newlines replaced by the string "\n". For example: cert="-----BEGIN CERTIFICATE-----\nXXXX\nXXXX\n-----END CERTIFICATE-----" ``` -It is _not_ the path to a certificate file (and there is no "certfile" -option). +It is _not_ the path to a certificate file, though you can set a +registry-scoped "certfile" path like +"//other-registry.tld/:certfile=/path/to/cert.pem". @@ -946,7 +947,8 @@ format with newlines replaced by the string "\n". For example: key="-----BEGIN PRIVATE KEY-----\nXXXX\nXXXX\n-----END PRIVATE KEY-----" ``` -It is _not_ the path to a key file (and there is no "keyfile" option). +It is _not_ the path to a key file, though you can set a registry-scoped +"keyfile" path like "//other-registry.tld/:keyfile=/path/to/key.pem". diff --git a/deps/npm/docs/output/commands/npm-audit.html b/deps/npm/docs/output/commands/npm-audit.html index ee315493a27d03..fa2c4ad52bce9c 100644 --- a/deps/npm/docs/output/commands/npm-audit.html +++ b/deps/npm/docs/output/commands/npm-audit.html @@ -171,13 +171,46 @@

Description

will cause the command to fail. This option does not filter the report output, it simply changes the command's failure threshold.

Audit Signatures

-

This command can also audit the integrity values of the packages in your -tree against any signatures present in the registry they were downloaded -from. npm will attempt to download the keys from /-/npm/v1/keys on -each the registry used to download any given package. It will then -check the dist.signatures object in the package itself, and verify the -sig present there using the keyid there, matching it with a key -returned from the registry. The command for this is npm audit signatures

+

To ensure the integrity of packages you download from the public npm registry, or any registry that supports signatures, you can verify the registry signatures of downloaded packages using the npm CLI.

+

Registry signatures can be verified using the following audit command:

+
$ npm audit signatures
+
+

The npm CLI supports registry signatures and signing keys provided by any registry if the following conventions are followed:

+
    +
  1. Signatures are provided in the package's packument in each published version within the dist object:
  2. +
+
"dist":{
+  "..omitted..": "..omitted..",
+  "signatures": [{
+    "keyid": "SHA256:{{SHA256_PUBLIC_KEY}}",
+    "sig": "a312b9c3cb4a1b693e8ebac5ee1ca9cc01f2661c14391917dcb111517f72370809..."
+  }]
+}
+
+

See this example of a signed package from the public npm registry.

+

The sig is generated using the following template: ${package.name}@${package.version}:${package.dist.integrity} and the keyid has to match one of the public signing keys below.

+
    +
  1. Public signing keys are provided at registry-host.tld/-/npm/v1/keys in the following format:
  2. +
+
{
+  "keys": [{
+    "expires": null,
+    "keyid": "SHA256:{{SHA256_PUBLIC_KEY}}",
+    "keytype": "ecdsa-sha2-nistp256",
+    "scheme": "ecdsa-sha2-nistp256",
+    "key": "{{B64_PUBLIC_KEY}}"
+  }]
+}
+
+

Keys response:

+ +

See this example key's response from the public npm registry.

Audit Endpoints

There are two audit endpoints that npm may use to fetch vulnerability information: the Bulk Advisory endpoint and the Quick Audit endpoint.

diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html index 4483459262c500..47b3bbc085e164 100644 --- a/deps/npm/docs/output/commands/npm-ls.html +++ b/deps/npm/docs/output/commands/npm-ls.html @@ -166,7 +166,7 @@

Description

the results to only the paths to the packages named. Note that nested packages will also show the paths to the specified packages. For example, running npm ls promzard in npm's source tree will show:

-
npm@8.14.0 /path/to/npm
+
npm@8.15.0 /path/to/npm
 └─┬ init-package-json@0.0.4
   └── promzard@0.1.5
 
diff --git a/deps/npm/docs/output/commands/npm.html b/deps/npm/docs/output/commands/npm.html index cbe5fbee3e1cbb..514017fd875943 100644 --- a/deps/npm/docs/output/commands/npm.html +++ b/deps/npm/docs/output/commands/npm.html @@ -149,7 +149,7 @@

Table of contents

Version

-

8.14.0

+

8.15.0

Description

npm is the package manager for the Node JavaScript platform. It puts modules in place so that node can find them, and manages dependency diff --git a/deps/npm/docs/output/configuring-npm/folders.html b/deps/npm/docs/output/configuring-npm/folders.html index 6d722466c52d83..2968dacd5ee78b 100644 --- a/deps/npm/docs/output/configuring-npm/folders.html +++ b/deps/npm/docs/output/configuring-npm/folders.html @@ -291,7 +291,7 @@

Example

For a graphical breakdown of what is installed where, use npm ls.

Publishing

Upon publishing, npm will look in the node_modules folder. If any of -the items there are not in the bundledDependencies array, then they will +the items there are not in the bundleDependencies array, then they will not be included in the package tarball.

This allows a package maintainer to install all of their dependencies (and dev dependencies) locally, but only re-publish those items that diff --git a/deps/npm/docs/output/configuring-npm/package-json.html b/deps/npm/docs/output/configuring-npm/package-json.html index 665dad43671cb9..354069b1a2c738 100644 --- a/deps/npm/docs/output/configuring-npm/package-json.html +++ b/deps/npm/docs/output/configuring-npm/package-json.html @@ -142,7 +142,7 @@

package.json

Table of contents

- +

Description

@@ -772,19 +772,19 @@

peerDependenciesMeta

if the soy-milk package is not installed on the host. This allows you to integrate and interact with a variety of host packages without requiring all of them to be installed.

-

bundledDependencies

+

bundleDependencies

This defines an array of package names that will be bundled when publishing the package.

In cases where you need to preserve npm packages locally or have them available through a single file download, you can bundle the packages in a -tarball file by specifying the package names in the bundledDependencies +tarball file by specifying the package names in the bundleDependencies array and executing npm pack.

For example:

If we define a package.json like this:

{
   "name": "awesome-web-framework",
   "version": "1.0.0",
-  "bundledDependencies": [
+  "bundleDependencies": [
     "renderized",
     "super-streams"
   ]
@@ -794,8 +794,8 @@ 

bundledDependencies

This file contains the dependencies renderized and super-streams which can be installed in a new project by executing npm install awesome-web-framework-1.0.0.tgz. Note that the package names do not include any versions, as that information is specified in dependencies.

-

If this is spelled "bundleDependencies", then that is also honored.

-

Alternatively, "bundledDependencies" can be defined as a boolean value. A +

If this is spelled "bundledDependencies", then that is also honored.

+

Alternatively, "bundleDependencies" can be defined as a boolean value. A value of true will bundle all dependencies, a value of false will bundle none.

optionalDependencies

diff --git a/deps/npm/docs/output/using-npm/config.html b/deps/npm/docs/output/using-npm/config.html index 900c08b3623d73..79bf005d61e15e 100644 --- a/deps/npm/docs/output/using-npm/config.html +++ b/deps/npm/docs/output/using-npm/config.html @@ -428,8 +428,9 @@

cert

newlines replaced by the string "\n". For example:

cert="-----BEGIN CERTIFICATE-----\nXXXX\nXXXX\n-----END CERTIFICATE-----"
 
-

It is not the path to a certificate file (and there is no "certfile" -option).

+

It is not the path to a certificate file, though you can set a +registry-scoped "certfile" path like +"//other-registry.tld/:certfile=/path/to/cert.pem".

ci-name

@@ -907,7 +908,8 @@

key

format with newlines replaced by the string "\n". For example:

key="-----BEGIN PRIVATE KEY-----\nXXXX\nXXXX\n-----END PRIVATE KEY-----"
 
-

It is not the path to a key file (and there is no "keyfile" option).

+

It is not the path to a key file, though you can set a registry-scoped +"keyfile" path like "//other-registry.tld/:keyfile=/path/to/key.pem".

legacy-bundling

diff --git a/deps/npm/lib/auth/sso.js b/deps/npm/lib/auth/sso.js index 9812a18cb99ca6..621ead5d21b658 100644 --- a/deps/npm/lib/auth/sso.js +++ b/deps/npm/lib/auth/sso.js @@ -52,7 +52,7 @@ const login = async (npm, { creds, registry, scope }) => { authType: ssoType, } - const { token, sso } = await otplease(opts, + const { token, sso } = await otplease(npm, opts, opts => profile.loginCouch(auth.username, auth.password, opts) ) diff --git a/deps/npm/lib/commands/access.js b/deps/npm/lib/commands/access.js index bc8ce48bacdad5..0a80da8ddd0066 100644 --- a/deps/npm/lib/commands/access.js +++ b/deps/npm/lib/commands/access.js @@ -180,7 +180,7 @@ class Access extends BaseCommand { modifyPackage (pkg, opts, fn, requireScope = true) { return this.getPackage(pkg, requireScope) - .then(pkgName => otplease(opts, opts => fn(pkgName, opts))) + .then(pkgName => otplease(this.npm, opts, opts => fn(pkgName, opts))) } async getPackage (name, requireScope) { diff --git a/deps/npm/lib/commands/adduser.js b/deps/npm/lib/commands/adduser.js index 5e23f40dc59680..2853269ef3deee 100644 --- a/deps/npm/lib/commands/adduser.js +++ b/deps/npm/lib/commands/adduser.js @@ -30,7 +30,7 @@ class AddUser extends BaseCommand { log.disableProgress() log.warn('adduser', - '`adduser` will be split into `login` and `register in a future version.' + '`adduser` will be split into `login` and `register` in a future version.' + ' `adduser` will become an alias of `register`.' + ' `login` (currently an alias) will become its own command.') log.notice('', `Log in on ${replaceInfo(registry)}`) diff --git a/deps/npm/lib/commands/deprecate.js b/deps/npm/lib/commands/deprecate.js index 862c214dbe592f..068bfdbcec717f 100644 --- a/deps/npm/lib/commands/deprecate.js +++ b/deps/npm/lib/commands/deprecate.js @@ -60,7 +60,7 @@ class Deprecate extends BaseCommand { packument.versions[v].deprecated = msg }) - return otplease(this.npm.flatOptions, opts => fetch(uri, { + return otplease(this.npm, this.npm.flatOptions, opts => fetch(uri, { ...opts, spec: p, method: 'PUT', diff --git a/deps/npm/lib/commands/dist-tag.js b/deps/npm/lib/commands/dist-tag.js index e74a3f1d435c9b..8052e4f7e4e38c 100644 --- a/deps/npm/lib/commands/dist-tag.js +++ b/deps/npm/lib/commands/dist-tag.js @@ -116,7 +116,7 @@ class DistTag extends BaseCommand { }, spec, } - await otplease(reqOpts, reqOpts => regFetch(url, reqOpts)) + await otplease(this.npm, reqOpts, reqOpts => regFetch(url, reqOpts)) this.npm.output(`+${t}: ${spec.name}@${version}`) } @@ -142,7 +142,7 @@ class DistTag extends BaseCommand { method: 'DELETE', spec, } - await otplease(reqOpts, reqOpts => regFetch(url, reqOpts)) + await otplease(this.npm, reqOpts, reqOpts => regFetch(url, reqOpts)) this.npm.output(`-${tag}: ${spec.name}@${version}`) } diff --git a/deps/npm/lib/commands/hook.js b/deps/npm/lib/commands/hook.js index a4619802d84298..bb3a34b8d2d1b0 100644 --- a/deps/npm/lib/commands/hook.js +++ b/deps/npm/lib/commands/hook.js @@ -22,7 +22,7 @@ class Hook extends BaseCommand { static ignoreImplicitWorkspace = true async exec (args) { - return otplease({ + return otplease(this.npm, { ...this.npm.flatOptions, }, (opts) => { switch (args[0]) { diff --git a/deps/npm/lib/commands/org.js b/deps/npm/lib/commands/org.js index e2202a9e9cf3b4..599b4b9c8758a3 100644 --- a/deps/npm/lib/commands/org.js +++ b/deps/npm/lib/commands/org.js @@ -33,7 +33,7 @@ class Org extends BaseCommand { } async exec ([cmd, orgname, username, role], cb) { - return otplease({ + return otplease(this.npm, { ...this.npm.flatOptions, }, opts => { switch (cmd) { diff --git a/deps/npm/lib/commands/owner.js b/deps/npm/lib/commands/owner.js index 732bb40a300502..824b64e044ecf2 100644 --- a/deps/npm/lib/commands/owner.js +++ b/deps/npm/lib/commands/owner.js @@ -202,7 +202,7 @@ class Owner extends BaseCommand { const dataPath = `/${spec.escapedName}/-rev/${encodeURIComponent(data._rev)}` try { - const res = await otplease(this.npm.flatOptions, opts => { + const res = await otplease(this.npm, this.npm.flatOptions, opts => { return npmFetch.json(dataPath, { ...opts, method: 'PUT', diff --git a/deps/npm/lib/commands/profile.js b/deps/npm/lib/commands/profile.js index fcf0eb7d53fa69..27060cf73a6502 100644 --- a/deps/npm/lib/commands/profile.js +++ b/deps/npm/lib/commands/profile.js @@ -221,7 +221,7 @@ class Profile extends BaseCommand { newUser[prop] = value - const result = await otplease(conf, conf => npmProfile.set(newUser, conf)) + const result = await otplease(this.npm, conf, conf => npmProfile.set(newUser, conf)) if (this.npm.config.get('json')) { this.npm.output(JSON.stringify({ [prop]: result[prop] }, null, 2)) diff --git a/deps/npm/lib/commands/publish.js b/deps/npm/lib/commands/publish.js index 579f5d6e74e67c..3d17866a684a45 100644 --- a/deps/npm/lib/commands/publish.js +++ b/deps/npm/lib/commands/publish.js @@ -61,7 +61,8 @@ class Publish extends BaseCommand { throw new Error('Tag name must not be a valid SemVer range: ' + defaultTag.trim()) } - const opts = { ...this.npm.flatOptions } + const opts = { ...this.npm.flatOptions, progress: false } + log.disableProgress() // you can publish name@version, ./foo.tgz, etc. // even though the default is the 'file:.' cwd. @@ -101,7 +102,7 @@ class Publish extends BaseCommand { const resolved = npa.resolve(manifest.name, manifest.version) const registry = npmFetch.pickRegistry(resolved, opts) const creds = this.npm.config.getCredentialsByURI(registry) - const noCreds = !creds.token && !creds.username + const noCreds = !(creds.token || creds.username || creds.certfile && creds.keyfile) const outputRegistry = replaceInfo(registry) if (noCreds) { @@ -116,7 +117,7 @@ class Publish extends BaseCommand { log.notice('', `Publishing to ${outputRegistry}${dryRun ? ' (dry-run)' : ''}`) if (!dryRun) { - await otplease(opts, opts => libpub(manifest, tarballData, opts)) + await otplease(this.npm, opts, opts => libpub(manifest, tarballData, opts)) } if (spec.type === 'directory' && !ignoreScripts) { diff --git a/deps/npm/lib/commands/team.js b/deps/npm/lib/commands/team.js index 640002456acad0..2d4fc663715e4e 100644 --- a/deps/npm/lib/commands/team.js +++ b/deps/npm/lib/commands/team.js @@ -44,7 +44,7 @@ class Team extends BaseCommand { // XXX: "description" option to libnpmteam is used as a description of the // team, but in npm's options, this is a boolean meaning "show the // description in npm search output". Hence its being set to null here. - await otplease({ ...this.npm.flatOptions }, opts => { + await otplease(this.npm, { ...this.npm.flatOptions }, opts => { entity = entity.replace(/^@/, '') switch (cmd) { case 'create': return this.create(entity, opts) diff --git a/deps/npm/lib/commands/token.js b/deps/npm/lib/commands/token.js index edfb07b9d3a9a4..cf3b8cbee53a4c 100644 --- a/deps/npm/lib/commands/token.js +++ b/deps/npm/lib/commands/token.js @@ -121,7 +121,7 @@ class Token extends BaseCommand { }) await Promise.all( toRemove.map(key => { - return otplease(conf, conf => { + return otplease(this.npm, conf, conf => { return profile.removeToken(key, conf) }) }) @@ -146,7 +146,7 @@ class Token extends BaseCommand { const validCIDR = this.validateCIDRList(cidr) log.info('token', 'creating') return pulseTillDone.withPromise( - otplease(conf, conf => { + otplease(this.npm, conf, conf => { return profile.createToken(password, readonly, validCIDR, conf) }) ) diff --git a/deps/npm/lib/commands/unpublish.js b/deps/npm/lib/commands/unpublish.js index ab929d98cadfa3..0e5ef3dc5e91d3 100644 --- a/deps/npm/lib/commands/unpublish.js +++ b/deps/npm/lib/commands/unpublish.js @@ -130,7 +130,7 @@ class Unpublish extends BaseCommand { } if (!dryRun) { - await otplease(opts, opts => libunpub(spec, opts)) + await otplease(this.npm, opts, opts => libunpub(spec, opts)) } if (!silent) { this.npm.output(`- ${pkgName}${pkgVersion}`) diff --git a/deps/npm/lib/utils/config/definitions.js b/deps/npm/lib/utils/config/definitions.js index 665ed1efe5e61c..7d6af2473f2bd6 100644 --- a/deps/npm/lib/utils/config/definitions.js +++ b/deps/npm/lib/utils/config/definitions.js @@ -436,8 +436,8 @@ define('cert', { cert="-----BEGIN CERTIFICATE-----\\nXXXX\\nXXXX\\n-----END CERTIFICATE-----" \`\`\` - It is _not_ the path to a certificate file (and there is no "certfile" - option). + It is _not_ the path to a certificate file, though you can set a registry-scoped + "certfile" path like "//other-registry.tld/:certfile=/path/to/cert.pem". `, flatten, }) @@ -1118,7 +1118,8 @@ define('key', { key="-----BEGIN PRIVATE KEY-----\\nXXXX\\nXXXX\\n-----END PRIVATE KEY-----" \`\`\` - It is _not_ the path to a key file (and there is no "keyfile" option). + It is _not_ the path to a key file, though you can set a registry-scoped + "keyfile" path like "//other-registry.tld/:keyfile=/path/to/key.pem". `, flatten, }) diff --git a/deps/npm/lib/utils/get-identity.js b/deps/npm/lib/utils/get-identity.js index f4aedb89b39573..41d882473ab7b1 100644 --- a/deps/npm/lib/utils/get-identity.js +++ b/deps/npm/lib/utils/get-identity.js @@ -9,8 +9,8 @@ module.exports = async (npm, opts) => { return creds.username } - // No username, but we have a token; fetch the username from registry - if (creds.token) { + // No username, but we have other credentials; fetch the username from registry + if (creds.token || creds.certfile && creds.keyfile) { const registryData = await npmFetch.json('/-/whoami', { ...opts }) return registryData.username } diff --git a/deps/npm/lib/utils/otplease.js b/deps/npm/lib/utils/otplease.js index 83985b6bc170d0..0e20e7a797ae04 100644 --- a/deps/npm/lib/utils/otplease.js +++ b/deps/npm/lib/utils/otplease.js @@ -1,17 +1,46 @@ -async function otplease (opts, fn) { +async function otplease (npm, opts, fn) { try { return await fn(opts) } catch (err) { - const readUserInfo = require('./read-user-info.js') - if (err.code !== 'EOTP' && (err.code !== 'E401' || !/one-time pass/.test(err.body))) { + if (!process.stdin.isTTY || !process.stdout.isTTY) { throw err - } else if (!process.stdin.isTTY || !process.stdout.isTTY) { - throw err - } else { + } + + if (isWebOTP(err)) { + const webAuth = require('./web-auth') + const openUrlPrompt = require('./open-url-prompt') + + const openerPromise = (url, emitter) => + openUrlPrompt( + npm, + url, + 'Authenticate your account at', + 'Press ENTER to open in the browser...', + emitter + ) + const otp = await webAuth(openerPromise, err.body.authUrl, err.body.doneUrl, opts) + return await fn({ ...opts, otp }) + } + + if (isClassicOTP(err)) { + const readUserInfo = require('./read-user-info.js') const otp = await readUserInfo.otp('This operation requires a one-time password.\nEnter OTP:') return await fn({ ...opts, otp }) } + + throw err + } +} + +function isWebOTP (err) { + if (!err.code === 'EOTP' || !err.body) { + return false } + return err.body.authUrl && err.body.doneUrl +} + +function isClassicOTP (err) { + return err.code === 'EOTP' || (err.code === 'E401' && /one-time pass/.test(err.body)) } module.exports = otplease diff --git a/deps/npm/lib/utils/web-auth.js b/deps/npm/lib/utils/web-auth.js new file mode 100644 index 00000000000000..ce551687098fc8 --- /dev/null +++ b/deps/npm/lib/utils/web-auth.js @@ -0,0 +1,20 @@ +const EventEmitter = require('events') +const { webAuthCheckLogin } = require('npm-profile') + +async function webAuth (opener, initialUrl, doneUrl, opts) { + const doneEmitter = new EventEmitter() + + const openPromise = opener(initialUrl, doneEmitter) + const webAuthCheckPromise = webAuthCheckLogin(doneUrl, { ...opts, cache: false }) + .then(authResult => { + // cancel open prompt if it's present + doneEmitter.emit('abort') + + return authResult.token + }) + + await openPromise + return await webAuthCheckPromise +} + +module.exports = webAuth diff --git a/deps/npm/man/man1/npm-audit.1 b/deps/npm/man/man1/npm-audit.1 index 63ef87d0a7126a..ac628b8a80c517 100644 --- a/deps/npm/man/man1/npm-audit.1 +++ b/deps/npm/man/man1/npm-audit.1 @@ -31,14 +31,74 @@ will cause the command to fail\. This option does not filter the report output, it simply changes the command's failure threshold\. .SS Audit Signatures .P -This command can also audit the integrity values of the packages in your -tree against any signatures present in the registry they were downloaded -from\. npm will attempt to download the keys from \fB/\-/npm/v1/keys\fP on -each the registry used to download any given package\. It will then -check the \fBdist\.signatures\fP object in the package itself, and verify the -\fBsig\fP present there using the \fBkeyid\fP there, matching it with a key -returned from the registry\. The command for this is \fBnpm audit -signatures\fP +To ensure the integrity of packages you download from the public npm registry, or any registry that supports signatures, you can verify the registry signatures of downloaded packages using the npm CLI\. +.P +Registry signatures can be verified using the following \fBaudit\fP command: +.P +.RS 2 +.nf +$ npm audit signatures +.fi +.RE +.P +The npm CLI supports registry signatures and signing keys provided by any registry if the following conventions are followed: +.RS 0 +.IP 1. 3 +Signatures are provided in the package's \fBpackument\fP in each published version within the \fBdist\fP object: + +.RE +.P +.RS 2 +.nf +"dist":{ + "\.\.omitted\.\.": "\.\.omitted\.\.", + "signatures": [{ + "keyid": "SHA256:{{SHA256_PUBLIC_KEY}}", + "sig": "a312b9c3cb4a1b693e8ebac5ee1ca9cc01f2661c14391917dcb111517f72370809\.\.\." + }] +} +.fi +.RE +.P +See this example \fIhttps://registry\.npmjs\.org/light\-cycle/1\.4\.3\fR of a signed package from the public npm registry\. +.P +The \fBsig\fP is generated using the following template: \fB${package\.name}@${package\.version}:${package\.dist\.integrity}\fP and the \fBkeyid\fP has to match one of the public signing keys below\. +.RS 0 +.IP 1. 3 +Public signing keys are provided at \fBregistry\-host\.tld/\-/npm/v1/keys\fP in the following format: + +.RE +.P +.RS 2 +.nf +{ + "keys": [{ + "expires": null, + "keyid": "SHA256:{{SHA256_PUBLIC_KEY}}", + "keytype": "ecdsa\-sha2\-nistp256", + "scheme": "ecdsa\-sha2\-nistp256", + "key": "{{B64_PUBLIC_KEY}}" + }] +} +.fi +.RE +.P +Keys response: +.RS 0 +.IP \(bu 2 +\fBexpires\fP: null or a simplified extended ISO 8601 format: \fBYYYY\-MM\-DDTHH:mm:ss\.sssZ\fP +.IP \(bu 2 +\fBkeydid\fP: sha256 fingerprint of the public key +.IP \(bu 2 +\fBkeytype\fP: only \fBecdsa\-sha2\-nistp256\fP is currently supported by the npm CLI +.IP \(bu 2 +\fBscheme\fP: only \fBecdsa\-sha2\-nistp256\fP is currently supported by the npm CLI +.IP \(bu 2 +\fBkey\fP: base64 encoded public key + +.RE +.P +See this example key's response from the public npm registry\|\. .SS Audit Endpoints .P There are two audit endpoints that npm may use to fetch vulnerability diff --git a/deps/npm/man/man1/npm-ls.1 b/deps/npm/man/man1/npm-ls.1 index ad4473f2d9660a..511f481a6ea9fd 100644 --- a/deps/npm/man/man1/npm-ls.1 +++ b/deps/npm/man/man1/npm-ls.1 @@ -26,7 +26,7 @@ example, running \fBnpm ls promzard\fP in npm's source tree will show: .P .RS 2 .nf -npm@8\.14\.0 /path/to/npm +npm@8\.15\.0 /path/to/npm └─┬ init\-package\-json@0\.0\.4 └── promzard@0\.1\.5 .fi diff --git a/deps/npm/man/man1/npm.1 b/deps/npm/man/man1/npm.1 index b9f54eec1ba16d..984dbc49192dd7 100644 --- a/deps/npm/man/man1/npm.1 +++ b/deps/npm/man/man1/npm.1 @@ -4,7 +4,7 @@ .SS Synopsis .SS Version .P -8\.14\.0 +8\.15\.0 .SS Description .P npm is the package manager for the Node JavaScript platform\. It puts diff --git a/deps/npm/man/man5/folders.5 b/deps/npm/man/man5/folders.5 index 3dda65825fa13c..924ee0fa243d1c 100644 --- a/deps/npm/man/man5/folders.5 +++ b/deps/npm/man/man5/folders.5 @@ -198,7 +198,7 @@ For a graphical breakdown of what is installed where, use \fBnpm ls\fP\|\. .SS Publishing .P Upon publishing, npm will look in the \fBnode_modules\fP folder\. If any of -the items there are not in the \fBbundledDependencies\fP array, then they will +the items there are not in the \fBbundleDependencies\fP array, then they will not be included in the package tarball\. .P This allows a package maintainer to install all of their dependencies diff --git a/deps/npm/man/man5/package-json.5 b/deps/npm/man/man5/package-json.5 index 78bfc39af2e95d..0fd5174f6aa7b6 100644 --- a/deps/npm/man/man5/package-json.5 +++ b/deps/npm/man/man5/package-json.5 @@ -924,14 +924,14 @@ Marking a peer dependency as optional ensures npm will not emit a warning if the \fBsoy\-milk\fP package is not installed on the host\. This allows you to integrate and interact with a variety of host packages without requiring all of them to be installed\. -.SS bundledDependencies +.SS bundleDependencies .P This defines an array of package names that will be bundled when publishing the package\. .P In cases where you need to preserve npm packages locally or have them available through a single file download, you can bundle the packages in a -tarball file by specifying the package names in the \fBbundledDependencies\fP +tarball file by specifying the package names in the \fBbundleDependencies\fP array and executing \fBnpm pack\fP\|\. .P For example: @@ -943,7 +943,7 @@ If we define a package\.json like this: { "name": "awesome\-web\-framework", "version": "1\.0\.0", - "bundledDependencies": [ + "bundleDependencies": [ "renderized", "super\-streams" ] @@ -957,9 +957,9 @@ can be installed in a new project by executing \fBnpm install awesome\-web\-framework\-1\.0\.0\.tgz\fP\|\. Note that the package names do not include any versions, as that information is specified in \fBdependencies\fP\|\. .P -If this is spelled \fB"bundleDependencies"\fP, then that is also honored\. +If this is spelled \fB"bundledDependencies"\fP, then that is also honored\. .P -Alternatively, \fB"bundledDependencies"\fP can be defined as a boolean value\. A +Alternatively, \fB"bundleDependencies"\fP can be defined as a boolean value\. A value of \fBtrue\fP will bundle all dependencies, a value of \fBfalse\fP will bundle none\. .SS optionalDependencies diff --git a/deps/npm/man/man7/config.7 b/deps/npm/man/man7/config.7 index 1674743956ff6b..bb0c65e9851255 100644 --- a/deps/npm/man/man7/config.7 +++ b/deps/npm/man/man7/config.7 @@ -400,8 +400,9 @@ cert="\-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-\\nXXXX\\nXXXX\\n\-\-\-\-\-END CERTIF .fi .RE .P -It is \fInot\fR the path to a certificate file (and there is no "certfile" -option)\. +It is \fInot\fR the path to a certificate file, though you can set a +registry\-scoped "certfile" path like +"//other\-registry\.tld/:certfile=/path/to/cert\.pem"\. .SS \fBci\-name\fP .RS 0 .IP \(bu 2 @@ -1012,7 +1013,8 @@ key="\-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-\\nXXXX\\nXXXX\\n\-\-\-\-\-END PRIVATE .fi .RE .P -It is \fInot\fR the path to a key file (and there is no "keyfile" option)\. +It is \fInot\fR the path to a key file, though you can set a registry\-scoped +"keyfile" path like "//other\-registry\.tld/:keyfile=/path/to/key\.pem"\. .SS \fBlegacy\-bundling\fP .RS 0 .IP \(bu 2 diff --git a/deps/npm/node_modules/@npmcli/config/lib/index.js b/deps/npm/node_modules/@npmcli/config/lib/index.js index 5b7ea68e91e36e..8c2b181ca9c618 100644 --- a/deps/npm/node_modules/@npmcli/config/lib/index.js +++ b/deps/npm/node_modules/@npmcli/config/lib/index.js @@ -698,9 +698,11 @@ class Config { this.delete(`${nerfed}:_password`, 'user') this.delete(`${nerfed}:username`, 'user') this.delete(`${nerfed}:email`, 'user') + this.delete(`${nerfed}:certfile`, 'user') + this.delete(`${nerfed}:keyfile`, 'user') } - setCredentialsByURI (uri, { token, username, password, email }) { + setCredentialsByURI (uri, { token, username, password, email, certfile, keyfile }) { const nerfed = nerfDart(uri) const def = nerfDart(this.get('registry')) @@ -733,6 +735,11 @@ class Config { this.delete(`${nerfed}:-authtoken`, 'user') this.delete(`${nerfed}:_authtoken`, 'user') this.delete(`${nerfed}:email`, 'user') + if (certfile && keyfile) { + this.set(`${nerfed}:certfile`, certfile, 'user') + this.set(`${nerfed}:keyfile`, keyfile, 'user') + // cert/key may be used in conjunction with other credentials, thus no `else` + } if (token) { this.set(`${nerfed}:_authToken`, token, 'user') this.delete(`${nerfed}:_password`, 'user') @@ -750,7 +757,7 @@ class Config { // protects against shoulder-hacks if password is memorable, I guess? const encoded = Buffer.from(password, 'utf8').toString('base64') this.set(`${nerfed}:_password`, encoded, 'user') - } else { + } else if (!certfile || !keyfile) { throw new Error('No credentials to set.') } } @@ -765,6 +772,14 @@ class Config { creds.email = email } + const certfileReg = this.get(`${nerfed}:certfile`) + const keyfileReg = this.get(`${nerfed}:keyfile`) + if (certfileReg && keyfileReg) { + creds.certfile = certfileReg + creds.keyfile = keyfileReg + // cert/key may be used in conjunction with other credentials, thus no `return` + } + const tokenReg = this.get(`${nerfed}:_authToken`) || this.get(`${nerfed}:_authtoken`) || this.get(`${nerfed}:-authtoken`) || diff --git a/deps/npm/node_modules/@npmcli/config/package.json b/deps/npm/node_modules/@npmcli/config/package.json index 2cc04e05be8c97..2f561c12233dc3 100644 --- a/deps/npm/node_modules/@npmcli/config/package.json +++ b/deps/npm/node_modules/@npmcli/config/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/config", - "version": "4.1.0", + "version": "4.2.0", "files": [ "bin/", "lib/" @@ -31,7 +31,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^3.0.1", - "@npmcli/template-oss": "3.3.2", + "@npmcli/template-oss": "3.5.0", "tap": "^16.0.1" }, "dependencies": { @@ -49,6 +49,6 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "3.3.2" + "version": "3.5.0" } } diff --git a/deps/npm/node_modules/make-fetch-happen/lib/cache/entry.js b/deps/npm/node_modules/make-fetch-happen/lib/cache/entry.js index 7a7572ba030c9d..4307962b889d0e 100644 --- a/deps/npm/node_modules/make-fetch-happen/lib/cache/entry.js +++ b/deps/npm/node_modules/make-fetch-happen/lib/cache/entry.js @@ -35,6 +35,7 @@ const KEEP_RESPONSE_HEADERS = [ 'etag', 'expires', 'last-modified', + 'link', 'location', 'pragma', 'vary', diff --git a/deps/npm/node_modules/make-fetch-happen/package.json b/deps/npm/node_modules/make-fetch-happen/package.json index e04c7645c4f2a1..8b21901f34fc12 100644 --- a/deps/npm/node_modules/make-fetch-happen/package.json +++ b/deps/npm/node_modules/make-fetch-happen/package.json @@ -1,6 +1,6 @@ { "name": "make-fetch-happen", - "version": "10.1.8", + "version": "10.2.0", "description": "Opinionated, caching, retrying fetch client", "main": "lib/index.js", "files": [ diff --git a/deps/npm/node_modules/npm-registry-fetch/lib/auth.js b/deps/npm/node_modules/npm-registry-fetch/lib/auth.js index 17da6a17d7193b..870ce0d923cd0f 100644 --- a/deps/npm/node_modules/npm-registry-fetch/lib/auth.js +++ b/deps/npm/node_modules/npm-registry-fetch/lib/auth.js @@ -1,4 +1,5 @@ 'use strict' +const fs = require('fs') const npa = require('npm-package-arg') const { URL } = require('url') @@ -7,7 +8,8 @@ const { URL } = require('url') const regKeyFromURI = (uri, opts) => { const parsed = new URL(uri) // try to find a config key indicating we have auth for this registry - // can be one of :_authToken, :_auth, or :_password and :username + // can be one of :_authToken, :_auth, :_password and :username, or + // :certfile and :keyfile // We walk up the "path" until we're left with just //[:], // stopping when we reach '//'. let regKey = `//${parsed.host}${parsed.pathname}` @@ -26,7 +28,8 @@ const regKeyFromURI = (uri, opts) => { const hasAuth = (regKey, opts) => ( opts[`${regKey}:_authToken`] || opts[`${regKey}:_auth`] || - opts[`${regKey}:username`] && opts[`${regKey}:_password`] + opts[`${regKey}:username`] && opts[`${regKey}:_password`] || + opts[`${regKey}:certfile`] && opts[`${regKey}:keyfile`] ) const sameHost = (a, b) => { @@ -44,6 +47,17 @@ const getRegistry = opts => { return scopeReg || opts.registry } +const maybeReadFile = file => { + try { + return fs.readFileSync(file, 'utf8') + } catch (er) { + if (er.code !== 'ENOENT') { + throw er + } + return null + } +} + const getAuth = (uri, opts = {}) => { const { forceAuth } = opts if (!uri) { @@ -59,6 +73,8 @@ const getAuth = (uri, opts = {}) => { username: forceAuth.username, password: forceAuth._password || forceAuth.password, auth: forceAuth._auth || forceAuth.auth, + certfile: forceAuth.certfile, + keyfile: forceAuth.keyfile, }) } @@ -82,6 +98,8 @@ const getAuth = (uri, opts = {}) => { [`${regKey}:username`]: username, [`${regKey}:_password`]: password, [`${regKey}:_auth`]: auth, + [`${regKey}:certfile`]: certfile, + [`${regKey}:keyfile`]: keyfile, } = opts return new Auth({ @@ -90,15 +108,19 @@ const getAuth = (uri, opts = {}) => { auth, username, password, + certfile, + keyfile, }) } class Auth { - constructor ({ token, auth, username, password, scopeAuthKey }) { + constructor ({ token, auth, username, password, scopeAuthKey, certfile, keyfile }) { this.scopeAuthKey = scopeAuthKey this.token = null this.auth = null this.isBasicAuth = false + this.cert = null + this.key = null if (token) { this.token = token } else if (auth) { @@ -108,6 +130,15 @@ class Auth { this.auth = Buffer.from(`${username}:${p}`, 'utf8').toString('base64') this.isBasicAuth = true } + // mTLS may be used in conjunction with another auth method above + if (certfile && keyfile) { + const cert = maybeReadFile(certfile, 'utf-8') + const key = maybeReadFile(keyfile, 'utf-8') + if (cert && key) { + this.cert = cert + this.key = key + } + } } } diff --git a/deps/npm/node_modules/npm-registry-fetch/lib/index.js b/deps/npm/node_modules/npm-registry-fetch/lib/index.js index c788febc33afb4..cc331a50c09635 100644 --- a/deps/npm/node_modules/npm-registry-fetch/lib/index.js +++ b/deps/npm/node_modules/npm-registry-fetch/lib/index.js @@ -112,10 +112,10 @@ function regFetch (uri, /* istanbul ignore next */ opts_ = {}) { cache: getCacheMode(opts), cachePath: opts.cache, ca: opts.ca, - cert: opts.cert, + cert: auth.cert || opts.cert, headers, integrity: opts.integrity, - key: opts.key, + key: auth.key || opts.key, localAddress: opts.localAddress, maxSockets: opts.maxSockets, memoize: opts.memoize, diff --git a/deps/npm/node_modules/npm-registry-fetch/package.json b/deps/npm/node_modules/npm-registry-fetch/package.json index 5f19697c3b19d7..8a0189a9ef74d5 100644 --- a/deps/npm/node_modules/npm-registry-fetch/package.json +++ b/deps/npm/node_modules/npm-registry-fetch/package.json @@ -1,6 +1,6 @@ { "name": "npm-registry-fetch", - "version": "13.2.0", + "version": "13.3.0", "description": "Fetch-based http client for use with npm registry APIs", "main": "lib", "files": [ diff --git a/deps/npm/package.json b/deps/npm/package.json index 80e7a4fb0f6795..969e8e160c28c5 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -1,5 +1,5 @@ { - "version": "8.14.0", + "version": "8.15.0", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -58,7 +58,7 @@ "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^5.0.4", "@npmcli/ci-detect": "^2.0.0", - "@npmcli/config": "^4.1.0", + "@npmcli/config": "^4.2.0", "@npmcli/fs": "^2.1.0", "@npmcli/map-workspaces": "^2.0.3", "@npmcli/package-json": "^2.0.0", @@ -90,7 +90,7 @@ "libnpmsearch": "^5.0.2", "libnpmteam": "^4.0.2", "libnpmversion": "^3.0.1", - "make-fetch-happen": "^10.1.8", + "make-fetch-happen": "^10.2.0", "minipass": "^3.1.6", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", @@ -103,7 +103,7 @@ "npm-package-arg": "^9.1.0", "npm-pick-manifest": "^7.0.1", "npm-profile": "^6.2.0", - "npm-registry-fetch": "^13.2.0", + "npm-registry-fetch": "^13.3.0", "npm-user-validate": "^1.0.1", "npmlog": "^6.0.2", "opener": "^1.5.2", @@ -209,7 +209,7 @@ "tap": "^16.0.1" }, "scripts": { - "dependencies": "node scripts/bundle-and-gitignore-deps.js", + "dependencies": "node scripts/bundle-and-gitignore-deps.js && node scripts/dependency-graph.js", "dumpconf": "env | grep npm | sort | uniq", "preversion": "bash scripts/update-authors.sh && git add AUTHORS && git commit -m \"chore: update AUTHORS\" || true", "licenses": "licensee --production --errors-only", diff --git a/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs b/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs index e2d248edf5b6c6..65a9ee02eb9524 100644 --- a/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/commands/publish.js.test.cjs @@ -56,7 +56,11 @@ Array [ ] ` -exports[`test/lib/commands/publish.js TAP has auth for scope configured registry > new package version 1`] = ` +exports[`test/lib/commands/publish.js TAP has mTLS auth for scope configured registry > new package version 1`] = ` ++ @npm/test-package@1.0.0 +` + +exports[`test/lib/commands/publish.js TAP has token auth for scope configured registry > new package version 1`] = ` + @npm/test-package@1.0.0 ` diff --git a/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs b/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs index 04d304a2254d99..89c9969d69424d 100644 --- a/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs @@ -404,8 +404,9 @@ newlines replaced by the string "\\n". For example: cert="-----BEGIN CERTIFICATE-----\\nXXXX\\nXXXX\\n-----END CERTIFICATE-----" \`\`\` -It is _not_ the path to a certificate file (and there is no "certfile" -option). +It is _not_ the path to a certificate file, though you can set a +registry-scoped "certfile" path like +"//other-registry.tld/:certfile=/path/to/cert.pem". ` exports[`test/lib/utils/config/definitions.js TAP > config description for ci-name 1`] = ` @@ -1016,7 +1017,8 @@ format with newlines replaced by the string "\\n". For example: key="-----BEGIN PRIVATE KEY-----\\nXXXX\\nXXXX\\n-----END PRIVATE KEY-----" \`\`\` -It is _not_ the path to a key file (and there is no "keyfile" option). +It is _not_ the path to a key file, though you can set a registry-scoped +"keyfile" path like "//other-registry.tld/:keyfile=/path/to/key.pem". ` exports[`test/lib/utils/config/definitions.js TAP > config description for legacy-bundling 1`] = ` diff --git a/deps/npm/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs b/deps/npm/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs index a291af6dedfae8..a9247f49c04187 100644 --- a/deps/npm/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs +++ b/deps/npm/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs @@ -230,8 +230,9 @@ newlines replaced by the string "\\n". For example: cert="-----BEGIN CERTIFICATE-----\\nXXXX\\nXXXX\\n-----END CERTIFICATE-----" \`\`\` -It is _not_ the path to a certificate file (and there is no "certfile" -option). +It is _not_ the path to a certificate file, though you can set a +registry-scoped "certfile" path like +"//other-registry.tld/:certfile=/path/to/cert.pem". @@ -819,7 +820,8 @@ format with newlines replaced by the string "\\n". For example: key="-----BEGIN PRIVATE KEY-----\\nXXXX\\nXXXX\\n-----END PRIVATE KEY-----" \`\`\` -It is _not_ the path to a key file (and there is no "keyfile" option). +It is _not_ the path to a key file, though you can set a registry-scoped +"keyfile" path like "//other-registry.tld/:keyfile=/path/to/key.pem". diff --git a/deps/npm/test/lib/commands/publish.js b/deps/npm/test/lib/commands/publish.js index 3cbe962382e21b..16b79df532d823 100644 --- a/deps/npm/test/lib/commands/publish.js +++ b/deps/npm/test/lib/commands/publish.js @@ -327,7 +327,7 @@ t.test('no auth for scope configured registry', async t => { ) }) -t.test('has auth for scope configured registry', async t => { +t.test('has token auth for scope configured registry', async t => { const spec = npa('@npm/test-package') const { npm, joinedOutput } = await loadMockNpm(t, { config: { @@ -356,6 +356,35 @@ t.test('has auth for scope configured registry', async t => { t.matchSnapshot(joinedOutput(), 'new package version') }) +t.test('has mTLS auth for scope configured registry', async t => { + const spec = npa('@npm/test-package') + const { npm, joinedOutput } = await loadMockNpm(t, { + config: { + '@npm:registry': alternateRegistry, + [`${alternateRegistry.slice(6)}/:certfile`]: '/some.cert', + [`${alternateRegistry.slice(6)}/:keyfile`]: '/some.key', + }, + prefixDir: { + 'package.json': JSON.stringify({ + name: '@npm/test-package', + version: '1.0.0', + }, null, 2), + }, + globals: ({ prefix }) => ({ + 'process.cwd': () => prefix, + }), + }) + const registry = new MockRegistry({ + tap: t, + registry: alternateRegistry, + }) + registry.nock.put(`/${spec.escapedName}`, body => { + return t.match(body, { name: '@npm/test-package' }) + }).reply(200, {}) + await npm.exec('publish', []) + t.matchSnapshot(joinedOutput(), 'new package version') +}) + t.test('workspaces', t => { const dir = { 'package.json': JSON.stringify( diff --git a/deps/npm/test/lib/commands/whoami.js b/deps/npm/test/lib/commands/whoami.js index ad7c223888df41..d63b49015f0d07 100644 --- a/deps/npm/test/lib/commands/whoami.js +++ b/deps/npm/test/lib/commands/whoami.js @@ -34,6 +34,20 @@ t.test('npm whoami --json', async t => { t.equal(JSON.parse(joinedOutput()), username, 'should print username') }) +t.test('npm whoami using mTLS', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { config: { + '//registry.npmjs.org/:certfile': '/some.cert', + '//registry.npmjs.org/:keyfile': '/some.key', + } }) + const registry = new MockRegistry({ + tap: t, + registry: npm.config.get('registry'), + }) + registry.whoami({ username }) + await npm.exec('whoami', []) + t.equal(joinedOutput(), username, 'should print username') +}) + t.test('credentials from token', async t => { const { npm, joinedOutput } = await loadMockNpm(t, { config: { diff --git a/deps/npm/test/lib/utils/otplease.js b/deps/npm/test/lib/utils/otplease.js index 025084ab4f2c5b..79eaa798e60539 100644 --- a/deps/npm/test/lib/utils/otplease.js +++ b/deps/npm/test/lib/utils/otplease.js @@ -1,17 +1,25 @@ const t = require('tap') + +const { fake: mockNpm } = require('../../fixtures/mock-npm') const mockGlobals = require('../../fixtures/mock-globals') const readUserInfo = { otp: async () => '1234', } +const webAuth = async (opener) => { + opener() + return '1234' +} const otplease = t.mock('../../../lib/utils/otplease.js', { '../../../lib/utils/read-user-info.js': readUserInfo, + '../../../lib/utils/open-url-prompt.js': () => {}, + '../../../lib/utils/web-auth': webAuth, }) t.test('returns function results on success', async (t) => { const fn = () => 'test string' - const result = await otplease({}, fn) + const result = await otplease(null, {}, fn) t.equal('test string', result) }) @@ -26,7 +34,7 @@ t.test('returns function results on otp success', async (t) => { } throw Object.assign(new Error('nope'), { code: 'EOTP' }) } - const result = await otplease({}, fn) + const result = await otplease(null, {}, fn) t.equal('success', result) }) @@ -51,7 +59,31 @@ t.test('prompts for otp for EOTP', async (t) => { t.end() } - await otplease({ some: 'prop' }, fn) + await otplease(null, { some: 'prop' }, fn) +}) + +t.test('returns function results on webauth success', async (t) => { + mockGlobals(t, { + 'process.stdin': { isTTY: true }, + 'process.stdout': { isTTY: true }, + }) + + const npm = mockNpm({ config: { browser: 'firefox' } }) + const fn = ({ otp }) => { + if (otp) { + return 'success' + } + throw Object.assign(new Error('nope'), { + code: 'EOTP', + body: { + authUrl: 'https://www.example.com/auth', + doneUrl: 'https://www.example.com/done', + }, + }) + } + + const result = await otplease(npm, {}, fn) + t.equal('success', result) }) t.test('prompts for otp for 401', async (t) => { @@ -78,7 +110,7 @@ t.test('prompts for otp for 401', async (t) => { t.end() } - await otplease({ some: 'prop' }, fn) + await otplease(null, { some: 'prop' }, fn) }) t.test('does not prompt for non-otp errors', async (t) => { @@ -95,7 +127,11 @@ t.test('does not prompt for non-otp errors', async (t) => { throw new Error('nope') } - t.rejects(otplease({ some: 'prop' }, fn), { message: 'nope' }, 'rejects with the original error') + t.rejects( + otplease(null, { some: 'prop' }, fn), + { message: 'nope' }, + 'rejects with the original error' + ) }) t.test('does not prompt if stdin or stdout is not a tty', async (t) => { @@ -112,5 +148,9 @@ t.test('does not prompt if stdin or stdout is not a tty', async (t) => { throw Object.assign(new Error('nope'), { code: 'EOTP' }) } - t.rejects(otplease({ some: 'prop' }, fn), { message: 'nope' }, 'rejects with the original error') + t.rejects( + otplease(null, { some: 'prop' }, fn), + { message: 'nope' }, + 'rejects with the original error' + ) }) diff --git a/deps/npm/test/lib/utils/web-auth.js b/deps/npm/test/lib/utils/web-auth.js new file mode 100644 index 00000000000000..ee8a17ecbc09d4 --- /dev/null +++ b/deps/npm/test/lib/utils/web-auth.js @@ -0,0 +1,32 @@ +const t = require('tap') + +const webAuthCheckLogin = async () => { + return { token: 'otp-token' } +} + +const webauth = t.mock('../../../lib/utils/web-auth.js', { + 'npm-profile': { webAuthCheckLogin }, +}) + +const initialUrl = 'https://example.com/auth' +const doneUrl = 'https://example.com/done' +const opts = {} + +t.test('returns token on success', async (t) => { + const opener = async () => {} + const result = await webauth(opener, initialUrl, doneUrl, opts) + t.equal(result, 'otp-token') +}) + +t.test('closes opener when auth check finishes', async (t) => { + const opener = (_url, emitter) => { + return new Promise((resolve, reject) => { + // the only way to finish this promise is to emit aboter on the emitter + emitter.addListener('abort', () => { + resolve() + }) + }) + } + const result = await webauth(opener, initialUrl, doneUrl, opts) + t.equal(result, 'otp-token') +})