feat(security): recovery key leaves the clipboard after 60 s, visibly (#156)
useSensitiveCopy: the recovery key's Copy button becomes 'Copied · clears in 60 s' and counts down; at zero the clipboard is cleared only if it still holds the key (readText() where permitted — if the browser refuses to read, nothing is wiped rather than risk eating something else). Any other copy made in the app cancels the timer. No setting. Verified headless with a fake clock: countdown ticks, clipboard emptied at 0; copying something else mid-countdown cancels and leaves that content untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -19,7 +19,7 @@ import { useSaveFile } from '../hooks/useSaveFile';
|
||||
import { useModalStyle } from '../hooks/useModalStyle';
|
||||
import { PasswordInput } from './password-input';
|
||||
import { ContainerColor } from '../styles/ContainerColor.css';
|
||||
import { copyToClipboard } from '../utils/dom';
|
||||
import { useSensitiveCopy } from '../hooks/useSensitiveCopy';
|
||||
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
|
||||
import { clearSecretStorageKeys } from '../../client/secretStorageKeys';
|
||||
import { ActionUIA, ActionUIAFlowsLoader } from './ActionUIA';
|
||||
@@ -232,9 +232,8 @@ function RecoveryKeyDisplay({ recoveryKey }: RecoveryKeyDisplayProps) {
|
||||
const [show, setShow] = useState(false);
|
||||
const saveFile = useSaveFile();
|
||||
|
||||
const handleCopy = () => {
|
||||
copyToClipboard(recoveryKey);
|
||||
};
|
||||
// [Gitea #156] The key leaves the clipboard again after 60 s, visibly.
|
||||
const { copy: handleCopy, secondsLeft } = useSensitiveCopy(recoveryKey);
|
||||
|
||||
const handleDownload = () => {
|
||||
const blob = new Blob([recoveryKey], {
|
||||
@@ -272,8 +271,10 @@ function RecoveryKeyDisplay({ recoveryKey }: RecoveryKeyDisplayProps) {
|
||||
</Box>
|
||||
</Box>
|
||||
<Box direction="Column" gap="200">
|
||||
<Button onClick={handleCopy}>
|
||||
<Text size="B400">Copy</Text>
|
||||
<Button onClick={handleCopy} aria-live="polite">
|
||||
<Text size="B400">
|
||||
{secondsLeft !== null ? `Copied · clears in ${secondsLeft} s` : 'Copy'}
|
||||
</Text>
|
||||
</Button>
|
||||
<Button onClick={handleDownload} fill="Soft">
|
||||
<Text size="B400">Download</Text>
|
||||
|
||||
Reference in New Issue
Block a user