From 124e5f2de7fbe2d4ef09b0c1aafd295f6179a6f2 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Mon, 7 Oct 2024 08:28:30 +0200 Subject: [PATCH] chore: migrate htpasswd package vitest (#4889) * migrate htpasswd package * update versions * Update htpasswd.passwords.test.ts --- .changeset/hip-eggs-serve.md | 5 + package.json | 2 +- packages/api/package.json | 4 +- packages/auth/package.json | 2 +- packages/config/package.json | 2 +- packages/core/tarball/package.json | 2 +- packages/middleware/package.json | 2 +- .../plugins/active-directory/package.json | 2 +- packages/plugins/htpasswd/jest.config.js | 3 - packages/plugins/htpasswd/package.json | 2 +- packages/plugins/htpasswd/src/crypt3.ts | 20 +- packages/plugins/htpasswd/src/crypto-utils.ts | 5 + packages/plugins/htpasswd/src/htpasswd.ts | 39 +- packages/plugins/htpasswd/src/utils.ts | 17 + .../htpasswd/tests/__fixtures__/htpasswd | 9 +- .../tests/__snapshots__/utils.test.ts.snap | 18 +- .../plugins/htpasswd/tests/crypt3.test.ts | 19 +- .../htpasswd/tests/htpasswd.adduser.test.ts | 254 +++++++++++++ .../htpasswd/tests/htpasswd.passwords.test.ts | 96 +++++ .../plugins/htpasswd/tests/htpasswd.test.ts | 346 +++--------------- packages/plugins/htpasswd/tests/utils.test.ts | 55 +-- packages/plugins/local-storage/package.json | 2 +- packages/proxy/package.json | 2 +- packages/server/express/package.json | 6 +- packages/server/fastify/package.json | 2 +- packages/store/package.json | 2 +- packages/tools/helpers/package.json | 2 +- packages/utils/package.json | 2 +- packages/verdaccio/package.json | 2 +- packages/web/package.json | 6 +- pnpm-lock.yaml | 216 +++++------ 31 files changed, 636 insertions(+), 510 deletions(-) create mode 100644 .changeset/hip-eggs-serve.md delete mode 100644 packages/plugins/htpasswd/jest.config.js create mode 100644 packages/plugins/htpasswd/src/crypto-utils.ts create mode 100644 packages/plugins/htpasswd/tests/htpasswd.adduser.test.ts create mode 100644 packages/plugins/htpasswd/tests/htpasswd.passwords.test.ts diff --git a/.changeset/hip-eggs-serve.md b/.changeset/hip-eggs-serve.md new file mode 100644 index 000000000..6f8ae9448 --- /dev/null +++ b/.changeset/hip-eggs-serve.md @@ -0,0 +1,5 @@ +--- +'verdaccio-htpasswd': patch +--- + +chore: add debug code to htpasswd package diff --git a/package.json b/package.json index bffb37605..d3e20ac3d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/api/package.json b/packages/api/package.json index cc95ceb84..5893ec016 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -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", diff --git a/packages/auth/package.json b/packages/auth/package.json index 26c1bd440..ad2f75dbf 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -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" diff --git a/packages/config/package.json b/packages/config/package.json index f0d0bfb79..aecce5f90 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -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", diff --git a/packages/core/tarball/package.json b/packages/core/tarball/package.json index 53dfb41d5..934e8ddeb 100644 --- a/packages/core/tarball/package.json +++ b/packages/core/tarball/package.json @@ -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", diff --git a/packages/middleware/package.json b/packages/middleware/package.json index 9bc3c0189..ccfb5fb15 100644 --- a/packages/middleware/package.json +++ b/packages/middleware/package.json @@ -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", diff --git a/packages/plugins/active-directory/package.json b/packages/plugins/active-directory/package.json index 270231cc7..1a509802c 100644 --- a/packages/plugins/active-directory/package.json +++ b/packages/plugins/active-directory/package.json @@ -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", diff --git a/packages/plugins/htpasswd/jest.config.js b/packages/plugins/htpasswd/jest.config.js deleted file mode 100644 index 1c3fbdb05..000000000 --- a/packages/plugins/htpasswd/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const config = require('../../../jest/config'); - -module.exports = Object.assign({}, config, {}); diff --git a/packages/plugins/htpasswd/package.json b/packages/plugins/htpasswd/package.json index 97dc5b5e4..6d0476050 100644 --- a/packages/plugins/htpasswd/package.json +++ b/packages/plugins/htpasswd/package.json @@ -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", diff --git a/packages/plugins/htpasswd/src/crypt3.ts b/packages/plugins/htpasswd/src/crypt3.ts index 56395d045..2468140a5 100644 --- a/packages/plugins/htpasswd/src/crypt3.ts +++ b/packages/plugins/htpasswd/src/crypt3.ts @@ -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}`); diff --git a/packages/plugins/htpasswd/src/crypto-utils.ts b/packages/plugins/htpasswd/src/crypto-utils.ts new file mode 100644 index 000000000..aab88a25e --- /dev/null +++ b/packages/plugins/htpasswd/src/crypto-utils.ts @@ -0,0 +1,5 @@ +import crypto from 'crypto'; + +export function randomBytes(bytes) { + return crypto.randomBytes(bytes); +} diff --git a/packages/plugins/htpasswd/src/htpasswd.ts b/packages/plugins/htpasswd/src/htpasswd.ts index 52ed56de1..d3d6d96bc 100644 --- a/packages/plugins/htpasswd/src/htpasswd.ts +++ b/packages/plugins/htpasswd/src/htpasswd.ts @@ -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 => { 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); } }); diff --git a/packages/plugins/htpasswd/src/utils.ts b/packages/plugins/htpasswd/src/utils.ts index c9a15df76..1fc27443b 100644 --- a/packages/plugins/htpasswd/src/utils.ts +++ b/packages/plugins/htpasswd/src/utils.ts @@ -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 { 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 { + 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(); +} diff --git a/packages/plugins/htpasswd/tests/__fixtures__/htpasswd b/packages/plugins/htpasswd/tests/__fixtures__/htpasswd index 5c8a14a3f..6db4da32b 100644 --- a/packages/plugins/htpasswd/tests/__fixtures__/htpasswd +++ b/packages/plugins/htpasswd/tests/__fixtures__/htpasswd @@ -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 diff --git a/packages/plugins/htpasswd/tests/__snapshots__/utils.test.ts.snap b/packages/plugins/htpasswd/tests/__snapshots__/utils.test.ts.snap index ba3c7f818..067449a60 100644 --- a/packages/plugins/htpasswd/tests/__snapshots__/utils.test.ts.snap +++ b/packages/plugins/htpasswd/tests/__snapshots__/utils.test.ts.snap @@ -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 " `; diff --git a/packages/plugins/htpasswd/tests/crypt3.test.ts b/packages/plugins/htpasswd/tests/crypt3.test.ts index 7df7c7f12..c13827eb9 100644 --- a/packages/plugins/htpasswd/tests/crypt3.test.ts +++ b/packages/plugins/htpasswd/tests/crypt3.test.ts @@ -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()), + randomBytes: (len: number): { toString: () => string } => { + return { + toString: (): string => '/UEGzD0RxSNDZA=='.substring(0, len), + }; + }, +})); describe('createSalt', () => { test('should match with the correct salt type', () => { diff --git a/packages/plugins/htpasswd/tests/htpasswd.adduser.test.ts b/packages/plugins/htpasswd/tests/htpasswd.adduser.test.ts new file mode 100644 index 000000000..a4875afc6 --- /dev/null +++ b/packages/plugins/htpasswd/tests/htpasswd.adduser.test.ts @@ -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()), + 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()), + 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()), + 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()), + 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()), + 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()), + 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()), + 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()), + 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()), + 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()), + 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); + }); + }); + }); +}); diff --git a/packages/plugins/htpasswd/tests/htpasswd.passwords.test.ts b/packages/plugins/htpasswd/tests/htpasswd.passwords.test.ts new file mode 100644 index 000000000..e487aa502 --- /dev/null +++ b/packages/plugins/htpasswd/tests/htpasswd.passwords.test.ts @@ -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()), + 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()), + 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); + }); + }); +}); diff --git a/packages/plugins/htpasswd/tests/htpasswd.test.ts b/packages/plugins/htpasswd/tests/htpasswd.test.ts index 65b5c6b61..3e2f28d72 100644 --- a/packages/plugins/htpasswd/tests/htpasswd.test.ts +++ b/packages/plugins/htpasswd/tests/htpasswd.test.ts @@ -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; +} 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; + } 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; - 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); }); }); diff --git a/packages/plugins/htpasswd/tests/utils.test.ts b/packages/plugins/htpasswd/tests/utils.test.ts index 8f152c711..70f28a7d0 100644 --- a/packages/plugins/htpasswd/tests/utils.test.ts +++ b/packages/plugins/htpasswd/tests/utils.test.ts @@ -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).status).toEqual(401); expect((input as HttpError).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).status).toEqual(409); expect((input as HttpError).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).status).toEqual(409); expect((input as HttpError).message).toEqual('username is already registered'); diff --git a/packages/plugins/local-storage/package.json b/packages/plugins/local-storage/package.json index a91f8ca7a..4930669e6 100644 --- a/packages/plugins/local-storage/package.json +++ b/packages/plugins/local-storage/package.json @@ -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", diff --git a/packages/proxy/package.json b/packages/proxy/package.json index 8e1cb363e..2f2d1ba5f 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -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", diff --git a/packages/server/express/package.json b/packages/server/express/package.json index ab7c3ef05..212d801e0 100644 --- a/packages/server/express/package.json +++ b/packages/server/express/package.json @@ -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", diff --git a/packages/server/fastify/package.json b/packages/server/fastify/package.json index cb090be25..e4d8a7650 100644 --- a/packages/server/fastify/package.json +++ b/packages/server/fastify/package.json @@ -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", diff --git a/packages/store/package.json b/packages/store/package.json index 692687768..233568e14 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -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", diff --git a/packages/tools/helpers/package.json b/packages/tools/helpers/package.json index daba76b0c..5b4b57c45 100644 --- a/packages/tools/helpers/package.json +++ b/packages/tools/helpers/package.json @@ -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", diff --git a/packages/utils/package.json b/packages/utils/package.json index 488acf648..079bd19f3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -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", diff --git a/packages/verdaccio/package.json b/packages/verdaccio/package.json index bd81081fa..a53d6fcf2 100644 --- a/packages/verdaccio/package.json +++ b/packages/verdaccio/package.json @@ -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" }, diff --git a/packages/web/package.json b/packages/web/package.json index 3bdc65eaf..90dfd8b9e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e7c165b2..50f1c06a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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