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:
parent
a0355872c6
commit
5909727ebc
13 changed files with 119 additions and 115 deletions
|
@ -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",
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 } });
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
|
|
@ -11,5 +11,5 @@ export enum PasscodeType {
|
|||
ForgotPassword = 'ForgotPassword',
|
||||
}
|
||||
export enum UsersPasswordEncryptionMethod {
|
||||
SaltAndPepper = 'SaltAndPepper',
|
||||
Argon2i = 'Argon2i',
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
112
pnpm-lock.yaml
generated
|
@ -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==}
|
||||
|
|
Loading…
Add table
Reference in a new issue