From 730878bc5a1ef5f39e4e01b98aca864e2f334325 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Tue, 2 Jun 2026 00:14:12 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=90=20feat:=20Use=20`SecretInput`=20fo?= =?UTF-8?q?r=20Sensitive=20Fields=20(#12955)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: use SecretInput for sensitive fields * fix: align auth SecretInput styles * chore: remove unused password i18n keys * fix: align SecretInput controls * fix: use SecretInput for dynamic credentials * fix: reveal SecretInput controls on hover * fix: align SecretInput eye icon and modernize controls The wrapper was a flex container, so passing 'mb-2' on the input made it contribute its margin to the wrapper's cross-axis size — the controls overlay spanned the inflated height and centered the toggle 4px below the input's true center. Switching the wrapper to a plain relative block collapses height back to the input. Also tightens the toggle/copy buttons (size-7 rounded-md with hover:bg-surface-hover) and adds a focus ring on the input. Auth pages still override className/buttonClassName so login/register styling is unchanged. * fix: remove focus ring from SecretInput * fix: keep green focus border on auth secret inputs SecretInput's modernized default uses focus-visible:border-border-heavy and hover:border-border-medium, which Tailwind emits after the auth pages' focus: rules and overrides them. Auth pages now also declare focus-visible:border-green-500 and hover:border-border-light so cn()/twMerge resolves them as the winners when classes are concatenated. * feat: add optional sensitive flag to MCP customUserVars Dynamic MCP credential fields all rendered as masked SecretInputs, which also hid non-secret setup values like usernames, project keys, and URLs. Add an optional `sensitive` flag to customUserVars and the plugin auth config. It defaults to masked when omitted, so existing configs keep the safe-by-default behavior; set `sensitive: false` to render a field as plain text. The flag is display-only — values remain encrypted at rest. --- api/server/controllers/mcp.js | 1 + client/src/common/types.ts | 2 + client/src/components/Auth/LoginForm.tsx | 31 +++---- client/src/components/Auth/Registration.tsx | 88 ++++++++++++------- client/src/components/Auth/ResetPassword.tsx | 38 ++++---- .../components/Chat/Input/MCPConfigDialog.tsx | 39 +++++--- .../Input/SetKeyDialog/CustomEndpoint.tsx | 1 + .../Input/SetKeyDialog/GoogleConfig.tsx | 1 + .../Input/SetKeyDialog/InputWithLabel.tsx | 54 +++++++++--- .../Input/SetKeyDialog/OpenAIConfig.tsx | 2 + .../Input/SetKeyDialog/OtherConfig.tsx | 1 + .../components/MCP/CustomUserVarsSection.tsx | 56 ++++++------ .../__tests__/CustomUserVarsSection.test.tsx | 19 +++- .../Account/TwoFactorPhases/QRPhase.tsx | 36 +++----- .../Nav/SettingsTabs/Data/AgentApiKeys.tsx | 46 +++------- .../Plugins/Store/PluginAuthForm.tsx | 65 ++++++++------ .../Store/__tests__/PluginAuthForm.spec.tsx | 25 +++++- .../SidePanel/Agents/Search/InputSection.tsx | 73 +++++---------- .../SidePanel/Builder/ActionsAuth.tsx | 39 +++++--- .../MCPServerDialog/sections/AuthSection.tsx | 15 +++- client/src/hooks/MCP/useMCPServerManager.ts | 2 + client/src/locales/en/translation.json | 2 - .../client/src/components/SecretInput.tsx | 52 +++++++++-- packages/data-provider/src/mcp.ts | 6 ++ packages/data-provider/src/schemas.ts | 2 + 25 files changed, 415 insertions(+), 281 deletions(-) diff --git a/api/server/controllers/mcp.js b/api/server/controllers/mcp.js index a50680f3d1..d58e1feacf 100644 --- a/api/server/controllers/mcp.js +++ b/api/server/controllers/mcp.js @@ -146,6 +146,7 @@ const getMCPTools = async (req, res) => { authField: key, label: value.title || key, description: value.description || '', + sensitive: value.sensitive, })); server.authenticated = false; } diff --git a/client/src/common/types.ts b/client/src/common/types.ts index d8f86f23f4..7be7bef749 100644 --- a/client/src/common/types.ts +++ b/client/src/common/types.ts @@ -16,6 +16,8 @@ export function isEphemeralAgent(agentId: string | null | undefined): boolean { export interface ConfigFieldDetail { title: string; description: string; + /** Whether the field holds a secret and should be masked (defaults to masked when omitted). */ + sensitive?: boolean; } export type CodeBarProps = { diff --git a/client/src/components/Auth/LoginForm.tsx b/client/src/components/Auth/LoginForm.tsx index c51c2002e3..3d0a2528d5 100644 --- a/client/src/components/Auth/LoginForm.tsx +++ b/client/src/components/Auth/LoginForm.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useContext } from 'react'; import { useForm } from 'react-hook-form'; import { Turnstile } from '@marsidev/react-turnstile'; -import { ThemeContext, Spinner, Button, isDark } from '@librechat/client'; +import { ThemeContext, SecretInput, Spinner, Button, isDark } from '@librechat/client'; import type { TLoginUser, TStartupConfig } from 'librechat-data-provider'; import type { TAuthContext } from '~/common'; import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider'; @@ -31,6 +31,13 @@ const LoginForm: React.FC = ({ onSubmit, startupConfig, error, const useUsernameLogin = config?.ldap?.username; const validTheme = isDark(theme) ? 'dark' : 'light'; const requireCaptcha = Boolean(startupConfig.turnstile?.siteKey); + const authInputClassName = + 'webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 hover:border-border-light focus:border-green-500 focus:outline-none focus-visible:border-green-500'; + const authSecretInputClassName = `${authInputClassName} h-auto pr-12`; + const authLabelClassName = + 'absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4'; + const authSecretButtonClassName = + 'size-9 rounded-xl text-text-secondary-alt hover:bg-transparent hover:text-text-primary'; useEffect(() => { if (error && error.includes('422') && !showResendLink) { @@ -102,13 +109,10 @@ const LoginForm: React.FC = ({ onSubmit, startupConfig, error, : (value) => validateEmail(value, localize('com_auth_email_pattern')), })} aria-invalid={!!errors.email} - className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none" + className={authInputClassName} placeholder=" " /> -