0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

Merge branch master into merge/sie-v2

This commit is contained in:
Xiao Yijun 2022-11-03 11:31:42 +08:00
commit 42de419f44
No known key found for this signature in database
GPG key ID: 6F648FC1262DB420
25 changed files with 253 additions and 149 deletions

View file

@ -0,0 +1,13 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
"ghcr.io/devcontainers/features/node:1": {}
},
"updateContentCommand": "npm i -g pnpm && pnpm i && pnpm prepack && pnpm cli connector add --official -p .",
"postStartCommand": "docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=p0stgr3s postgres:14-alpine",
"postAttachCommand": "pnpm cli db seed && [[ ! -z $CODESPACES ]] && export ENDPOINT=https://$CODESPACE_NAME-3001.preview.app.github.dev",
"containerEnv": {
"DB_URL": "postgres://postgres:p0stgr3s@localhost:5432/logto",
"TRUST_PROXY_HEADER": "1"
}
}

View file

@ -27,7 +27,7 @@ jobs:
main-lint:
# avoid out of memory issue since macOS has bigger memory
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
runs-on: macos-latest
runs-on: ubuntu-latest-4-cores
steps:
- uses: actions/checkout@v3

1
.gitignore vendored
View file

@ -29,6 +29,7 @@ cache
.idea/
*.pem
.history
fly.toml
# connectors
/packages/core/connectors

View file

@ -19,7 +19,7 @@ tasks:
pnpm start:dev
env:
TRUST_PROXY_HEADER: 1
DB_URL: postgres://postgres:p0stgr3s@127.0.0.1:5432
DB_URL: postgres://postgres:p0stgr3s@127.0.0.1:5432/logto
ports:
- name: Logto

View file

@ -32,6 +32,7 @@
"silverhand",
"slonik",
"stylelint",
"topbar"
"topbar",
"hasura"
]
}

View file

@ -22,7 +22,7 @@
"@logto/language-kit": "1.0.0-beta.20",
"@logto/phrases": "workspace:^",
"@logto/phrases-ui": "workspace:^",
"@logto/react": "1.0.0-beta.10",
"@logto/react": "1.0.0-beta.11",
"@logto/schemas": "workspace:^",
"@mdx-js/react": "^1.6.22",
"@parcel/core": "2.7.0",

View file

