mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
refactor(core)!: update user scopes (#1922)
* refactor(core)!: update user scopes * refactor(core): add tests * refactor: update per review
This commit is contained in:
parent
0567fc6347
commit
8d22b5c468
12 changed files with 320 additions and 33 deletions
|
@ -1,6 +1,6 @@
|
|||
import { LogtoProvider } from '@logto/react';
|
||||
import { adminConsoleApplicationId, managementResource } from '@logto/schemas/lib/seeds';
|
||||
import { getBasename } from '@logto/shared';
|
||||
import { getBasename, UserScope } from '@logto/shared';
|
||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { SWRConfig } from 'swr';
|
||||
|
||||
|
@ -96,6 +96,7 @@ const App = () => (
|
|||
endpoint: window.location.origin,
|
||||
appId: adminConsoleApplicationId,
|
||||
resources: [managementResource.indicator],
|
||||
scopes: [UserScope.Identities, UserScope.CustomData],
|
||||
}}
|
||||
>
|
||||
<Main />
|
||||
|
|
|
@ -16,14 +16,19 @@ const UserInfo = () => {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [user, setUser] = useState<Pick<IdTokenClaims, 'sub' | 'username' | 'avatar'>>();
|
||||
const [user, setUser] =
|
||||
useState<Pick<Record<string, unknown> & IdTokenClaims, 'sub' | 'username' | 'picture'>>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (isAuthenticated) {
|
||||
const userInfo = await getIdTokenClaims();
|
||||
setUser(userInfo ?? { sub: '', username: 'N/A' }); // Provide a fallback to avoid infinite loading state
|
||||
// TODO: revert after SDK updated
|
||||
setUser({
|
||||
picture: undefined,
|
||||
...(userInfo ?? { sub: '', username: 'N/A' }),
|
||||
}); // Provide a fallback to avoid infinite loading state
|
||||
}
|
||||
})();
|
||||
}, [isAuthenticated, getIdTokenClaims]);
|
||||
|
@ -32,7 +37,7 @@ const UserInfo = () => {
|
|||
return <UserInfoSkeleton />;
|
||||
}
|
||||
|
||||
const { sub: id, username, avatar } = user;
|
||||
const { sub: id, username, picture } = user;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -43,7 +48,8 @@ const UserInfo = () => {
|
|||
setShowDropdown(true);
|
||||
}}
|
||||
>
|
||||
<img src={avatar || generateAvatarPlaceHolderById(id)} />
|
||||
{/* TODO: revert after SDK updated */}
|
||||
<img src={picture ? String(picture) : generateAvatarPlaceHolderById(id)} />
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.name}>{username}</div>
|
||||
</div>
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"@silverhand/ts-config": "1.0.0",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/etag": "^1.8.1",
|
||||
"@types/http-errors": "^1.8.2",
|
||||
"@types/inquirer": "^8.2.1",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
|
@ -87,6 +88,7 @@
|
|||
"@types/tar": "^6.1.2",
|
||||
"copyfiles": "^2.4.1",
|
||||
"eslint": "^8.21.0",
|
||||
"http-errors": "^1.6.3",
|
||||
"jest": "^28.1.3",
|
||||
"jest-matcher-specific-error": "^1.0.0",
|
||||
"lint-staged": "^13.0.0",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { existsSync } from 'fs';
|
||||
import { mkdir } from 'fs/promises';
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
|
@ -19,6 +20,8 @@ export const addConnectors = async (directory: string) => {
|
|||
});
|
||||
|
||||
if (!add.value) {
|
||||
await mkdir(directory);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createMockContext } from '@shopify/jest-koa-mocks';
|
||||
import createHttpError from 'http-errors';
|
||||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
||||
|
@ -32,6 +33,13 @@ describe('koaErrorHandler middleware', () => {
|
|||
expect(ctx.body).toEqual(error.body);
|
||||
});
|
||||
|
||||
// Koa will handle `HttpError` with a built-in manner. Hence it needs to return 200 here.
|
||||
it('expect to return 200 if error type is HttpError', async () => {
|
||||
next.mockRejectedValueOnce(createHttpError(404, 'not good'));
|
||||
await koaErrorHandler()(ctx, next);
|
||||
expect(ctx.status).toEqual(200);
|
||||
});
|
||||
|
||||
it('expect to return orginal body if not error found', async () => {
|
||||
await koaErrorHandler()(ctx, next);
|
||||
expect(ctx.status).toEqual(200);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { RequestErrorBody } from '@logto/schemas';
|
||||
import { Middleware } from 'koa';
|
||||
import { HttpError, Middleware } from 'koa';
|
||||
|
||||
import envSet from '@/env-set';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
@ -24,6 +24,11 @@ export default function koaErrorHandler<StateT, ContextT, BodyT>(): Middleware<
|
|||
return;
|
||||
}
|
||||
|
||||
// Koa will handle `HttpError` with a built-in manner.
|
||||
if (error instanceof HttpError) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.status = 500;
|
||||
ctx.body = { message: 'Internal server error.' };
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
import { CustomClientMetadataKey, userInfoSelectFields } from '@logto/schemas';
|
||||
import { CustomClientMetadataKey } from '@logto/schemas';
|
||||
import { userClaims } from '@logto/shared';
|
||||
import Koa from 'koa';
|
||||
import mount from 'koa-mount';
|
||||
import pick from 'lodash.pick';
|
||||
import { Provider, errors } from 'oidc-provider';
|
||||
import { snakeCase } from 'snake-case';
|
||||
import snakecaseKeys from 'snakecase-keys';
|
||||
|
||||
import envSet from '@/env-set';
|
||||
|
@ -18,6 +17,8 @@ import { findUserById } from '@/queries/user';
|
|||
import { routes } from '@/routes/consts';
|
||||
import { addOidcEventListeners } from '@/utils/oidc-provider-event-listener';
|
||||
|
||||
import { claimToUserKey, getUserClaims } from './scope';
|
||||
|
||||
export default async function initOidc(app: Koa): Promise<Provider> {
|
||||
const { issuer, cookieKeys, privateJwks, defaultIdTokenTtl, defaultRefreshTokenTtl } =
|
||||
envSet.values.oidc;
|
||||
|
@ -99,27 +100,33 @@ export default async function initOidc(app: Koa): Promise<Provider> {
|
|||
ctx.request.origin === origin ||
|
||||
isOriginAllowed(origin, client.metadata(), client.redirectUris),
|
||||
// https://github.com/panva/node-oidc-provider/blob/main/recipes/claim_configuration.md
|
||||
claims: {
|
||||
profile: userInfoSelectFields.map((value) => snakeCase(value)),
|
||||
},
|
||||
// Note node-provider will append `claims` here to the default claims instead of overriding
|
||||
claims: userClaims,
|
||||
// https://github.com/panva/node-oidc-provider/tree/main/docs#findaccount
|
||||
findAccount: async (_ctx, sub) => {
|
||||
const user = await findUserById(sub);
|
||||
const { username, name, avatar, roleNames } = user;
|
||||
|
||||
return {
|
||||
accountId: sub,
|
||||
claims: async (use) => {
|
||||
claims: async (use, scope, claims, rejected) => {
|
||||
return snakecaseKeys(
|
||||
{
|
||||
/**
|
||||
* This line is required because:
|
||||
* 1. TypeScript will complain since `Object.fromEntries()` has a fixed key type `string`
|
||||
* 2. Scope `openid` is removed from `UserScope` enum
|
||||
*/
|
||||
sub,
|
||||
username,
|
||||
name,
|
||||
avatar,
|
||||
roleNames,
|
||||
...(use === 'userinfo' && pick(user, ...userInfoSelectFields)),
|
||||
...Object.fromEntries(
|
||||
getUserClaims(use, scope, claims, rejected).map((claim) => [
|
||||
claim,
|
||||
user[claimToUserKey[claim]],
|
||||
])
|
||||
),
|
||||
},
|
||||
{ deep: false }
|
||||
{
|
||||
deep: false,
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
64
packages/core/src/oidc/scope.test.ts
Normal file
64
packages/core/src/oidc/scope.test.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { getUserClaims } from './scope';
|
||||
|
||||
const use = {
|
||||
idToken: 'id_token',
|
||||
userinfo: 'userinfo',
|
||||
};
|
||||
|
||||
describe('OIDC getUserClaims()', () => {
|
||||
it('should return proper ID Token claims', () => {
|
||||
expect(getUserClaims(use.idToken, 'openid profile', {}, [])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
]);
|
||||
|
||||
expect(getUserClaims(use.idToken, 'openid profile email phone', {}, [])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
'email',
|
||||
'email_verified',
|
||||
'phone_number',
|
||||
'phone_number_verified',
|
||||
]);
|
||||
|
||||
expect(getUserClaims(use.idToken, 'openid profile custom_data identities', {}, [])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
]);
|
||||
|
||||
expect(getUserClaims(use.idToken, 'openid profile email', {}, ['email_verified'])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
'email',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return proper Userinfo claims', () => {
|
||||
expect(getUserClaims(use.userinfo, 'openid profile custom_data identities', {}, [])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
'custom_data',
|
||||
'identities',
|
||||
]);
|
||||
});
|
||||
|
||||
// Ignore `_claims` since [Claims Parameter](https://github.com/panva/node-oidc-provider/tree/main/docs#featuresclaimsparameter) is not enabled
|
||||
it('should ignore claims parameter', () => {
|
||||
expect(getUserClaims(use.idToken, 'openid profile custom_data', { email: null }, [])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
]);
|
||||
});
|
||||
});
|
48
packages/core/src/oidc/scope.ts
Normal file
48
packages/core/src/oidc/scope.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { User } from '@logto/schemas';
|
||||
import { idTokenClaims, UserClaim, userinfoClaims, UserScope } from '@logto/shared';
|
||||
import { Nullable } from '@silverhand/essentials';
|
||||
import { ClaimsParameterMember } from 'oidc-provider';
|
||||
|
||||
export const claimToUserKey: Readonly<Record<UserClaim, keyof User>> = Object.freeze({
|
||||
name: 'name',
|
||||
picture: 'avatar',
|
||||
username: 'username',
|
||||
role_names: 'roleNames',
|
||||
email: 'primaryEmail',
|
||||
// LOG-4165: Change to proper key/function once profile fulfilling implemented
|
||||
email_verified: 'primaryEmail',
|
||||
phone_number: 'primaryPhone',
|
||||
// LOG-4165: Change to proper key/function once profile fulfilling implemented
|
||||
phone_number_verified: 'primaryPhone',
|
||||
custom_data: 'customData',
|
||||
identities: 'identities',
|
||||
});
|
||||
|
||||
// Ignore `_claims` since [Claims Parameter](https://github.com/panva/node-oidc-provider/tree/main/docs#featuresclaimsparameter) is not enabled
|
||||
export const getUserClaims = (
|
||||
use: string,
|
||||
scope: string,
|
||||
_claims: Record<string, Nullable<ClaimsParameterMember>>,
|
||||
rejected: string[]
|
||||
): UserClaim[] => {
|
||||
const scopes = scope.split(' ');
|
||||
const isUserinfo = use === 'userinfo';
|
||||
const allScopes = Object.values(UserScope);
|
||||
|
||||
return scopes
|
||||
.flatMap((raw) => {
|
||||
const scope = allScopes.find((value) => value === raw);
|
||||
|
||||
if (!scope) {
|
||||
// Ignore invalid scopes
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isUserinfo) {
|
||||
return [...idTokenClaims[scope], ...userinfoClaims[scope]];
|
||||
}
|
||||
|
||||
return idTokenClaims[scope];
|
||||
})
|
||||
.filter((claim) => !rejected.includes(claim));
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
export * from './utilities';
|
||||
export * from './regex';
|
||||
export * from './language';
|
||||
export * from './scope';
|
||||
|
|
85
packages/shared/src/scope.ts
Normal file
85
packages/shared/src/scope.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
export enum PreservedScope {
|
||||
OpenId = 'openid',
|
||||
OfflineAccess = 'offline_access',
|
||||
}
|
||||
|
||||
export type UserClaim =
|
||||
| 'name'
|
||||
| 'picture'
|
||||
| 'username'
|
||||
| 'role_names'
|
||||
| 'email'
|
||||
| 'email_verified'
|
||||
| 'phone_number'
|
||||
| 'phone_number_verified'
|
||||
| 'custom_data'
|
||||
| 'identities';
|
||||
|
||||
/**
|
||||
* Scopes for ID Token and Userinfo Endpoint.
|
||||
*/
|
||||
export enum UserScope {
|
||||
/**
|
||||
* Scope for basic user info.
|
||||
*
|
||||
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
||||
*/
|
||||
Profile = 'profile',
|
||||
/**
|
||||
* Scope for user email address.
|
||||
*
|
||||
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
||||
*/
|
||||
Email = 'email',
|
||||
/**
|
||||
* Scope for user phone number.
|
||||
*
|
||||
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
||||
*/
|
||||
Phone = 'phone',
|
||||
/**
|
||||
* Scope for user's custom data.
|
||||
*
|
||||
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
||||
*/
|
||||
CustomData = 'custom_data',
|
||||
/**
|
||||
* Scope for user's social identity details.
|
||||
*
|
||||
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
||||
*/
|
||||
Identities = 'identities',
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapped claims that ID Token includes.
|
||||
*/
|
||||
export const idTokenClaims: Readonly<Record<UserScope, UserClaim[]>> = Object.freeze({
|
||||
[UserScope.Profile]: ['name', 'picture', 'username', 'role_names'],
|
||||
[UserScope.Email]: ['email', 'email_verified'],
|
||||
[UserScope.Phone]: ['phone_number', 'phone_number_verified'],
|
||||
[UserScope.CustomData]: [],
|
||||
[UserScope.Identities]: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* Additional claims that Userinfo Endpoint returns.
|
||||
*/
|
||||
export const userinfoClaims: Readonly<Record<UserScope, UserClaim[]>> = Object.freeze({
|
||||
[UserScope.Profile]: [],
|
||||
[UserScope.Email]: [],
|
||||
[UserScope.Phone]: [],
|
||||
[UserScope.CustomData]: ['custom_data'],
|
||||
[UserScope.Identities]: ['identities'],
|
||||
});
|
||||
|
||||
export const userClaims: Readonly<Record<UserScope, UserClaim[]>> = Object.freeze(
|
||||
// Hard to infer type directly, use `as` for a workaround.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
Object.fromEntries(
|
||||
Object.values(UserScope).map((current) => [
|
||||
current,
|
||||
[...idTokenClaims[current], ...userinfoClaims[current]],
|
||||
])
|
||||
) as Record<UserScope, UserClaim[]>
|
||||
);
|
83
pnpm-lock.yaml
generated
83
pnpm-lock.yaml
generated
|
@ -186,6 +186,7 @@ importers:
|
|||
'@silverhand/ts-config': 1.0.0
|
||||
'@types/debug': ^4.1.7
|
||||
'@types/etag': ^1.8.1
|
||||
'@types/http-errors': ^1.8.2
|
||||
'@types/inquirer': ^8.2.1
|
||||
'@types/jest': ^28.1.6
|
||||
'@types/js-yaml': ^4.0.5
|
||||
|
@ -211,6 +212,7 @@ importers:
|
|||
etag: ^1.8.1
|
||||
got: ^11.8.2
|
||||
hash-wasm: ^4.9.0
|
||||
http-errors: ^1.6.3
|
||||
i18next: ^21.8.16
|
||||
iconv-lite: 0.6.3
|
||||
inquirer: ^8.2.2
|
||||
|
@ -298,6 +300,7 @@ importers:
|
|||
'@silverhand/ts-config': 1.0.0_typescript@4.7.4
|
||||
'@types/debug': 4.1.7
|
||||
'@types/etag': 1.8.1
|
||||
'@types/http-errors': 1.8.2
|
||||
'@types/inquirer': 8.2.1
|
||||
'@types/jest': 28.1.6
|
||||
'@types/js-yaml': 4.0.5
|
||||
|
@ -314,6 +317,7 @@ importers:
|
|||
'@types/tar': 6.1.2
|
||||
copyfiles: 2.4.1
|
||||
eslint: 8.21.0
|
||||
http-errors: 1.8.1
|
||||
jest: 28.1.3_@types+node@16.11.12
|
||||
jest-matcher-specific-error: 1.0.0
|
||||
lint-staged: 13.0.0
|
||||
|
@ -3810,7 +3814,7 @@ packages:
|
|||
eslint-import-resolver-typescript: 3.4.0_jatgrcxl4x7ywe7ak6cnjca2ae
|
||||
eslint-plugin-consistent-default-export-name: 0.0.15
|
||||
eslint-plugin-eslint-comments: 3.2.0_eslint@8.21.0
|
||||
eslint-plugin-import: 2.26.0_eslint@8.21.0
|
||||
eslint-plugin-import: 2.26.0_klqlxqqxnpnfpttri4irupweri
|
||||
eslint-plugin-no-use-extend-native: 0.5.0
|
||||
eslint-plugin-node: 11.1.0_eslint@8.21.0
|
||||
eslint-plugin-prettier: 4.2.1_h62lvancfh4b7r6zn2dgodrh5e
|
||||
|
@ -3820,6 +3824,7 @@ packages:
|
|||
eslint-plugin-unused-imports: 2.0.0_i7ihj7mda6acsfp32zwgvvndem
|
||||
prettier: 2.7.1
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
@ -3853,7 +3858,7 @@ packages:
|
|||
'@jest/types': 28.1.3
|
||||
deepmerge: 4.2.2
|
||||
identity-obj-proxy: 3.0.0
|
||||
jest: 28.1.3_k5ytkvaprncdyzidqqws5bqksq
|
||||
jest: 28.1.3_@types+node@16.11.12
|
||||
jest-matcher-specific-error: 1.0.0
|
||||
jest-transform-stub: 2.0.0
|
||||
ts-jest: 28.0.7_lhw3xkmzugq5tscs3x2ndm4sby
|
||||
|
@ -4285,8 +4290,8 @@ packages:
|
|||
/@types/http-cache-semantics/4.0.1:
|
||||
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
|
||||
|
||||
/@types/http-errors/1.8.1:
|
||||
resolution: {integrity: sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==}
|
||||
/@types/http-errors/1.8.2:
|
||||
resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==}
|
||||
dev: true
|
||||
|
||||
/@types/inquirer/8.2.1:
|
||||
|
@ -4387,7 +4392,7 @@ packages:
|
|||
'@types/content-disposition': 0.5.4
|
||||
'@types/cookies': 0.7.7
|
||||
'@types/http-assert': 1.5.3
|
||||
'@types/http-errors': 1.8.1
|
||||
'@types/http-errors': 1.8.2
|
||||
'@types/keygrip': 1.0.2
|
||||
'@types/koa-compose': 3.2.5
|
||||
'@types/node': 16.11.12
|
||||
|
@ -5922,8 +5927,8 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
is-text-path: 1.0.1
|
||||
JSONStream: 1.3.5
|
||||
is-text-path: 1.0.1
|
||||
lodash: 4.17.21
|
||||
meow: 8.1.2
|
||||
split2: 3.2.2
|
||||
|
@ -6213,16 +6218,38 @@ packages:
|
|||
|
||||
/debug/2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
dev: true
|
||||
|
||||
/debug/3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
dev: true
|
||||
|
||||
/debug/3.2.7_supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
supports-color: 5.5.0
|
||||
dev: true
|
||||
|
||||
/debug/4.3.3:
|
||||
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
@ -6341,7 +6368,7 @@ packages:
|
|||
resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=}
|
||||
|
||||
/depd/1.1.2:
|
||||
resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=}
|
||||
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
/depd/2.0.0:
|
||||
|
@ -6754,6 +6781,8 @@ packages:
|
|||
dependencies:
|
||||
debug: 3.2.7
|
||||
resolve: 1.22.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-typescript/3.4.0_jatgrcxl4x7ywe7ak6cnjca2ae:
|
||||
|
@ -6766,7 +6795,7 @@ packages:
|
|||
debug: 4.3.4
|
||||
enhanced-resolve: 5.10.0
|
||||
eslint: 8.21.0
|
||||
eslint-plugin-import: 2.26.0_eslint@8.21.0
|
||||
eslint-plugin-import: 2.26.0_klqlxqqxnpnfpttri4irupweri
|
||||
get-tsconfig: 4.2.0
|
||||
globby: 13.1.2
|
||||
is-core-module: 2.9.0
|
||||
|
@ -6776,12 +6805,31 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-module-utils/2.7.3:
|
||||
/eslint-module-utils/2.7.3_dirjbmf3bsnpt3git34hjh5rju:
|
||||
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
eslint-import-resolver-node: '*'
|
||||
eslint-import-resolver-typescript: '*'
|
||||
eslint-import-resolver-webpack: '*'
|
||||
peerDependenciesMeta:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
eslint-import-resolver-node:
|
||||
optional: true
|
||||
eslint-import-resolver-typescript:
|
||||
optional: true
|
||||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.32.0_qugx7qdu5zevzvxaiqyxfiwquq
|
||||
debug: 3.2.7
|
||||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-import-resolver-typescript: 3.4.0_jatgrcxl4x7ywe7ak6cnjca2ae
|
||||
find-up: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-consistent-default-export-name/0.0.15:
|
||||
|
@ -6814,19 +6862,24 @@ packages:
|
|||
ignore: 5.2.0
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-import/2.26.0_eslint@8.21.0:
|
||||
/eslint-plugin-import/2.26.0_klqlxqqxnpnfpttri4irupweri:
|
||||
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
||||
peerDependenciesMeta:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.32.0_qugx7qdu5zevzvxaiqyxfiwquq
|
||||
array-includes: 3.1.4
|
||||
array.prototype.flat: 1.2.5
|
||||
debug: 2.6.9
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.21.0
|
||||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-module-utils: 2.7.3
|
||||
eslint-module-utils: 2.7.3_dirjbmf3bsnpt3git34hjh5rju
|
||||
has: 1.0.3
|
||||
is-core-module: 2.9.0
|
||||
is-glob: 4.0.3
|
||||
|
@ -6834,6 +6887,10 @@ packages:
|
|||
object.values: 1.1.5
|
||||
resolve: 1.22.0
|
||||
tsconfig-paths: 3.14.1
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-no-use-extend-native/0.5.0:
|
||||
|
@ -11339,7 +11396,7 @@ packages:
|
|||
requiresBuild: true
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
debug: 3.2.7
|
||||
debug: 3.2.7_supports-color@5.5.0
|
||||
ignore-by-default: 1.0.1
|
||||
minimatch: 3.1.2
|
||||
pstree.remy: 1.1.8
|
||||
|
@ -14807,7 +14864,7 @@ packages:
|
|||
'@jest/types': 28.1.3
|
||||
bs-logger: 0.2.6
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
jest: 28.1.3_k5ytkvaprncdyzidqqws5bqksq
|
||||
jest: 28.1.3_@types+node@16.11.12
|
||||
jest-util: 28.1.3
|
||||
json5: 2.2.1
|
||||
lodash.memoize: 4.1.2
|
||||
|
|
Loading…
Add table
Reference in a new issue