0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

feat(console): add signing certificate reader (#5009)

* feat(console): add signing certificate reader

* fix(core): throw invalid certificate SsoConnectorError only when failed to construct a certificate
This commit is contained in:
Darcy Ye 2023-12-04 14:04:24 +08:00 committed by GitHub
parent 0bec4cd1d3
commit 3bab5351a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 311 additions and 110 deletions

View file

@ -63,8 +63,8 @@ export type ParsedSsoIdentityProviderConfig<T extends SsoProviderName> =
entityId: string;
signInEndpoint: string;
x509Certificate: string;
expiresAt: number;
isValid: boolean;
certificateExpiresAt: number;
isCertificateValid: boolean;
};
}
: never;

View file

@ -1,7 +1,8 @@
import { type AdminConsoleKey } from '@logto/phrases';
import { Theme } from '@logto/schemas';
import { useCallback } from 'react';
import { useDropzone, type FileRejection } from 'react-dropzone';
import { useFormContext } from 'react-hook-form';
import { useDropzone, type FileRejection, type Accept } from 'react-dropzone';
import { type FieldError } from 'react-hook-form';
import Delete from '@/assets/icons/delete.svg';
import FileIconDark from '@/assets/icons/file-icon-dark.svg';
@ -11,29 +12,29 @@ import Button from '@/ds-components/Button';
import IconButton from '@/ds-components/IconButton';
import useTheme from '@/hooks/use-theme';
import { type SamlGuideFormType } from '../../../EnterpriseSso/types';
import { calculateXmlFileSize } from '../SamlMetadataForm/utils';
import { calculateFileSize } from '../SamlMetadataForm/utils';
import * as styles from './index.module.scss';
const xmlMimeTypes = ['application/xml', 'text/xml'];
const xmlFileName = 'identity provider metadata.xml'; // Real file name does not matter, use a generic name.
const xmlFileSizeLimit = 500 * 1024; // 500 KB
const fileSizeLimit = 500 * 1024; // 500 KB
type Props = {
onChange: (xmlContent?: string) => void;
export type Props = {
onChange: (fileContent?: string) => void;
value?: string;
attributes: {
accept: Accept; // File reader accepted file types.
buttonTitle: AdminConsoleKey; // I18n key for the button title.
defaultFilename: string; // Default file name.
defaultFileMimeType: string; // Default file MIME type when calculating the file size.
};
fieldError?: FieldError;
setError: (error: FieldError) => void;
};
function XmlFileReader({ onChange, value }: Props) {
function FileReader({ onChange, value, attributes, fieldError, setError }: Props) {
const theme = useTheme();
const {
setError,
formState: {
errors: { metadata: metadataError },
},
} = useFormContext<SamlGuideFormType>();
const { accept, buttonTitle, defaultFilename, defaultFileMimeType } = attributes;
/**
* As you can see, per `useDropzone` hook's config, there are at most one file, if file is rejected, then we can return as long as we get the error message.
@ -43,7 +44,7 @@ function XmlFileReader({ onChange, value }: Props) {
if (fileRejection.length > 0) {
const fileErrors = fileRejection[0]?.errors;
if (fileErrors?.[0]?.message) {
setError('metadata', {
setError({
type: 'custom',
message: fileErrors[0]?.message,
});
@ -56,8 +57,8 @@ function XmlFileReader({ onChange, value }: Props) {
return;
}
const xmlContent = await acceptedFile.text();
onChange(xmlContent);
const fileContent = await acceptedFile.text();
onChange(fileContent);
},
[onChange, setError]
);
@ -70,22 +71,22 @@ function XmlFileReader({ onChange, value }: Props) {
onDrop,
noDrag: true, // Only allow file selection via the file input.
maxFiles: 1,
maxSize: xmlFileSizeLimit,
maxSize: fileSizeLimit,
multiple: false, // Upload only one file at a time.
accept: Object.fromEntries(xmlMimeTypes.map((mimeType) => [mimeType, []])),
accept,
});
return (
<div>
{value ? (
<div className={styles.preview}>
{theme === Theme.Dark ? <FileIcon /> : <FileIconDark />}
{theme === Theme.Dark ? <FileIconDark /> : <FileIcon />}
<div className={styles.fileInfo}>
<span className={styles.fileName}>{xmlFileName}</span>
<span className={styles.fileName}>{defaultFilename}</span>
{/* Not using `File.size` since the file content (variable `value` in this case) is stored in DB in string type */}
<span className={styles.fileSize}>{`${(calculateXmlFileSize(value) / 1024).toFixed(
2
)} KB`}</span>
<span className={styles.fileSize}>{`${(
calculateFileSize(value, defaultFilename, defaultFileMimeType) / 1024
).toFixed(2)} KB`}</span>
</div>
<IconButton
className={styles.delete}
@ -99,18 +100,14 @@ function XmlFileReader({ onChange, value }: Props) {
) : (
<>
<div {...getRootProps()}>
<Button
icon={<UploaderIcon />}
title="enterprise_sso_details.upload_idp_metadata_button_text"
size="large"
/>
<Button icon={<UploaderIcon />} title={buttonTitle} size="large" />
<input {...getInputProps({ className: styles.fileInput })} />
</div>
{Boolean(metadataError) && <div className={styles.error}>{metadataError?.message}</div>}
{Boolean(fieldError) && <div className={styles.error}>{fieldError?.message}</div>}
</>
)}
</div>
);
}
export default XmlFileReader;
export default FileReader;

