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;
}
.hideTextContainerContent {
input {
-webkit-text-security: disc;
}
}
.container {
display: flex;
align-items: center;

View file

@ -9,21 +9,52 @@ import {
forwardRef,
type Ref,
useEffect,
useState,
} 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';
type Props = Omit<HTMLProps<HTMLInputElement>, 'size'> & {
error?: string | boolean;
icon?: ReactElement;
suffix?: ReactElement;
isConfidential?: boolean;
};
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>>
) {
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);
useEffect(() => {
@ -54,6 +85,7 @@ function TextInput(
className={classNames(
styles.container,
error && styles.error,
isConfidential && isContentHidden && type === 'text' && styles.hideTextContainerContent,
icon && styles.withIcon,
disabled && styles.disabled,
readOnly && styles.readOnly
@ -61,9 +93,9 @@ function TextInput(
>
{icon && <span className={styles.icon}>{icon}</span>}
<input type={type} {...rest} ref={innerRef} disabled={disabled} readOnly={readOnly} />
{suffix &&
cloneElement(suffix, {
className: classNames([suffix.props.className, styles.suffix]),
{suffixIcon &&
cloneElement(suffixIcon, {
className: classNames([suffixIcon.props.className, styles.suffix]),
})}
</div>
{Boolean(error) && typeof error === 'string' && (

View file

@ -60,7 +60,13 @@ function ConfigForm({ formItems }: Props) {
});
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) {

View file

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