mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-24 21:15:51 +01:00
chore: migrate htpasswd package vitest (#4889)
* migrate htpasswd package * update versions * Update htpasswd.passwords.test.ts
This commit is contained in:
parent
51b0368c49
commit
124e5f2de7
5
.changeset/hip-eggs-serve.md
Normal file
5
.changeset/hip-eggs-serve.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'verdaccio-htpasswd': patch
|
||||
---
|
||||
|
||||
chore: add debug code to htpasswd package
|
@ -128,7 +128,7 @@
|
||||
"verdaccio-auth-memory": "workspace:*",
|
||||
"verdaccio-htpasswd": "workspace:*",
|
||||
"verdaccio-memory": "workspace:*",
|
||||
"vitest": "2.0.4"
|
||||
"vitest": "2.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/api",
|
||||
"version": "7.1.0-next-8.2",
|
||||
"version": "8.1.0-next-8.2",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@ -44,7 +44,7 @@
|
||||
"@verdaccio/logger": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/middleware": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/store": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"abortcontroller-polyfill": "1.7.5",
|
||||
"body-parser": "1.20.3",
|
||||
"cookies": "0.9.0",
|
||||
|
@ -43,7 +43,7 @@
|
||||
"@verdaccio/loaders": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/logger": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/signature": "workspace:8.0.0-next-8.1",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"debug": "4.3.7",
|
||||
"lodash": "4.17.21",
|
||||
"verdaccio-htpasswd": "workspace:13.0.0-next-8.2"
|
||||
|
@ -39,7 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"debug": "4.3.7",
|
||||
"js-yaml": "4.1.0",
|
||||
"lodash": "4.17.21",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/url": "workspace:13.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"debug": "4.3.7",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"lodash": "4.17.21",
|
||||
|
@ -42,7 +42,7 @@
|
||||
"@verdaccio/config": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/core": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/url": "workspace:13.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"debug": "4.3.7",
|
||||
"express": "4.21.0",
|
||||
"express-rate-limit": "5.5.1",
|
||||
|
@ -46,7 +46,7 @@
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"build": "pnpm run build:js && pnpm run build:types",
|
||||
"test": "jest"
|
||||
"test": "vitest run"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
@ -1,3 +0,0 @@
|
||||
const config = require('../../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
@ -51,7 +51,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "jest",
|
||||
"test": "vitest run",
|
||||
"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",
|
||||
|
@ -1,13 +1,7 @@
|
||||
/** Node.js Crypt(3) Library
|
||||
Inspired by (and intended to be compatible with) sendanor/crypt3
|
||||
see https://github.com/sendanor/node-crypt3
|
||||
The key difference is the removal of the dependency on the unix crypt(3) function
|
||||
which is not platform independent, and requires compilation. Instead, a pure
|
||||
javascript version is used.
|
||||
*/
|
||||
import crypto from 'crypto';
|
||||
import crypt from 'unix-crypt-td-js';
|
||||
|
||||
import { randomBytes } from './crypto-utils';
|
||||
|
||||
export enum EncryptionMethod {
|
||||
md5 = 'md5',
|
||||
sha1 = 'sha1',
|
||||
@ -27,19 +21,19 @@ export function createSalt(type: EncryptionMethod = EncryptionMethod.crypt): str
|
||||
switch (type) {
|
||||
case EncryptionMethod.crypt:
|
||||
// Legacy crypt salt with no prefix (only the first 2 bytes will be used).
|
||||
return crypto.randomBytes(2).toString('base64');
|
||||
return randomBytes(2).toString('base64');
|
||||
|
||||
case EncryptionMethod.md5:
|
||||
return '$1$' + crypto.randomBytes(10).toString('base64');
|
||||
return '$1$' + randomBytes(10).toString('base64');
|
||||
|
||||
case EncryptionMethod.blowfish:
|
||||
return '$2a$' + crypto.randomBytes(10).toString('base64');
|
||||
return '$2a$' + randomBytes(10).toString('base64');
|
||||
|
||||
case EncryptionMethod.sha256:
|
||||
return '$5$' + crypto.randomBytes(10).toString('base64');
|
||||
return '$5$' + randomBytes(10).toString('base64');
|
||||
|
||||
case EncryptionMethod.sha512:
|
||||
return '$6$' + crypto.randomBytes(10).toString('base64');
|
||||
return '$6$' + randomBytes(10).toString('base64');
|
||||
|
||||
default:
|
||||
throw new TypeError(`Unknown salt type at crypt3.createSalt: ${type}`);
|
||||
|
5
packages/plugins/htpasswd/src/crypto-utils.ts
Normal file
5
packages/plugins/htpasswd/src/crypto-utils.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
export function randomBytes(bytes) {
|
||||
return crypto.randomBytes(bytes);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import buildDebug from 'debug';
|
||||
import fs from 'fs';
|
||||
import { readFile, stat, writeFile } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
import { constants, pluginUtils } from '@verdaccio/core';
|
||||
@ -14,6 +14,7 @@ import {
|
||||
lockAndRead,
|
||||
parseHTPasswd,
|
||||
sanityCheck,
|
||||
stringToUtf8,
|
||||
verifyPassword,
|
||||
} from './utils';
|
||||
|
||||
@ -114,10 +115,13 @@ export default class HTPasswd
|
||||
public authenticate(user: string, password: string, cb: Callback): void {
|
||||
debug('authenticate %s', user);
|
||||
this.reload(async (err) => {
|
||||
debug('reloaded');
|
||||
if (err) {
|
||||
debug('error %o', err);
|
||||
return cb(err.code === 'ENOENT' ? null : err);
|
||||
}
|
||||
if (!this.users[user]) {
|
||||
debug('user %s not found', user);
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
@ -127,6 +131,7 @@ export default class HTPasswd
|
||||
passwordValid = await verifyPassword(password, this.users[user]);
|
||||
const durationMs = new Date().getTime() - start.getTime();
|
||||
if (durationMs > this.slowVerifyMs) {
|
||||
debug('password for user "%s" took %sms to verify', user, durationMs);
|
||||
this.logger.warn(
|
||||
{ user, durationMs },
|
||||
'Password for user "@{user}" took @{durationMs}ms to verify'
|
||||
@ -136,6 +141,7 @@ export default class HTPasswd
|
||||
this.logger.error({ message: err.message }, 'Unable to verify user password: @{message}');
|
||||
}
|
||||
if (!passwordValid) {
|
||||
debug('password invalid for %s', user);
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
@ -165,15 +171,17 @@ export default class HTPasswd
|
||||
const pathPass = this.path;
|
||||
debug('adduser %s', user);
|
||||
let sanity = await sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
|
||||
|
||||
debug('sanity check: %s', sanity);
|
||||
// preliminary checks, just to ensure that file won't be reloaded if it's
|
||||
// not needed
|
||||
if (sanity) {
|
||||
debug('sanity check failed');
|
||||
return realCb(sanity, false);
|
||||
}
|
||||
|
||||
lockAndRead(pathPass, async (err, res): Promise<void> => {
|
||||
let locked = false;
|
||||
debug('locked and read');
|
||||
|
||||
// callback that cleans up lock first
|
||||
const cb = (err): void => {
|
||||
@ -188,6 +196,7 @@ export default class HTPasswd
|
||||
};
|
||||
|
||||
if (!err) {
|
||||
debug('locked');
|
||||
locked = true;
|
||||
}
|
||||
|
||||
@ -195,20 +204,25 @@ export default class HTPasswd
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
return cb(err);
|
||||
}
|
||||
debug('read file');
|
||||
const body = (res || '').toString('utf8');
|
||||
this.users = parseHTPasswd(body);
|
||||
|
||||
debug('parsed users');
|
||||
// real checks, to prevent race conditions
|
||||
// parsing users after reading file.
|
||||
sanity = await sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
|
||||
|
||||
debug('sanity check: %s', sanity);
|
||||
if (sanity) {
|
||||
debug('sanity check failed');
|
||||
return cb(sanity);
|
||||
}
|
||||
|
||||
try {
|
||||
debug('add user to htpasswd file');
|
||||
this._writeFile(await addUserToHTPasswd(body, user, password, this.hashConfig), cb);
|
||||
debug('user added');
|
||||
} catch (err: any) {
|
||||
debug('error %o', err);
|
||||
return cb(err);
|
||||
}
|
||||
});
|
||||
@ -220,7 +234,8 @@ export default class HTPasswd
|
||||
*/
|
||||
public reload(callback: Callback): void {
|
||||
debug('reload users');
|
||||
fs.stat(this.path, (err, stats) => {
|
||||
debug('path: %s', this.path);
|
||||
stat(this.path, (err, stats) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@ -230,7 +245,7 @@ export default class HTPasswd
|
||||
|
||||
this.lastTime = stats.mtime;
|
||||
|
||||
fs.readFile(this.path, 'utf8', (err, buffer) => {
|
||||
readFile(this.path, 'utf8', (err, buffer) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@ -241,12 +256,8 @@ export default class HTPasswd
|
||||
});
|
||||
}
|
||||
|
||||
private _stringToUt8(authentication: string): string {
|
||||
return (authentication || '').toString();
|
||||
}
|
||||
|
||||
private _writeFile(body: string, cb: Callback): void {
|
||||
fs.writeFile(this.path, body, (err) => {
|
||||
writeFile(this.path, body, (err) => {
|
||||
if (err) {
|
||||
cb(err);
|
||||
} else {
|
||||
@ -271,7 +282,9 @@ export default class HTPasswd
|
||||
newPassword: string,
|
||||
realCb: Callback
|
||||
): void {
|
||||
debug('change password %s', user);
|
||||
lockAndRead(this.path, async (err, res) => {
|
||||
debug('locked and read');
|
||||
let locked = false;
|
||||
const pathPassFile = this.path;
|
||||
|
||||
@ -295,15 +308,17 @@ export default class HTPasswd
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const body = this._stringToUt8(res);
|
||||
const body = stringToUtf8(res);
|
||||
this.users = parseHTPasswd(body);
|
||||
|
||||
try {
|
||||
debug('change password for user %s', user);
|
||||
this._writeFile(
|
||||
await changePasswordToHTPasswd(body, user, password, newPassword, this.hashConfig),
|
||||
cb
|
||||
);
|
||||
} catch (err: any) {
|
||||
debug('error changing password %o', err);
|
||||
return cb(err);
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import md5 from 'apache-md5';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import crypto from 'crypto';
|
||||
import buildDebug from 'debug';
|
||||
import createError, { HttpError } from 'http-errors';
|
||||
|
||||
import { API_ERROR, HTTP_STATUS, constants } from '@verdaccio/core';
|
||||
@ -9,6 +10,7 @@ import { Callback } from '@verdaccio/types';
|
||||
|
||||
import crypt3 from './crypt3';
|
||||
|
||||
const debug = buildDebug('verdaccio:plugin:htpasswd:utils');
|
||||
export const DEFAULT_BCRYPT_ROUNDS = 10;
|
||||
|
||||
type HtpasswdHashAlgorithm = constants.HtpasswdHashAlgorithm;
|
||||
@ -84,6 +86,7 @@ export async function generateHtpasswdLine(
|
||||
): Promise<string> {
|
||||
let hash: string;
|
||||
|
||||
debug('algorithm %o', hashConfig.algorithm);
|
||||
switch (hashConfig.algorithm) {
|
||||
case constants.HtpasswdHashAlgorithm.bcrypt:
|
||||
hash = await bcrypt.hash(passwd, hashConfig.rounds || DEFAULT_BCRYPT_ROUNDS);
|
||||
@ -154,6 +157,7 @@ export async function sanityCheck(
|
||||
|
||||
// check for user or password
|
||||
if (!user || !password) {
|
||||
debug('username or password is missing');
|
||||
err = Error(API_ERROR.USERNAME_PASSWORD_REQUIRED);
|
||||
err.status = HTTP_STATUS.BAD_REQUEST;
|
||||
return err;
|
||||
@ -162,6 +166,7 @@ export async function sanityCheck(
|
||||
const hash = users[user];
|
||||
|
||||
if (maxUsers < 0) {
|
||||
debug('registration is disabled');
|
||||
err = Error(API_ERROR.REGISTRATION_DISABLED);
|
||||
err.status = HTTP_STATUS.CONFLICT;
|
||||
return err;
|
||||
@ -170,19 +175,23 @@ export async function sanityCheck(
|
||||
if (hash) {
|
||||
const auth = await verifyFn(password, users[user]);
|
||||
if (auth) {
|
||||
debug(`user ${user} already exists`);
|
||||
err = Error(API_ERROR.USERNAME_ALREADY_REGISTERED);
|
||||
err.status = HTTP_STATUS.CONFLICT;
|
||||
return err;
|
||||
}
|
||||
debug(`user ${user} exists but password is wrong`);
|
||||
err = Error(API_ERROR.UNAUTHORIZED_ACCESS);
|
||||
err.status = HTTP_STATUS.UNAUTHORIZED;
|
||||
return err;
|
||||
} else if (Object.keys(users).length >= maxUsers) {
|
||||
debug('maximum amount of users reached');
|
||||
err = Error(API_ERROR.MAX_USERS_REACHED);
|
||||
err.status = HTTP_STATUS.FORBIDDEN;
|
||||
return err;
|
||||
}
|
||||
|
||||
debug('sanity check passed');
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -203,17 +212,25 @@ export async function changePasswordToHTPasswd(
|
||||
newPasswd: string,
|
||||
hashConfig: HtpasswdHashConfig
|
||||
): Promise<string> {
|
||||
debug('change password for user %o', user);
|
||||
let lines = body.split('\n');
|
||||
const userLineIndex = lines.findIndex((line) => line.split(':', 1).shift() === user);
|
||||
if (userLineIndex === -1) {
|
||||
debug('user %o does not exist', user);
|
||||
throw new Error(`Unable to change password for user '${user}': user does not currently exist`);
|
||||
}
|
||||
const [username, hash] = lines[userLineIndex].split(':', 2);
|
||||
const passwordValid = await verifyPassword(passwd, hash);
|
||||
if (!passwordValid) {
|
||||
debug(`invalid old password`);
|
||||
throw new Error(`Unable to change password for user '${user}': invalid old password`);
|
||||
}
|
||||
const updatedUserLine = await generateHtpasswdLine(username, newPasswd, hashConfig);
|
||||
lines.splice(userLineIndex, 1, updatedUserLine);
|
||||
debug('password changed');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function stringToUtf8(authentication: string): string {
|
||||
return (authentication || '').toString();
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
test:$6FrCaT/v0dwE:autocreated 2018-01-17T03:40:22.958Z
|
||||
username:$66to3JK5RgZM:autocreated 2018-01-17T03:40:46.315Z
|
||||
bcrypt:$2y$04$K2Cn3StiXx4CnLmcTW/ymekOrj7WlycZZF9xgmoJ/U0zGPqSLPVBe
|
||||
addUserToHTPasswd:$2a$10$1S5n7.HV3AVfwT9nV80jYe7l5.QW1ngo4auWNb9RGzNg0rujj0rju:autocreated 2024-10-06T16:29:04.403Z
|
||||
test:$2a$10$87g0lK5cS.sOSeXI1bPuJOHhWa6P6lO6w0i56diSURwEJA1NI5FAK:autocreated 2024-10-06T17:09:52.886Z
|
||||
usernotpresent:$2a$10$NUdzERnHhgPqA.YSfdoEyOF6XUMD7fRC8HVFYqKsNTLWIHAK7aFUa:autocreated 2018-01-14T11:17:40.712Z
|
||||
username:$2a$10$......................7zqaLmaKtn.i7IjPfuPGY2Ah/mNM6Sy:autocreated 2024-10-06T20:10:14.306Z
|
||||
bcrypt:$2a$10$......................7zqaLmaKtn.i7IjPfuPGY2Ah/mNM6Sy:autocreated 2024-10-06T20:10:14.493Z
|
||||
test1111:$2a$10$......................asuBXCa3eM.Brm3xGVSOss5X1FkQMli:autocreated 2024-10-06T20:10:36.258Z
|
||||
|
@ -1,40 +1,40 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`addUserToHTPasswd - bcrypt should add new htpasswd to the end 1`] = `
|
||||
exports[`addUserToHTPasswd - bcrypt > should add new htpasswd to the end 1`] = `
|
||||
"username:$2a$10$......................7zqaLmaKtn.i7IjPfuPGY2Ah/mNM6Sy:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`addUserToHTPasswd - bcrypt should add new htpasswd to the end in multiline input 1`] = `
|
||||
exports[`addUserToHTPasswd - bcrypt > should add new htpasswd to the end in multiline input 1`] = `
|
||||
"test1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
test2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z
|
||||
username:$2a$10$......................7zqaLmaKtn.i7IjPfuPGY2Ah/mNM6Sy:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`addUserToHTPasswd - bcrypt should throw an error for incorrect username with space 1`] = `"username should not contain non-uri-safe characters"`;
|
||||
exports[`addUserToHTPasswd - bcrypt > should throw an error for incorrect username with space 1`] = `[InternalServerError: username should not contain non-uri-safe characters]`;
|
||||
|
||||
exports[`changePasswordToHTPasswd should change the password 1`] = `
|
||||
exports[`changePasswordToHTPasswd > should change the password 1`] = `
|
||||
"root:$2a$10$......................0qqDmeqkAfPx68M2ArX8hVzcVNft5Ha:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`generateHtpasswdLine should correctly generate line for bcrypt 1`] = `
|
||||
exports[`generateHtpasswdLine > should correctly generate line for bcrypt 1`] = `
|
||||
"username:$2a$04$......................LAtw7/ohmmBAhnXqmkuIz83Rl5Qdjhm:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`generateHtpasswdLine should correctly generate line for crypt 1`] = `
|
||||
exports[`generateHtpasswdLine > should correctly generate line for crypt 1`] = `
|
||||
"username:$66to3JK5RgZM:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`generateHtpasswdLine should correctly generate line for md5 1`] = `
|
||||
exports[`generateHtpasswdLine > should correctly generate line for md5 1`] = `
|
||||
"username:$apr1$MMMMMMMM$2lGUwLC3NFfN74jH51z1W.:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`generateHtpasswdLine should correctly generate line for sha1 1`] = `
|
||||
exports[`generateHtpasswdLine > should correctly generate line for sha1 1`] = `
|
||||
"username:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=:autocreated 2018-01-14T11:17:40.712Z
|
||||
"
|
||||
`;
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { EncryptionMethod, createSalt } from '../src/crypt3';
|
||||
|
||||
jest.mock('crypto', () => {
|
||||
return {
|
||||
randomBytes: (len: number): { toString: () => string } => {
|
||||
return {
|
||||
toString: (): string => '/UEGzD0RxSNDZA=='.substring(0, len),
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock('../src/crypto-utils', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('../src/crypto-utils')>()),
|
||||
randomBytes: (len: number): { toString: () => string } => {
|
||||
return {
|
||||
toString: (): string => '/UEGzD0RxSNDZA=='.substring(0, len),
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe('createSalt', () => {
|
||||
test('should match with the correct salt type', () => {
|
||||
|
254
packages/plugins/htpasswd/tests/htpasswd.adduser.test.ts
Normal file
254
packages/plugins/htpasswd/tests/htpasswd.adduser.test.ts
Normal file
@ -0,0 +1,254 @@
|
||||
/* eslint-disable new-cap */
|
||||
import MockDate from 'mockdate';
|
||||
import path from 'path';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { Config, parseConfigFile } from '@verdaccio/config';
|
||||
import { API_ERROR, constants, fileUtils, pluginUtils } from '@verdaccio/core';
|
||||
|
||||
import HTPasswd, { HTPasswdConfig } from '../src/htpasswd';
|
||||
|
||||
const options = {
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
config: new Config(parseConfigFile(path.join(__dirname, './__fixtures__/config.yaml'))),
|
||||
} as any as pluginUtils.PluginOptions;
|
||||
|
||||
const config = {
|
||||
file: './htpasswd',
|
||||
max_users: 1000,
|
||||
} as HTPasswdConfig;
|
||||
|
||||
vi.mock('../src/crypto-utils', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('../src/crypto-utils')>()),
|
||||
randomBytes: (): { toString: () => string } => {
|
||||
return {
|
||||
toString: (): string => 'token',
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe('HTPasswd', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
const tempPath = await fileUtils.createTempFolder('htpasswd');
|
||||
const file = path.join(tempPath, './htpasswd');
|
||||
wrapper = new HTPasswd({ ...config, file }, options);
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('addUser()', () => {
|
||||
test('it should not pass sanity check', async () => {
|
||||
vi.doMock('../src/utils.ts', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('../src/utils')>()),
|
||||
sanityCheck: vi.fn((): Error => Error(API_ERROR.UNAUTHORIZED_ACCESS)),
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
return new Promise((done) => {
|
||||
const callback = (error: Error): void => {
|
||||
expect(error.message).toEqual(API_ERROR.UNAUTHORIZED_ACCESS);
|
||||
done(true);
|
||||
};
|
||||
wrapper.adduser('test', 'somerandompassword', callback);
|
||||
});
|
||||
});
|
||||
|
||||
test('it should add the user', async () => {
|
||||
const tempPath = await fileUtils.createTempFolder('htpasswd');
|
||||
const file = path.join(tempPath, './htpasswd');
|
||||
const wrapper = new HTPasswd(
|
||||
{
|
||||
...config,
|
||||
file,
|
||||
},
|
||||
options
|
||||
);
|
||||
return new Promise((done) => {
|
||||
MockDate.set('2018-01-14T11:17:40.712Z');
|
||||
const callback = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toBeTruthy();
|
||||
done(true);
|
||||
};
|
||||
wrapper.adduser('usernotpresent', 'somerandompassword', callback);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addUser() error handling', () => {
|
||||
test('sanityCheck should return an Error', async () => {
|
||||
vi.doMock('../src/utils.ts', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('../src/utils')>()),
|
||||
sanityCheck: vi.fn((): Error => Error('some error')),
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
await new Promise((done) => {
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('sanityCheck', 'test', (sanity) => {
|
||||
expect(sanity.message).toBeDefined();
|
||||
expect(sanity.message).toMatch('some error');
|
||||
done(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('lockAndRead should return an Error', async () => {
|
||||
vi.doMock('../src/utils.ts', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('../src/utils')>()),
|
||||
lockAndRead: (_a, b): any => b(new Error('lock error')),
|
||||
HtpasswdHashAlgorithm: constants.HtpasswdHashAlgorithm,
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
await new Promise((done) => {
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('lockAndRead', 'test', (sanity) => {
|
||||
expect(sanity.message).toBeDefined();
|
||||
expect(sanity.message).toMatch('lock error');
|
||||
done(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('addUserToHTPasswd should return an Error', async () => {
|
||||
vi.doMock('../src/utils.ts', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('../src/utils')>()),
|
||||
addUserToHTPasswd: () => {
|
||||
throw new Error('addUserToHTPasswd error');
|
||||
},
|
||||
lockAndRead: (_a, b): any => b(null, ''),
|
||||
unlockFile: (_a, b): any => b(),
|
||||
HtpasswdHashAlgorithm: constants.HtpasswdHashAlgorithm,
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
await new Promise((done) => {
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('addUserToHTPasswd', 'test', () => {
|
||||
done(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.skip('writeFile should return an Error', async () => {
|
||||
vi.doMock('../src/utils.ts', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('../src/utils')>()),
|
||||
sanityCheck: () => Promise.resolve(null),
|
||||
parseHTPasswd: (): void => {},
|
||||
lockAndRead: (_a, b): any => b(null, ''),
|
||||
addUserToHTPasswd: (): void => {},
|
||||
HtpasswdHashAlgorithm: constants.HtpasswdHashAlgorithm,
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
await new Promise((done) => {
|
||||
vi.doMock('fs', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('fs')>()),
|
||||
writeFile: vi.fn((_name, _data, callback) => {
|
||||
callback(new Error('write error'));
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('addUserToHTPasswd', 'test', (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('write error');
|
||||
done(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('reload()', () => {
|
||||
test('it should read the file and set the users', () => {
|
||||
return new Promise((done) => {
|
||||
wrapper.adduser('sanityCheck', 'test', () => {
|
||||
const callback = (): void => {
|
||||
expect(wrapper.users).toHaveProperty('sanityCheck');
|
||||
done(true);
|
||||
};
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('reload should fails on check file', async () => {
|
||||
vi.doMock('fs', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('fs')>()),
|
||||
stat: vi.fn((path, callback) => {
|
||||
callback(new Error('stat error'), null);
|
||||
}),
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
await new Promise((done) => {
|
||||
wrapper.adduser('sanityCheck', 'test', () => {
|
||||
const callback = (err): void => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('stat error');
|
||||
done(true);
|
||||
};
|
||||
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('reload times match', async () => {
|
||||
vi.doMock('fs', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('fs')>()),
|
||||
stat: vi.fn((_path, callback) => {
|
||||
callback(null, {
|
||||
mtime: null,
|
||||
});
|
||||
}),
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
await new Promise((done) => {
|
||||
const callback = (err): void => {
|
||||
expect(err).toBeUndefined();
|
||||
done(true);
|
||||
};
|
||||
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
});
|
||||
|
||||
test('reload should fails on read file', async () => {
|
||||
vi.doMock('fs', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('fs')>()),
|
||||
readFile: vi.fn((_name, _format, callback) => {
|
||||
callback(new Error('read error'), null);
|
||||
}),
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
await new Promise((done) => {
|
||||
const callback = (err): void => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('read error');
|
||||
done(true);
|
||||
};
|
||||
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
96
packages/plugins/htpasswd/tests/htpasswd.passwords.test.ts
Normal file
96
packages/plugins/htpasswd/tests/htpasswd.passwords.test.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import path from 'path';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { Config, parseConfigFile } from '@verdaccio/config';
|
||||
import { fileUtils, pluginUtils } from '@verdaccio/core';
|
||||
|
||||
import HTPasswd, { HTPasswdConfig } from '../src/htpasswd';
|
||||
|
||||
const options = {
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
config: new Config(parseConfigFile(path.join(__dirname, './__fixtures__/config.yaml'))),
|
||||
} as any as pluginUtils.PluginOptions;
|
||||
|
||||
const config = {
|
||||
file: './htpasswd',
|
||||
max_users: 1000,
|
||||
} as HTPasswdConfig;
|
||||
|
||||
vi.mock('../src/crypto-utils', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('../src/crypto-utils')>()),
|
||||
randomBytes: (): { toString: () => string } => {
|
||||
return {
|
||||
toString: (): string => '$6',
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe('HTPasswd', () => {
|
||||
let wrapper;
|
||||
let file;
|
||||
beforeEach(async () => {
|
||||
const tempPath = await fileUtils.createTempFolder('htpasswd');
|
||||
file = path.join(tempPath, './htpasswd');
|
||||
wrapper = new HTPasswd({ ...config, file }, options);
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
await new Promise((done) => {
|
||||
wrapper.adduser('sanityCheck', 'test', () => {
|
||||
done(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('changePassword - it should throw an error for user not found', () => {
|
||||
return new Promise((done) => {
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.message).toBe(
|
||||
`Unable to change password for user 'usernotpresent': user does not currently exist`
|
||||
);
|
||||
expect(isSuccess).toBeFalsy();
|
||||
done(true);
|
||||
};
|
||||
wrapper.changePassword('usernotpresent', 'oldPassword', 'newPassword', callback);
|
||||
});
|
||||
});
|
||||
|
||||
test('changePassword - it should throw an error for wrong password', () => {
|
||||
return new Promise((done) => {
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.message).toBe(
|
||||
`Unable to change password for user 'sanityCheck': invalid old password`
|
||||
);
|
||||
expect(isSuccess).toBeFalsy();
|
||||
done(true);
|
||||
};
|
||||
wrapper.changePassword('sanityCheck', 'wrongPassword', 'newPassword', callback);
|
||||
});
|
||||
});
|
||||
|
||||
test('changePassword - it should change password', async () => {
|
||||
let dataToWrite: any;
|
||||
vi.doMock('fs', async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal<typeof import('fs')>()),
|
||||
writeFile: vi.fn((_name, data, callback) => {
|
||||
dataToWrite = data;
|
||||
callback();
|
||||
}),
|
||||
};
|
||||
});
|
||||
const HTPasswd = (await import('../src/htpasswd')).default;
|
||||
const localWrapper = new HTPasswd({ ...config, file }, options);
|
||||
await new Promise((done) => {
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).toBeNull();
|
||||
expect(isSuccess).toBeTruthy();
|
||||
expect(dataToWrite.indexOf('sanityCheck')).not.toEqual(-1);
|
||||
done(true);
|
||||
};
|
||||
|
||||
localWrapper.changePassword('sanityCheck', 'test', 'newPassword', callback);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,18 +1,17 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs';
|
||||
import MockDate from 'mockdate';
|
||||
import path from 'path';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { Config, parseConfigFile } from '@verdaccio/config';
|
||||
import { constants, pluginUtils } from '@verdaccio/core';
|
||||
import { pluginUtils } from '@verdaccio/core';
|
||||
|
||||
import HTPasswd, { DEFAULT_SLOW_VERIFY_MS, HTPasswdConfig } from '../src/htpasswd';
|
||||
|
||||
const options = {
|
||||
logger: { warn: jest.fn(), info: jest.fn() },
|
||||
logger: { warn: vi.fn(), info: vi.fn() },
|
||||
config: new Config(parseConfigFile(path.join(__dirname, './__fixtures__/config.yaml'))),
|
||||
} as any as pluginUtils.PluginOptions<HTPasswdConfig>;
|
||||
} as any as pluginUtils.PluginOptions;
|
||||
|
||||
const config = {
|
||||
file: './htpasswd',
|
||||
@ -24,11 +23,11 @@ describe('HTPasswd', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = new HTPasswd(config, options);
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
|
||||
// @ts-ignore
|
||||
crypto.randomBytes = jest.fn(() => {
|
||||
crypto.randomBytes = vi.fn(() => {
|
||||
return {
|
||||
toString: (): string => '$6',
|
||||
};
|
||||
@ -36,15 +35,15 @@ describe('HTPasswd', () => {
|
||||
});
|
||||
|
||||
describe('constructor()', () => {
|
||||
const error = jest.fn();
|
||||
const warn = jest.fn();
|
||||
const info = jest.fn();
|
||||
const error = vi.fn();
|
||||
const warn = vi.fn();
|
||||
const info = vi.fn();
|
||||
const emptyPluginOptions = {
|
||||
config: {
|
||||
configPath: '',
|
||||
},
|
||||
logger: { warn, info, error },
|
||||
} as any as pluginUtils.PluginOptions<HTPasswdConfig>;
|
||||
} as any as pluginUtils.PluginOptions;
|
||||
|
||||
test('should ensure file path configuration exists', () => {
|
||||
expect(function () {
|
||||
@ -64,288 +63,61 @@ describe('HTPasswd', () => {
|
||||
});
|
||||
|
||||
describe('authenticate()', () => {
|
||||
test('it should authenticate user with given credentials', (done) => {
|
||||
const users = [
|
||||
{ username: 'test', password: 'test' },
|
||||
{ username: 'username', password: 'password' },
|
||||
{ username: 'bcrypt', password: 'password' },
|
||||
];
|
||||
let usersAuthenticated = 0;
|
||||
const generateCallback = (username) => (error, userGroups) => {
|
||||
usersAuthenticated += 1;
|
||||
expect(error).toBeNull();
|
||||
expect(userGroups).toContain(username);
|
||||
usersAuthenticated === users.length && done();
|
||||
};
|
||||
users.forEach(({ username, password }) =>
|
||||
wrapper.authenticate(username, password, generateCallback(username))
|
||||
);
|
||||
test.each([
|
||||
{ username: 'test1111', password: 'test1111' },
|
||||
{ username: 'username', password: 'password' },
|
||||
{ username: 'bcrypt', password: 'password' },
|
||||
])('it should authenticate user $username with given credentials', ({ username, password }) => {
|
||||
return new Promise((done) => {
|
||||
const generateCallback = (username) => (error, userGroups) => {
|
||||
expect(error).toBeNull();
|
||||
expect(userGroups).toContain(username);
|
||||
done();
|
||||
};
|
||||
wrapper.adduser(username, password, () => {
|
||||
wrapper.authenticate(username, password, generateCallback(username));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('it should not authenticate user with given credentials', (done) => {
|
||||
const users = ['test', 'username', 'bcrypt'];
|
||||
let usersAuthenticated = 0;
|
||||
const generateCallback = () => (error, userGroups) => {
|
||||
usersAuthenticated += 1;
|
||||
expect(error).toBeNull();
|
||||
expect(userGroups).toBeFalsy();
|
||||
usersAuthenticated === users.length && done();
|
||||
};
|
||||
users.forEach((username) =>
|
||||
wrapper.authenticate(username, 'somerandompassword', generateCallback())
|
||||
);
|
||||
test.each([
|
||||
{ username: 'test1111', password: 'test1111' },
|
||||
{ username: 'username', password: 'password' },
|
||||
{ username: 'bcrypt', password: 'password' },
|
||||
])('it should not authenticate use $username with given credentials', ({ username }) => {
|
||||
return new Promise((done) => {
|
||||
const generateCallback = () => (error) => {
|
||||
expect(error).toBeNull();
|
||||
// expect(userGroups).toBeFalsy();
|
||||
done();
|
||||
};
|
||||
wrapper.authenticate(username, 'somerandompassword', generateCallback());
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: flakes on CI
|
||||
test.skip('it should warn on slow password verification', (done) => {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
bcrypt.compare = jest.fn((_passwd, _hash) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, DEFAULT_SLOW_VERIFY_MS + 1)).then(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
const callback = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toContain('bcrypt');
|
||||
const mockWarn = options.logger.warn as jest.MockedFn<jest.MockableFunction>;
|
||||
expect(mockWarn.mock.calls.length).toBe(1);
|
||||
const [{ user, durationMs }, message] = mockWarn.mock.calls[0];
|
||||
expect(user).toEqual('bcrypt');
|
||||
expect(durationMs).toBeGreaterThan(DEFAULT_SLOW_VERIFY_MS);
|
||||
expect(message).toEqual('Password for user "@{user}" took @{durationMs}ms to verify');
|
||||
done();
|
||||
};
|
||||
wrapper.authenticate('bcrypt', 'password', callback);
|
||||
}, 18000);
|
||||
});
|
||||
|
||||
describe('addUser()', () => {
|
||||
test('it should not pass sanity check', (done) => {
|
||||
const callback = (a): void => {
|
||||
expect(a.message).toEqual('unauthorized access');
|
||||
done();
|
||||
};
|
||||
wrapper.adduser('test', 'somerandompassword', callback);
|
||||
});
|
||||
|
||||
test('it should add the user', (done) => {
|
||||
let dataToWrite;
|
||||
// @ts-ignore
|
||||
fs.writeFile = jest.fn((name, data, callback) => {
|
||||
dataToWrite = data;
|
||||
callback();
|
||||
});
|
||||
|
||||
MockDate.set('2018-01-14T11:17:40.712Z');
|
||||
|
||||
const callback = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toBeTruthy();
|
||||
expect(fs.writeFile).toHaveBeenCalled();
|
||||
expect(dataToWrite.indexOf('usernotpresent')).not.toEqual(-1);
|
||||
done();
|
||||
};
|
||||
wrapper.adduser('usernotpresent', 'somerandompassword', callback);
|
||||
});
|
||||
|
||||
describe('addUser() error handling', () => {
|
||||
test('sanityCheck should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: (): Error => Error('some error'),
|
||||
HtpasswdHashAlgorithm: constants.HtpasswdHashAlgorithm,
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('sanityCheck', 'test', (sanity) => {
|
||||
expect(sanity.message).toBeDefined();
|
||||
expect(sanity.message).toMatch('some error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('lockAndRead should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: (): any => null,
|
||||
lockAndRead: (_a, b): any => b(new Error('lock error')),
|
||||
HtpasswdHashAlgorithm: constants.HtpasswdHashAlgorithm,
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('lockAndRead', 'test', (sanity) => {
|
||||
expect(sanity.message).toBeDefined();
|
||||
expect(sanity.message).toMatch('lock error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('addUserToHTPasswd should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: (): any => null,
|
||||
parseHTPasswd: (): void => {},
|
||||
lockAndRead: (_a, b): any => b(null, ''),
|
||||
unlockFile: (_a, b): any => b(),
|
||||
HtpasswdHashAlgorithm: constants.HtpasswdHashAlgorithm,
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('addUserToHTPasswd', 'test', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFile should return an Error', (done) => {
|
||||
jest.doMock('../src/utils.ts', () => {
|
||||
return {
|
||||
sanityCheck: () => Promise.resolve(null),
|
||||
parseHTPasswd: (): void => {},
|
||||
lockAndRead: (_a, b): any => b(null, ''),
|
||||
addUserToHTPasswd: (): void => {},
|
||||
HtpasswdHashAlgorithm: constants.HtpasswdHashAlgorithm,
|
||||
};
|
||||
});
|
||||
jest.doMock('fs', () => {
|
||||
const original = jest.requireActual('fs');
|
||||
return {
|
||||
...original,
|
||||
writeFile: jest.fn((_name, _data, callback) => {
|
||||
callback(new Error('write error'));
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.adduser('addUserToHTPasswd', 'test', (err) => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('write error');
|
||||
done();
|
||||
test.skip('it should warn on slow password verification', () => {
|
||||
return new Promise((done) => {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
bcrypt.compare = vi.fn((_passwd, _hash) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, DEFAULT_SLOW_VERIFY_MS + 1)).then(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
const callback = (a, b): void => {
|
||||
expect(a).toBeNull();
|
||||
expect(b).toContain('bcrypt');
|
||||
const mockWarn = options.logger.warn as any;
|
||||
expect(mockWarn.mock.calls.length).toBe(1);
|
||||
const [{ user, durationMs }, message] = mockWarn.mock.calls[0];
|
||||
expect(user).toEqual('bcrypt');
|
||||
expect(durationMs).toBeGreaterThan(DEFAULT_SLOW_VERIFY_MS);
|
||||
expect(message).toEqual('Password for user "@{user}" took @{durationMs}ms to verify');
|
||||
done(true);
|
||||
};
|
||||
wrapper.authenticate('bcrypt', 'password', callback);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reload()', () => {
|
||||
test('it should read the file and set the users', (done) => {
|
||||
const output = {
|
||||
test: '$6FrCaT/v0dwE',
|
||||
username: '$66to3JK5RgZM',
|
||||
bcrypt: '$2y$04$K2Cn3StiXx4CnLmcTW/ymekOrj7WlycZZF9xgmoJ/U0zGPqSLPVBe',
|
||||
};
|
||||
const callback = (): void => {
|
||||
expect(wrapper.users).toEqual(output);
|
||||
done();
|
||||
};
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
|
||||
test('reload should fails on check file', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
stat: (_name, callback): void => {
|
||||
callback(new Error('stat error'), null);
|
||||
},
|
||||
};
|
||||
});
|
||||
const callback = (err): void => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('stat error');
|
||||
done();
|
||||
};
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
|
||||
test('reload times match', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
stat: (_name, callback): void => {
|
||||
callback(null, {
|
||||
mtime: null,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
const callback = (err): void => {
|
||||
expect(err).toBeUndefined();
|
||||
done();
|
||||
};
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
|
||||
test('reload should fails on read file', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
stat: jest.requireActual('fs').stat,
|
||||
readFile: (_name, _format, callback): void => {
|
||||
callback(new Error('read error'), null);
|
||||
},
|
||||
};
|
||||
});
|
||||
const callback = (err): void => {
|
||||
expect(err).not.toBeNull();
|
||||
expect(err.message).toMatch('read error');
|
||||
done();
|
||||
};
|
||||
|
||||
const HTPasswd = require('../src/htpasswd.ts').default;
|
||||
const wrapper = new HTPasswd(config, options);
|
||||
wrapper.reload(callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('changePassword - it should throw an error for user not found', (done) => {
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.message).toBe(
|
||||
`Unable to change password for user 'usernotpresent': user does not currently exist`
|
||||
);
|
||||
expect(isSuccess).toBeFalsy();
|
||||
done();
|
||||
};
|
||||
wrapper.changePassword('usernotpresent', 'oldPassword', 'newPassword', callback);
|
||||
});
|
||||
|
||||
test('changePassword - it should throw an error for wrong password', (done) => {
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.message).toBe(
|
||||
`Unable to change password for user 'username': invalid old password`
|
||||
);
|
||||
expect(isSuccess).toBeFalsy();
|
||||
done();
|
||||
};
|
||||
wrapper.changePassword('username', 'wrongPassword', 'newPassword', callback);
|
||||
});
|
||||
|
||||
test('changePassword - it should change password', (done) => {
|
||||
let dataToWrite;
|
||||
// @ts-ignore
|
||||
fs.writeFile = jest.fn((_name, data, callback) => {
|
||||
dataToWrite = data;
|
||||
callback();
|
||||
});
|
||||
const callback = (error, isSuccess): void => {
|
||||
expect(error).toBeNull();
|
||||
expect(isSuccess).toBeTruthy();
|
||||
expect(fs.writeFile).toHaveBeenCalled();
|
||||
expect(dataToWrite.indexOf('username')).not.toEqual(-1);
|
||||
done();
|
||||
};
|
||||
wrapper.changePassword('username', 'password', 'newPassword', callback);
|
||||
});
|
||||
});
|
||||
|
@ -2,6 +2,7 @@
|
||||
import crypto from 'crypto';
|
||||
import { HttpError } from 'http-errors';
|
||||
import MockDate from 'mockdate';
|
||||
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { constants } from '@verdaccio/core';
|
||||
|
||||
@ -16,8 +17,8 @@ import {
|
||||
verifyPassword,
|
||||
} from '../src/utils';
|
||||
|
||||
const mockReadFile = jest.fn();
|
||||
const mockUnlockFile = jest.fn();
|
||||
const mockReadFile = vi.fn();
|
||||
const mockUnlockFile = vi.fn();
|
||||
|
||||
const defaultHashConfig = {
|
||||
algorithm: constants.HtpasswdHashAlgorithm.bcrypt,
|
||||
@ -27,34 +28,34 @@ const defaultHashConfig = {
|
||||
const mockTimeAndRandomBytes = () => {
|
||||
MockDate.set('2018-01-14T11:17:40.712Z');
|
||||
// @ts-ignore: Module has no default export
|
||||
crypto.randomBytes = jest.fn(() => {
|
||||
crypto.randomBytes = vi.fn(() => {
|
||||
return {
|
||||
toString: (): string => '$6',
|
||||
};
|
||||
});
|
||||
Math.random = jest.fn(() => 0.38849);
|
||||
Math.random = vi.fn(() => 0.38849);
|
||||
};
|
||||
|
||||
jest.mock('@verdaccio/file-locking', () => ({
|
||||
vi.mock('@verdaccio/file-locking', () => ({
|
||||
readFile: () => mockReadFile(),
|
||||
unlockFile: () => mockUnlockFile(),
|
||||
}));
|
||||
|
||||
describe('parseHTPasswd', () => {
|
||||
it('should parse the password for a single line', () => {
|
||||
test('should parse the password for a single line', () => {
|
||||
const input = 'test:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z';
|
||||
const output = { test: '$6b9MlB3WUELU' };
|
||||
expect(parseHTPasswd(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should parse the password for two lines', () => {
|
||||
test('should parse the password for two lines', () => {
|
||||
const input = `user1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
user2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z`;
|
||||
const output = { user1: '$6b9MlB3WUELU', user2: '$6FrCaT/v0dwE' };
|
||||
expect(parseHTPasswd(input)).toEqual(output);
|
||||
});
|
||||
|
||||
it('should parse the password for multiple lines', () => {
|
||||
test('should parse the password for multiple lines', () => {
|
||||
const input = `user1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
user2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z
|
||||
user3:$6FrCdfd\v0dwE:autocreated 2017-12-14T13:30:20.838Z
|
||||
@ -70,35 +71,35 @@ user4:$6FrCasdvppdwE:autocreated 2017-12-14T13:30:20.838Z`;
|
||||
});
|
||||
|
||||
describe('verifyPassword', () => {
|
||||
it('should verify the MD5/Crypt3 password with true', async () => {
|
||||
test('should verify the MD5/Crypt3 password with true', async () => {
|
||||
const input = ['test', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0'];
|
||||
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the MD5/Crypt3 password with false', async () => {
|
||||
test('should verify the MD5/Crypt3 password with false', async () => {
|
||||
const input = ['testpasswordchanged', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0'];
|
||||
expect(await verifyPassword(input[0], input[1])).toBeFalsy();
|
||||
});
|
||||
it('should verify the plain password with true', async () => {
|
||||
test('should verify the plain password with true', async () => {
|
||||
const input = ['testpasswordchanged', '{PLAIN}testpasswordchanged'];
|
||||
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the plain password with false', async () => {
|
||||
test('should verify the plain password with false', async () => {
|
||||
const input = ['testpassword', '{PLAIN}testpasswordchanged'];
|
||||
expect(await verifyPassword(input[0], input[1])).toBeFalsy();
|
||||
});
|
||||
it('should verify the crypto SHA password with true', async () => {
|
||||
test('should verify the crypto SHA password with true', async () => {
|
||||
const input = ['testpassword', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0='];
|
||||
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the crypto SHA password with false', async () => {
|
||||
test('should verify the crypto SHA password with false', async () => {
|
||||
const input = ['testpasswordchanged', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0='];
|
||||
expect(await verifyPassword(input[0], input[1])).toBeFalsy();
|
||||
});
|
||||
it('should verify the bcrypt password with true', async () => {
|
||||
test('should verify the bcrypt password with true', async () => {
|
||||
const input = ['testpassword', '$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO'];
|
||||
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
|
||||
});
|
||||
it('should verify the bcrypt password with false', async () => {
|
||||
test('should verify the bcrypt password with false', async () => {
|
||||
const input = [
|
||||
'testpasswordchanged',
|
||||
'$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO',
|
||||
@ -112,22 +113,22 @@ describe('generateHtpasswdLine', () => {
|
||||
|
||||
const [user, passwd] = ['username', 'password'];
|
||||
|
||||
it('should correctly generate line for md5', async () => {
|
||||
test('should correctly generate line for md5', async () => {
|
||||
const md5Conf = { algorithm: constants.HtpasswdHashAlgorithm.md5 };
|
||||
expect(await generateHtpasswdLine(user, passwd, md5Conf)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should correctly generate line for sha1', async () => {
|
||||
test('should correctly generate line for sha1', async () => {
|
||||
const sha1Conf = { algorithm: constants.HtpasswdHashAlgorithm.sha1 };
|
||||
expect(await generateHtpasswdLine(user, passwd, sha1Conf)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should correctly generate line for crypt', async () => {
|
||||
test('should correctly generate line for crypt', async () => {
|
||||
const cryptConf = { algorithm: constants.HtpasswdHashAlgorithm.crypt };
|
||||
expect(await generateHtpasswdLine(user, passwd, cryptConf)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should correctly generate line for bcrypt', async () => {
|
||||
test('should correctly generate line for bcrypt', async () => {
|
||||
const bcryptAlgoConfig = {
|
||||
algorithm: constants.HtpasswdHashAlgorithm.bcrypt,
|
||||
rounds: 2,
|
||||
@ -139,14 +140,14 @@ describe('generateHtpasswdLine', () => {
|
||||
describe('addUserToHTPasswd - bcrypt', () => {
|
||||
beforeAll(mockTimeAndRandomBytes);
|
||||
|
||||
it('should add new htpasswd to the end', async () => {
|
||||
test('should add new htpasswd to the end', async () => {
|
||||
const input = ['', 'username', 'password'];
|
||||
expect(
|
||||
await addUserToHTPasswd(input[0], input[1], input[2], defaultHashConfig)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add new htpasswd to the end in multiline input', async () => {
|
||||
test('should add new htpasswd to the end in multiline input', async () => {
|
||||
const body = `test1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z
|
||||
test2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z`;
|
||||
const input = [body, 'username', 'password'];
|
||||
@ -155,7 +156,7 @@ describe('addUserToHTPasswd - bcrypt', () => {
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw an error for incorrect username with space', async () => {
|
||||
test('should throw an error for incorrect username with space', async () => {
|
||||
const [a, b, c] = ['', 'firstname lastname', 'password'];
|
||||
await expect(
|
||||
addUserToHTPasswd(a, b, c, defaultHashConfig)
|
||||
@ -163,7 +164,7 @@ describe('addUserToHTPasswd - bcrypt', () => {
|
||||
});
|
||||
});
|
||||
describe('lockAndRead', () => {
|
||||
it('should call the readFile method', () => {
|
||||
test('should call the readFile method', () => {
|
||||
const cb = (): void => {};
|
||||
lockAndRead('.htpasswd', cb);
|
||||
expect(mockReadFile).toHaveBeenCalled();
|
||||
@ -178,7 +179,7 @@ describe('sanityCheck', () => {
|
||||
});
|
||||
|
||||
test('should throw error for user already exists', async () => {
|
||||
const verifyFn = jest.fn();
|
||||
const verifyFn = vi.fn();
|
||||
const input = await sanityCheck('test', users.test, verifyFn, users, Infinity);
|
||||
expect((input as HttpError<number>).status).toEqual(401);
|
||||
expect((input as HttpError<number>).message).toEqual('unauthorized access');
|
||||
@ -230,7 +231,7 @@ describe('sanityCheck', () => {
|
||||
});
|
||||
|
||||
test('should throw error for existing username and password', async () => {
|
||||
const verifyFn = jest.fn(() => true);
|
||||
const verifyFn = vi.fn(() => true);
|
||||
const input = await sanityCheck('test', users.test, verifyFn, users, 2);
|
||||
expect((input as HttpError<number>).status).toEqual(409);
|
||||
expect((input as HttpError<number>).message).toEqual('username is already registered');
|
||||
@ -240,7 +241,7 @@ describe('sanityCheck', () => {
|
||||
test(
|
||||
'should throw error for existing username and password with max number ' + 'of users reached',
|
||||
async () => {
|
||||
const verifyFn = jest.fn(() => true);
|
||||
const verifyFn = vi.fn(() => true);
|
||||
const input = await sanityCheck('test', users.test, verifyFn, users, 1);
|
||||
expect((input as HttpError<number>).status).toEqual(409);
|
||||
expect((input as HttpError<number>).message).toEqual('username is already registered');
|
||||
|
@ -38,7 +38,7 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/file-locking": "workspace:13.0.0-next-8.1",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"core-js": "3.37.1",
|
||||
"debug": "4.3.7",
|
||||
"globby": "11.1.0",
|
||||
|
@ -40,7 +40,7 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/config": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/core": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"JSONStream": "1.3.5",
|
||||
"debug": "4.3.7",
|
||||
"got-cjs": "12.5.4",
|
||||
|
@ -29,7 +29,7 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/api": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/api": "workspace:8.1.0-next-8.2",
|
||||
"@verdaccio/auth": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/core": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/config": "workspace:8.0.0-next-8.2",
|
||||
@ -37,8 +37,8 @@
|
||||
"@verdaccio/logger": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/middleware": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/store": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/web": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"@verdaccio/web": "workspace:8.1.0-next-8.2",
|
||||
"verdaccio-audit": "workspace:13.0.0-next-8.2",
|
||||
"compression": "1.7.4",
|
||||
"cors": "2.8.5",
|
||||
|
@ -39,7 +39,7 @@
|
||||
"@verdaccio/logger": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/store": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/tarball": "workspace:13.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"core-js": "3.37.1",
|
||||
"debug": "4.3.7",
|
||||
"fastify": "4.25.2",
|
||||
|
@ -48,7 +48,7 @@
|
||||
"@verdaccio/search": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/tarball": "workspace:13.0.0-next-8.2",
|
||||
"@verdaccio/url": "workspace:13.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"JSONStream": "1.3.5",
|
||||
"debug": "4.3.7",
|
||||
"lodash": "4.17.21",
|
||||
|
@ -15,7 +15,7 @@
|
||||
"@verdaccio/logger": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/middleware": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/types": "workspace:13.0.0-next-8.1",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"body-parser": "1.20.3",
|
||||
"debug": "4.3.7",
|
||||
"express": "4.21.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/utils",
|
||||
"version": "7.1.0-next-8.2",
|
||||
"version": "8.1.0-next-8.2",
|
||||
"description": "verdaccio utilities",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
|
@ -43,7 +43,7 @@
|
||||
"@verdaccio/logger": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/node-api": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/ui-theme": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"verdaccio-audit": "workspace:13.0.0-next-8.2",
|
||||
"verdaccio-htpasswd": "workspace:13.0.0-next-8.2"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/web",
|
||||
"version": "7.1.0-next-8.2",
|
||||
"version": "8.1.0-next-8.2",
|
||||
"description": "web ui middleware",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@ -33,13 +33,13 @@
|
||||
"@verdaccio/store": "workspace:8.0.0-next-8.2",
|
||||
"@verdaccio/tarball": "workspace:13.0.0-next-8.2",
|
||||
"@verdaccio/url": "workspace:13.0.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/utils": "workspace:8.1.0-next-8.2",
|
||||
"debug": "4.3.7",
|
||||
"express": "4.21.0",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/api": "workspace:7.1.0-next-8.2",
|
||||
"@verdaccio/api": "workspace:8.1.0-next-8.2",
|
||||
"@verdaccio/test-helper": "workspace:4.0.0-next-8.0",
|
||||
"@verdaccio/types": "workspace:13.0.0-next-8.1",
|
||||
"jsdom": "20.0.3",
|
||||
|
216
pnpm-lock.yaml
generated
216
pnpm-lock.yaml
generated
@ -229,7 +229,7 @@ importers:
|
||||
version: link:packages/plugins/ui-theme
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 2.0.5
|
||||
version: 2.0.5(vitest@2.0.4)
|
||||
version: 2.0.5(vitest@2.1.2)
|
||||
aria-query:
|
||||
specifier: 5.1.3
|
||||
version: 5.1.3
|
||||
@ -348,8 +348,8 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:packages/plugins/memory
|
||||
vitest:
|
||||
specifier: 2.0.4
|
||||
version: 2.0.4(@types/node@20.14.12)
|
||||
specifier: 2.1.2
|
||||
version: 2.1.2(@types/node@20.14.12)
|
||||
|
||||
e2e/cli/cli-commons:
|
||||
devDependencies:
|
||||
@ -528,7 +528,7 @@ importers:
|
||||
specifier: workspace:8.0.0-next-8.2
|
||||
version: link:../store
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
abortcontroller-polyfill:
|
||||
specifier: 1.7.5
|
||||
@ -589,7 +589,7 @@ importers:
|
||||
specifier: workspace:8.0.0-next-8.1
|
||||
version: link:../signature
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
debug:
|
||||
specifier: 4.3.7
|
||||
@ -651,7 +651,7 @@ importers:
|
||||
specifier: workspace:8.0.0-next-8.2
|
||||
version: link:../core/core
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
debug:
|
||||
specifier: 4.3.7
|
||||
@ -723,7 +723,7 @@ importers:
|
||||
specifier: workspace:13.0.0-next-8.2
|
||||
version: link:../url
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../../utils
|
||||
debug:
|
||||
specifier: 4.3.7
|
||||
@ -907,7 +907,7 @@ importers:
|
||||
specifier: workspace:13.0.0-next-8.2
|
||||
version: link:../core/url
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
debug:
|
||||
specifier: 4.3.7
|
||||
@ -1083,7 +1083,7 @@ importers:
|
||||
specifier: workspace:13.0.0-next-8.1
|
||||
version: link:../../core/file-locking
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../../utils
|
||||
core-js:
|
||||
specifier: 3.37.1
|
||||
@ -1391,7 +1391,7 @@ importers:
|
||||
specifier: workspace:8.0.0-next-8.2
|
||||
version: link:../core/core
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
JSONStream:
|
||||
specifier: 1.3.5
|
||||
@ -1483,7 +1483,7 @@ importers:
|
||||
packages/server/express:
|
||||
dependencies:
|
||||
'@verdaccio/api':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../../api
|
||||
'@verdaccio/auth':
|
||||
specifier: workspace:8.0.0-next-8.2
|
||||
@ -1507,10 +1507,10 @@ importers:
|
||||
specifier: workspace:8.0.0-next-8.2
|
||||
version: link:../../store
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../../utils
|
||||
'@verdaccio/web':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../../web
|
||||
compression:
|
||||
specifier: 1.7.4
|
||||
@ -1562,7 +1562,7 @@ importers:
|
||||
specifier: workspace:13.0.0-next-8.2
|
||||
version: link:../../core/tarball
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../../utils
|
||||
core-js:
|
||||
specifier: 3.37.1
|
||||
@ -1660,7 +1660,7 @@ importers:
|
||||
specifier: workspace:13.0.0-next-8.2
|
||||
version: link:../core/url
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
JSONStream:
|
||||
specifier: 1.3.5
|
||||
@ -1810,7 +1810,7 @@ importers:
|
||||
specifier: workspace:13.0.0-next-8.1
|
||||
version: link:../../core/types
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../../utils
|
||||
body-parser:
|
||||
specifier: 1.20.3
|
||||
@ -2069,7 +2069,7 @@ importers:
|
||||
specifier: workspace:8.0.0-next-8.2
|
||||
version: link:../plugins/ui-theme
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
verdaccio-audit:
|
||||
specifier: workspace:13.0.0-next-8.2
|
||||
@ -2151,7 +2151,7 @@ importers:
|
||||
specifier: workspace:13.0.0-next-8.2
|
||||
version: link:../core/url
|
||||
'@verdaccio/utils':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../utils
|
||||
debug:
|
||||
specifier: 4.3.7
|
||||
@ -2164,7 +2164,7 @@ importers:
|
||||
version: 4.17.21
|
||||
devDependencies:
|
||||
'@verdaccio/api':
|
||||
specifier: workspace:7.1.0-next-8.2
|
||||
specifier: workspace:8.1.0-next-8.2
|
||||
version: link:../api
|
||||
'@verdaccio/test-helper':
|
||||
specifier: workspace:4.0.0-next-8.0
|
||||
@ -7401,6 +7401,10 @@ packages:
|
||||
/@jridgewell/sourcemap-codec@1.4.15:
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.5.0:
|
||||
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping@0.3.20:
|
||||
resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==}
|
||||
dependencies:
|
||||
@ -11595,7 +11599,7 @@ packages:
|
||||
http-status-codes: 2.2.0
|
||||
dev: true
|
||||
|
||||
/@vitest/coverage-v8@2.0.5(vitest@2.0.4):
|
||||
/@vitest/coverage-v8@2.0.5(vitest@2.1.2):
|
||||
resolution: {integrity: sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==}
|
||||
peerDependencies:
|
||||
vitest: 2.0.5
|
||||
@ -11612,58 +11616,69 @@ packages:
|
||||
std-env: 3.7.0
|
||||
test-exclude: 7.0.1
|
||||
tinyrainbow: 1.2.0
|
||||
vitest: 2.0.4(@types/node@20.14.12)
|
||||
vitest: 2.1.2(@types/node@20.14.12)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@vitest/expect@2.0.4:
|
||||
resolution: {integrity: sha512-39jr5EguIoanChvBqe34I8m1hJFI4+jxvdOpD7gslZrVQBKhh8H9eD7J/LJX4zakrw23W+dITQTDqdt43xVcJw==}
|
||||
/@vitest/expect@2.1.2:
|
||||
resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==}
|
||||
dependencies:
|
||||
'@vitest/spy': 2.0.4
|
||||
'@vitest/utils': 2.0.4
|
||||
'@vitest/spy': 2.1.2
|
||||
'@vitest/utils': 2.1.2
|
||||
chai: 5.1.1
|
||||
tinyrainbow: 1.2.0
|
||||
dev: true
|
||||
|
||||
/@vitest/pretty-format@2.0.4:
|
||||
resolution: {integrity: sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw==}
|
||||
/@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.3.1):
|
||||
resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==}
|
||||
peerDependencies:
|
||||
'@vitest/spy': 2.1.2
|
||||
msw: ^2.3.5
|
||||
vite: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.2
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.11
|
||||
vite: 5.3.1(@types/node@20.14.12)
|
||||
dev: true
|
||||
|
||||
/@vitest/pretty-format@2.1.2:
|
||||
resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==}
|
||||
dependencies:
|
||||
tinyrainbow: 1.2.0
|
||||
dev: true
|
||||
|
||||
/@vitest/pretty-format@2.0.5:
|
||||
resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==}
|
||||
/@vitest/runner@2.1.2:
|
||||
resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==}
|
||||
dependencies:
|
||||
tinyrainbow: 1.2.0
|
||||
dev: true
|
||||
|
||||
/@vitest/runner@2.0.4:
|
||||
resolution: {integrity: sha512-Gk+9Su/2H2zNfNdeJR124gZckd5st4YoSuhF1Rebi37qTXKnqYyFCd9KP4vl2cQHbtuVKjfEKrNJxHHCW8thbQ==}
|
||||
dependencies:
|
||||
'@vitest/utils': 2.0.4
|
||||
'@vitest/utils': 2.1.2
|
||||
pathe: 1.1.2
|
||||
dev: true
|
||||
|
||||
/@vitest/snapshot@2.0.4:
|
||||
resolution: {integrity: sha512-or6Mzoz/pD7xTvuJMFYEtso1vJo1S5u6zBTinfl+7smGUhqybn6VjzCDMhmTyVOFWwkCMuNjmNNxnyXPgKDoPw==}
|
||||
/@vitest/snapshot@2.1.2:
|
||||
resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==}
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.0.4
|
||||
magic-string: 0.30.10
|
||||
'@vitest/pretty-format': 2.1.2
|
||||
magic-string: 0.30.11
|
||||
pathe: 1.1.2
|
||||
dev: true
|
||||
|
||||
/@vitest/spy@2.0.4:
|
||||
resolution: {integrity: sha512-uTXU56TNoYrTohb+6CseP8IqNwlNdtPwEO0AWl+5j7NelS6x0xZZtP0bDWaLvOfUbaYwhhWp1guzXUxkC7mW7Q==}
|
||||
/@vitest/spy@2.1.2:
|
||||
resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==}
|
||||
dependencies:
|
||||
tinyspy: 3.0.0
|
||||
dev: true
|
||||
|
||||
/@vitest/utils@2.0.4:
|
||||
resolution: {integrity: sha512-Zc75QuuoJhOBnlo99ZVUkJIuq4Oj0zAkrQ2VzCqNCx6wAwViHEh5Fnp4fiJTE9rA+sAoXRf00Z9xGgfEzV6fzQ==}
|
||||
/@vitest/utils@2.1.2:
|
||||
resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==}
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.0.4
|
||||
estree-walker: 3.0.3
|
||||
'@vitest/pretty-format': 2.1.2
|
||||
loupe: 3.1.1
|
||||
tinyrainbow: 1.2.0
|
||||
dev: true
|
||||
@ -17515,21 +17530,6 @@ packages:
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
/execa@8.0.1:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
get-stream: 8.0.1
|
||||
human-signals: 5.0.0
|
||||
is-stream: 3.0.0
|
||||
merge-stream: 2.0.0
|
||||
npm-run-path: 5.3.0
|
||||
onetime: 6.0.0
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 3.0.0
|
||||
dev: true
|
||||
|
||||
/executable@4.1.1:
|
||||
resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==}
|
||||
engines: {node: '>=4'}
|
||||
@ -18577,11 +18577,6 @@ packages:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/get-stream@8.0.1:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
dev: true
|
||||
|
||||
/get-symbol-description@1.0.2:
|
||||
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -19506,11 +19501,6 @@ packages:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
/human-signals@5.0.0:
|
||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
dev: true
|
||||
|
||||
/humanize-ms@1.2.1:
|
||||
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||
dependencies:
|
||||
@ -20228,11 +20218,6 @@ packages:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/is-stream@3.0.0:
|
||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/is-string@1.0.7:
|
||||
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -21812,6 +21797,12 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/magic-string@0.30.11:
|
||||
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
dev: true
|
||||
|
||||
/magicast@0.3.4:
|
||||
resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==}
|
||||
dependencies:
|
||||
@ -22511,11 +22502,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/mimic-response@1.0.1:
|
||||
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
|
||||
engines: {node: '>=4'}
|
||||
@ -23365,13 +23351,6 @@ packages:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
/npm-run-path@5.3.0:
|
||||
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
path-key: 4.0.0
|
||||
dev: true
|
||||
|
||||
/npm-to-yarn@2.0.0:
|
||||
resolution: {integrity: sha512-/IbjiJ7vqbxfxJxAZ+QI9CCRjnIbvGxn5KQcSY9xHh0lMKc/Sgqmm7yp7KPmd6TiTZX5/KiSBKlkGHo59ucZbg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@ -23718,13 +23697,6 @@ packages:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
/onetime@6.0.0:
|
||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
dev: true
|
||||
|
||||
/open@7.4.2:
|
||||
resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
|
||||
engines: {node: '>=8'}
|
||||
@ -24207,11 +24179,6 @@ packages:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/path-key@4.0.0:
|
||||
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
@ -28452,11 +28419,6 @@ packages:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
/strip-final-newline@3.0.0:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/strip-indent@1.0.1:
|
||||
resolution: {integrity: sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -29128,8 +29090,12 @@ packages:
|
||||
/tiny-warning@1.0.3:
|
||||
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
|
||||
|
||||
/tinybench@2.8.0:
|
||||
resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==}
|
||||
/tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
dev: true
|
||||
|
||||
/tinyexec@0.3.0:
|
||||
resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==}
|
||||
dev: true
|
||||
|
||||
/tinypool@1.0.0:
|
||||
@ -30346,15 +30312,14 @@ packages:
|
||||
remove-trailing-separator: 1.1.0
|
||||
replace-ext: 1.0.1
|
||||
|
||||
/vite-node@2.0.4(@types/node@20.14.12):
|
||||
resolution: {integrity: sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA==}
|
||||
/vite-node@2.1.2(@types/node@20.14.12):
|
||||
resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.3.7(supports-color@5.5.0)
|
||||
pathe: 1.1.2
|
||||
tinyrainbow: 1.2.0
|
||||
vite: 5.3.1(@types/node@20.14.12)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
@ -30403,15 +30368,15 @@ packages:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vitest@2.0.4(@types/node@20.14.12):
|
||||
resolution: {integrity: sha512-luNLDpfsnxw5QSW4bISPe6tkxVvv5wn2BBs/PuDRkhXZ319doZyLOBr1sjfB5yCEpTiU7xCAdViM8TNVGPwoog==}
|
||||
/vitest@2.1.2(@types/node@20.14.12):
|
||||
resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
'@vitest/browser': 2.0.4
|
||||
'@vitest/ui': 2.0.4
|
||||
'@vitest/browser': 2.1.2
|
||||
'@vitest/ui': 2.1.2
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
@ -30428,29 +30393,30 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@types/node': 20.14.12
|
||||
'@vitest/expect': 2.0.4
|
||||
'@vitest/pretty-format': 2.0.5
|
||||
'@vitest/runner': 2.0.4
|
||||
'@vitest/snapshot': 2.0.4
|
||||
'@vitest/spy': 2.0.4
|
||||
'@vitest/utils': 2.0.4
|
||||
'@vitest/expect': 2.1.2
|
||||
'@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.3.1)
|
||||
'@vitest/pretty-format': 2.1.2
|
||||
'@vitest/runner': 2.1.2
|
||||
'@vitest/snapshot': 2.1.2
|
||||
'@vitest/spy': 2.1.2
|
||||
'@vitest/utils': 2.1.2
|
||||
chai: 5.1.1
|
||||
debug: 4.3.7(supports-color@5.5.0)
|
||||
execa: 8.0.1
|
||||
magic-string: 0.30.10
|
||||
magic-string: 0.30.11
|
||||
pathe: 1.1.2
|
||||
std-env: 3.7.0
|
||||
tinybench: 2.8.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 0.3.0
|
||||
tinypool: 1.0.0
|
||||
tinyrainbow: 1.2.0
|
||||
vite: 5.3.1(@types/node@20.14.12)
|
||||
vite-node: 2.0.4(@types/node@20.14.12)
|
||||
vite-node: 2.1.2(@types/node@20.14.12)
|
||||
why-is-node-running: 2.3.0
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
- lightningcss
|
||||
- msw
|
||||
- sass
|
||||
- stylus
|
||||
- sugarss
|
||||
|
Loading…
Reference in New Issue
Block a user