Fix: Editing a secret clears its masked value

This commit is contained in:
Maurus Decimus
2026-04-22 20:09:53 +02:00
parent 35e0ef74b9
commit 27ba55fbf6
9 changed files with 463 additions and 13 deletions
+4 -5
View File
@@ -48,6 +48,7 @@ import { jmapGet, jmapSet, jmapRequest, getAccountId } from '@/services/jmap/cli
import { calculateJmapPatch } from '@/lib/jmapPatch';
import { friendlySetError } from '@/lib/jmapErrors';
import { coerceLabel } from '@/lib/objectOptions';
import { SECRET_MASK } from '@/lib/jmapUtils';
import { toast } from '@/hooks/use-toast';
import { logFormChange } from '@/lib/debug';
import { FieldWidget } from '@/components/forms/FieldWidget';
@@ -468,7 +469,7 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
const isSecret =
fieldDef.type.type === 'string' &&
(fieldDef.type.format === 'secret' || fieldDef.type.format === 'secretText');
if (isSecret && formData[fieldName] === '*****') continue;
if (isSecret && formData[fieldName] === SECRET_MASK) continue;
createPayload[fieldName] = formData[fieldName];
}
}
@@ -531,7 +532,7 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
if (
fieldDef?.type.type === 'string' &&
(fieldDef.type.format === 'secret' || fieldDef.type.format === 'secretText') &&
patchValue === '*****'
patchValue === SECRET_MASK
) {
continue;
}
@@ -1101,8 +1102,6 @@ function buildRenderableField(
return { formField, field, visible, enterpriseDisabled };
}
const SECRET_MASK_PLACEHOLDER = '*****';
function isOtpAuthValid(data: unknown): boolean {
if (data == null || typeof data !== 'object' || Array.isArray(data)) return true;
const obj = data as Record<string, unknown>;
@@ -1118,7 +1117,7 @@ function isOtpAuthValid(data: unknown): boolean {
const otpUrl = obj.otpUrl;
const otpCode = obj.otpCode;
if (otpUrl == null || otpUrl === '') return true;
if (otpCode == null || otpCode === '' || otpCode === SECRET_MASK_PLACEHOLDER) {
if (otpCode == null || otpCode === '' || otpCode === SECRET_MASK) {
return false;
}
return true;
+2 -2
View File
@@ -41,6 +41,7 @@ import { resolveSchema, resolveVariantForm, resolveObject, buildEmbeddedDefaults
import { useAccountStore } from '@/stores/accountStore';
import { useEffectiveEdition } from '@/components/forms/FormEditionContext';
import { useObjectList, useObjectLabel, useNoPermissionMessage, type ObjectOption } from '@/lib/objectOptions';
import { SECRET_MASK } from '@/lib/jmapUtils';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';
import { jmapGet, getAccountId } from '@/services/jmap/client';
@@ -457,8 +458,6 @@ interface SecretInputProps {
multiline: boolean;
}
const SECRET_MASK = '****';
function SecretInput({ value, onChange, readOnly, placeholder, minLength, maxLength, multiline }: SecretInputProps) {
const [visible, setVisible] = useState(false);
const [localValue, setLocalValue] = useState(() => (value === SECRET_MASK || value === '' ? '' : value));
@@ -482,6 +481,7 @@ function SecretInput({ value, onChange, readOnly, placeholder, minLength, maxLen
};
const commit = () => {
if (isMasked && localValue === '') return;
if (localValue !== value) onChange(localValue);
};
+1 -2
View File
@@ -13,6 +13,7 @@ import { Loader2, ShieldCheck, ShieldOff } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { SECRET_MASK } from '@/lib/jmapUtils';
interface OtpAuthValue {
otpUrl?: string | null;
@@ -25,8 +26,6 @@ interface OtpAuthFieldProps {
readOnly: boolean;
}
const SECRET_MASK = '*****';
const STALWART_IMAGE_URL = 'https://stalw.art/img/favicon-32x32.png';
function buildOtpAuthUrl(totp: OTPAuth.TOTP): string {
+2 -2
View File
@@ -8,7 +8,7 @@ import { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Check, X, HelpCircle, ChevronRight } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { jmapMapToArray } from '@/lib/jmapUtils';
import { jmapMapToArray, SECRET_MASK } from '@/lib/jmapUtils';
import { Badge } from '@/components/ui/badge';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
@@ -212,7 +212,7 @@ function StringValue({ value, format }: { value: unknown; format: string }) {
return <pre className="whitespace-pre-wrap break-all rounded bg-muted/50 p-2 text-xs font-mono">{str}</pre>;
}
if (format === 'secret' || format === 'secretText') {
return <span className="text-muted-foreground">*****</span>;
return <span className="text-muted-foreground">{SECRET_MASK}</span>;
}
return <span className="break-all">{str}</span>;
}