mirror of
https://github.com/logto-io/logto.git
synced 2025-02-03 21:48:55 -05:00
feat(core,console): handle connector target confict (#3251)
This commit is contained in:
parent
f25a9d343c
commit
13fcf6f3c2
14 changed files with 225 additions and 76 deletions
|
@ -103,7 +103,6 @@ const ConnectorContent = ({ isDeleted, connectorData, onConnectorUpdated }: Prop
|
||||||
learnMoreLink={getDocumentationUrl('/docs/references/connectors')}
|
learnMoreLink={getDocumentationUrl('/docs/references/connectors')}
|
||||||
>
|
>
|
||||||
<BasicForm
|
<BasicForm
|
||||||
connectorType={connectorData.type}
|
|
||||||
isStandard={connectorData.isStandard}
|
isStandard={connectorData.isStandard}
|
||||||
isDarkDefaultVisible={Boolean(connectorData.metadata.logoDark)}
|
isDarkDefaultVisible={Boolean(connectorData.metadata.logoDark)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,6 +6,31 @@
|
||||||
margin-top: _.unit(0.5);
|
margin-top: _.unit(0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--color-text);
|
||||||
|
font: var(--font-body-2);
|
||||||
|
margin-top: _.unit(0.5);
|
||||||
|
background-color: var(--color-danger-toast-background);
|
||||||
|
padding: _.unit(3) _.unit(4);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: _.unit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
span {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
> ul {
|
||||||
|
padding-left: _.unit(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.fieldButton {
|
.fieldButton {
|
||||||
margin-top: _.unit(2);
|
margin-top: _.unit(2);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { ConnectorType } from '@logto/connector-kit';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import CaretDown from '@/assets/images/caret-down.svg';
|
import CaretDown from '@/assets/images/caret-down.svg';
|
||||||
import CaretUp from '@/assets/images/caret-up.svg';
|
import CaretUp from '@/assets/images/caret-up.svg';
|
||||||
|
import Error from '@/assets/images/toast-error.svg';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import FormField from '@/components/FormField';
|
import FormField from '@/components/FormField';
|
||||||
import Select from '@/components/Select';
|
import Select from '@/components/Select';
|
||||||
import TextInput from '@/components/TextInput';
|
import TextInput from '@/components/TextInput';
|
||||||
import TextLink from '@/components/TextLink';
|
import TextLink from '@/components/TextLink';
|
||||||
|
import UnnamedTrans from '@/components/UnnamedTrans';
|
||||||
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
import useDocumentationUrl from '@/hooks/use-documentation-url';
|
||||||
import { uriValidator } from '@/utils/validator';
|
import { uriValidator } from '@/utils/validator';
|
||||||
|
|
||||||
|
@ -21,14 +22,14 @@ type Props = {
|
||||||
isAllowEditTarget?: boolean;
|
isAllowEditTarget?: boolean;
|
||||||
isDarkDefaultVisible?: boolean;
|
isDarkDefaultVisible?: boolean;
|
||||||
isStandard?: boolean;
|
isStandard?: boolean;
|
||||||
connectorType: ConnectorType;
|
conflictConnectorName?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BasicForm = ({
|
const BasicForm = ({
|
||||||
isAllowEditTarget,
|
isAllowEditTarget,
|
||||||
isDarkDefaultVisible,
|
isDarkDefaultVisible,
|
||||||
connectorType,
|
|
||||||
isStandard,
|
isStandard,
|
||||||
|
conflictConnectorName,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const { getDocumentationUrl } = useDocumentationUrl();
|
const { getDocumentationUrl } = useDocumentationUrl();
|
||||||
|
@ -104,48 +105,74 @@ const BasicForm = ({
|
||||||
onClick={toggleDarkVisible}
|
onClick={toggleDarkVisible}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<FormField
|
|
||||||
isRequired
|
|
||||||
title="connectors.guide.target"
|
|
||||||
tip={(closeTipHandler) => (
|
|
||||||
<Trans
|
|
||||||
components={{
|
|
||||||
a: (
|
|
||||||
<TextLink
|
|
||||||
href={getDocumentationUrl('/docs/references/connectors/#target')}
|
|
||||||
target="_blank"
|
|
||||||
onClick={closeTipHandler}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('connectors.guide.target_tooltip')}
|
|
||||||
</Trans>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
placeholder={t('connectors.guide.target_placeholder')}
|
|
||||||
hasError={Boolean(errors.target)}
|
|
||||||
disabled={!isAllowEditTarget}
|
|
||||||
{...register('target', { required: true })}
|
|
||||||
/>
|
|
||||||
<div className={styles.tip}>{t('connectors.guide.target_tip')}</div>
|
|
||||||
</FormField>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{connectorType === ConnectorType.Social && (
|
<FormField
|
||||||
<FormField title="connectors.guide.sync_profile">
|
isRequired
|
||||||
<Controller
|
title="connectors.guide.target"
|
||||||
name="syncProfile"
|
tip={(closeTipHandler) => (
|
||||||
control={control}
|
<Trans
|
||||||
rules={{ required: true }}
|
components={{
|
||||||
render={({ field: { onChange, value } }) => (
|
a: (
|
||||||
<Select options={syncProfileOptions} value={value} onChange={onChange} />
|
<TextLink
|
||||||
)}
|
href={getDocumentationUrl('/docs/references/connectors/#target')}
|
||||||
/>
|
target="_blank"
|
||||||
<div className={styles.tip}>{t('connectors.guide.sync_profile_tip')}</div>
|
onClick={closeTipHandler}
|
||||||
</FormField>
|
/>
|
||||||
)}
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('connectors.guide.target_tooltip')}
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
placeholder={t('connectors.guide.target_placeholder')}
|
||||||
|
hasError={Boolean(errors.target)}
|
||||||
|
disabled={!isAllowEditTarget}
|
||||||
|
{...register('target', { required: true })}
|
||||||
|
/>
|
||||||
|
<div className={styles.tip}>{t('connectors.guide.target_tip')}</div>
|
||||||
|
{conflictConnectorName && (
|
||||||
|
<div className={styles.error}>
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<Error />
|
||||||
|
</div>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Trans
|
||||||
|
components={{
|
||||||
|
span: <UnnamedTrans resource={conflictConnectorName} />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('connectors.guide.target_conflict')}
|
||||||
|
</Trans>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Trans
|
||||||
|
components={{
|
||||||
|
span: <UnnamedTrans resource={conflictConnectorName} />,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('connectors.guide.target_conflict_line2')}
|
||||||
|
</Trans>
|
||||||
|
</li>
|
||||||
|
<li>{t('connectors.guide.target_conflict_line3')}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FormField>
|
||||||
|
<FormField title="connectors.guide.sync_profile">
|
||||||
|
<Controller
|
||||||
|
name="syncProfile"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: true }}
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Select options={syncProfileOptions} value={value} onChange={onChange} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className={styles.tip}>{t('connectors.guide.sync_profile_tip')}</div>
|
||||||
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { generateStandardId } from '@logto/core-kit';
|
import { generateStandardId } from '@logto/core-kit';
|
||||||
import { isLanguageTag } from '@logto/language-kit';
|
import { isLanguageTag } from '@logto/language-kit';
|
||||||
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
|
import type { ConnectorFactoryResponse, ConnectorResponse, RequestErrorBody } from '@logto/schemas';
|
||||||
import { ConnectorType } from '@logto/schemas';
|
import { ConnectorType } from '@logto/schemas';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import { useState } from 'react';
|
import { HTTPError } from 'ky';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -30,19 +31,30 @@ import { useConfigParser } from '../ConnectorForm/hooks';
|
||||||
import { initFormData, parseFormConfig } from '../ConnectorForm/utils';
|
import { initFormData, parseFormConfig } from '../ConnectorForm/utils';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
const targetErrorCode = 'connector.multiple_target_with_same_platform';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
connector: ConnectorFactoryResponse;
|
connector: ConnectorFactoryResponse;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Guide = ({ connector, onClose }: Props) => {
|
const Guide = ({ connector, onClose }: Props) => {
|
||||||
const api = useApi();
|
const api = useApi({ hideErrorToast: true });
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [callbackConnectorId, setCallbackConnectorId] = useState<string>(generateStandardId());
|
const [callbackConnectorId, setCallbackConnectorId] = useState<string>(generateStandardId());
|
||||||
const { updateConfigs } = useConfigs();
|
const { updateConfigs } = useConfigs();
|
||||||
const parseJsonConfig = useConfigParser();
|
const parseJsonConfig = useConfigParser();
|
||||||
|
const [conflictConnectorName, setConflictConnectorName] = useState<Record<string, string>>();
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const { id: connectorId, type: connectorType, name, readme, formItems } = connector;
|
const {
|
||||||
|
id: connectorId,
|
||||||
|
type: connectorType,
|
||||||
|
name,
|
||||||
|
readme,
|
||||||
|
formItems,
|
||||||
|
target,
|
||||||
|
isStandard,
|
||||||
|
} = connector;
|
||||||
const { title, content } = splitMarkdownByTitle(readme);
|
const { title, content } = splitMarkdownByTitle(readme);
|
||||||
const { language } = i18next;
|
const { language } = i18next;
|
||||||
const connectorName = conditional(isLanguageTag(language) && name[language]) ?? name.en;
|
const connectorName = conditional(isLanguageTag(language) && name[language]) ?? name.en;
|
||||||
|
@ -59,13 +71,24 @@ const Guide = ({ connector, onClose }: Props) => {
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
watch,
|
watch,
|
||||||
|
setValue,
|
||||||
|
setError,
|
||||||
} = methods;
|
} = methods;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSocialConnector && !isStandard) {
|
||||||
|
setValue('target', target);
|
||||||
|
}
|
||||||
|
}, [isSocialConnector, target, isStandard, setValue]);
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
if (isSubmitting) {
|
if (isSubmitting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recover error state
|
||||||
|
setConflictConnectorName(undefined);
|
||||||
|
|
||||||
const { formItems, isStandard, id: connectorId, type } = connector;
|
const { formItems, isStandard, id: connectorId, type } = connector;
|
||||||
const config = formItems ? parseFormConfig(data, formItems) : parseJsonConfig(data.config);
|
const config = formItems ? parseFormConfig(data, formItems) : parseJsonConfig(data.config);
|
||||||
const { syncProfile, name, logo, logoDark, target } = data;
|
const { syncProfile, name, logo, logoDark, target } = data;
|
||||||
|
@ -88,23 +111,41 @@ const Guide = ({ connector, onClose }: Props) => {
|
||||||
? { ...basePayload, syncProfile: syncProfile === SyncProfileMode.EachSignIn }
|
? { ...basePayload, syncProfile: syncProfile === SyncProfileMode.EachSignIn }
|
||||||
: basePayload;
|
: basePayload;
|
||||||
|
|
||||||
const createdConnector = await api
|
try {
|
||||||
.post('api/connectors', {
|
const createdConnector = await api
|
||||||
json: payload,
|
.post('api/connectors', {
|
||||||
})
|
json: payload,
|
||||||
.json<ConnectorResponse>();
|
})
|
||||||
|
.json<ConnectorResponse>();
|
||||||
|
|
||||||
await updateConfigs({
|
await updateConfigs({
|
||||||
...conditional(!isSocialConnector && { passwordlessConfigured: true }),
|
...conditional(!isSocialConnector && { passwordlessConfigured: true }),
|
||||||
});
|
});
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
toast.success(t('general.saved'));
|
toast.success(t('general.saved'));
|
||||||
navigate(
|
navigate(
|
||||||
`/connectors/${isSocialConnector ? ConnectorsTabs.Social : ConnectorsTabs.Passwordless}/${
|
`/connectors/${isSocialConnector ? ConnectorsTabs.Social : ConnectorsTabs.Passwordless}/${
|
||||||
createdConnector.id
|
createdConnector.id
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof HTTPError) {
|
||||||
|
const { response } = error;
|
||||||
|
const metadata = await response.json<
|
||||||
|
RequestErrorBody<{ connectorName: Record<string, string> }>
|
||||||
|
>();
|
||||||
|
|
||||||
|
if (metadata.code === targetErrorCode) {
|
||||||
|
setConflictConnectorName(metadata.data.connectorName);
|
||||||
|
setError('target', {}, { shouldFocus: true });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -135,9 +176,9 @@ const Guide = ({ connector, onClose }: Props) => {
|
||||||
<div>{t('connectors.guide.general_setting')}</div>
|
<div>{t('connectors.guide.general_setting')}</div>
|
||||||
</div>
|
</div>
|
||||||
<BasicForm
|
<BasicForm
|
||||||
isAllowEditTarget
|
isAllowEditTarget={connector.isStandard}
|
||||||
connectorType={connector.type}
|
|
||||||
isStandard={connector.isStandard}
|
isStandard={connector.isStandard}
|
||||||
|
conflictConnectorName={conflictConnectorName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -158,16 +158,26 @@ export default function connectorRoutes<T extends AuthedRouter>(
|
||||||
|
|
||||||
if (connectorFactory.type === ConnectorType.Social) {
|
if (connectorFactory.type === ConnectorType.Social) {
|
||||||
const connectors = await getLogtoConnectors();
|
const connectors = await getLogtoConnectors();
|
||||||
|
const duplicateConnector = connectors
|
||||||
|
.filter(({ type }) => type === ConnectorType.Social)
|
||||||
|
.find(
|
||||||
|
({ metadata: { target, platform } }) =>
|
||||||
|
target ===
|
||||||
|
(metadata ? cleanDeep(metadata).target : connectorFactory.metadata.target) &&
|
||||||
|
platform === connectorFactory.metadata.platform
|
||||||
|
);
|
||||||
assertThat(
|
assertThat(
|
||||||
!connectors
|
!duplicateConnector,
|
||||||
.filter(({ type }) => type === ConnectorType.Social)
|
new RequestError(
|
||||||
.some(
|
{
|
||||||
({ metadata: { target, platform } }) =>
|
code: 'connector.multiple_target_with_same_platform',
|
||||||
target ===
|
status: 422,
|
||||||
(metadata ? cleanDeep(metadata).target : connectorFactory.metadata.target) &&
|
},
|
||||||
platform === connectorFactory.metadata.platform
|
{
|
||||||
),
|
connectorId: duplicateConnector?.metadata.id,
|
||||||
new RequestError({ code: 'connector.multiple_target_with_same_platform', status: 422 })
|
connectorName: duplicateConnector?.metadata.name,
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,12 @@ const connectors = {
|
||||||
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
||||||
|
target_conflict:
|
||||||
|
'The IdP name entered matches the existing <span>name</span>. Using the same idp name may cause unexpected sign-in behavior where users may access the same account through two different connectors.', // UNTRANSLATED
|
||||||
|
target_conflict_line2:
|
||||||
|
'If you\'d like to replace the current connector with the same identity provider and allow previous users to sign in without registering again, please delete the <span>name</span> connector and create a new one with the same "IdP name".', // UNTRANSLATED
|
||||||
|
target_conflict_line3:
|
||||||
|
'If you\'d like to connect to a different identity provider, please modify the "IdP name" and proceed.', // UNTRANSLATED
|
||||||
config: 'Enter your config JSON', // UNTRANSLATED
|
config: 'Enter your config JSON', // UNTRANSLATED
|
||||||
sync_profile: 'Sync profile information', // UNTRANSLATED
|
sync_profile: 'Sync profile information', // UNTRANSLATED
|
||||||
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
||||||
|
|
|
@ -55,6 +55,12 @@ const connectors = {
|
||||||
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.',
|
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.',
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'"IdP name" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "IdP name" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>',
|
'"IdP name" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "IdP name" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>',
|
||||||
|
target_conflict:
|
||||||
|
'The IdP name entered matches the existing <span>name</span> connector. Using the same idp name may cause unexpected sign-in behavior where users may access the same account through two different connectors.',
|
||||||
|
target_conflict_line2:
|
||||||
|
'If you\'d like to replace the current connector with the same identity provider and allow previous users to sign in without registering again, please delete the <span>name</span> connector and create a new one with the same "IdP name".',
|
||||||
|
target_conflict_line3:
|
||||||
|
'If you\'d like to connect to a different identity provider, please modify the "IdP name" and proceed.',
|
||||||
config: 'Enter your config JSON',
|
config: 'Enter your config JSON',
|
||||||
sync_profile: 'Sync profile information',
|
sync_profile: 'Sync profile information',
|
||||||
sync_profile_only_at_sign_up: 'Only sync at sign-up',
|
sync_profile_only_at_sign_up: 'Only sync at sign-up',
|
||||||
|
|
|
@ -56,6 +56,12 @@ const connectors = {
|
||||||
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
||||||
|
target_conflict:
|
||||||
|
'The IdP name entered matches the existing <span>name</span>. Using the same idp name may cause unexpected sign-in behavior where users may access the same account through two different connectors.', // UNTRANSLATED
|
||||||
|
target_conflict_line2:
|
||||||
|
'If you\'d like to replace the current connector with the same identity provider and allow previous users to sign in without registering again, please delete the <span>name</span> connector and create a new one with the same "IdP name".', // UNTRANSLATED
|
||||||
|
target_conflict_line3:
|
||||||
|
'If you\'d like to connect to a different identity provider, please modify the "IdP name" and proceed.', // UNTRANSLATED
|
||||||
config: 'Enter your config JSON', // UNTRANSLATED
|
config: 'Enter your config JSON', // UNTRANSLATED
|
||||||
sync_profile: 'Sync profile information', // UNTRANSLATED
|
sync_profile: 'Sync profile information', // UNTRANSLATED
|
||||||
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
||||||
|
|
|
@ -54,6 +54,12 @@ const connectors = {
|
||||||
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'Logto의 소셜 연동에서의 "목표"는 소셜 정보의 원천을 뜻해요. Logto의 디자인은 충돌을 피하기 위해서 같은 "목표"를 허용하지 않아요. 연동을 추가한 후에는 값을 변경할 수 없으므로 주의해주세요. <a>자세히 알아보기</a>',
|
'Logto의 소셜 연동에서의 "목표"는 소셜 정보의 원천을 뜻해요. Logto의 디자인은 충돌을 피하기 위해서 같은 "목표"를 허용하지 않아요. 연동을 추가한 후에는 값을 변경할 수 없으므로 주의해주세요. <a>자세히 알아보기</a>',
|
||||||
|
target_conflict:
|
||||||
|
'The IdP name entered matches the existing <span>name</span>. Using the same idp name may cause unexpected sign-in behavior where users may access the same account through two different connectors.', // UNTRANSLATED
|
||||||
|
target_conflict_line2:
|
||||||
|
'If you\'d like to replace the current connector with the same identity provider and allow previous users to sign in without registering again, please delete the <span>name</span> connector and create a new one with the same "IdP name".', // UNTRANSLATED
|
||||||
|
target_conflict_line3:
|
||||||
|
'If you\'d like to connect to a different identity provider, please modify the "IdP name" and proceed.', // UNTRANSLATED
|
||||||
config: '여기에 JSON을 입력',
|
config: '여기에 JSON을 입력',
|
||||||
sync_profile: '프로필 정보 동기화',
|
sync_profile: '프로필 정보 동기화',
|
||||||
sync_profile_only_at_sign_up: '회원가입할 때 동기화',
|
sync_profile_only_at_sign_up: '회원가입할 때 동기화',
|
||||||
|
|
|
@ -54,6 +54,12 @@ const connectors = {
|
||||||
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
||||||
|
target_conflict:
|
||||||
|
'The IdP name entered matches the existing <span>name</span>. Using the same idp name may cause unexpected sign-in behavior where users may access the same account through two different connectors.', // UNTRANSLATED
|
||||||
|
target_conflict_line2:
|
||||||
|
'If you\'d like to replace the current connector with the same identity provider and allow previous users to sign in without registering again, please delete the <span>name</span> connector and create a new one with the same "IdP name".', // UNTRANSLATED
|
||||||
|
target_conflict_line3:
|
||||||
|
'If you\'d like to connect to a different identity provider, please modify the "IdP name" and proceed.', // UNTRANSLATED
|
||||||
config: 'Digite seu JSON aqui',
|
config: 'Digite seu JSON aqui',
|
||||||
sync_profile: 'Sincronizar informações de perfil',
|
sync_profile: 'Sincronizar informações de perfil',
|
||||||
sync_profile_only_at_sign_up: 'Sincronizar apenas no registro',
|
sync_profile_only_at_sign_up: 'Sincronizar apenas no registro',
|
||||||
|
|
|
@ -55,6 +55,12 @@ const connectors = {
|
||||||
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
||||||
|
target_conflict:
|
||||||
|
'The IdP name entered matches the existing <span>name</span>. Using the same idp name may cause unexpected sign-in behavior where users may access the same account through two different connectors.', // UNTRANSLATED
|
||||||
|
target_conflict_line2:
|
||||||
|
'If you\'d like to replace the current connector with the same identity provider and allow previous users to sign in without registering again, please delete the <span>name</span> connector and create a new one with the same "IdP name".', // UNTRANSLATED
|
||||||
|
target_conflict_line3:
|
||||||
|
'If you\'d like to connect to a different identity provider, please modify the "IdP name" and proceed.', // UNTRANSLATED
|
||||||
config: 'Enter your config JSON', // UNTRANSLATED
|
config: 'Enter your config JSON', // UNTRANSLATED
|
||||||
sync_profile: 'Sync profile information', // UNTRANSLATED
|
sync_profile: 'Sync profile information', // UNTRANSLATED
|
||||||
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
||||||
|
|
|
@ -56,6 +56,12 @@ const connectors = {
|
||||||
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
'The value of “IdP name” can be a unique identifier string to distinguish your social identifies. This setting cannot be changed after the connector is built.', // UNTRANSLATED
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
'"Target" in Logto social connectors refers to the "source" of your social identities. In Logto design, we do not accept the same "target" of a specific platform to avoid conflicts. You should be very careful before you add a connector since you CAN NOT change its value once you create it. <a>Learn more.</a>', // UNTRANSLATED
|
||||||
|
target_conflict:
|
||||||
|
'The IdP name entered matches the existing <span>name</span>. Using the same idp name may cause unexpected sign-in behavior where users may access the same account through two different connectors.', // UNTRANSLATED
|
||||||
|
target_conflict_line2:
|
||||||
|
'If you\'d like to replace the current connector with the same identity provider and allow previous users to sign in without registering again, please delete the <span>name</span> connector and create a new one with the same "IdP name".', // UNTRANSLATED
|
||||||
|
target_conflict_line3:
|
||||||
|
'If you\'d like to connect to a different identity provider, please modify the "IdP name" and proceed.', // UNTRANSLATED
|
||||||
config: 'Enter your config JSON', // UNTRANSLATED
|
config: 'Enter your config JSON', // UNTRANSLATED
|
||||||
sync_profile: 'Sync profile information', // UNTRANSLATED
|
sync_profile: 'Sync profile information', // UNTRANSLATED
|
||||||
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
sync_profile_only_at_sign_up: 'Only sync at sign-up', // UNTRANSLATED
|
||||||
|
|
|
@ -51,6 +51,11 @@ const connectors = {
|
||||||
'在“身份供应商名称”字段中输入唯一的标识符字符串,用于区分社交身份来源。注意,在连接器创建成功后,无法再次修改此设置。',
|
'在“身份供应商名称”字段中输入唯一的标识符字符串,用于区分社交身份来源。注意,在连接器创建成功后,无法再次修改此设置。',
|
||||||
target_tooltip:
|
target_tooltip:
|
||||||
'Logto 社交连接器的「target」指的是社交身份的「来源」。在 Logto 的设计里,我们不允许某一平台的连接器中有相同的「target」以避免身份的冲突。在添加连接器时,你需要格外小心,我们「不允许」用户在创建之后更改「target」的值。 <a>了解更多</a>',
|
'Logto 社交连接器的「target」指的是社交身份的「来源」。在 Logto 的设计里,我们不允许某一平台的连接器中有相同的「target」以避免身份的冲突。在添加连接器时,你需要格外小心,我们「不允许」用户在创建之后更改「target」的值。 <a>了解更多</a>',
|
||||||
|
target_conflict:
|
||||||
|
'此「身份供应商名称」值与现有的 <span>name</span> 连接器相同。使用相同的身份供应商名称会导致不符合预期的登录行为,用户可能通过两个不同的连接器访问同一个帐户。',
|
||||||
|
target_conflict_line2:
|
||||||
|
'如果你想替换当前的连接器,并连接相同的身份提供商(IdP),以便先前的用户可以直接登录而无需重新注册,请先删除 <span>name</span> 连接器,再创建一个新的连接器并使用相同的「身份供应商名称」值。',
|
||||||
|
target_conflict_line3: '如果您想连接一个新的身份验证提供程序,请修改「身份供应商名称」并继续。',
|
||||||
config: '粘贴你的 JSON 代码',
|
config: '粘贴你的 JSON 代码',
|
||||||
sync_profile: '开启用户资料同步',
|
sync_profile: '开启用户资料同步',
|
||||||
sync_profile_only_at_sign_up: '首次注册时同步',
|
sync_profile_only_at_sign_up: '首次注册时同步',
|
||||||
|
|
|
@ -6,9 +6,9 @@ export type RequestErrorMetadata = Record<string, unknown> & {
|
||||||
expose?: boolean;
|
expose?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RequestErrorBody = {
|
export type RequestErrorBody<T = unknown> = {
|
||||||
message: string;
|
message: string;
|
||||||
data: unknown;
|
data: T;
|
||||||
code: LogtoErrorCode;
|
code: LogtoErrorCode;
|
||||||
details?: string;
|
details?: string;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue