From 0f64c172752b34b2905ac9da676eca106c76be37 Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Tue, 16 Apr 2024 00:56:06 +0800 Subject: [PATCH] feat(console): update user access immediately on tenant role updates --- .../connector-logto-email/package.json | 2 +- packages/console/package.json | 4 +- .../src/containers/ConsoleContent/hooks.ts | 58 ++++++++++++ .../src/containers/ConsoleContent/index.tsx | 3 + .../routes/tenant-settings.tsx | 4 +- .../src/hooks/use-current-tenant-scopes.ts | 91 +++++++++---------- .../TenantBasicSettings/ProfileForm/index.tsx | 4 +- .../TenantBasicSettings/index.tsx | 4 +- .../TenantDomainSettings/index.tsx | 4 +- .../TenantMembers/EditMemberModal/index.tsx | 3 + .../TenantMembers/Invitations/index.tsx | 4 +- .../TenantMembers/Members/index.tsx | 4 +- .../TenantSettings/TenantMembers/hooks.ts | 4 +- .../TenantSettings/TenantMembers/index.tsx | 4 +- .../src/pages/TenantSettings/index.tsx | 4 +- packages/core/package.json | 2 +- packages/demo-app/package.json | 2 +- packages/integration-tests/package.json | 2 +- pnpm-lock.yaml | 60 +++++------- 19 files changed, 163 insertions(+), 100 deletions(-) create mode 100644 packages/console/src/containers/ConsoleContent/hooks.ts diff --git a/packages/connectors/connector-logto-email/package.json b/packages/connectors/connector-logto-email/package.json index fb5f6d8a668a..1f3612a845c6 100644 --- a/packages/connectors/connector-logto-email/package.json +++ b/packages/connectors/connector-logto-email/package.json @@ -48,6 +48,6 @@ "access": "public" }, "devDependencies": { - "@logto/cloud": "0.2.5-ab8a489" + "@logto/cloud": "0.2.5-821690c" } } diff --git a/packages/console/package.json b/packages/console/package.json index f6adf53eb965..f7ea0ace8e80 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -28,13 +28,13 @@ "@fontsource/roboto-mono": "^5.0.0", "@jest/types": "^29.5.0", "@logto/app-insights": "workspace:^1.4.0", - "@logto/cloud": "0.2.5-94f7bcc", + "@logto/cloud": "0.2.5-821690c", "@logto/connector-kit": "workspace:^3.0.0", "@logto/core-kit": "workspace:^2.4.0", "@logto/language-kit": "workspace:^1.1.0", "@logto/phrases": "workspace:^1.10.0", "@logto/phrases-experience": "workspace:^1.6.1", - "@logto/react": "^3.0.5", + "@logto/react": "^3.0.8", "@logto/schemas": "workspace:^1.15.0", "@logto/shared": "workspace:^3.1.0", "@mdx-js/react": "^1.6.22", diff --git a/packages/console/src/containers/ConsoleContent/hooks.ts b/packages/console/src/containers/ConsoleContent/hooks.ts new file mode 100644 index 000000000000..7d07263d1075 --- /dev/null +++ b/packages/console/src/containers/ConsoleContent/hooks.ts @@ -0,0 +1,58 @@ +import { Prompt, useLogto } from '@logto/react'; +import { getTenantOrganizationId } from '@logto/schemas'; +import { useContext, useEffect, useState } from 'react'; + +import { TenantsContext } from '@/contexts/TenantsProvider'; +import useCurrentTenantScopes from '@/hooks/use-current-tenant-scopes'; +import useRedirectUri from '@/hooks/use-redirect-uri'; +import { saveRedirect } from '@/utils/storage'; + +/** + * Listens to the tenant scope changes for the current signed-in user. This hook will fetch the tenant scopes + * for the user, and compare it with the "scope" token claim in access token. After comparing the scopes: + * - If the user has been granted new scopes, it will re-consent to obtain the additional scopes. + * - If the user has been revoked scopes, it will clear the cached access token and renew one with shrunk scopes. + * + * Note: This hook should only be used once in the ConsoleContent component. + */ +const useTenantScopeListener = () => { + const { currentTenantId } = useContext(TenantsContext); + const { clearAccessToken, clearAllTokens, getOrganizationTokenClaims, signIn } = useLogto(); + const [tokenClaims, setTokenClaims] = useState(); + const redirectUri = useRedirectUri(); + const { scopes = [], isLoading } = useCurrentTenantScopes(); + + useEffect(() => { + (async () => { + const organizationId = getTenantOrganizationId(currentTenantId); + const claims = await getOrganizationTokenClaims(organizationId); + setTokenClaims(claims?.scope?.split(' ') ?? []); + })(); + }, [currentTenantId, getOrganizationTokenClaims]); + + useEffect(() => { + if (isLoading || tokenClaims === undefined) { + return; + } + const hasScopesGranted = scopes.some((scope) => !tokenClaims.includes(scope)); + const hasScopesRevoked = tokenClaims.some((claim) => !scopes.includes(claim)); + if (hasScopesGranted) { + (async () => { + // User has been newly granted scopes. Need to re-consent to obtain the additional scopes. + saveRedirect(); + await clearAllTokens(); + void signIn({ + redirectUri: redirectUri.href, + prompt: Prompt.Consent, + }); + })(); + } + if (hasScopesRevoked) { + // User has been revoked scopes. Need to clear the cached access token and it will be renewed + // automatically with shrunk scopes. + void clearAccessToken(); + } + }, [clearAccessToken, clearAllTokens, isLoading, redirectUri.href, scopes, signIn, tokenClaims]); +}; + +export default useTenantScopeListener; diff --git a/packages/console/src/containers/ConsoleContent/index.tsx b/packages/console/src/containers/ConsoleContent/index.tsx index 47bf53847dff..3449daa4139b 100644 --- a/packages/console/src/containers/ConsoleContent/index.tsx +++ b/packages/console/src/containers/ConsoleContent/index.tsx @@ -6,11 +6,14 @@ import { useConsoleRoutes } from '@/hooks/use-console-routes'; import type { AppContentOutletContext } from '../AppContent/types'; import Sidebar from './Sidebar'; +import useTenantScopeListener from './hooks'; import * as styles from './index.module.scss'; function ConsoleContent() { const { scrollableContent } = useOutletContext(); const routes = useConsoleRoutes(); + // Use this hook here to make sure console listens to user tenant scope changes. + useTenantScopeListener(); return (
diff --git a/packages/console/src/hooks/use-console-routes/routes/tenant-settings.tsx b/packages/console/src/hooks/use-console-routes/routes/tenant-settings.tsx index 7b2ead37c5ee..eb08de787157 100644 --- a/packages/console/src/hooks/use-console-routes/routes/tenant-settings.tsx +++ b/packages/console/src/hooks/use-console-routes/routes/tenant-settings.tsx @@ -14,7 +14,9 @@ import TenantMembers from '@/pages/TenantSettings/TenantMembers'; export const useTenantSettings = () => { const { isDevTenant } = useContext(TenantsContext); - const { canManageTenant } = useCurrentTenantScopes(); + const { + access: { canManageTenant }, + } = useCurrentTenantScopes(); const tenantSettings: RouteObject = useMemo( () => ({ diff --git a/packages/console/src/hooks/use-current-tenant-scopes.ts b/packages/console/src/hooks/use-current-tenant-scopes.ts index 91fdce9d30d1..56e98a579300 100644 --- a/packages/console/src/hooks/use-current-tenant-scopes.ts +++ b/packages/console/src/hooks/use-current-tenant-scopes.ts @@ -1,61 +1,52 @@ -import { useLogto } from '@logto/react'; -import { TenantScope, getTenantOrganizationId } from '@logto/schemas'; -import { useContext, useEffect, useState } from 'react'; +import { TenantScope } from '@logto/schemas'; +import { useContext, useMemo } from 'react'; +import useSWR from 'swr'; +import { useAuthedCloudApi } from '@/cloud/hooks/use-cloud-api'; import { TenantsContext } from '@/contexts/TenantsProvider'; +import { type RequestError } from './use-api'; +import useCurrentUser from './use-current-user'; + const useCurrentTenantScopes = () => { const { currentTenantId, isInitComplete } = useContext(TenantsContext); - const { isAuthenticated, getOrganizationTokenClaims } = useLogto(); - - const [scopes, setScopes] = useState([]); - const [canInviteMember, setCanInviteMember] = useState(false); - const [canRemoveMember, setCanRemoveMember] = useState(false); - const [canUpdateMemberRole, setCanUpdateMemberRole] = useState(false); - const [canManageTenant, setCanManageTenant] = useState(false); + const cloudApi = useAuthedCloudApi(); + const { user } = useCurrentUser(); + const userId = user?.id ?? ''; - useEffect(() => { - (async () => { - if (isAuthenticated && isInitComplete) { - const organizationId = getTenantOrganizationId(currentTenantId); - const claims = await getOrganizationTokenClaims(organizationId); - const allScopes = claims?.scope?.split(' ') ?? []; - setScopes(allScopes); + const { + data: scopes, + isLoading, + mutate, + } = useSWR( + userId && isInitComplete && `api/tenants/${currentTenantId}/members/${userId}/scopes`, + async () => { + const scopes = await cloudApi.get('/api/tenants/:tenantId/members/:userId/scopes', { + params: { tenantId: currentTenantId, userId }, + }); + return scopes.map(({ name }) => name); + } + ); - for (const scope of allScopes) { - switch (scope) { - case TenantScope.InviteMember: { - setCanInviteMember(true); - break; - } - case TenantScope.RemoveMember: { - setCanRemoveMember(true); - break; - } - case TenantScope.UpdateMemberRole: { - setCanUpdateMemberRole(true); - break; - } - case TenantScope.ManageTenant: { - setCanManageTenant(true); - break; - } - default: { - break; - } - } - } - } - })(); - }, [currentTenantId, getOrganizationTokenClaims, isAuthenticated, isInitComplete]); + const access = useMemo( + () => ({ + canInviteMember: Boolean(scopes?.includes(TenantScope.InviteMember)), + canRemoveMember: Boolean(scopes?.includes(TenantScope.RemoveMember)), + canUpdateMemberRole: Boolean(scopes?.includes(TenantScope.UpdateMemberRole)), + canManageTenant: Boolean(scopes?.includes(TenantScope.ManageTenant)), + }), + [scopes] + ); - return { - canInviteMember, - canRemoveMember, - canUpdateMemberRole, - canManageTenant, - scopes, - }; + return useMemo( + () => ({ + isLoading, + scopes, + access, + mutate, + }), + [isLoading, scopes, access, mutate] + ); }; export default useCurrentTenantScopes; diff --git a/packages/console/src/pages/TenantSettings/TenantBasicSettings/ProfileForm/index.tsx b/packages/console/src/pages/TenantSettings/TenantBasicSettings/ProfileForm/index.tsx index f050d91de141..042e3fe45f62 100644 --- a/packages/console/src/pages/TenantSettings/TenantBasicSettings/ProfileForm/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantBasicSettings/ProfileForm/index.tsx @@ -16,7 +16,9 @@ type Props = { }; function ProfileForm({ currentTenantId }: Props) { - const { canManageTenant } = useCurrentTenantScopes(); + const { + access: { canManageTenant }, + } = useCurrentTenantScopes(); const { register, formState: { errors }, diff --git a/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx b/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx index d60310ae48d5..894cd4e6aa07 100644 --- a/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantBasicSettings/index.tsx @@ -30,7 +30,9 @@ const tenantProfileToForm = (tenant?: TenantResponse): TenantSettingsForm => { function TenantBasicSettings() { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const { canManageTenant } = useCurrentTenantScopes(); + const { + access: { canManageTenant }, + } = useCurrentTenantScopes(); const api = useCloudApi(); const { currentTenant, diff --git a/packages/console/src/pages/TenantSettings/TenantDomainSettings/index.tsx b/packages/console/src/pages/TenantSettings/TenantDomainSettings/index.tsx index ab09201e8103..b573021acb7b 100644 --- a/packages/console/src/pages/TenantSettings/TenantDomainSettings/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantDomainSettings/index.tsx @@ -23,7 +23,9 @@ function TenantDomainSettings() { const { data: customDomain, isLoading: isLoadingCustomDomain, mutate } = useCustomDomain(true); const { getDocumentationUrl } = useDocumentationUrl(); const api = useApi(); - const { canManageTenant } = useCurrentTenantScopes(); + const { + access: { canManageTenant }, + } = useCurrentTenantScopes(); if (isLoadingCustomDomain) { return ; diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/EditMemberModal/index.tsx b/packages/console/src/pages/TenantSettings/TenantMembers/EditMemberModal/index.tsx index f16d28990df0..ef76bcef105c 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/EditMemberModal/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantMembers/EditMemberModal/index.tsx @@ -12,6 +12,7 @@ import FormField from '@/ds-components/FormField'; import ModalLayout from '@/ds-components/ModalLayout'; import Select, { type Option } from '@/ds-components/Select'; import { useConfirmModal } from '@/hooks/use-confirm-modal'; +import useCurrentTenantScopes from '@/hooks/use-current-tenant-scopes'; import * as modalStyles from '@/scss/modal.module.scss'; type Props = { @@ -23,6 +24,7 @@ type Props = { function EditMemberModal({ user, isOpen, onClose }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.tenant_members' }); const { currentTenantId } = useContext(TenantsContext); + const { mutate: mutateUserTenantScopes } = useCurrentTenantScopes(); const [isLoading, setIsLoading] = useState(false); const [role, setRole] = useState(TenantRole.Collaborator); @@ -57,6 +59,7 @@ function EditMemberModal({ user, isOpen, onClose }: Props) { params: { tenantId: currentTenantId, userId: user.id }, body: { roleName: role }, }); + void mutateUserTenantScopes(); onClose(); } finally { setIsLoading(false); diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx b/packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx index 9b2eb0ae6bbb..63cdaa6c7cc8 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx @@ -53,7 +53,9 @@ function Invitations() { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.tenant_members' }); const cloudApi = useAuthedCloudApi(); const { currentTenantId } = useContext(TenantsContext); - const { canInviteMember, canRemoveMember } = useCurrentTenantScopes(); + const { + access: { canInviteMember, canRemoveMember }, + } = useCurrentTenantScopes(); const { data, error, isLoading, mutate } = useSWR( 'api/tenants/:tenantId/invitations', diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/Members/index.tsx b/packages/console/src/pages/TenantSettings/TenantMembers/Members/index.tsx index 9b4687c670b9..ad9bfdbd7cd7 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/Members/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantMembers/Members/index.tsx @@ -24,7 +24,9 @@ function Members() { const cloudApi = useAuthedCloudApi(); const { currentTenantId } = useContext(TenantsContext); const { user: currentUser } = useCurrentUser(); - const { canRemoveMember, canUpdateMemberRole } = useCurrentTenantScopes(); + const { + access: { canRemoveMember, canUpdateMemberRole }, + } = useCurrentTenantScopes(); const { data, error, isLoading, mutate } = useSWR( `api/tenants/:tenantId/members`, diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/hooks.ts b/packages/console/src/pages/TenantSettings/TenantMembers/hooks.ts index b154f551f9bd..5b983d29787a 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/hooks.ts +++ b/packages/console/src/pages/TenantSettings/TenantMembers/hooks.ts @@ -13,7 +13,9 @@ import { hasReachedQuotaLimit, hasSurpassedQuotaLimit } from '@/utils/quota'; const useTenantMembersUsage = () => { const { currentPlan } = useContext(SubscriptionDataContext); const { currentTenantId } = useContext(TenantsContext); - const { canInviteMember } = useCurrentTenantScopes(); + const { + access: { canInviteMember }, + } = useCurrentTenantScopes(); const cloudApi = useAuthedCloudApi(); diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/index.tsx b/packages/console/src/pages/TenantSettings/TenantMembers/index.tsx index 52655714d026..83f9f5cbdb85 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantMembers/index.tsx @@ -30,7 +30,9 @@ function TenantMembers() { const { hasTenantMembersSurpassedLimit } = useTenantMembersUsage(); const { navigate, match } = useTenantPathname(); const [showInviteModal, setShowInviteModal] = useState(false); - const { canInviteMember } = useCurrentTenantScopes(); + const { + access: { canInviteMember }, + } = useCurrentTenantScopes(); const isInvitationTab = match( `/tenant-settings/${TenantSettingsTabs.Members}/${invitationsRoute}` diff --git a/packages/console/src/pages/TenantSettings/index.tsx b/packages/console/src/pages/TenantSettings/index.tsx index e1daa7c820b1..e72881a7c4c2 100644 --- a/packages/console/src/pages/TenantSettings/index.tsx +++ b/packages/console/src/pages/TenantSettings/index.tsx @@ -12,7 +12,9 @@ import * as styles from './index.module.scss'; function TenantSettings() { const { isDevTenant } = useContext(TenantsContext); - const { canManageTenant } = useCurrentTenantScopes(); + const { + access: { canManageTenant }, + } = useCurrentTenantScopes(); return (
diff --git a/packages/core/package.json b/packages/core/package.json index 4d7b9cf810e4..40eaa3b2c6a7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -92,7 +92,7 @@ "zod": "^3.22.4" }, "devDependencies": { - "@logto/cloud": "0.2.5-94f7bcc", + "@logto/cloud": "0.2.5-821690c", "@silverhand/eslint-config": "5.0.0", "@silverhand/ts-config": "5.0.0", "@types/debug": "^4.1.7", diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index 8531591a2f1f..04df28f302a9 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -22,7 +22,7 @@ "@logto/core-kit": "workspace:^2.4.0", "@logto/language-kit": "workspace:^1.1.0", "@logto/phrases": "workspace:^1.10.0", - "@logto/react": "^3.0.5", + "@logto/react": "^3.0.8", "@logto/schemas": "workspace:^1.15.0", "@parcel/core": "2.9.3", "@parcel/transformer-sass": "2.9.3", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index c1a244e6bd91..76b8be9081db 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -27,7 +27,7 @@ "@logto/connector-kit": "workspace:^3.0.0", "@logto/core-kit": "workspace:^", "@logto/js": "^4.1.1", - "@logto/node": "^2.4.4", + "@logto/node": "^2.4.7", "@logto/schemas": "workspace:^1.15.0", "@logto/shared": "workspace:^3.1.0", "@silverhand/eslint-config": "5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c78bd650882d..e9c4a7802e64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1235,8 +1235,8 @@ importers: version: 3.22.4 devDependencies: '@logto/cloud': - specifier: 0.2.5-ab8a489 - version: 0.2.5-ab8a489(zod@3.22.4) + specifier: 0.2.5-821690c + version: 0.2.5-821690c(zod@3.22.4) '@rollup/plugin-commonjs': specifier: ^25.0.0 version: 25.0.7(rollup@4.12.0) @@ -2715,8 +2715,8 @@ importers: specifier: workspace:^1.4.0 version: link:../app-insights '@logto/cloud': - specifier: 0.2.5-94f7bcc - version: 0.2.5-94f7bcc(zod@3.22.4) + specifier: 0.2.5-821690c + version: 0.2.5-821690c(zod@3.22.4) '@logto/connector-kit': specifier: workspace:^3.0.0 version: link:../toolkit/connector-kit @@ -2733,8 +2733,8 @@ importers: specifier: workspace:^1.6.1 version: link:../phrases-experience '@logto/react': - specifier: ^3.0.5 - version: 3.0.5(react@18.2.0) + specifier: ^3.0.8 + version: 3.0.8(react@18.2.0) '@logto/schemas': specifier: workspace:^1.15.0 version: link:../schemas @@ -3205,8 +3205,8 @@ importers: version: 3.22.4 devDependencies: '@logto/cloud': - specifier: 0.2.5-94f7bcc - version: 0.2.5-94f7bcc(zod@3.22.4) + specifier: 0.2.5-821690c + version: 0.2.5-821690c(zod@3.22.4) '@silverhand/eslint-config': specifier: 5.0.0 version: 5.0.0(eslint@8.44.0)(prettier@3.0.0)(typescript@5.3.3) @@ -3319,8 +3319,8 @@ importers: specifier: workspace:^1.10.0 version: link:../phrases '@logto/react': - specifier: ^3.0.5 - version: 3.0.5(react@18.2.0) + specifier: ^3.0.8 + version: 3.0.8(react@18.2.0) '@logto/schemas': specifier: workspace:^1.15.0 version: link:../schemas @@ -3638,8 +3638,8 @@ importers: specifier: ^4.1.1 version: 4.1.1 '@logto/node': - specifier: ^2.4.4 - version: 2.4.4 + specifier: ^2.4.7 + version: 2.4.7 '@logto/schemas': specifier: workspace:^1.15.0 version: link:../schemas @@ -7630,16 +7630,16 @@ packages: tiny-cookie: 2.4.1 dev: false - /@logto/browser@2.2.7: - resolution: {integrity: sha512-+tB4QWB4/JSO5pXItX491mRR4Id5dsYlEJchI0gPC8JNX7cl4968/oDXhqQ42XWqFnqX3W5Wx7RgKeV6JtTMhg==} + /@logto/browser@2.2.10: + resolution: {integrity: sha512-y6NauaxctqpfApccP6uFVmpg/vG1OhsDVLD4Pdpzbmj3whl63Nb17yxSTQHt4eYNKmSZJ2SzudAnMnVEYD91iQ==} dependencies: - '@logto/client': 2.6.3 + '@logto/client': 2.6.6 '@silverhand/essentials': 2.9.0 js-base64: 3.7.5 dev: true - /@logto/client@2.6.3: - resolution: {integrity: sha512-uZphb17TZD2rXTiYfhPaIpiavMbUec+WwznIWIm2wJ9x4th8UO05egw9eTPiSaoEOZSuoPs6oWBROP1SQ00iBg==} + /@logto/client@2.6.6: + resolution: {integrity: sha512-QT7jMnzEIWHBNrf9/M8p1OErRBbbNZjoekXGji5aZCyUh975hh8+GEBL21HV71FT3H/5Cq4Gf1GzUbAIW3izMA==} dependencies: '@logto/js': 4.1.1 '@silverhand/essentials': 2.9.0 @@ -7647,18 +7647,8 @@ packages: jose: 5.2.2 dev: true - /@logto/cloud@0.2.5-94f7bcc(zod@3.22.4): - resolution: {integrity: sha512-1nY3o1/gXgEIqgvjel2no0X3rR+BGnfozB7Vev+FY2qTkDyQIWRtHAnx+kkv4iEIIFcZW86LRNlvfjDUqR2yIg==} - engines: {node: ^20.9.0} - dependencies: - '@silverhand/essentials': 2.9.0 - '@withtyped/server': 0.13.3(zod@3.22.4) - transitivePeerDependencies: - - zod - dev: true - - /@logto/cloud@0.2.5-ab8a489(zod@3.22.4): - resolution: {integrity: sha512-nUD1n2CDe/nu6x4cOhXfJ5VyKKDqkKv+a/u9zSfbIMxIF0nShybd2LiCYJDO0SPuMqLnmlYFg+79KrdPCNvjIQ==} + /@logto/cloud@0.2.5-821690c(zod@3.22.4): + resolution: {integrity: sha512-eVTlJxknWbvmaeaitKzPPMTx6C4GK4TLTb97hFr91E2u6SwKP+csE3oMBgL7ZdoDLOGG+nY+j08JpVMQ8QdOWw==} engines: {node: ^20.9.0} dependencies: '@silverhand/essentials': 2.9.0 @@ -7674,20 +7664,20 @@ packages: camelcase-keys: 7.0.2 dev: true - /@logto/node@2.4.4: - resolution: {integrity: sha512-3qkhXQKGZX5cVBfWT6n2l0kN9ln3fPShXngHaY5LTBBRd0b2e20h1XIrXCdoGoMmdSp1zntEo2PMv0+fBodzcw==} + /@logto/node@2.4.7: + resolution: {integrity: sha512-AlANeqY1NIt93EBcRzrTmyAVHXOHpszTJK+qe1ok50rmZlTmX2p7yQvrg0/Ehwf/+4Rla5vooAR+HIFMaOmPpQ==} dependencies: - '@logto/client': 2.6.3 + '@logto/client': 2.6.6 '@silverhand/essentials': 2.9.0 js-base64: 3.7.5 dev: true - /@logto/react@3.0.5(react@18.2.0): - resolution: {integrity: sha512-oCwKBGRf79QRo/MixPi8C8myZwHOx7eMon3/05nho0iiwBPllI2zSUJ7jUOnlFFnKTOLYV03l8pEMFnF+ODKyw==} + /@logto/react@3.0.8(react@18.2.0): + resolution: {integrity: sha512-p3pV4rX4g8ZwHQ159mxI+pP3Bwome47dNEmP1hI8/10WqdIPXGYTnfYn5c2l4Y2DyslYyK3ur2Sy4i4K6ept9A==} peerDependencies: react: '>=16.8.0 || ^18.0.0' dependencies: - '@logto/browser': 2.2.7 + '@logto/browser': 2.2.10 '@silverhand/essentials': 2.9.0 react: 18.2.0 dev: true