mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Clear sensitive clipboard content after a delay
After copying the nsec, ncryptsec or seed words, schedule the clipboard to be cleared after 60 seconds. The clipboard is only cleared if it still holds the copied secret, so later copies are left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015enAP3dJTvB521BuULqU8P
This commit is contained in:
@@ -3,13 +3,12 @@ package com.greenart7c3.nostrsigner.models
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.Clipboard
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.DataStoreAccess
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.service.nip44v3.Nip44v3
|
||||
import com.greenart7c3.nostrsigner.ui.newSensitivePlainText
|
||||
import com.greenart7c3.nostrsigner.ui.setSensitiveClip
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
@@ -120,11 +119,7 @@ class Account(
|
||||
didBackup = true
|
||||
val nsec = getNsec()
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
clipboardManager.setClipEntry(
|
||||
ClipEntry(
|
||||
newSensitivePlainText("", nsec),
|
||||
),
|
||||
)
|
||||
clipboardManager.setSensitiveClip("", nsec)
|
||||
|
||||
Toast.makeText(
|
||||
Amber.instance,
|
||||
|
||||
@@ -4,6 +4,15 @@ import android.content.ClipData
|
||||
import android.content.ClipDescription
|
||||
import android.os.Build
|
||||
import android.os.PersistableBundle
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.Clipboard
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** How long a copied secret stays on the clipboard before it is cleared. */
|
||||
const val SENSITIVE_CLIPBOARD_CLEAR_DELAY_MS = 60_000L
|
||||
|
||||
/**
|
||||
* Creates a [ClipData] flagged as sensitive so the system (Android 13+) avoids
|
||||
@@ -22,3 +31,43 @@ fun newSensitivePlainText(label: CharSequence, text: CharSequence): ClipData {
|
||||
clipData.description.extras = extras
|
||||
return clipData
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a secret to the clipboard flagged as sensitive content and schedules it
|
||||
* to be cleared after [clearAfterMillis]. The clipboard is only cleared if it
|
||||
* still contains the copied secret, so anything the user copies afterwards is
|
||||
* left untouched.
|
||||
*/
|
||||
suspend fun Clipboard.setSensitiveClip(
|
||||
label: CharSequence,
|
||||
text: CharSequence,
|
||||
scope: CoroutineScope = Amber.instance.applicationIOScope,
|
||||
clearAfterMillis: Long = SENSITIVE_CLIPBOARD_CLEAR_DELAY_MS,
|
||||
) {
|
||||
setClipEntry(ClipEntry(newSensitivePlainText(label, text)))
|
||||
scheduleSensitiveClear(text, scope, clearAfterMillis)
|
||||
}
|
||||
|
||||
private fun Clipboard.scheduleSensitiveClear(
|
||||
copiedValue: CharSequence,
|
||||
scope: CoroutineScope,
|
||||
delayMillis: Long,
|
||||
) {
|
||||
scope.launch {
|
||||
delay(delayMillis)
|
||||
val currentText = getClipEntry()?.clipData?.let { clip ->
|
||||
if (clip.itemCount > 0) clip.getItemAt(0).text?.toString() else null
|
||||
}
|
||||
if (currentText == copiedValue.toString()) {
|
||||
clearClipboard()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Clipboard.clearClipboard() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
nativeClipboard.clearPrimaryClip()
|
||||
} else {
|
||||
setClipEntry(ClipEntry(ClipData.newPlainText("", "")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.autofill.ContentType
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.Clipboard
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -106,7 +105,7 @@ import com.greenart7c3.nostrsigner.ui.components.IconRow
|
||||
import com.greenart7c3.nostrsigner.ui.components.MarkdownText
|
||||
import com.greenart7c3.nostrsigner.ui.components.SeedWordsPage
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import com.greenart7c3.nostrsigner.ui.newSensitivePlainText
|
||||
import com.greenart7c3.nostrsigner.ui.setSensitiveClip
|
||||
import com.greenart7c3.nostrsigner.ui.theme.Size35dp
|
||||
import com.greenart7c3.nostrsigner.ui.theme.fromHex
|
||||
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
|
||||
@@ -797,7 +796,7 @@ private fun encryptCopyNSec(
|
||||
account.didBackup = true
|
||||
onBackupDone()
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
clipboardManager.setClipEntry(ClipEntry(newSensitivePlainText("", key)))
|
||||
clipboardManager.setSensitiveClip("", key)
|
||||
Toast.makeText(context, context.getString(R.string.secret_key_copied_to_clipboard), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
onLoading(false)
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -31,7 +30,7 @@ import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.ui.newSensitivePlainText
|
||||
import com.greenart7c3.nostrsigner.ui.setSensitiveClip
|
||||
import com.greenart7c3.nostrsigner.ui.verticalScrollbar
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -74,11 +73,7 @@ fun SeedWordsPage(
|
||||
AmberButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
clipboardManager.setClipEntry(
|
||||
ClipEntry(
|
||||
newSensitivePlainText("", seedWords.joinToString(" ")),
|
||||
),
|
||||
)
|
||||
clipboardManager.setSensitiveClip("", seedWords.joinToString(" "))
|
||||
}
|
||||
},
|
||||
text = stringResource(R.string.copy_to_clipboard),
|
||||
|
||||
Reference in New Issue
Block a user