View file

@ -19,9 +19,6 @@
font: var(--font-body-2);
overflow-wrap: break-word;
word-wrap: break-word;
display: flex;
flex-direction: row;
align-items: center;
}
}
@ -30,18 +27,25 @@
}
}
.indicator {
width: 10px;
height: 10px;
margin-right: _.unit(2);
border-radius: 50%;
background: var(--color-on-success-container);
}
.certificatePreview {
display: flex;
flex-direction: row;
align-items: center;
font: var(--font-body-2);
.errorStatus {
background: var(--color-on-error-container);
}
.indicator {
width: 10px;
height: 10px;
margin-right: _.unit(2);
border-radius: 50%;
background: var(--color-on-success-container);
}
.copyToClipboard {
margin-left: _.unit(1);
.errorStatus {
background: var(--color-on-error-container);
}
.copyToClipboard {
margin-left: _.unit(1);
}
}

View file

@ -15,51 +15,66 @@ type Props = {
identityProviderConfig: ParsedSsoIdentityProviderConfig<SsoProviderName.SAML>['identityProvider'];
};
type CertificatePreviewProps = {
identityProviderConfig: {
x509Certificate: string;
certificateExpiresAt: number;
isCertificateValid: boolean;
};
className?: string;
};
export function CertificatePreview({
identityProviderConfig: { x509Certificate, certificateExpiresAt, isCertificateValid },
className,
}: CertificatePreviewProps) {
const { language } = i18next;
return (
<div className={classNames(styles.certificatePreview, className)}>
<div className={classNames(styles.indicator, !isCertificateValid && styles.errorStatus)} />
<DynamicT
forKey="enterprise_sso_details.saml_preview.certificate_content"
interpolation={{
date: new Date(certificateExpiresAt).toLocaleDateString(
// TODO: @darcyYe check whether can use date-fns later, may need a Logto locale to date-fns locale mapping.
conditional(isLanguageTag(language) && language) ?? 'en',
{
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}
),
}}
/>
<CopyToClipboard className={styles.copyToClipboard} variant="icon" value={x509Certificate} />
</div>
);
}
function ParsedConfigPreview({ identityProviderConfig }: Props) {
const { t } = useTranslation(undefined, {
keyPrefix: 'admin_console.enterprise_sso_details.saml_preview',
});
const { language } = i18next;
if (!identityProviderConfig) {
return null;
}
const { entityId, signInEndpoint, x509Certificate, expiresAt, isValid } = identityProviderConfig;
return (
<div className={styles.container}>
<div>
<div className={styles.title}>{t('sign_on_url')}</div>
<div className={styles.content}>{signInEndpoint}</div>
<div className={styles.content}>{identityProviderConfig.signInEndpoint}</div>
</div>
<div>
<div className={styles.title}>{t('entity_id')}</div>
<div className={styles.content}>{entityId}</div>
<div className={styles.content}>{identityProviderConfig.entityId}</div>
</div>
<div>
<div className={styles.title}>{t('x509_certificate')}</div>
<div className={styles.content}>
<div className={classNames(styles.indicator, !isValid && styles.errorStatus)} />
<DynamicT
forKey="enterprise_sso_details.saml_preview.certificate_content"
interpolation={{
date: new Date(expiresAt).toLocaleDateString(
// TODO: check if Logto's language tags are compatible.
conditional(isLanguageTag(language) && language) ?? 'en',
{
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
}
),
}}
/>
<CopyToClipboard
className={styles.copyToClipboard}
variant="icon"
value={x509Certificate}
/>
<CertificatePreview identityProviderConfig={identityProviderConfig} />
</div>
</div>
</div>

View file

@ -5,3 +5,7 @@
font: var(--font-body-2);
margin-top: _.unit(0.5);
}
.certificatePreview {
margin-top: _.unit(1);
}

View file

@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
import FormField from '@/ds-components/FormField';
import InlineNotification from '@/ds-components/InlineNotification';
import TextInput from '@/ds-components/TextInput';
import Textarea from '@/ds-components/Textarea';
import {
type ParsedSsoIdentityProviderConfig,
type SamlGuideFormType,
@ -14,9 +13,9 @@ import {
} from '@/pages/EnterpriseSso/types.js';
import { uriValidator } from '@/utils/validator';
import XmlFileReader from '../XmlFileReader';
import FileReader, { type Props as FileReaderProps } from '../FileReader';
import ParsedConfigPreview from './ParsedConfigPreview';
import ParsedConfigPreview, { CertificatePreview } from './ParsedConfigPreview';
import SwitchFormatButton, { FormFormat } from './SwitchFormatButton';
import * as styles from './index.module.scss';
@ -30,6 +29,30 @@ type SamlMetadataFormProps = {
providerConfig?: ParsedSsoIdentityProviderConfig<SsoProviderName.SAML>;
};
type KeyType = keyof Pick<SamlGuideFormType, 'metadata' | 'x509Certificate'>; // I.e. 'metadata' | 'x509Certificate'.
const keyToAttributes: Record<KeyType, FileReaderProps['attributes']> = {
// Accept xml file.
metadata: {
buttonTitle: 'enterprise_sso.metadata.saml.metadata_xml_uploader_text',
accept: {
'application/xml': [],
'text/xml': [],
},
defaultFilename: 'identity provider metadata.xml',
defaultFileMimeType: 'application/xml',
},
x509Certificate: {
buttonTitle: 'enterprise_sso_details.upload_signing_certificate_button_text',
accept: {
'application/x-x509-user-cert': ['.crt', '.cer'],
'application/x-x509-ca-cert': ['.crt', '.cer'],
'application/x-pem-file': ['.pem'],
},
defaultFilename: 'signing certificate.cer',
defaultFileMimeType: 'application/x-x509-user-cert',
},
};
function SamlMetadataFormFields({
formFormat,
identityProviderConfig,
@ -37,6 +60,7 @@ function SamlMetadataFormFields({
}: SamlMetadataFormFieldsProps) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
setError,
control,
register,
formState: { errors },
@ -69,10 +93,36 @@ function SamlMetadataFormFields({
/>
</FormField>
<FormField isRequired title="enterprise_sso.metadata.saml.certificate_field_name">
<Textarea
{...register('x509Certificate', { required: true })}
placeholder={t('enterprise_sso.metadata.saml.certificate_placeholder')}
error={Boolean(errors.x509Certificate)}
<Controller
control={control}
name="x509Certificate"
rules={{
validate: (value) => {
if (!value) {
return t('enterprise_sso.metadata.saml.certificate_required');
}
return true;
},
}}
render={({ field: { onChange, value } }) => (
<>
<FileReader
attributes={keyToAttributes.x509Certificate}
value={value}
fieldError={errors.x509Certificate}
setError={(error) => {
setError('x509Certificate', error);
}}
onChange={onChange}
/>
{value && identityProviderConfig && (
<CertificatePreview
className={styles.certificatePreview}
identityProviderConfig={identityProviderConfig}
/>
)}
</>
)}
/>
</FormField>
</>
@ -86,7 +136,15 @@ function SamlMetadataFormFields({
control={control}
name="metadata"
render={({ field: { onChange, value } }) => (
<XmlFileReader value={value} onChange={onChange} />
<FileReader
attributes={keyToAttributes.metadata}
value={value}
fieldError={errors.metadata}
setError={(error) => {
setError('metadata', error);
}}
onChange={onChange}
/>
)}
/>
</FormField>

View file

@ -1,6 +1,10 @@
// In Bytes.
export const calculateXmlFileSize = (xmlContent: string): number => {
const blob = new Blob([xmlContent], { type: 'application/xml' });
const file = new File([blob], 'identity provider metadata.xml');
export const calculateFileSize = (
xmlContent: string,
fileName: string,
mimeType: string
): number => {
const blob = new Blob([xmlContent], { type: mimeType });
const file = new File([blob], fileName);
return file.size;
};

View file

@ -33,13 +33,7 @@ type Props<T extends SsoProviderName> = {
// This component contains only `data.config`.
function Connection<T extends SsoProviderName>({ isDeleted, data, onUpdated }: Props<T>) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
id: ssoConnectorId,
connectorName: ssoConnectorName,
providerName,
providerConfig,
config,
} = data;
const { id: ssoConnectorId, providerName, providerConfig, config } = data;
const api = useApi();

View file

@ -28,6 +28,7 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
case ConnectorErrorCodes.InvalidRequestParameters:
case ConnectorErrorCodes.InsufficientRequestParameters:
case ConnectorErrorCodes.InvalidConfig:
case ConnectorErrorCodes.InvalidCertificate:
case ConnectorErrorCodes.InvalidResponse: {
throw new RequestError({ code: `connector.${code}`, status: 400 }, data);
}

View file

@ -1,7 +1,7 @@
import { X509Certificate } from 'node:crypto';
import * as validator from '@authenio/samlify-node-xmllint';
import { type Optional, appendPath } from '@silverhand/essentials';
import { type Optional, appendPath, tryThat } from '@silverhand/essentials';
import { conditional } from '@silverhand/essentials';
import { HTTPError, got } from 'got';
import * as saml from 'samlify';
@ -62,24 +62,36 @@ export const parseXmlMetadata = (
saml.Constants.wording.certUse.signing
);
try {
const certificate = getPemCertificate(rawX509Certificate);
const expiresAt = new Date(certificate.validTo).getTime();
const certificate = tryThat(
() => getPemCertificate(rawX509Certificate),
(error) => {
throw new SsoConnectorError(SsoConnectorErrorCodes.InvalidCertificate, {
config: { ...rawSamlMetadata, x509Certificate: rawX509Certificate },
error,
});
}
);
const certificateExpiresAt = new Date(certificate.validTo).getTime();
// The return type of `samlify`
return samlIdentityProviderMetadataGuard.parse({
...rawSamlMetadata,
expiresAt,
isValid: expiresAt > Date.now(),
x509Certificate: certificate.toJSON(), // This returns the parsed certificate in string-type.
});
} catch (error: unknown) {
const payload = {
...rawSamlMetadata,
certificateExpiresAt,
isCertificateValid: certificateExpiresAt > Date.now(),
x509Certificate: certificate.toJSON(), // This returns the parsed certificate in string-type.
};
// The return type of `samlify`
const result = samlIdentityProviderMetadataGuard.safeParse(payload);
if (!result.success) {
throw new SsoConnectorError(SsoConnectorErrorCodes.InvalidMetadata, {
message: SsoConnectorConfigErrorCodes.InvalidConnectorConfig,
metadata: rawSamlMetadata,
error,
metadata: payload,
error: result.error,
});
}
return result.data;
};
/**

View file

@ -4,6 +4,7 @@ import { type JsonObject } from '@logto/schemas';
export enum SsoConnectorErrorCodes {
InvalidMetadata = 'invalid_metadata',
InvalidConfig = 'invalid_config',
InvalidCertificate = 'invalid_certificate',
AuthorizationFailed = 'authorization_failed',
InvalidResponse = 'invalid_response',
InvalidRequestParameters = 'invalid_request_parameters',
@ -18,6 +19,7 @@ export enum SsoConnectorConfigErrorCodes {
const connectorErrorCodeMap: { [key in SsoConnectorErrorCodes]: ConnectorErrorCodes } = {
[SsoConnectorErrorCodes.InvalidMetadata]: ConnectorErrorCodes.InvalidMetadata,
[SsoConnectorErrorCodes.InvalidConfig]: ConnectorErrorCodes.InvalidConfig,
[SsoConnectorErrorCodes.InvalidCertificate]: ConnectorErrorCodes.InvalidCertificate,
[SsoConnectorErrorCodes.InvalidResponse]: ConnectorErrorCodes.InvalidResponse,
[SsoConnectorErrorCodes.InvalidRequestParameters]: ConnectorErrorCodes.InvalidRequestParameters,
[SsoConnectorErrorCodes.AuthorizationFailed]: ConnectorErrorCodes.AuthorizationFailed,
@ -38,6 +40,14 @@ export class SsoConnectorError extends ConnectorError {
}
);
constructor(
code: SsoConnectorErrorCodes.InvalidCertificate,
data: {
config: JsonObject | undefined;
error?: unknown;
}
);
constructor(
code: SsoConnectorErrorCodes.InvalidRequestParameters,
data: { url: string; params: unknown; error?: unknown }

View file

@ -40,8 +40,8 @@ export const samlIdentityProviderMetadataGuard = z.object({
entityId: z.string(),
signInEndpoint: z.string(),
x509Certificate: z.string(),
expiresAt: z.number(), // Timestamp in milliseconds.
isValid: z.boolean(),
certificateExpiresAt: z.number(), // Timestamp in milliseconds.
isCertificateValid: z.boolean(),
});
export type SamlIdentityProviderMetadata = z.infer<typeof samlIdentityProviderMetadataGuard>;

View file

@ -9,6 +9,9 @@ const connector = {
insufficient_request_parameters:
'Die Anfrage enthält möglicherweise nicht alle erforderlichen Eingabeparameter.',
invalid_config: 'Die Konfiguration des Connectors ist ungültig.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'Die Antwort des Connectors ist ungültig.',
template_not_found:
'Die richtige Vorlage in der Connector-Konfiguration konnte nicht gefunden werden.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,8 @@ const connector = {
invalid_request_parameters: 'The request is with wrong input parameter(s).',
insufficient_request_parameters: 'The request might miss some input parameters.',
invalid_config: "The connector's config is invalid.",
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: "The connector's response is invalid.",
template_not_found: 'Unable to find correct template in connector config.',
template_not_supported: 'The connector does not support this template type.',

View file

@ -1,7 +1,6 @@
const single_sign_on = {
forbidden_domains: 'Public email domains are not allowed.',
duplicated_domains: 'There are duplicate domains.',
/** UNTRANSLATED */
invalid_domain_format: 'Invalid domain format.',
duplicate_connector_name: 'Connector name already exists. Please choose a different name.',
};

View file

@ -43,6 +43,7 @@ const enterprise_sso_details = {
upload_idp_metadata_description_oidc:
'Configure the credentials and OIDC token information copied from the identity provider.',
upload_idp_metadata_button_text: 'Upload metadata XML file',
upload_signing_certificate_button_text: 'Upload signing certificate file',
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
email_domain_field_required: 'Email domain is required to enable enterprise SSO.',

View file

@ -59,6 +59,7 @@ const enterprise_sso = {
idp_entity_id_field_name: 'IdP entity ID (Issuer)',
certificate_field_name: 'Signing certificate',
certificate_placeholder: 'Copy and paste the x509 certificate',
certificate_required: 'Signing certificate is required.',
},
oidc: {
client_id_field_name: 'Client ID',

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: 'La solicitud contiene parámetros de entrada incorrectos.',
insufficient_request_parameters: 'La solicitud puede faltar algunos parámetros de entrada.',
invalid_config: 'La configuración del conector es inválida.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'La respuesta del conector es inválida.',
template_not_found:
'No se puede encontrar la plantilla correcta en la configuración del conector.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: "La requête contient des paramètres d'entrée incorrects.",
insufficient_request_parameters: 'Certains paramètres peuvent manquer dans la requête.',
invalid_config: "La configuration du connecteur n'est pas valide.",
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: "La réponse du connecteur n'est pas valide.",
template_not_found: 'Impossible de trouver le bon modèle dans la configuration du connecteur.',
template_not_supported: 'Le connecteur ne prend pas en charge ce type de modèle.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -89,6 +89,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: 'La richiesta contiene parametri di input errati.',
insufficient_request_parameters: 'La richiesta potrebbe mancare di alcuni parametri di input.',
invalid_config: 'La configurazione del connettore non è valida.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'La risposta del connettore non è valida.',
template_not_found:
'Impossibile trovare il modello corretto nella configurazione del connettore.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: 'リクエストに誤った入力パラメータが含まれています。',
insufficient_request_parameters: 'リクエストには、入力パラメータが不足している可能性があります。',
invalid_config: 'コネクタの設定が無効です。',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'コネクタのレスポンスが無効です。',
template_not_found: 'コネクタ構成から正しいテンプレートを見つけることができませんでした。',
template_not_supported: 'コネクタはこのテンプレートタイプをサポートしていません。',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: '잘못된 요청 파라미터가 있어요.',
insufficient_request_parameters: '요청 데이터에서 일부 정보가 없어요.',
invalid_config: '연동 설정이 유효하지 않아요.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: '연동 응답이 유효하지 않아요.',
template_not_found: '연동 예제 설정을 찾을 수 없어요.',
template_not_supported: '연동이 이 템플릿 타입을 지원하지 않아요.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -87,6 +87,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -9,6 +9,9 @@ const connector = {
'Żądanie jest z nieprawidłowym parametrem wejściowym/lub parametrami wejściowymi.',
insufficient_request_parameters: 'Żądanie może nie zawierać niektórych parametrów wejściowych.',
invalid_config: 'Konfiguracja łącznika jest nieprawidłowa.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'Odpowiedź łącznika jest nieprawidłowa.',
template_not_found: 'Nie można znaleźć poprawnego szablonu w konfiguracji łącznika.',
template_not_supported: 'Łącznik nie obsługuje tego typu szablonu.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: 'A solicitação está com parâmetro(s) de entrada incorreto(s).',
insufficient_request_parameters: 'A solicitação pode perder alguns parâmetros de entrada.',
invalid_config: 'A configuração do conector é inválida.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'A resposta do conector é inválida.',
template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.',
template_not_supported: 'O conector não suporta esse tipo de modelo.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: 'O pedido tem parâmetros de entrada inválidos.',
insufficient_request_parameters: 'A solicitação pode perder alguns parâmetros de entrada.',
invalid_config: 'A configuração do conector é inválida.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'A resposta do conector é inválida.',
template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.',
template_not_supported: 'O conector não suporta este tipo de modelo.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: 'Запрос содержит неверный входной параметр (ы).',
insufficient_request_parameters: 'В запросе может не хватать некоторых входных параметров.',
invalid_config: 'Конфигурация коннектора недействительна.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'Ответ коннектора недействителен.',
template_not_found: 'Невозможно найти правильный шаблон в конфигурации коннектора.',
template_not_supported: 'Коннектор не поддерживает этот тип шаблона.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: 'İstek yanlış girdi parametreleri ile gönderildi.',
insufficient_request_parameters: 'İstek, bazı input parametrelerini atlayabilir.',
invalid_config: 'Bağlayıcının ayarları geçersiz.',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: 'Bağlayıcının yanıtı geçersiz.',
template_not_found: 'Bağlayıcı yapılandırmasında doğru şablon bulunamıyor.',
template_not_supported: 'Bağlayıcı bu şablon türünü desteklemiyor.',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -88,6 +88,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: '请求参数错误',
insufficient_request_parameters: '请求参数缺失',
invalid_config: '连接器配置错误',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: '连接器错误响应',
template_not_found: '无法从连接器配置中找到对应的模板',
template_not_supported: '连接器不支持此模板类型。',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -87,6 +87,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: '請求參數錯誤',
insufficient_request_parameters: '請求參數缺失',
invalid_config: '連接器配置錯誤',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: '連接器錯誤響應',
template_not_found: '無法從連接器配置中找到對應的模板',
template_not_supported: '連接器不支援此模板類型。',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -87,6 +87,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -8,6 +8,9 @@ const connector = {
invalid_request_parameters: '請求參數錯誤',
insufficient_request_parameters: '請求參數缺失',
invalid_config: '連接器配置錯誤',
/** UNTRANSLATED */
invalid_certificate:
"The connector's certificate is invalid, please make sure the certificate is in PEM encoding.",
invalid_response: '連接器錯誤響應',
template_not_found: '無法從連接器配置中找到對應的模板',
template_not_supported: '連接器不支援此模板類型',

View file

@ -82,6 +82,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_signing_certificate_button_text: 'Upload signing certificate file',
/** UNTRANSLATED */
configure_domain_field_info_text:
'Add email domain to guide enterprise users to their identity provider for Single Sign-on.',
/** UNTRANSLATED */

View file

@ -87,6 +87,8 @@ const enterprise_sso = {
certificate_field_name: 'Signing certificate',
/** UNTRANSLATED */
certificate_placeholder: 'Copy and paste the x509 certificate',
/** UNTRANSLATED */
certificate_required: 'Signing certificate is required.',
},
oidc: {
/** UNTRANSLATED */

View file

@ -6,6 +6,7 @@ export enum ConnectorErrorCodes {
InvalidRequestParameters = 'invalid_request_parameters',
InsufficientRequestParameters = 'insufficient_request_parameters',
InvalidConfig = 'invalid_config',
InvalidCertificate = 'invalid_certificate',
InvalidResponse = 'invalid_response',
/** The template is not found for the given type. */
TemplateNotFound = 'template_not_found',