0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

refactor(core): use argon2 for password encryption (#738)

* refactor(core): use argon2 for password encryption

* refactor(core): adjust time cost
This commit is contained in:
Gao Sun 2022-05-06 16:38:38 +08:00 committed by GitHub
parent a0355872c6
commit 5909727ebc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 119 additions and 115 deletions

View file

@ -32,6 +32,7 @@
"@logto/phrases": "^0.1.0",
"@logto/schemas": "^0.1.0",
"@silverhand/essentials": "^1.1.0",
"argon2": "^0.28.5",
"chalk": "^4",
"dayjs": "^1.10.5",
"decamelize": "^5.0.0",

View file

@ -9,7 +9,6 @@ export const mockUser: User = {
roleNames: ['admin'],
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordEncryptionSalt: null,
name: null,
avatar: null,
identities: {
@ -31,7 +30,6 @@ export const mockUserList: User[] = [
roleNames: ['admin'],
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordEncryptionSalt: null,
name: null,
avatar: null,
identities: {},
@ -47,7 +45,6 @@ export const mockUserList: User[] = [
roleNames: ['admin'],
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordEncryptionSalt: null,
name: null,
avatar: null,
identities: {},
@ -63,7 +60,6 @@ export const mockUserList: User[] = [
roleNames: ['admin'],
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordEncryptionSalt: null,
name: null,
avatar: null,
identities: {},
@ -79,7 +75,6 @@ export const mockUserList: User[] = [
roleNames: ['admin'],
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordEncryptionSalt: null,
name: null,
avatar: null,
identities: {},
@ -95,7 +90,6 @@ export const mockUserList: User[] = [
roleNames: ['admin'],
passwordEncrypted: null,
passwordEncryptionMethod: null,
passwordEncryptionSalt: null,
name: null,
avatar: null,
identities: {},

View file

@ -53,12 +53,10 @@ describe('generateUserId()', () => {
});
describe('encryptUserPassword()', () => {
it('generates salt, encrypted and method', () => {
const { passwordEncryptionMethod, passwordEncrypted, passwordEncryptionSalt } =
encryptUserPassword('user-id', 'password');
expect(passwordEncryptionMethod).toEqual(UsersPasswordEncryptionMethod.SaltAndPepper);
expect(passwordEncrypted).toHaveLength(64);
expect(passwordEncryptionSalt).toHaveLength(21);
it('generates salt, encrypted and method', async () => {
const { passwordEncryptionMethod, passwordEncrypted } = await encryptUserPassword('password');
expect(passwordEncryptionMethod).toEqual(UsersPasswordEncryptionMethod.Argon2i);
expect(passwordEncrypted).toContain('argon2');
});
});

View file

@ -1,5 +1,5 @@
import { User, UsersPasswordEncryptionMethod } from '@logto/schemas';
import { nanoid } from 'nanoid';
import argon2 from 'argon2';
import pRetry from 'p-retry';
import { findUserByUsername, hasUserWithId, updateUserById } from '@/queries/user';
@ -23,24 +23,20 @@ export const generateUserId = async (retries = 500) =>
{ retries, factor: 0 } // No need for exponential backoff
);
export const encryptUserPassword = (
userId: string,
export const encryptUserPassword = async (
password: string
): {
passwordEncryptionSalt: string;
): Promise<{
passwordEncrypted: string;
passwordEncryptionMethod: UsersPasswordEncryptionMethod;
} => {
const passwordEncryptionSalt = nanoid();
const passwordEncryptionMethod = UsersPasswordEncryptionMethod.SaltAndPepper;
const passwordEncrypted = encryptPassword(
userId,
}> => {
const passwordEncryptionMethod = UsersPasswordEncryptionMethod.Argon2i;
const passwordEncrypted = await encryptPassword(
password,
passwordEncryptionSalt,
passwordEncryptionMethod
);
return { passwordEncrypted, passwordEncryptionMethod, passwordEncryptionSalt };
return { passwordEncrypted, passwordEncryptionMethod };
};
export const findUserByUsernameAndPassword = async (
@ -48,18 +44,13 @@ export const findUserByUsernameAndPassword = async (
password: string
): Promise<User> => {
const user = await findUserByUsername(username);
const { id, passwordEncrypted, passwordEncryptionMethod, passwordEncryptionSalt } = user;
const { passwordEncrypted, passwordEncryptionMethod } = user;
assertThat(
passwordEncrypted && passwordEncryptionMethod && passwordEncryptionSalt,
'session.invalid_sign_in_method'
);
assertThat(passwordEncrypted && passwordEncryptionMethod, 'session.invalid_sign_in_method');
assertThat(
encryptPassword(id, password, passwordEncryptionSalt, passwordEncryptionMethod) ===
passwordEncrypted,
'session.invalid_credentials'
);
const result = await argon2.verify(passwordEncrypted, password);
assertThat(result, 'session.invalid_credentials');
return user;
};

View file

@ -53,9 +53,8 @@ jest.mock('@/queries/user', () => ({
jest.mock('@/lib/user', () => ({
generateUserId: jest.fn(() => 'fooId'),
encryptUserPassword: jest.fn(() => ({
passwordEncryptionSalt: 'salt',
passwordEncrypted: 'password',
passwordEncryptionMethod: 'saltAndPepper',
passwordEncryptionMethod: 'Argon2i',
})),
}));
@ -242,7 +241,7 @@ describe('adminUserRoutes', () => {
const mockedUserId = 'foo';
const password = '123456';
const response = await userRequest.patch(`/users/${mockedUserId}/password`).send({ password });
expect(encryptUserPassword).toHaveBeenCalledWith(mockedUserId, password);
expect(encryptUserPassword).toHaveBeenCalledWith(password);
expect(updateUserById).toHaveBeenCalledTimes(1);
expect(response.status).toEqual(200);
expect(response.body).toEqual({

View file

@ -88,15 +88,13 @@ export default function adminUserRoutes<T extends AuthedRouter>(router: T) {
const id = await generateUserId();
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
encryptUserPassword(id, password);
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
const user = await insertUser({
id,
username,
passwordEncrypted,
passwordEncryptionMethod,
passwordEncryptionSalt,
name,
});
@ -169,13 +167,11 @@ export default function adminUserRoutes<T extends AuthedRouter>(router: T) {
await findUserById(userId);
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
encryptUserPassword(userId, password);
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
const user = await updateUserById(userId, {
passwordEncrypted,
passwordEncryptionMethod,
passwordEncryptionSalt,
});
ctx.body = pick(user, ...userInfoSelectFields);

View file

@ -23,10 +23,9 @@ jest.mock('@/lib/user', () => ({
return { id: 'user1' };
},
generateUserId: () => 'user1',
encryptUserPassword: (userId: string, password: string) => ({
passwordEncrypted: userId + '_' + password + '_user1',
passwordEncryptionMethod: 'SaltAndPepper',
passwordEncryptionSalt: 'user1',
encryptUserPassword: (password: string) => ({
passwordEncrypted: password + '_user1',
passwordEncryptionMethod: 'Argon2i',
}),
updateLastSignInAt: async (...args: unknown[]) => updateUserById(...args),
}));
@ -490,9 +489,8 @@ describe('sessionRoutes', () => {
expect.objectContaining({
id: 'user1',
username: 'username',
passwordEncrypted: 'user1_password_user1',
passwordEncryptionMethod: 'SaltAndPepper',
passwordEncryptionSalt: 'user1',
passwordEncrypted: 'password_user1',
passwordEncryptionMethod: 'Argon2i',
})
);
expect(response.body).toHaveProperty('redirectTo');

View file

@ -346,15 +346,13 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
const id = await generateUserId();
ctx.log(type, { userId: id });
const { passwordEncryptionSalt, passwordEncrypted, passwordEncryptionMethod } =
encryptUserPassword(id, password);
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
await insertUser({
id,
username,
passwordEncrypted,
passwordEncryptionMethod,
passwordEncryptionSalt,
});
await updateLastSignInAt(id);
await assignInteractionResults(ctx, provider, { login: { accountId: id } });

View file

@ -1,39 +1,18 @@
import { createHash } from 'crypto';
import { UsersPasswordEncryptionMethod } from '@logto/schemas';
import { repeat } from '@silverhand/essentials';
import argon2 from 'argon2';
import envSet from '@/env-set';
import assertThat from '@/utils/assert-that';
export const encryptPassword = (
id: string,
export const encryptPassword = async (
password: string,
salt: string,
method: UsersPasswordEncryptionMethod
): string => {
): Promise<string> => {
assertThat(
// FIXME:
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
method === UsersPasswordEncryptionMethod.SaltAndPepper,
method === UsersPasswordEncryptionMethod.Argon2i,
'password.unsupported_encryption_method',
{ method }
);
const sum = [...id].reduce(
(accumulator, current) => accumulator + (current.codePointAt(0) ?? 0),
0
);
const { peppers, iterationCount } = envSet.values.password;
const pepper = peppers[sum % peppers.length];
assertThat(pepper, 'password.pepper_not_found');
const result = repeat(iterationCount, password, (password) =>
createHash('sha256')
.update(salt + password + pepper)
.digest('hex')
);
return result;
return argon2.hash(password, { timeCost: 10 });
};

View file

@ -11,5 +11,5 @@ export enum PasscodeType {
ForgotPassword = 'ForgotPassword',
}
export enum UsersPasswordEncryptionMethod {
SaltAndPepper = 'SaltAndPepper',
Argon2i = 'Argon2i',
}

View file

@ -21,7 +21,6 @@ export type CreateUser = {
primaryPhone?: string | null;
passwordEncrypted?: string | null;
passwordEncryptionMethod?: UsersPasswordEncryptionMethod | null;
passwordEncryptionSalt?: string | null;
name?: string | null;
avatar?: string | null;
applicationId?: string | null;
@ -38,7 +37,6 @@ export type User = {
primaryPhone: string | null;
passwordEncrypted: string | null;
passwordEncryptionMethod: UsersPasswordEncryptionMethod | null;
passwordEncryptionSalt: string | null;
name: string | null;
avatar: string | null;
applicationId: string | null;
@ -55,7 +53,6 @@ const createGuard: Guard<CreateUser> = z.object({
primaryPhone: z.string().nullable().optional(),
passwordEncrypted: z.string().nullable().optional(),
passwordEncryptionMethod: z.nativeEnum(UsersPasswordEncryptionMethod).nullable().optional(),
passwordEncryptionSalt: z.string().nullable().optional(),
name: z.string().nullable().optional(),
avatar: z.string().nullable().optional(),
applicationId: z.string().nullable().optional(),
@ -75,7 +72,6 @@ export const Users: GeneratedSchema<CreateUser> = Object.freeze({
primaryPhone: 'primary_phone',
passwordEncrypted: 'password_encrypted',
passwordEncryptionMethod: 'password_encryption_method',
passwordEncryptionSalt: 'password_encryption_salt',
name: 'name',
avatar: 'avatar',
applicationId: 'application_id',
@ -91,7 +87,6 @@ export const Users: GeneratedSchema<CreateUser> = Object.freeze({
'primaryPhone',
'passwordEncrypted',
'passwordEncryptionMethod',
'passwordEncryptionSalt',
'name',
'avatar',
'applicationId',

View file

@ -1,4 +1,4 @@
create type users_password_encryption_method as enum ('SaltAndPepper');
create type users_password_encryption_method as enum ('Argon2i');
create table users (
id varchar(12) not null,
@ -7,7 +7,6 @@ create table users (
primary_phone varchar(128) unique,
password_encrypted varchar(128),
password_encryption_method users_password_encryption_method,
password_encryption_salt varchar(128),
name varchar(128),
avatar varchar(256),
application_id varchar(21) references applications(id),

112
pnpm-lock.yaml generated
View file

@ -571,6 +571,7 @@ importers:
'@types/node': ^16.3.1
'@types/oidc-provider': ^7.8.0
'@types/supertest': ^2.0.11
argon2: ^0.28.5
chalk: ^4
copyfiles: ^2.4.1
dayjs: ^1.10.5
@ -626,6 +627,7 @@ importers:
'@logto/phrases': link:../phrases
'@logto/schemas': link:../schemas
'@silverhand/essentials': 1.1.2
argon2: 0.28.5
chalk: 4.1.2
dayjs: 1.10.7
decamelize: 5.0.1
@ -5282,6 +5284,24 @@ packages:
react: 17.0.2
dev: true
/@mapbox/node-pre-gyp/1.0.9:
resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==}
hasBin: true
dependencies:
detect-libc: 2.0.1
https-proxy-agent: 5.0.0
make-dir: 3.1.0
node-fetch: 2.6.7
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
semver: 7.3.7
tar: 6.1.11
transitivePeerDependencies:
- encoding
- supports-color
dev: false
/@mdx-js/mdx/1.6.22:
resolution: {integrity: sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==}
dependencies:
@ -6337,6 +6357,11 @@ packages:
webcrypto-core: 1.7.3
dev: true
/@phc/format/1.0.0:
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
engines: {node: '>=10'}
dev: false
/@polka/url/1.0.0-next.21:
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
dev: true
@ -7671,7 +7696,6 @@ packages:
/abbrev/1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: true
/abortcontroller-polyfill/1.7.3:
resolution: {integrity: sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==}
@ -7759,7 +7783,6 @@ packages:
debug: 4.3.3
transitivePeerDependencies:
- supports-color
dev: true
/agentkeepalive/4.2.1:
resolution: {integrity: sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==}
@ -7924,7 +7947,6 @@ packages:
/aproba/2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
dev: true
/are-we-there-yet/1.1.7:
resolution: {integrity: sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==}
@ -7933,6 +7955,14 @@ packages:
readable-stream: 2.3.7
dev: true
/are-we-there-yet/2.0.0:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
engines: {node: '>=10'}
dependencies:
delegates: 1.0.0
readable-stream: 3.6.0
dev: false
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
@ -7941,6 +7971,19 @@ packages:
resolution: {integrity: sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==}
dev: true
/argon2/0.28.5:
resolution: {integrity: sha512-kGFCctzc3VWmR1aCOYjNgvoTmVF5uVBUtWlXCKKO54d1K+31zRz45KAcDIqMo2746ozv/52d25nfEekitaXP0w==}
engines: {node: '>=12.0.0'}
requiresBuild: true
dependencies:
'@mapbox/node-pre-gyp': 1.0.9
'@phc/format': 1.0.0
node-addon-api: 4.3.0
transitivePeerDependencies:
- encoding
- supports-color
dev: false
/argparse/1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
dependencies:
@ -8797,7 +8840,6 @@ packages:
/chownr/2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
dev: true
/chrome-trace-event/1.0.3:
resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==}
@ -8980,6 +9022,11 @@ packages:
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
/color-support/1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
hasBin: true
dev: false
/colord/2.9.2:
resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==}
dev: true
@ -9116,7 +9163,6 @@ packages:
/console-control-strings/1.1.0:
resolution: {integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=}
dev: true
/content-disposition/0.5.2:
resolution: {integrity: sha1-DPaLud318r55YcOoUXjLhdunjLQ=}
@ -9826,6 +9872,11 @@ packages:
hasBin: true
dev: true
/detect-libc/2.0.1:
resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
engines: {node: '>=8'}
dev: false
/detect-newline/3.1.0:
resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
engines: {node: '>=8'}
@ -11194,7 +11245,6 @@ packages:
engines: {node: '>= 8'}
dependencies:
minipass: 3.1.6
dev: true
/fs-monkey/1.0.3:
resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==}
@ -11202,7 +11252,6 @@ packages:
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
@ -11232,6 +11281,21 @@ packages:
wide-align: 1.1.5
dev: true
/gauge/3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
dependencies:
aproba: 2.0.0
color-support: 1.1.3
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.7
string-width: 4.2.3
strip-ansi: 6.0.1
wide-align: 1.1.5
dev: false
/generic-names/4.0.0:
resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==}
dependencies:
@ -11413,7 +11477,6 @@ packages:
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/global-dirs/0.1.1:
resolution: {integrity: sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=}
@ -11638,7 +11701,6 @@ packages:
/has-unicode/2.0.1:
resolution: {integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=}
dev: true
/has-yarn/2.1.0:
resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==}
@ -12026,7 +12088,6 @@ packages:
debug: 4.3.3
transitivePeerDependencies:
- supports-color
dev: true
/human-signals/2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
@ -12214,7 +12275,6 @@ packages:
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits/2.0.1:
resolution: {integrity: sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=}
@ -14104,7 +14164,6 @@ packages:
engines: {node: '>=8'}
dependencies:
semver: 6.3.0
dev: true
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
@ -14796,7 +14855,6 @@ packages:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimist-options/4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
@ -14869,7 +14927,6 @@ packages:
engines: {node: '>=8'}
dependencies:
yallist: 4.0.0
dev: true
/minizlib/1.3.3:
resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==}
@ -14883,7 +14940,6 @@ packages:
dependencies:
minipass: 3.1.6
yallist: 4.0.0
dev: true
/mkdirp-infer-owner/2.0.0:
resolution: {integrity: sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==}
@ -14905,7 +14961,6 @@ packages:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: true
/modify-values/1.0.1:
resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==}
@ -15046,6 +15101,10 @@ packages:
resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
dev: true
/node-addon-api/4.3.0:
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
dev: false
/node-cleanup/2.1.2:
resolution: {integrity: sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=}
dev: true
@ -15066,7 +15125,6 @@ packages:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: true
/node-forge/1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
@ -15166,7 +15224,6 @@ packages:
hasBin: true
dependencies:
abbrev: 1.1.1
dev: true
/normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
@ -15315,6 +15372,15 @@ packages:
set-blocking: 2.0.0
dev: true
/npmlog/5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
gauge: 3.0.2
set-blocking: 2.0.0
dev: false
/nprogress/0.2.0:
resolution: {integrity: sha1-y480xTIT2JVyP8urkH6UIq28r7E=}
dev: true
@ -15360,7 +15426,6 @@ packages:
/object-assign/4.1.1:
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
engines: {node: '>=0.10.0'}
dev: true
/object-hash/2.2.0:
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
@ -18004,7 +18069,6 @@ packages:
hasBin: true
dependencies:
glob: 7.2.0
dev: true
/roarr/2.15.4:
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
@ -18192,7 +18256,6 @@ packages:
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true
dev: true
/semver/7.0.0:
resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==}
@ -18212,7 +18275,6 @@ packages:
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/send/0.17.2:
resolution: {integrity: sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==}
@ -18291,7 +18353,6 @@ packages:
/set-blocking/2.0.0:
resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=}
dev: true
/setimmediate/1.0.5:
resolution: {integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=}
@ -19301,7 +19362,6 @@ packages:
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
dev: true
/temp-dir/1.0.0:
resolution: {integrity: sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=}
@ -19505,7 +19565,6 @@ packages:
/tr46/0.0.3:
resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=}
dev: true
/tr46/1.0.1:
resolution: {integrity: sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=}
@ -20478,7 +20537,6 @@ packages:
/webidl-conversions/3.0.1:
resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=}
dev: true
/webidl-conversions/4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
@ -20717,7 +20775,6 @@ packages:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: true
/whatwg-url/7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
@ -20765,7 +20822,6 @@ packages:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
dependencies:
string-width: 4.2.3
dev: true
/widest-line/3.1.0:
resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}