mirror of
https://github.com/logto-io/logto.git
synced 2025-01-13 21:30:30 -05:00
feat(console,core)!: use rbac scope to control management resource (#2942)
This commit is contained in:
parent
9292bc086d
commit
9ed66a8593
9 changed files with 59 additions and 57 deletions
|
@ -23,7 +23,7 @@
|
||||||
"@logto/language-kit": "workspace:*",
|
"@logto/language-kit": "workspace:*",
|
||||||
"@logto/phrases": "workspace:*",
|
"@logto/phrases": "workspace:*",
|
||||||
"@logto/phrases-ui": "workspace:*",
|
"@logto/phrases-ui": "workspace:*",
|
||||||
"@logto/react": "1.0.0-beta.14",
|
"@logto/react": "1.0.0-beta.15",
|
||||||
"@logto/schemas": "workspace:*",
|
"@logto/schemas": "workspace:*",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"@parcel/core": "2.8.2",
|
"@parcel/core": "2.8.2",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { UserScope } from '@logto/core-kit';
|
import { UserScope } from '@logto/core-kit';
|
||||||
import { LogtoProvider } from '@logto/react';
|
import { LogtoProvider } from '@logto/react';
|
||||||
import { adminConsoleApplicationId, managementResource } from '@logto/schemas';
|
import {
|
||||||
|
adminConsoleApplicationId,
|
||||||
|
managementResource,
|
||||||
|
managementResourceScope,
|
||||||
|
} from '@logto/schemas';
|
||||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||||
import { SWRConfig } from 'swr';
|
import { SWRConfig } from 'swr';
|
||||||
|
|
||||||
|
@ -139,7 +143,7 @@ const App = () => (
|
||||||
endpoint: window.location.origin,
|
endpoint: window.location.origin,
|
||||||
appId: adminConsoleApplicationId,
|
appId: adminConsoleApplicationId,
|
||||||
resources: [managementResource.indicator],
|
resources: [managementResource.indicator],
|
||||||
scopes: [UserScope.Identities, UserScope.CustomData],
|
scopes: [UserScope.Identities, UserScope.CustomData, managementResourceScope.name],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Main />
|
<Main />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { UserRole } from '@logto/schemas';
|
import { managementResourceScope } from '@logto/schemas';
|
||||||
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
||||||
import type { Context } from 'koa';
|
import type { Context } from 'koa';
|
||||||
import type { IRouterParamContext } from 'koa-router';
|
import type { IRouterParamContext } from 'koa-router';
|
||||||
|
@ -15,7 +15,9 @@ const { jest } = import.meta;
|
||||||
const { mockEsm } = createMockUtils(jest);
|
const { mockEsm } = createMockUtils(jest);
|
||||||
|
|
||||||
const { jwtVerify } = mockEsm('jose', () => ({
|
const { jwtVerify } = mockEsm('jose', () => ({
|
||||||
jwtVerify: jest.fn().mockReturnValue({ payload: { sub: 'fooUser', role_names: ['admin'] } }),
|
jwtVerify: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ payload: { sub: 'fooUser', scope: managementResourceScope.name } }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const koaAuth = await pickDefault(import('./koa-auth.js'));
|
const koaAuth = await pickDefault(import('./koa-auth.js'));
|
||||||
|
@ -169,7 +171,7 @@ describe('koaAuth middleware', () => {
|
||||||
expect(ctx.auth).toEqual({ type: 'app', id: 'bar' });
|
expect(ctx.auth).toEqual({ type: 'app', id: 'bar' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('expect to throw if jwt role_names is missing', async () => {
|
it('expect to throw if jwt scope is missing', async () => {
|
||||||
jwtVerify.mockImplementationOnce(() => ({ payload: { sub: 'fooUser' } }));
|
jwtVerify.mockImplementationOnce(() => ({ payload: { sub: 'fooUser' } }));
|
||||||
|
|
||||||
ctx.request = {
|
ctx.request = {
|
||||||
|
@ -179,14 +181,14 @@ describe('koaAuth middleware', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(koaAuth(envSetForTest, UserRole.Admin)(ctx, next)).rejects.toMatchError(
|
await expect(
|
||||||
forbiddenError
|
koaAuth(envSetForTest, managementResourceScope.name)(ctx, next)
|
||||||
);
|
).rejects.toMatchError(forbiddenError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('expect to throw if jwt role_names does not include admin', async () => {
|
it('expect to throw if jwt scope does not include management resource scope', async () => {
|
||||||
jwtVerify.mockImplementationOnce(() => ({
|
jwtVerify.mockImplementationOnce(() => ({
|
||||||
payload: { sub: 'fooUser', role_names: ['foo'] },
|
payload: { sub: 'fooUser', scope: 'foo' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ctx.request = {
|
ctx.request = {
|
||||||
|
@ -196,9 +198,9 @@ describe('koaAuth middleware', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(koaAuth(envSetForTest, UserRole.Admin)(ctx, next)).rejects.toMatchError(
|
await expect(
|
||||||
forbiddenError
|
koaAuth(envSetForTest, managementResourceScope.name)(ctx, next)
|
||||||
);
|
).rejects.toMatchError(forbiddenError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('expect to throw unauthorized error if unknown error occurs', async () => {
|
it('expect to throw unauthorized error if unknown error occurs', async () => {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import type { IncomingHttpHeaders } from 'http';
|
import type { IncomingHttpHeaders } from 'http';
|
||||||
|
|
||||||
import { UserRole, managementResource } from '@logto/schemas';
|
import { managementResource, managementResourceScope } from '@logto/schemas';
|
||||||
import type { Optional } from '@silverhand/essentials';
|
import type { Optional } from '@silverhand/essentials';
|
||||||
import { conditional } from '@silverhand/essentials';
|
|
||||||
import { jwtVerify } from 'jose';
|
import { jwtVerify } from 'jose';
|
||||||
import type { MiddlewareType, Request } from 'koa';
|
import type { MiddlewareType, Request } from 'koa';
|
||||||
import type { IRouterParamContext } from 'koa-router';
|
import type { IRouterParamContext } from 'koa-router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { EnvSet } from '#src/env-set/index.js';
|
import { EnvSet } from '#src/env-set/index.js';
|
||||||
import RequestError from '#src/errors/RequestError/index.js';
|
import RequestError from '#src/errors/RequestError/index.js';
|
||||||
|
@ -42,6 +42,7 @@ const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) =
|
||||||
type TokenInfo = {
|
type TokenInfo = {
|
||||||
sub: string;
|
sub: string;
|
||||||
clientId: unknown;
|
clientId: unknown;
|
||||||
|
scopes: string[];
|
||||||
roleNames?: string[];
|
roleNames?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,13 +55,13 @@ export const verifyBearerTokenFromRequest = async (
|
||||||
const userId = request.headers['development-user-id']?.toString() ?? developmentUserId;
|
const userId = request.headers['development-user-id']?.toString() ?? developmentUserId;
|
||||||
|
|
||||||
if ((!isProduction || isIntegrationTest) && userId) {
|
if ((!isProduction || isIntegrationTest) && userId) {
|
||||||
return { sub: userId, clientId: undefined, roleNames: [UserRole.Admin] };
|
return { sub: userId, clientId: undefined, scopes: [managementResourceScope.name] };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { localJWKSet, issuer } = envSet.oidc;
|
const { localJWKSet, issuer } = envSet.oidc;
|
||||||
const {
|
const {
|
||||||
payload: { sub, client_id: clientId, role_names: roleNames },
|
payload: { sub, client_id: clientId, scope = '' },
|
||||||
} = await jwtVerify(extractBearerTokenFromHeaders(request.headers), localJWKSet, {
|
} = await jwtVerify(extractBearerTokenFromHeaders(request.headers), localJWKSet, {
|
||||||
issuer,
|
issuer,
|
||||||
audience: resourceIndicator,
|
audience: resourceIndicator,
|
||||||
|
@ -68,7 +69,7 @@ export const verifyBearerTokenFromRequest = async (
|
||||||
|
|
||||||
assertThat(sub, new RequestError({ code: 'auth.jwt_sub_missing', status: 401 }));
|
assertThat(sub, new RequestError({ code: 'auth.jwt_sub_missing', status: 401 }));
|
||||||
|
|
||||||
return { sub, clientId, roleNames: conditional(Array.isArray(roleNames) && roleNames) };
|
return { sub, clientId, scopes: z.string().parse(scope).split(' ') };
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof RequestError) {
|
if (error instanceof RequestError) {
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -80,18 +81,18 @@ export const verifyBearerTokenFromRequest = async (
|
||||||
|
|
||||||
export default function koaAuth<StateT, ContextT extends IRouterParamContext, ResponseBodyT>(
|
export default function koaAuth<StateT, ContextT extends IRouterParamContext, ResponseBodyT>(
|
||||||
envSet: EnvSet,
|
envSet: EnvSet,
|
||||||
forRole?: UserRole
|
forScope?: string
|
||||||
): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
|
): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
const { sub, clientId, roleNames } = await verifyBearerTokenFromRequest(
|
const { sub, clientId, scopes } = await verifyBearerTokenFromRequest(
|
||||||
envSet,
|
envSet,
|
||||||
ctx.request,
|
ctx.request,
|
||||||
managementResource.indicator
|
managementResource.indicator
|
||||||
);
|
);
|
||||||
|
|
||||||
if (forRole) {
|
if (forScope) {
|
||||||
assertThat(
|
assertThat(
|
||||||
roleNames?.includes(forRole),
|
scopes.includes(forScope),
|
||||||
new RequestError({ code: 'auth.forbidden', status: 403 })
|
new RequestError({ code: 'auth.forbidden', status: 403 })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { UserRole } from '@logto/schemas';
|
import { managementResourceScope } from '@logto/schemas';
|
||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
import Router from 'koa-router';
|
import Router from 'koa-router';
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ const createRouters = (tenant: TenantContext) => {
|
||||||
interactionRoutes(interactionRouter, tenant);
|
interactionRoutes(interactionRouter, tenant);
|
||||||
|
|
||||||
const managementRouter: AuthedRouter = new Router();
|
const managementRouter: AuthedRouter = new Router();
|
||||||
managementRouter.use(koaAuth(tenant.envSet, UserRole.Admin));
|
managementRouter.use(koaAuth(tenant.envSet, managementResourceScope.name));
|
||||||
applicationRoutes(managementRouter, tenant);
|
applicationRoutes(managementRouter, tenant);
|
||||||
settingRoutes(managementRouter, tenant);
|
settingRoutes(managementRouter, tenant);
|
||||||
connectorRoutes(managementRouter, tenant);
|
connectorRoutes(managementRouter, tenant);
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { adminConsoleApplicationId, UserRole } from '@logto/schemas';
|
import {
|
||||||
|
adminConsoleApplicationId,
|
||||||
|
managementResourceId,
|
||||||
|
managementResourceScope,
|
||||||
|
} from '@logto/schemas';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
import type Router from 'koa-router';
|
import type Router from 'koa-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
@ -29,12 +33,15 @@ export default function consentRoutes<T>(
|
||||||
|
|
||||||
const { accountId } = session;
|
const { accountId } = session;
|
||||||
|
|
||||||
// Temp solution before migrating to RBAC. Block non-admin user from consenting to admin console
|
// Block non-admin user from consenting to admin console
|
||||||
if (String(client_id) === adminConsoleApplicationId) {
|
if (String(client_id) === adminConsoleApplicationId) {
|
||||||
const { roleNames } = await libraries.users.findUserByIdWithRoles(accountId);
|
const scopes = await libraries.users.findUserScopesForResourceId(
|
||||||
|
accountId,
|
||||||
|
managementResourceId
|
||||||
|
);
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
roleNames.includes(UserRole.Admin),
|
scopes.some(({ name }) => name === managementResourceScope.name),
|
||||||
new RequestError({ code: 'auth.forbidden', status: 401 })
|
new RequestError({ code: 'auth.forbidden', status: 401 })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"@logto/core-kit": "workspace:*",
|
"@logto/core-kit": "workspace:*",
|
||||||
"@logto/language-kit": "workspace:*",
|
"@logto/language-kit": "workspace:*",
|
||||||
"@logto/phrases": "workspace:*",
|
"@logto/phrases": "workspace:*",
|
||||||
"@logto/react": "1.0.0-beta.14",
|
"@logto/react": "1.0.0-beta.15",
|
||||||
"@logto/schemas": "workspace:*",
|
"@logto/schemas": "workspace:*",
|
||||||
"@parcel/core": "2.8.2",
|
"@parcel/core": "2.8.2",
|
||||||
"@parcel/transformer-sass": "2.8.2",
|
"@parcel/transformer-sass": "2.8.2",
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"@jest/types": "^29.1.2",
|
"@jest/types": "^29.1.2",
|
||||||
"@logto/connector-kit": "workspace:*",
|
"@logto/connector-kit": "workspace:*",
|
||||||
"@logto/js": "1.0.0-beta.14",
|
"@logto/js": "1.0.0-beta.14",
|
||||||
"@logto/node": "1.0.0-beta.14",
|
"@logto/node": "1.0.0-beta.15",
|
||||||
"@logto/schemas": "workspace:*",
|
"@logto/schemas": "workspace:*",
|
||||||
"@peculiar/webcrypto": "^1.3.3",
|
"@peculiar/webcrypto": "^1.3.3",
|
||||||
"@silverhand/eslint-config": "1.3.0",
|
"@silverhand/eslint-config": "1.3.0",
|
||||||
|
|
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
|
@ -109,7 +109,7 @@ importers:
|
||||||
'@logto/language-kit': workspace:*
|
'@logto/language-kit': workspace:*
|
||||||
'@logto/phrases': workspace:*
|
'@logto/phrases': workspace:*
|
||||||
'@logto/phrases-ui': workspace:*
|
'@logto/phrases-ui': workspace:*
|
||||||
'@logto/react': 1.0.0-beta.14
|
'@logto/react': 1.0.0-beta.15
|
||||||
'@logto/schemas': workspace:*
|
'@logto/schemas': workspace:*
|
||||||
'@mdx-js/react': ^1.6.22
|
'@mdx-js/react': ^1.6.22
|
||||||
'@parcel/core': 2.8.2
|
'@parcel/core': 2.8.2
|
||||||
|
@ -178,7 +178,7 @@ importers:
|
||||||
'@logto/language-kit': link:../toolkit/language-kit
|
'@logto/language-kit': link:../toolkit/language-kit
|
||||||
'@logto/phrases': link:../phrases
|
'@logto/phrases': link:../phrases
|
||||||
'@logto/phrases-ui': link:../phrases-ui
|
'@logto/phrases-ui': link:../phrases-ui
|
||||||
'@logto/react': 1.0.0-beta.14_react@18.2.0
|
'@logto/react': 1.0.0-beta.15_react@18.2.0
|
||||||
'@logto/schemas': link:../schemas
|
'@logto/schemas': link:../schemas
|
||||||
'@mdx-js/react': 1.6.22_react@18.2.0
|
'@mdx-js/react': 1.6.22_react@18.2.0
|
||||||
'@parcel/core': 2.8.2
|
'@parcel/core': 2.8.2
|
||||||
|
@ -408,7 +408,7 @@ importers:
|
||||||
'@logto/core-kit': workspace:*
|
'@logto/core-kit': workspace:*
|
||||||
'@logto/language-kit': workspace:*
|
'@logto/language-kit': workspace:*
|
||||||
'@logto/phrases': workspace:*
|
'@logto/phrases': workspace:*
|
||||||
'@logto/react': 1.0.0-beta.14
|
'@logto/react': 1.0.0-beta.15
|
||||||
'@logto/schemas': workspace:*
|
'@logto/schemas': workspace:*
|
||||||
'@parcel/core': 2.8.2
|
'@parcel/core': 2.8.2
|
||||||
'@parcel/transformer-sass': 2.8.2
|
'@parcel/transformer-sass': 2.8.2
|
||||||
|
@ -436,7 +436,7 @@ importers:
|
||||||
'@logto/core-kit': link:../toolkit/core-kit
|
'@logto/core-kit': link:../toolkit/core-kit
|
||||||
'@logto/language-kit': link:../toolkit/language-kit
|
'@logto/language-kit': link:../toolkit/language-kit
|
||||||
'@logto/phrases': link:../phrases
|
'@logto/phrases': link:../phrases
|
||||||
'@logto/react': 1.0.0-beta.14_react@18.2.0
|
'@logto/react': 1.0.0-beta.15_react@18.2.0
|
||||||
'@logto/schemas': link:../schemas
|
'@logto/schemas': link:../schemas
|
||||||
'@parcel/core': 2.8.2
|
'@parcel/core': 2.8.2
|
||||||
'@parcel/transformer-sass': 2.8.2_@parcel+core@2.8.2
|
'@parcel/transformer-sass': 2.8.2_@parcel+core@2.8.2
|
||||||
|
@ -466,7 +466,7 @@ importers:
|
||||||
'@jest/types': ^29.1.2
|
'@jest/types': ^29.1.2
|
||||||
'@logto/connector-kit': workspace:*
|
'@logto/connector-kit': workspace:*
|
||||||
'@logto/js': 1.0.0-beta.14
|
'@logto/js': 1.0.0-beta.14
|
||||||
'@logto/node': 1.0.0-beta.14
|
'@logto/node': 1.0.0-beta.15
|
||||||
'@logto/schemas': workspace:*
|
'@logto/schemas': workspace:*
|
||||||
'@peculiar/webcrypto': ^1.3.3
|
'@peculiar/webcrypto': ^1.3.3
|
||||||
'@silverhand/eslint-config': 1.3.0
|
'@silverhand/eslint-config': 1.3.0
|
||||||
|
@ -494,7 +494,7 @@ importers:
|
||||||
'@jest/types': 29.1.2
|
'@jest/types': 29.1.2
|
||||||
'@logto/connector-kit': link:../toolkit/connector-kit
|
'@logto/connector-kit': link:../toolkit/connector-kit
|
||||||
'@logto/js': 1.0.0-beta.14
|
'@logto/js': 1.0.0-beta.14
|
||||||
'@logto/node': 1.0.0-beta.14
|
'@logto/node': 1.0.0-beta.15
|
||||||
'@logto/schemas': link:../schemas
|
'@logto/schemas': link:../schemas
|
||||||
'@peculiar/webcrypto': 1.3.3
|
'@peculiar/webcrypto': 1.3.3
|
||||||
'@silverhand/eslint-config': 1.3.0_k3lfx77tsvurbevhk73p7ygch4
|
'@silverhand/eslint-config': 1.3.0_k3lfx77tsvurbevhk73p7ygch4
|
||||||
|
@ -2310,26 +2310,14 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@logto/browser/1.0.0-beta.14:
|
/@logto/browser/1.0.0-beta.15:
|
||||||
resolution: {integrity: sha512-yjD1qtRXbX2E5Jgr5F1BK4SRwNhIlbJZK1yZLZNvOltEG76NhfoqvCI8P5PGIiPwvunB2lqPNJFsNOSI3k0Q+w==}
|
resolution: {integrity: sha512-AbIvIAO9GYXa2G2Komx0+pQ/PrSIdXCpEVZUtjEhQXQ07gmkeq4TRv3Q68H7HyHTVS1S2Rd5eZpWcqTcrj7DhQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/client': 1.0.0-beta.14
|
'@logto/client': 1.0.0-beta.15
|
||||||
'@silverhand/essentials': 1.3.0
|
'@silverhand/essentials': 1.3.0
|
||||||
js-base64: 3.7.3
|
js-base64: 3.7.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@logto/client/1.0.0-beta.14:
|
|
||||||
resolution: {integrity: sha512-quhQJ4rjb1Djhspeq2F5pFxXdgjN5UaWei6nnbUfp12CDhRojKrLJIGl+FDx/HSWuG0b93nwxKnJeJsiX/8E3Q==}
|
|
||||||
dependencies:
|
|
||||||
'@logto/core-kit': 1.0.0-beta.20
|
|
||||||
'@logto/js': 1.0.0-beta.14
|
|
||||||
'@silverhand/essentials': 1.3.0
|
|
||||||
camelcase-keys: 7.0.2
|
|
||||||
jose: 4.11.1
|
|
||||||
lodash.get: 4.4.2
|
|
||||||
lodash.once: 4.1.1
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@logto/client/1.0.0-beta.15:
|
/@logto/client/1.0.0-beta.15:
|
||||||
resolution: {integrity: sha512-+CrgyUcBcTILpfMPtwIEwBD60XgXUCdu7MpnvNZjd0sNaUpAoyFbUiRKzvbFeF7w9Nc4zO/kgAwbk36kqTXsvw==}
|
resolution: {integrity: sha512-+CrgyUcBcTILpfMPtwIEwBD60XgXUCdu7MpnvNZjd0sNaUpAoyFbUiRKzvbFeF7w9Nc4zO/kgAwbk36kqTXsvw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2371,8 +2359,8 @@ packages:
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@logto/node/1.0.0-beta.14:
|
/@logto/node/1.0.0-beta.15:
|
||||||
resolution: {integrity: sha512-+0S6lBBcG3pOmjEMRQnD+6X0MJ3V3E/4In59ckl/uVr/UgIufvOKWJwWCfsVKyguaO3QweJn19x7YkF8FyO31g==}
|
resolution: {integrity: sha512-ELTnVZqKwRH7NIa3C2EWEmWGvocPexpcdpWE9GHFt4N7nYB1mcFoeB5yni6uLdkUNpoW1cKfoGg8ZXEPlaodGQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/client': 1.0.0-beta.15
|
'@logto/client': 1.0.0-beta.15
|
||||||
'@silverhand/essentials': 1.3.0
|
'@silverhand/essentials': 1.3.0
|
||||||
|
@ -2382,12 +2370,12 @@ packages:
|
||||||
- encoding
|
- encoding
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@logto/react/1.0.0-beta.14_react@18.2.0:
|
/@logto/react/1.0.0-beta.15_react@18.2.0:
|
||||||
resolution: {integrity: sha512-lHuwpHzJkIbHj/VvhzxmL7hWkyDYA8rInv0sm0M21br43OotgP7fMc62Wj78ty+QIj+or5UGwCcULBa8HySQcQ==}
|
resolution: {integrity: sha512-G/GZFtPifv9Ln+iL8ka+LhfnawyC2UUFzcEIomO5qP5fN11+eYES80O6a0jNGPm3+8t6otefYPO8skyByohm/w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.8.0 || ^18.0.0'
|
react: '>=16.8.0 || ^18.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@logto/browser': 1.0.0-beta.14
|
'@logto/browser': 1.0.0-beta.15
|
||||||
'@silverhand/essentials': 1.3.0
|
'@silverhand/essentials': 1.3.0
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
Loading…
Add table
Reference in a new issue