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

feat(console): should mask confidential connector config values by default (#3645)

This commit is contained in:
Darcy Ye 2023-04-11 16:11:10 +08:00 committed by GitHub
parent e4fc54000e
commit 6bb4098409
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 5 deletions

View file

@ -6,6 +6,12 @@
display: none; display: none;
} }
.hideTextContainerContent {
input {
-webkit-text-security: disc;
}
}
.container { .container {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -9,21 +9,52 @@ import {
forwardRef, forwardRef,
type Ref, type Ref,
useEffect, useEffect,
useState,
} from 'react'; } from 'react';
import EyeClosed from '@/assets/images/eye-closed.svg';
import Eye from '@/assets/images/eye.svg';
import IconButton from '@/components/IconButton';
import * as styles from './index.module.scss'; import * as styles from './index.module.scss';
type Props = Omit<HTMLProps<HTMLInputElement>, 'size'> & { type Props = Omit<HTMLProps<HTMLInputElement>, 'size'> & {
error?: string | boolean; error?: string | boolean;
icon?: ReactElement; icon?: ReactElement;
suffix?: ReactElement; suffix?: ReactElement;
isConfidential?: boolean;
}; };
function TextInput( function TextInput(
{ error, icon, suffix, disabled, className, readOnly, type = 'text', ...rest }: Props, {
error,
icon,
suffix,
disabled,
className,
readOnly,
type = 'text',
isConfidential = false,
...rest
}: Props,
reference: Ref<Nullable<HTMLInputElement>> reference: Ref<Nullable<HTMLInputElement>>
) { ) {
const innerRef = useRef<HTMLInputElement>(null); const innerRef = useRef<HTMLInputElement>(null);
const [isContentHidden, setIsContentHidden] = useState(true);
const toggleHiddenContent = () => {
setIsContentHidden((previous) => !previous);
};
const suffixIcon =
isConfidential && type === 'text' ? (
<IconButton onClick={toggleHiddenContent}>
{isContentHidden ? <EyeClosed /> : <Eye />}
</IconButton>
) : (
suffix
);
useImperativeHandle(reference, () => innerRef.current); useImperativeHandle(reference, () => innerRef.current);
useEffect(() => { useEffect(() => {
@ -54,6 +85,7 @@ function TextInput(
className={classNames( className={classNames(
styles.container, styles.container,
error && styles.error, error && styles.error,
isConfidential && isContentHidden && type === 'text' && styles.hideTextContainerContent,
icon && styles.withIcon, icon && styles.withIcon,
disabled && styles.disabled, disabled && styles.disabled,
readOnly && styles.readOnly readOnly && styles.readOnly
@ -61,9 +93,9 @@ function TextInput(
> >
{icon && <span className={styles.icon}>{icon}</span>} {icon && <span className={styles.icon}>{icon}</span>}
<input type={type} {...rest} ref={innerRef} disabled={disabled} readOnly={readOnly} /> <input type={type} {...rest} ref={innerRef} disabled={disabled} readOnly={readOnly} />
{suffix && {suffixIcon &&
cloneElement(suffix, { cloneElement(suffixIcon, {
className: classNames([suffix.props.className, styles.suffix]), className: classNames([suffixIcon.props.className, styles.suffix]),
})} })}
</div> </div>
{Boolean(error) && typeof error === 'string' && ( {Boolean(error) && typeof error === 'string' && (

View file

@ -60,7 +60,13 @@ function ConfigForm({ formItems }: Props) {
}); });
if (item.type === ConnectorConfigFormItemType.Text) { if (item.type === ConnectorConfigFormItemType.Text) {
return <TextInput {...buildCommonProperties()} />; return (
<TextInput
{...buildCommonProperties()}
// TODO: update connectors form config and remove RegExp check
isConfidential={item.isConfidential ?? /(Key|Secret)$/.test(item.key)}
/>
);
} }
if (item.type === ConnectorConfigFormItemType.MultilineText) { if (item.type === ConnectorConfigFormItemType.MultilineText) {

View file

@ -114,6 +114,7 @@ const baseConfigFormItem = {
.optional(), .optional(),
description: z.string().optional(), description: z.string().optional(),
tooltip: z.string().optional(), tooltip: z.string().optional(),
isConfidential: z.boolean().optional(), // For `Text` type only.
}; };
const connectorConfigFormItemGuard = z.discriminatedUnion('type', [ const connectorConfigFormItemGuard = z.discriminatedUnion('type', [