mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
feat(console): reuse existing oidc/saml form for OOTB SSO connector config (#4955)
This commit is contained in:
parent
a36ae032e3
commit
f4383a3e58
8 changed files with 70 additions and 29 deletions
|
@ -29,7 +29,11 @@ function BasicInfo({ ssoConnectorId, providerName, providerConfig }: Props) {
|
|||
const { tenantEndpoint } = useContext(AppDataContext);
|
||||
const { applyDomain: applyCustomDomain } = useCustomDomain();
|
||||
|
||||
if (providerName === SsoProviderName.OIDC) {
|
||||
if (
|
||||
[SsoProviderName.OIDC, SsoProviderName.GOOGLE_WORKSPACE, SsoProviderName.OKTA].includes(
|
||||
providerName
|
||||
)
|
||||
) {
|
||||
return (
|
||||
<FormField title="enterprise_sso.basic_info.oidc.redirect_uri_field_name">
|
||||
{/* Generated and passed in by Admin console. */}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { type SsoProviderName } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { type ParsedSsoIdentityProviderConfig } from '@/pages/EnterpriseSso/types.js';
|
||||
|
@ -7,16 +8,17 @@ import * as styles from './index.module.scss';
|
|||
|
||||
type Props = {
|
||||
providerConfig: ParsedSsoIdentityProviderConfig<SsoProviderName.OIDC>;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function ParsedConfigPreview({ providerConfig }: Props) {
|
||||
function ParsedConfigPreview({ providerConfig, className }: Props) {
|
||||
const { t } = useTranslation(undefined, {
|
||||
keyPrefix: 'admin_console.enterprise_sso_details.oidc_preview',
|
||||
});
|
||||
const { authorizationEndpoint, tokenEndpoint, userinfoEndpoint, jwksUri, issuer } =
|
||||
providerConfig;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={classNames(styles.container, className)}>
|
||||
<div>
|
||||
<div className={styles.title}>{t('authorization_endpoint')}</div>
|
||||
<div className={styles.content}>{authorizationEndpoint}</div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.copyToClipboard {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.oidcConfigPreview {
|
||||
margin-top: _.unit(3);
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { type SsoProviderName } from '@logto/schemas';
|
||||
import { SsoProviderName } from '@logto/schemas';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import CopyToClipboard from '@/ds-components/CopyToClipboard';
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import TextInput from '@/ds-components/TextInput';
|
||||
|
@ -12,15 +13,17 @@ import {
|
|||
} from '@/pages/EnterpriseSso/types.js';
|
||||
|
||||
import ParsedConfigPreview from './ParsedConfigPreview';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
isGuidePage?: boolean;
|
||||
providerConfig?: ParsedSsoIdentityProviderConfig<SsoProviderName.OIDC>;
|
||||
config?: SsoConnectorConfig<SsoProviderName.OIDC>;
|
||||
providerName: SsoProviderName;
|
||||
};
|
||||
|
||||
// Do not show inline notification and parsed config preview if it is on guide page.
|
||||
function OidcMetadataForm({ isGuidePage, providerConfig, config }: Props) {
|
||||
function OidcMetadataForm({ isGuidePage, providerConfig, config, providerName }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const {
|
||||
register,
|
||||
|
@ -56,22 +59,35 @@ function OidcMetadataForm({ isGuidePage, providerConfig, config }: Props) {
|
|||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
isRequired={isFieldCheckRequired}
|
||||
isRequired={isFieldCheckRequired && providerName !== SsoProviderName.GOOGLE_WORKSPACE}
|
||||
title="enterprise_sso.metadata.oidc.issuer_field_name"
|
||||
>
|
||||
<TextInput
|
||||
{...register('issuer', { required: isFieldCheckRequired })}
|
||||
error={Boolean(errors.issuer)}
|
||||
/>
|
||||
{isFieldCheckRequired && providerConfig && config?.issuer && (
|
||||
<ParsedConfigPreview providerConfig={providerConfig} />
|
||||
{providerName === SsoProviderName.GOOGLE_WORKSPACE ? (
|
||||
<CopyToClipboard
|
||||
className={styles.copyToClipboard}
|
||||
variant="border"
|
||||
// TODO: this hard-coded value should align with the `googleIssuer` value defined in `packages/core/src/sso/GoogleWorkspaceSsoConnector/index.ts`.
|
||||
value={providerConfig?.issuer ?? 'https://accounts.google.com'}
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
{...register('issuer', {
|
||||
required: isFieldCheckRequired,
|
||||
})}
|
||||
error={Boolean(errors.issuer)}
|
||||
/>
|
||||
)}
|
||||
{isFieldCheckRequired &&
|
||||
providerConfig &&
|
||||
(config?.issuer ?? providerName === SsoProviderName.GOOGLE_WORKSPACE) && (
|
||||
<ParsedConfigPreview
|
||||
className={styles.oidcConfigPreview}
|
||||
providerConfig={providerConfig}
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField title="enterprise_sso.metadata.oidc.scope_field_name">
|
||||
<TextInput
|
||||
{...register('scope', { required: isFieldCheckRequired })}
|
||||
error={Boolean(errors.scope)}
|
||||
/>
|
||||
<TextInput {...register('scope')} error={Boolean(errors.scope)} />
|
||||
</FormField>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -30,7 +30,6 @@ type Props<T extends SsoProviderName> = {
|
|||
isOpen: boolean;
|
||||
connector: SsoConnectorWithProviderConfigWithGeneric<T>;
|
||||
onClose: (ssoConnectorId?: string) => void;
|
||||
isReadOnly?: boolean;
|
||||
};
|
||||
|
||||
type GuideCardProps = {
|
||||
|
@ -58,7 +57,7 @@ function GuideCard({ cardOrder, title, description, children, className }: Guide
|
|||
);
|
||||
}
|
||||
|
||||
function Guide<T extends SsoProviderName>({ isOpen, connector, onClose, isReadOnly }: Props<T>) {
|
||||
function Guide<T extends SsoProviderName>({ isOpen, connector, onClose }: Props<T>) {
|
||||
const {
|
||||
id: ssoConnectorId,
|
||||
connectorName: ssoConnectorName,
|
||||
|
@ -143,13 +142,17 @@ function Guide<T extends SsoProviderName>({ isOpen, connector, onClose, isReadOn
|
|||
providerConfig={providerConfig}
|
||||
/>
|
||||
</GuideCard>
|
||||
{providerName === SsoProviderName.OIDC ? (
|
||||
{[
|
||||
SsoProviderName.OIDC,
|
||||
SsoProviderName.GOOGLE_WORKSPACE,
|
||||
SsoProviderName.OKTA,
|
||||
].includes(providerName) ? (
|
||||
<GuideCard
|
||||
cardOrder={2}
|
||||
title="enterprise_sso.metadata.title"
|
||||
description="enterprise_sso.metadata.description"
|
||||
>
|
||||
<OidcMetadataForm isGuidePage />
|
||||
<OidcMetadataForm isGuidePage providerName={providerName} />
|
||||
</GuideCard>
|
||||
) : (
|
||||
<>
|
||||
|
@ -158,7 +161,7 @@ function Guide<T extends SsoProviderName>({ isOpen, connector, onClose, isReadOn
|
|||
title="enterprise_sso.attribute_mapping.title"
|
||||
description="enterprise_sso.attribute_mapping.description"
|
||||
>
|
||||
<SamlAttributeMapping isReadOnly={isReadOnly} />
|
||||
<SamlAttributeMapping />
|
||||
</GuideCard>
|
||||
<GuideCard
|
||||
cardOrder={3}
|
||||
|
|
|
@ -139,10 +139,13 @@ function EnterpriseSsoConnectors() {
|
|||
dataIndex: 'status',
|
||||
colSpan: 186,
|
||||
render: ({ providerConfig, providerName }) => {
|
||||
const inUse =
|
||||
providerName === SsoProviderName.OIDC
|
||||
? Boolean(providerConfig)
|
||||
: Boolean(providerConfig?.identityProvider);
|
||||
const inUse = [
|
||||
SsoProviderName.OIDC,
|
||||
SsoProviderName.GOOGLE_WORKSPACE,
|
||||
SsoProviderName.OKTA,
|
||||
].includes(providerName)
|
||||
? Boolean(providerConfig)
|
||||
: Boolean(providerConfig?.identityProvider);
|
||||
return (
|
||||
<Tag type="state" status={inUse ? 'success' : 'error'} variant="plain">
|
||||
{t(
|
||||
|
@ -208,7 +211,6 @@ function EnterpriseSsoConnectors() {
|
|||
// eslint-disable-next-line no-restricted-syntax
|
||||
connectorForGuide as SsoConnectorWithProviderConfigWithGeneric<SsoProviderName>
|
||||
}
|
||||
isReadOnly={connectorForGuide.providerName !== 'SAML'}
|
||||
onClose={async (connectorId) => {
|
||||
if (connectorId) {
|
||||
navigate(buildDetailsPathname(connectorId), { replace: true });
|
||||
|
|
|
@ -86,10 +86,13 @@ function Connection<T extends SsoProviderName>({ isDeleted, data, onUpdated }: P
|
|||
title="enterprise_sso_details.upload_idp_metadata_title"
|
||||
description="enterprise_sso_details.upload_idp_metadata_description"
|
||||
>
|
||||
{providerName === SsoProviderName.OIDC ? (
|
||||
{[SsoProviderName.OIDC, SsoProviderName.GOOGLE_WORKSPACE, SsoProviderName.OKTA].includes(
|
||||
providerName
|
||||
) ? (
|
||||
// Can not infer the type by narrowing down the value of `providerName`, so we need to cast it.
|
||||
<OidcMetadataForm
|
||||
isGuidePage={false}
|
||||
providerName={providerName}
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config={config as SsoConnectorConfig<SsoProviderName.OIDC>}
|
||||
providerConfig={
|
||||
|
@ -127,7 +130,7 @@ function Connection<T extends SsoProviderName>({ isDeleted, data, onUpdated }: P
|
|||
providerConfig={providerConfig}
|
||||
/>
|
||||
</FormCard>
|
||||
{providerName === SsoProviderName.SAML && (
|
||||
{[SsoProviderName.SAML, SsoProviderName.AZURE_AD].includes(providerName) && (
|
||||
<FormCard
|
||||
title="enterprise_sso_details.attribute_mapping_title"
|
||||
description="enterprise_sso_details.attribute_mapping_description"
|
||||
|
|
|
@ -57,7 +57,9 @@ function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
|
|||
|
||||
const inUse =
|
||||
ssoConnector &&
|
||||
(ssoConnector.providerName === SsoProviderName.OIDC
|
||||
([SsoProviderName.OIDC, SsoProviderName.GOOGLE_WORKSPACE, SsoProviderName.OKTA].includes(
|
||||
ssoConnector.providerName
|
||||
)
|
||||
? Boolean(ssoConnector.providerConfig)
|
||||
: Boolean(
|
||||
ssoConnector.providerConfig &&
|
||||
|
|
Loading…
Add table
Reference in a new issue