@ -2,7 +2,7 @@ import { useLogto } from '@logto/react';
import type { RequestErrorBody } from '@logto/schemas';
import { managementResource } from '@logto/schemas/lib/seeds';
import ky from 'ky';
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
@ -19,10 +19,16 @@ export class RequestError extends Error {
}
}
const useToastError = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
type Props = {
hideErrorToast?: boolean;
};
const toastError = async (response: Response) => {
const useApi = ({ hideErrorToast }: Props = {}) => {
const { isAuthenticated, getAccessToken } = useLogto();
const { t, i18n } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const toastError = useCallback(
async (response: Response) => {
const fallbackErrorMessage = t('errors.unknown_server_error');
try {
@ -31,19 +37,9 @@ const useToastError = () => {
} catch {
toast.error(fallbackErrorMessage);
}
};
return toastError;
};
type Props = {
hideErrorToast?: boolean;
};
const useApi = ({ hideErrorToast }: Props = {}) => {
const { isAuthenticated, getAccessToken } = useLogto();
const { i18n } = useTranslation();
const toastError = useToastError();
},
[t]
);
const api = useMemo(
() =>

View file

@ -196,12 +196,11 @@ export default function usePosition({
const anchorRect = anchorRef.current.getBoundingClientRect();
const overlayRect = overlayRef.current.getBoundingClientRect();
const { scrollTop, scrollLeft } = document.documentElement;
const verticalTop = anchorRect.y - overlayRect.height + scrollTop - offset.vertical;
const verticalTop = anchorRect.y - overlayRect.height - offset.vertical;
const verticalCenter =
anchorRect.y - anchorRect.height / 2 - overlayRect.height / 2 + scrollTop + offset.vertical;
const verticalBottom = anchorRect.y + anchorRect.height + scrollTop + offset.vertical;
anchorRect.y - anchorRect.height / 2 - overlayRect.height / 2 + offset.vertical;
const verticalBottom = anchorRect.y + anchorRect.height + offset.vertical;
const verticalPositionMap = {
top: verticalTop,
@ -209,11 +208,10 @@ export default function usePosition({
bottom: verticalBottom,
};
const horizontalStart = anchorRect.x + scrollLeft + offset.horizontal;
const horizontalStart = anchorRect.x + offset.horizontal;
const horizontalCenter =
anchorRect.x + anchorRect.width / 2 - overlayRect.width / 2 + scrollLeft + offset.horizontal;
const horizontalEnd =
anchorRect.x + anchorRect.width - overlayRect.width + scrollLeft + offset.horizontal;
anchorRect.x + anchorRect.width / 2 - overlayRect.width / 2 + offset.horizontal;
const horizontalEnd = anchorRect.x + anchorRect.width - overlayRect.width + offset.horizontal;
const horizontalPositionMap = {
start: horizontalStart,

View file

@ -2,6 +2,7 @@ import type { IncomingHttpHeaders } from 'http';
import { UserRole } from '@logto/schemas';
import { managementResource } from '@logto/schemas/lib/seeds';
import type { Optional } from '@silverhand/essentials';
import { conditional } from '@silverhand/essentials';
import { jwtVerify } from 'jose';
import type { MiddlewareType, Request } from 'koa';
@ -49,7 +50,7 @@ type TokenInfo = {
// eslint-disable-next-line complexity
export const verifyBearerTokenFromRequest = async (
request: Request,
resourceIndicator = managementResource.indicator
resourceIndicator: Optional<string>
): Promise<TokenInfo> => {
const { isProduction, isIntegrationTest, developmentUserId } = envSet.values;
const userId = request.headers['development-user-id']?.toString() ?? developmentUserId;
@ -83,7 +84,10 @@ export default function koaAuth<StateT, ContextT extends IRouterParamContext, Re
forRole?: UserRole
): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
return async (ctx, next) => {
const { sub, clientId, roleNames } = await verifyBearerTokenFromRequest(ctx.request);
const { sub, clientId, roleNames } = await verifyBearerTokenFromRequest(
ctx.request,
managementResource.indicator
);
if (forRole) {
assertThat(

View file

@ -0,0 +1,106 @@
import RequestError from '@/errors/RequestError';
import * as functions from '@/middleware/koa-auth';
import { createRequester } from '@/utils/test-utils';
import authnRoutes from './authn';
describe('authn route for Hasura', () => {
const request = createRequester({ anonymousRoutes: authnRoutes });
const mockUserId = 'foo';
const mockExpectedRole = 'some_role';
const mockUnauthorizedRole = 'V';
const keys = Object.freeze({
expectedRole: 'Expected-Role',
hasuraUserId: 'X-Hasura-User-Id',
hasuraRole: 'X-Hasura-Role',
});
describe('with successful verification', () => {
beforeEach(() => {
jest.spyOn(functions, 'verifyBearerTokenFromRequest').mockResolvedValue({
clientId: 'ok',
sub: mockUserId,
roleNames: [mockExpectedRole],
});
});
it('has expected role', async () => {
const response = await request
.get('/authn/hasura')
.query({ resource: 'https://api.logto.io' })
.set(keys.expectedRole, mockExpectedRole);
expect(response.status).toEqual(200);
expect(response.body).toEqual({
[keys.hasuraUserId]: mockUserId,
[keys.hasuraRole]: mockExpectedRole,
});
});
it('throws 401 if no expected role present', async () => {
const response = await request
.get('/authn/hasura')
.query({ resource: 'https://api.logto.io' })
.set(keys.expectedRole, mockExpectedRole + '1');
expect(response.status).toEqual(401);
});
it('falls back to unauthorized role if no expected role present', async () => {
const response = await request
.get('/authn/hasura')
.query({ resource: 'https://api.logto.io', unauthorizedRole: mockUnauthorizedRole })
.set(keys.expectedRole, mockExpectedRole + '1');
expect(response.status).toEqual(200);
expect(response.body).toEqual({
[keys.hasuraUserId]: mockUserId,
[keys.hasuraRole]: mockUnauthorizedRole,
});
});
});
describe('with failed verification', () => {
beforeEach(() => {
jest
.spyOn(functions, 'verifyBearerTokenFromRequest')
.mockImplementation(async (_, resource) => {
if (resource) {
throw new RequestError({ code: 'auth.jwt_sub_missing', status: 401 });
}
return { clientId: 'not ok', sub: mockUserId };
});
});
it('throws 401 if no unauthorized role presents', async () => {
const response = await request
.get('/authn/hasura')
.query({ resource: 'https://api.logto.io' })
.set(keys.expectedRole, mockExpectedRole);
expect(response.status).toEqual(401);
});
it('falls back to unauthorized role with user id if no expected resource present', async () => {
const response = await request
.get('/authn/hasura')
.query({ resource: 'https://api.logto.io', unauthorizedRole: mockUnauthorizedRole })
.set(keys.expectedRole, mockExpectedRole);
expect(response.status).toEqual(200);
expect(response.body).toEqual({
[keys.hasuraUserId]: mockUserId,
[keys.hasuraRole]: mockUnauthorizedRole,
});
});
it('falls back to unauthorized role if JWT is invalid', async () => {
jest
.spyOn(functions, 'verifyBearerTokenFromRequest')
.mockRejectedValue(new RequestError({ code: 'auth.jwt_sub_missing', status: 401 }));
const response = await request
.get('/authn/hasura')
.query({ resource: 'https://api.logto.io', unauthorizedRole: mockUnauthorizedRole });
expect(response.status).toEqual(200);
expect(response.body).toEqual({
[keys.hasuraRole]: mockUnauthorizedRole,
});
});
});
});

View file

@ -16,15 +16,39 @@ export default function authnRoutes<T extends AnonymousRouter>(router: T) {
router.get(
'/authn/hasura',
koaGuard({
query: z.object({ resource: z.string().min(1) }),
query: z.object({ resource: z.string().min(1), unauthorizedRole: z.string().optional() }),
status: [200, 401],
}),
async (ctx, next) => {
const { resource, unauthorizedRole } = ctx.guard.query;
const expectedRole = ctx.headers['expected-role']?.toString();
const { sub, roleNames } = await verifyBearerTokenFromRequest(
ctx.request,
ctx.guard.query.resource
);
const verifyToken = async (expectedResource?: string) => {
try {
return await verifyBearerTokenFromRequest(ctx.request, expectedResource);
} catch {
return {
sub: undefined,
roleNames: undefined,
};
}
};
const { sub, roleNames } = await verifyToken(resource);
if (unauthorizedRole && (!expectedRole || !roleNames?.includes(expectedRole))) {
ctx.body = {
'X-Hasura-User-Id':
sub ??
// When the previous token verification throws, the reason could be resource mismatch.
// So we verify the token again with no resource provided.
(await verifyToken().then(({ sub }) => sub)),
'X-Hasura-Role': unauthorizedRole,
};
ctx.status = 200;
return next();
}
if (expectedRole) {
assertThat(

View file

@ -20,7 +20,7 @@
"@logto/core-kit": "1.0.0-beta.20",
"@logto/language-kit": "1.0.0-beta.20",
"@logto/phrases": "workspace:^",
"@logto/react": "1.0.0-beta.10",
"@logto/react": "1.0.0-beta.11",
"@logto/schemas": "workspace:^",
"@parcel/core": "2.7.0",
"@parcel/transformer-sass": "2.7.0",

View file

@ -15,7 +15,7 @@
},
"devDependencies": {
"@jest/types": "^29.1.2",
"@logto/node": "1.0.0-beta.10",
"@logto/node": "1.0.0-beta.11",
"@logto/schemas": "workspace:^",
"@peculiar/webcrypto": "^1.3.3",
"@silverhand/eslint-config": "1.3.0",

View file

@ -30,7 +30,7 @@ const translation = {
got_it: '知道了',
sign_in_with: '通过 {{name}} 登录',
forgot_password: '重置密码',
switch_to: '用{{method}}登录',
switch_to: '切换到{{method}}',
},
description: {
email: '邮箱',

View file

@ -67,7 +67,7 @@ const errors = {
connector_id_mismatch: '传入的连接器 ID 与 session 中保存的记录不一致',
connector_session_not_found: '无法找到连接器登录信息,请尝试重新登录。',
verification_session_not_found: '验证失败,请重新验证。',
verification_expired: '无密码验证已过期。请返回重新验证。',
verification_expired: '当前页面已超时。为确保你的账号安全,请重新验证。',
unauthorized: '请先登录',
unsupported_prompt_name: '不支持的 prompt name',
forgot_password_not_enabled: '忘记密码功能没有开启。',

View file

@ -55,21 +55,6 @@
}
}
.outline {
border: _.border(var(--color-brand-default));
background: transparent;
color: var(--color-type-link);
&.disabled,
&:disabled {
border-color: var(--color-type-disable);
color: var(--color-type-disable);
}
&:active {
background: var(--color-overlay-brand-pressed);
}
}
:global(body.desktop) {
.primary {
@ -91,14 +76,4 @@
background: var(--color-overlay-neutral-hover);
}
}
.outline {
&:focus-visible {
outline: 3px solid var(--color-overlay-brand-focused);
}
&:not(:disabled):not(:active):hover {
background: var(--color-overlay-brand-hover);
}
}
}

View file

@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import * as styles from './index.module.scss';
export type ButtonType = 'primary' | 'secondary' | 'outline';
export type ButtonType = 'primary' | 'secondary';
type BaseProps = Omit<HTMLProps<HTMLButtonElement>, 'type' | 'size' | 'title'> & {
htmlType?: 'button' | 'submit' | 'reset';

View file

@ -37,7 +37,7 @@ const AcModal = ({
</div>
<div className={styles.content}>{children}</div>
<div className={styles.footer}>
<Button title={cancelText} type="outline" size="small" onClick={onClose} />
<Button title={cancelText} type="secondary" size="small" onClick={onClose} />
{onConfirm && <Button title={confirmText} size="small" onClick={onConfirm} />}
</div>
</div>

View file

@ -35,13 +35,6 @@
&::placeholder {
color: var(--color-type-secondary);
}
// Overwrite webkit auto-fill style
&:-webkit-autofill {
box-shadow: 0 0 0 30px var(--color-bg-body) inset;
-webkit-text-fill-color: var(--color-type-primary);
transition: none;
}
}
&:focus-within {

View file

@ -4,30 +4,38 @@
padding: _.unit(3) _.unit(4);
font: var(--font-body-2);
color: var(--color-type-primary);
background: var(--color-alert-99);
margin: 0 auto _.unit(2);
@include _.flex_row;
&:focus-visible {
outline: none;
}
}
.icon {
color: var(--color-alert-70);
width: 20px;
height: 20px;
margin-right: _.unit(3);
}
&.alert {
background: var(--color-alert-99);
.icon {
color: var(--color-alert-70);
}
}
&.info {
background: var(--color-neutral-variant-80);
.icon {
color: var(--color-neutral-variant-60);
}
}
}
.message {
flex: 1;
margin-right: _.unit(4);
}
.link {
text-decoration: underline;
cursor: pointer;
max-width: 20%;
}
@ -35,10 +43,4 @@
.notification {
border-radius: var(--radius);
}
.link {
&:hover {
color: var(--color-brand-default);
}
}
}

View file

@ -1,38 +1,23 @@
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import InfoIcon from '@/assets/icons/info-icon.svg';
import { onKeyDownHandler } from '@/utils/a11y';
import TextLink from '../TextLink';
import * as styles from './index.module.scss';
type Props = {
className?: string;
message: string;
onClose: () => void;
type?: 'info' | 'alert';
};
const Notification = ({ className, message, onClose }: Props) => {
const { t } = useTranslation();
const Notification = ({ className, message, onClose, type = 'info' }: Props) => {
return (
<div className={classNames(styles.notification, className)}>
<div className={classNames(styles.notification, styles[type], className)}>
<InfoIcon className={styles.icon} />
<div className={styles.message}>{message}</div>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a
role="button"
tabIndex={0}
className={styles.link}
onClick={onClose}
onKeyDown={onKeyDownHandler({
Esc: onClose,
Enter: onClose,
' ': onClose,
})}
>
{t('action.got_it')}
</a>
<TextLink text="action.got_it" className={styles.link} onClick={onClose} />
</div>
);
};

View file

@ -9,6 +9,12 @@
color: var(--color-brand-default);
text-decoration: none;
font: var(--font-label-2);
border-radius: _.unit(1);
padding: _.unit(1) _.unit(0.5);
&:active {
background: var(--color-overlay-brand-pressed);
}
}
&.secondary {
@ -21,10 +27,15 @@
:global(body.desktop) {
.link {
&.primary:hover {
text-decoration: underline;
background: var(--color-overlay-brand-hover);
}
&.secondary:hover {
&.primary:focus-visible {
outline: _.border(var(--color-overlay-brand-focused));
}
&.secondary:hover,
&.secondary:active {
color: var(--color-brand-default);
}
}

View file

@ -20,6 +20,7 @@
--color-neutral-95: #eff1f1;
--color-neutral-100: #fff;
--color-neutral-variant-60: #928f9a;
--color-neutral-variant-80: #e5e1ec;
--color-danger-30: #930006;
@ -104,6 +105,7 @@
--color-neutral-99: #191c1d;
--color-neutral-100: #000;
--color-neutral-variant-60: #928f9a;
--color-neutral-variant-80: #5f5d67;
--color-neutral-variant-90: #47464e;

View file

@ -18,6 +18,9 @@ export const onKeyDownHandler =
if (typeof callback === 'object') {
callback[key]?.(event);
if (callback[key]) {
event.preventDefault();
}
}
};

View file

@ -109,7 +109,7 @@ importers:
'@logto/language-kit': 1.0.0-beta.20
'@logto/phrases': workspace:^
'@logto/phrases-ui': workspace:^
'@logto/react': 1.0.0-beta.10
'@logto/react': 1.0.0-beta.11
'@logto/schemas': workspace:^
'@mdx-js/react': ^1.6.22
'@parcel/core': 2.7.0
@ -180,7 +180,7 @@ importers:
'@logto/language-kit': 1.0.0-beta.20
'@logto/phrases': link:../phrases
'@logto/phrases-ui': link:../phrases-ui
'@logto/react': 1.0.0-beta.10_react@18.2.0
'@logto/react': 1.0.0-beta.11_react@18.2.0
'@logto/schemas': link:../schemas
'@mdx-js/react': 1.6.22_react@18.2.0
'@parcel/core': 2.7.0
@ -416,7 +416,7 @@ importers:
'@logto/core-kit': 1.0.0-beta.20
'@logto/language-kit': 1.0.0-beta.20
'@logto/phrases': workspace:^
'@logto/react': 1.0.0-beta.10
'@logto/react': 1.0.0-beta.11
'@logto/schemas': workspace:^
'@parcel/core': 2.7.0
'@parcel/transformer-sass': 2.7.0
@ -443,7 +443,7 @@ importers:
'@logto/core-kit': 1.0.0-beta.20
'@logto/language-kit': 1.0.0-beta.20
'@logto/phrases': link:../phrases
'@logto/react': 1.0.0-beta.10_react@18.2.0
'@logto/react': 1.0.0-beta.11_react@18.2.0
'@logto/schemas': link:../schemas
'@parcel/core': 2.7.0
'@parcel/transformer-sass': 2.7.0_@parcel+core@2.7.0
@ -470,7 +470,7 @@ importers:
packages/integration-tests:
specifiers:
'@jest/types': ^29.1.2
'@logto/node': 1.0.0-beta.10
'@logto/node': 1.0.0-beta.11
'@logto/schemas': workspace:^
'@peculiar/webcrypto': ^1.3.3
'@silverhand/eslint-config': 1.3.0
@ -495,7 +495,7 @@ importers:
typescript: ^4.7.4
devDependencies:
'@jest/types': 29.1.2
'@logto/node': 1.0.0-beta.10
'@logto/node': 1.0.0-beta.11
'@logto/schemas': link:../schemas
'@peculiar/webcrypto': 1.3.3
'@silverhand/eslint-config': 1.3.0_swk2g7ygmfleszo5c33j4vooni
@ -2300,19 +2300,19 @@ packages:
dev: true
optional: true
/@logto/browser/1.0.0-beta.10:
resolution: {integrity: sha512-ziZv8TTWwzK9PgBtioF9Wplfaj0J/InyxSBmfgFS5PX54GAVfOy3uGBi9hGL6HDRPj3SYs2U1bi21YPlF9z/8w==}
/@logto/browser/1.0.0-beta.11:
resolution: {integrity: sha512-Ofdj5UqLzwoW66XGnff+1U6Lix90qI2Q30+TISJz6soCm97oqJdWpY2SRlMAfnvDobsFPqAxyCAz49Cg16h50Q==}
dependencies:
'@logto/client': 1.0.0-beta.10
'@logto/client': 1.0.0-beta.11
'@silverhand/essentials': 1.3.0
js-base64: 3.7.2
dev: true
/@logto/client/1.0.0-beta.10:
resolution: {integrity: sha512-XHkOJdvxsBix/8cZ3a6Hx3UiiQkaJBDF9D7BGM8lZxPz5nsGq8YkX5ZRvjVCX9Y3fE3nRb5Iv1ccWvV192lUWg==}
/@logto/client/1.0.0-beta.11:
resolution: {integrity: sha512-7Nl+53JPgB0wjMU9zJH6SrOj4OKn0Kl9U1cumt/IHSYuaDyV7TgvR/HaaeSZuZ0JxbMg66Riee87Qx9Tv46k6A==}
dependencies:
'@logto/core-kit': 1.0.0-beta.19
'@logto/js': 1.0.0-beta.10
'@logto/core-kit': 1.0.0-beta.20
'@logto/js': 1.0.0-beta.11
'@silverhand/essentials': 1.3.0
camelcase-keys: 7.0.2
jose: 4.6.0
@ -2340,16 +2340,6 @@ packages:
zod: 3.19.1
dev: false
/@logto/core-kit/1.0.0-beta.19:
resolution: {integrity: sha512-cqwfz+Ic/t7mV23QUEXWeRaLTqN71NSv02adaul8VWZQg4IePOlWLLaiTyY6ods88f3ZOarIBlJ5fYexw3J8qg==}
engines: {node: ^16.0.0}
dependencies:
'@logto/language-kit': 1.0.0-beta.20
color: 4.2.3
nanoid: 3.3.4
zod: 3.19.1
dev: true
/@logto/core-kit/1.0.0-beta.20:
resolution: {integrity: sha512-seYvL/aGYRfO4d0FYfKIW/Cu9PnFMRpRM5/oRXwXbcbv+LY1a3TcAX0itrVXeBygIrxiAmWd9DL7CGIWzb48Qg==}
engines: {node: ^16.0.0}
@ -2359,10 +2349,10 @@ packages:
nanoid: 3.3.4
zod: 3.19.1
/@logto/js/1.0.0-beta.10:
resolution: {integrity: sha512-mMzverjbeKtGjSb0NmEUHzDBRrXhCPOydCE37yhzL/qORiehyblPqntw3lLrf5oCNUKaxv7PzT5q/Lfbxb3Q8g==}
/@logto/js/1.0.0-beta.11:
resolution: {integrity: sha512-V0cV+T+DFcpqAAIjfdiEuEYG+ePASR7VbPM1zMLv02S/5zz3pStedbLrksZaSVUOmQvsieEF5d5/kZ8ZshQa0A==}
dependencies:
'@logto/core-kit': 1.0.0-beta.19
'@logto/core-kit': 1.0.0-beta.20
'@silverhand/essentials': 1.3.0
camelcase-keys: 7.0.2
jose: 4.6.0
@ -2375,10 +2365,10 @@ packages:
dependencies:
zod: 3.19.1
/@logto/node/1.0.0-beta.10:
resolution: {integrity: sha512-4st77cD1h/bCIrt+BttDHva/I7ibLw0ilvi30uadEwuH0Ih8WFYvlyi2V1wOljd5Ym+fqSMbyKXQQIwba6CenQ==}
/@logto/node/1.0.0-beta.11:
resolution: {integrity: sha512-nsa9RtrzBRmLMJNeNzutJ72bLwZpGUr4lhBAze7YXJi6o9ujvZOLgdvZAZeoSdxxBhTilwbDWNXmj8n3Ndavaw==}
dependencies:
'@logto/client': 1.0.0-beta.10
'@logto/client': 1.0.0-beta.11
'@silverhand/essentials': 1.3.0
js-base64: 3.7.2
node-fetch: 2.6.7
@ -2386,12 +2376,12 @@ packages:
- encoding
dev: true
/@logto/react/1.0.0-beta.10_react@18.2.0:
resolution: {integrity: sha512-MutQplD5VkUqYIhnaQgNiRpbQTRK/OS2Ut70j99mVax/frImlIu1m1YeuE8NEqGmB9SBQvJoP5kECxVNf2a4Nw==}
/@logto/react/1.0.0-beta.11_react@18.2.0:
resolution: {integrity: sha512-W9L1QrJml4tHlrUkmdaJRsj4ln+SgwGAeyF+QhmKBq5CoSR+EauE63ySLhPn2KFVrc3m/sioejJIVy+1sUe21w==}
peerDependencies:
react: '>=16.8.0 || ^18.0.0'
dependencies:
'@logto/browser': 1.0.0-beta.10
'@logto/browser': 1.0.0-beta.11
'@silverhand/essentials': 1.3.0
react: 18.2.0
dev: true