mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-30 22:34:10 -05:00
feat: signature package (#3653)
* feat: signature package * feat: signature package
This commit is contained in:
parent
399cf9c47c
commit
ddb6a22396
27 changed files with 298 additions and 32 deletions
8
.changeset/kind-ladybugs-admire.md
Normal file
8
.changeset/kind-ladybugs-admire.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
'@verdaccio/auth': minor
|
||||
'@verdaccio/config': minor
|
||||
'@verdaccio/signature': minor
|
||||
'@verdaccio/ui-components': minor
|
||||
---
|
||||
|
||||
feat: signature package
|
|
@ -4,7 +4,7 @@ module.exports = Object.assign({}, config, {
|
|||
coverageThreshold: {
|
||||
global: {
|
||||
// FIXME: increase to 90
|
||||
lines: 42,
|
||||
lines: 30,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -43,10 +43,10 @@
|
|||
"@verdaccio/config": "workspace:6.0.0-6-next.62",
|
||||
"@verdaccio/loaders": "workspace:6.0.0-6-next.31",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.30",
|
||||
"@verdaccio/signature": "workspace:6.0.0-6-next.1",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.30",
|
||||
"debug": "4.3.4",
|
||||
"express": "4.18.2",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"verdaccio-htpasswd": "workspace:11.0.0-6-next.32"
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '@verdaccio/core';
|
||||
import { asyncLoadPlugin } from '@verdaccio/loaders';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { aesEncrypt, parseBasicPayload, signPayload } from '@verdaccio/signature';
|
||||
import {
|
||||
AllowAccess,
|
||||
Callback,
|
||||
|
@ -27,9 +28,6 @@ import {
|
|||
} from '@verdaccio/types';
|
||||
import { getMatchedPackagesSpec, isFunction, isNil } from '@verdaccio/utils';
|
||||
|
||||
import { signPayload } from './jwt-token';
|
||||
import { aesEncrypt } from './legacy-token';
|
||||
import { parseBasicPayload } from './token';
|
||||
import {
|
||||
convertPayloadToBase64,
|
||||
getDefaultPlugins,
|
||||
|
@ -47,6 +45,7 @@ export interface TokenEncryption {
|
|||
aesEncrypt(buf: string): string | void;
|
||||
}
|
||||
|
||||
// remove
|
||||
export interface AESPayload {
|
||||
user: string;
|
||||
password: string;
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
export { Auth, TokenEncryption } from './auth';
|
||||
export { Auth } from './auth';
|
||||
export * from './utils';
|
||||
export * from './legacy-token';
|
||||
export * from './jwt-token';
|
||||
export * from './token';
|
||||
|
|
|
@ -11,12 +11,10 @@ import {
|
|||
errorUtils,
|
||||
pluginUtils,
|
||||
} from '@verdaccio/core';
|
||||
import { aesDecrypt, parseBasicPayload, verifyPayload } from '@verdaccio/signature';
|
||||
import { AuthPackageAllow, Config, Logger, RemoteUser, Security } from '@verdaccio/types';
|
||||
|
||||
import { AESPayload, TokenEncryption } from './auth';
|
||||
import { verifyPayload } from './jwt-token';
|
||||
import { aesDecrypt } from './legacy-token';
|
||||
import { parseBasicPayload } from './token';
|
||||
|
||||
const debug = buildDebug('verdaccio:auth:utils');
|
||||
|
||||
|
|
|
@ -17,24 +17,22 @@ import {
|
|||
errorUtils,
|
||||
} from '@verdaccio/core';
|
||||
import { setup } from '@verdaccio/logger';
|
||||
import { aesDecrypt, signPayload, verifyPayload } from '@verdaccio/signature';
|
||||
import { Config, RemoteUser, Security } from '@verdaccio/types';
|
||||
import { buildToken, buildUserBuffer, getAuthenticatedMessage } from '@verdaccio/utils';
|
||||
import type { AllowActionCallbackResponse } from '@verdaccio/utils';
|
||||
|
||||
import {
|
||||
ActionsAllowed,
|
||||
AllowActionCallbackResponse,
|
||||
Auth,
|
||||
aesDecrypt,
|
||||
allow_action,
|
||||
getApiToken,
|
||||
getDefaultPlugins,
|
||||
getMiddlewareCredentials,
|
||||
signPayload,
|
||||
verifyJWTPayload,
|
||||
verifyPayload,
|
||||
} from '../src';
|
||||
|
||||
setup([]);
|
||||
setup({});
|
||||
|
||||
const parseConfigurationFile = (conf) => {
|
||||
const { name, ext } = path.parse(conf);
|
||||
|
@ -452,6 +450,7 @@ describe('Auth utilities', () => {
|
|||
const config: Config = getConfig('security-legacy', secret);
|
||||
const auth: Auth = new Auth(config);
|
||||
await auth.init();
|
||||
// @ts-expect-error
|
||||
const token = auth.aesEncrypt(null);
|
||||
const security: Security = config.security;
|
||||
const credentials = getMiddlewareCredentials(
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
{
|
||||
"path": "../loaders"
|
||||
},
|
||||
{
|
||||
"path": "../signature"
|
||||
},
|
||||
{
|
||||
"path": "../logger/logger"
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@ export const TOKEN_VALID_LENGTH = 32;
|
|||
|
||||
/**
|
||||
* Secret key must have 32 characters.
|
||||
* @deprecated
|
||||
*/
|
||||
export function generateRandomSecretKey(): string {
|
||||
return randomBytes(TOKEN_VALID_LENGTH).toString('base64').substring(0, TOKEN_VALID_LENGTH);
|
||||
|
|
3
packages/signature/.babelrc
Normal file
3
packages/signature/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../.babelrc"
|
||||
}
|
3
packages/signature/jest.config.js
Normal file
3
packages/signature/jest.config.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
const config = require('../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
53
packages/signature/package.json
Normal file
53
packages/signature/package.json
Normal file
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "@verdaccio/signature",
|
||||
"version": "6.0.0-6-next.1",
|
||||
"description": "verdaccio signature utils",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "https",
|
||||
"url": "https://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"keywords": [
|
||||
"private",
|
||||
"package",
|
||||
"repository",
|
||||
"registry",
|
||||
"enterprise",
|
||||
"modules",
|
||||
"proxy",
|
||||
"server",
|
||||
"verdaccio"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"watch": "pnpm build:js -- --watch",
|
||||
"build": "pnpm run build:js && pnpm run build:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"debug": "4.3.4",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.62",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.21"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio"
|
||||
}
|
||||
}
|
10
packages/signature/src/index.ts
Normal file
10
packages/signature/src/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export {
|
||||
aesDecryptDeprecated,
|
||||
aesEncryptDeprecated,
|
||||
generateRandomSecretKeyDeprecated,
|
||||
} from './legacy-signature';
|
||||
export { aesDecrypt, aesEncrypt } from './signature';
|
||||
export { signPayload, verifyPayload } from './jwt-token';
|
||||
export * as utils from './utils';
|
||||
export * as types from './types';
|
||||
export { parseBasicPayload } from './token';
|
51
packages/signature/src/legacy-signature/index.ts
Normal file
51
packages/signature/src/legacy-signature/index.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { createCipher, createDecipher } from 'crypto';
|
||||
|
||||
import { generateRandomHexString } from '../utils';
|
||||
|
||||
export const defaultAlgorithm = 'aes192';
|
||||
export const defaultTarballHashAlgorithm = 'sha1';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param buf
|
||||
* @param secret
|
||||
* @returns
|
||||
*/
|
||||
export function aesEncryptDeprecated(buf: Buffer, secret: string): Buffer {
|
||||
// deprecated (it will be removed in Verdaccio 6), it is a breaking change
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options
|
||||
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
|
||||
const c = createCipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param buf
|
||||
* @param secret
|
||||
* @returns
|
||||
*/
|
||||
export function aesDecryptDeprecated(buf: Buffer, secret: string): Buffer {
|
||||
try {
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options
|
||||
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
|
||||
const c = createDecipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
} catch (_) {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
}
|
||||
|
||||
export const TOKEN_VALID_LENGTH_DEPRECATED = 64;
|
||||
|
||||
/**
|
||||
* Genrate a secret key of 64 characters.
|
||||
* @deprecated keys should be length max of 64
|
||||
*/
|
||||
export function generateRandomSecretKeyDeprecated(): string {
|
||||
return generateRandomHexString(6);
|
||||
}
|
|
@ -14,8 +14,6 @@ const debug = buildDebug('verdaccio:auth:token:legacy');
|
|||
export const defaultAlgorithm = process.env.VERDACCIO_LEGACY_ALGORITHM || 'aes-256-ctr';
|
||||
const inputEncoding: CharacterEncoding = 'utf8';
|
||||
const outputEncoding: BinaryToTextEncoding = 'hex';
|
||||
// For AES, this is always 16
|
||||
const IV_LENGTH = 16;
|
||||
// Must be 256 bits (32 characters)
|
||||
// https://stackoverflow.com/questions/50963160/invalid-key-length-in-crypto-createcipheriv#50963356
|
||||
const VERDACCIO_LEGACY_ENCRYPTION_KEY = process.env.VERDACCIO_LEGACY_ENCRYPTION_KEY;
|
||||
|
@ -25,7 +23,8 @@ export function aesEncrypt(value: string, key: string): string | void {
|
|||
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
|
||||
debug('encrypt %o', value);
|
||||
debug('algorithm %o', defaultAlgorithm);
|
||||
const iv = Buffer.from(randomBytes(IV_LENGTH));
|
||||
// IV must be a buffer of length 16
|
||||
const iv = Buffer.from(randomBytes(16));
|
||||
const secretKey = VERDACCIO_LEGACY_ENCRYPTION_KEY || key;
|
||||
const isKeyValid = secretKey?.length === TOKEN_VALID_LENGTH;
|
||||
debug('length secret key %o', secretKey?.length);
|
|
@ -1,5 +1,10 @@
|
|||
import { BasicPayload } from './utils';
|
||||
import { BasicPayload } from './types';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param credentials
|
||||
* @returns
|
||||
*/
|
||||
export function parseBasicPayload(credentials: string): BasicPayload {
|
||||
const index = credentials.indexOf(':');
|
||||
if (index < 0) {
|
6
packages/signature/src/types.ts
Normal file
6
packages/signature/src/types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface AESPayload {
|
||||
user: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export type BasicPayload = AESPayload | void;
|
40
packages/signature/src/utils.ts
Normal file
40
packages/signature/src/utils.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Hash, createHash, pseudoRandomBytes, randomBytes } from 'crypto';
|
||||
|
||||
export const defaultTarballHashAlgorithm = 'sha1';
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export function createTarballHash(algorithm = defaultTarballHashAlgorithm): Hash {
|
||||
return createHash(algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Express doesn't do ETAGS with requests <= 1024b
|
||||
* we use md5 here, it works well on 1k+ bytes, but with fewer data
|
||||
* could improve performance using crc32 after benchmarks.
|
||||
* @param {Object} data
|
||||
* @return {String}
|
||||
*/
|
||||
export function stringToMD5(data: Buffer | string): string {
|
||||
return createHash('md5').update(data).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param length
|
||||
* @returns
|
||||
*/
|
||||
export function generateRandomHexString(length = 8): string {
|
||||
return pseudoRandomBytes(length).toString('hex');
|
||||
}
|
||||
|
||||
export const TOKEN_VALID_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* Generate a secret of 32 characters.
|
||||
*/
|
||||
export function generateRandomSecretKey(): string {
|
||||
return randomBytes(TOKEN_VALID_LENGTH).toString('base64').substring(0, TOKEN_VALID_LENGTH);
|
||||
}
|
13
packages/signature/test/jwt.spec.ts
Normal file
13
packages/signature/test/jwt.spec.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { createRemoteUser } from '@verdaccio/config';
|
||||
|
||||
import { signPayload, verifyPayload } from '../src';
|
||||
|
||||
describe('verifyJWTPayload', () => {
|
||||
test('should verify the token and return a remote user', async () => {
|
||||
const remoteUser = createRemoteUser('foo', []);
|
||||
const token = await signPayload(remoteUser, '12345');
|
||||
const verifiedToken = verifyPayload(token, '12345');
|
||||
expect(verifiedToken.groups).toEqual(remoteUser.groups);
|
||||
expect(verifiedToken.name).toEqual(remoteUser.name);
|
||||
});
|
||||
});
|
23
packages/signature/test/legacy-token-deprecated.spec.ts
Normal file
23
packages/signature/test/legacy-token-deprecated.spec.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {
|
||||
aesDecryptDeprecated,
|
||||
aesEncryptDeprecated,
|
||||
generateRandomSecretKeyDeprecated,
|
||||
} from '../src';
|
||||
|
||||
describe('test deprecated crypto utils', () => {
|
||||
test('decrypt payload flow', () => {
|
||||
const secret = generateRandomSecretKeyDeprecated();
|
||||
const payload = 'juan:password';
|
||||
const token = aesEncryptDeprecated(Buffer.from(payload), secret);
|
||||
const data = aesDecryptDeprecated(token, secret);
|
||||
|
||||
expect(data.toString()).toEqual(payload.toString());
|
||||
});
|
||||
|
||||
test('crypt fails if secret is incorrect', () => {
|
||||
const payload = 'juan:password';
|
||||
expect(aesEncryptDeprecated(Buffer.from(payload), 'fake_token').toString()).not.toEqual(
|
||||
Buffer.from(payload)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import { aesDecrypt, aesEncrypt } from '../src/legacy-token';
|
||||
import { aesDecrypt, aesEncrypt } from '../src';
|
||||
|
||||
describe('test crypto utils', () => {
|
||||
test('decrypt payload flow', () => {
|
18
packages/signature/test/utilts.spec.ts
Normal file
18
packages/signature/test/utilts.spec.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
TOKEN_VALID_LENGTH,
|
||||
createTarballHash,
|
||||
generateRandomSecretKey,
|
||||
stringToMD5,
|
||||
} from '../src/utils';
|
||||
|
||||
test('token generation length is valid', () => {
|
||||
expect(generateRandomSecretKey()).toHaveLength(TOKEN_VALID_LENGTH);
|
||||
});
|
||||
|
||||
test('string to md5 has valid length', () => {
|
||||
expect(stringToMD5(Buffer.from('foo'))).toHaveLength(32);
|
||||
});
|
||||
|
||||
test('create a hash of content', () => {
|
||||
expect(typeof createTarballHash().update('1').digest('hex')).toEqual('string');
|
||||
});
|
9
packages/signature/tsconfig.build.json
Normal file
9
packages/signature/tsconfig.build.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
17
packages/signature/tsconfig.json
Normal file
17
packages/signature/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../tsconfig.reference.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build",
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": ["src/**/*.ts", "types/*.d.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../config"
|
||||
},
|
||||
{
|
||||
"path": "../utils"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -7,10 +7,6 @@
|
|||
"homepage": "https://verdaccio.org",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"files": [
|
||||
"./build",
|
||||
"./src"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "cross-env TZ=UTC jest --config jest/jest.config.js",
|
||||
"test:html": "cross-env TZ=UTC jest --config jest/jest.config.js --coverage-reporters=html",
|
||||
|
|
|
@ -391,11 +391,11 @@ importers:
|
|||
'@verdaccio/core': workspace:6.0.0-6-next.62
|
||||
'@verdaccio/loaders': workspace:6.0.0-6-next.31
|
||||
'@verdaccio/logger': workspace:6.0.0-6-next.30
|
||||
'@verdaccio/signature': workspace:6.0.0-6-next.1
|
||||
'@verdaccio/types': workspace:11.0.0-6-next.21
|
||||
'@verdaccio/utils': workspace:6.0.0-6-next.30
|
||||
debug: 4.3.4
|
||||
express: 4.18.2
|
||||
jsonwebtoken: 9.0.0
|
||||
lodash: 4.17.21
|
||||
verdaccio-htpasswd: workspace:11.0.0-6-next.32
|
||||
dependencies:
|
||||
|
@ -403,10 +403,10 @@ importers:
|
|||
'@verdaccio/core': link:../core/core
|
||||
'@verdaccio/loaders': link:../loaders
|
||||
'@verdaccio/logger': link:../logger/logger
|
||||
'@verdaccio/signature': link:../signature
|
||||
'@verdaccio/utils': link:../utils
|
||||
debug: 4.3.4
|
||||
express: 4.18.2
|
||||
jsonwebtoken: 9.0.0
|
||||
lodash: 4.17.21
|
||||
verdaccio-htpasswd: link:../plugins/htpasswd
|
||||
devDependencies:
|
||||
|
@ -1108,6 +1108,21 @@ importers:
|
|||
'@verdaccio/types': link:../../core/types
|
||||
ts-node: 10.9.1_5cc4fd05cdbba7efed0227e61dc065e1
|
||||
|
||||
packages/signature:
|
||||
specifiers:
|
||||
'@verdaccio/config': workspace:6.0.0-6-next.62
|
||||
'@verdaccio/types': workspace:11.0.0-6-next.21
|
||||
debug: 4.3.4
|
||||
jsonwebtoken: 9.0.0
|
||||
lodash: 4.17.21
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
jsonwebtoken: 9.0.0
|
||||
lodash: 4.17.21
|
||||
devDependencies:
|
||||
'@verdaccio/config': link:../config
|
||||
'@verdaccio/types': link:../core/types
|
||||
|
||||
packages/standalone:
|
||||
specifiers:
|
||||
'@verdaccio/cli': workspace:6.0.0-6-next.62
|
||||
|
@ -10911,7 +10926,7 @@ packages:
|
|||
dev: true
|
||||
|
||||
/buffer-equal-constant-time/1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=}
|
||||
|
||||
/buffer-from/1.1.1:
|
||||
resolution: {integrity: sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==}
|
||||
|
@ -15583,7 +15598,7 @@ packages:
|
|||
map-cache: 0.2.2
|
||||
|
||||
/fresh/0.5.2:
|
||||
resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=}
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
/friendly-errors-webpack-plugin/1.7.0_webpack@5.75.0:
|
||||
|
@ -18164,7 +18179,7 @@ packages:
|
|||
dev: true
|
||||
|
||||
/jsonparse/1.3.1:
|
||||
resolution: {integrity: sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=}
|
||||
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
|
||||
engines: {'0': node >= 0.2.0}
|
||||
dev: false
|
||||
|
||||
|
@ -19074,7 +19089,7 @@ packages:
|
|||
dev: true
|
||||
|
||||
/merge-descriptors/1.0.1:
|
||||
resolution: {integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=}
|
||||
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
|
||||
|
||||
/merge-stream/2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
|
|
Loading…
Reference in a new issue