mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Merge pull request #322 from greenart7c3/claude/migrate-pin-to-datastore-2u6eF
Migrate PIN storage from SharedPreferences to encrypted DataStore
This commit is contained in:
@@ -26,8 +26,21 @@ object DataStoreAccess {
|
||||
)
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var appDataStore: DataStore<Preferences>? = null
|
||||
|
||||
private fun getAppDataStore(context: Context): DataStore<Preferences> = appDataStore ?: synchronized(this) {
|
||||
appDataStore ?: PreferenceDataStoreFactory.create(
|
||||
scope = Amber.instance.applicationIOScope,
|
||||
produceFile = {
|
||||
context.applicationContext.preferencesDataStoreFile("app_datastore")
|
||||
},
|
||||
).also { appDataStore = it }
|
||||
}
|
||||
|
||||
val NOSTR_PRIVKEY = stringPreferencesKey("nostr_privkey")
|
||||
val SEED_WORDS = stringPreferencesKey("seed_words")
|
||||
val PIN = stringPreferencesKey("pin")
|
||||
|
||||
suspend fun saveEncryptedKey(context: Context, npub: String, key: Preferences.Key<String>, value: String) {
|
||||
val encrypted = SecureCryptoHelper.encrypt(value)
|
||||
@@ -52,4 +65,20 @@ object DataStoreAccess {
|
||||
prefs.clear()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun savePin(context: Context, pin: String?) {
|
||||
getAppDataStore(context).edit { prefs ->
|
||||
if (pin == null) {
|
||||
prefs.remove(PIN)
|
||||
} else {
|
||||
prefs[PIN] = SecureCryptoHelper.encrypt(pin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadPin(context: Context): String? {
|
||||
val prefs = getAppDataStore(context).data.first()
|
||||
val encrypted = prefs[PIN] ?: return null
|
||||
return SecureCryptoHelper.decrypt(encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,22 +157,20 @@ object LocalPreferences {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadPinFromEncryptedStorage(): String? {
|
||||
suspend fun loadPinFromEncryptedStorage(): String? {
|
||||
val context = Amber.instance
|
||||
return sharedPrefs(context).getString(SettingsKeys.PIN.key, null)
|
||||
// Migration: move PIN from plain SharedPreferences to encrypted DataStore
|
||||
val legacyPin = sharedPrefs(context).getString(SettingsKeys.PIN.key, null)
|
||||
if (legacyPin != null) {
|
||||
DataStoreAccess.savePin(context, legacyPin)
|
||||
sharedPrefs(context).edit { remove(SettingsKeys.PIN.key) }
|
||||
}
|
||||
return DataStoreAccess.loadPin(context)
|
||||
}
|
||||
|
||||
fun savePinToEncryptedStorage(pin: String?) {
|
||||
suspend fun savePinToEncryptedStorage(pin: String?) {
|
||||
val context = Amber.instance
|
||||
sharedPrefs(context).edit {
|
||||
apply {
|
||||
if (pin == null) {
|
||||
remove(SettingsKeys.PIN.key)
|
||||
} else {
|
||||
putString(SettingsKeys.PIN.key, pin)
|
||||
}
|
||||
}
|
||||
}
|
||||
DataStoreAccess.savePin(context, pin)
|
||||
}
|
||||
|
||||
suspend fun reloadApp() {
|
||||
|
||||
@@ -90,8 +90,12 @@ fun SecurityScreen(
|
||||
.padding(horizontal = 8.dp)
|
||||
.clickable {
|
||||
if (setupPin) {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin")
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
navController.navigate(Route.SetupPin.route)
|
||||
}
|
||||
@@ -105,8 +109,12 @@ fun SecurityScreen(
|
||||
checked = setupPin,
|
||||
onCheckedChange = {
|
||||
if (setupPin) {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin")
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigate("${Route.ConfirmPin.route.split("/")[0]}/$pin")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
navController.navigate(Route.SetupPin.route)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.greenart7c3.nostrsigner.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.navigation.NavController
|
||||
@@ -10,6 +11,8 @@ import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.ui.components.RandomPinInput
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SetupPinScreen(
|
||||
@@ -32,6 +35,7 @@ fun ConfirmPinScreen(
|
||||
navController: NavController,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
Column(
|
||||
modifier,
|
||||
) {
|
||||
@@ -41,22 +45,21 @@ fun ConfirmPinScreen(
|
||||
ToastManager.toast(context.getString(R.string.pin), context.getString(R.string.pin_does_not_match))
|
||||
} else {
|
||||
val usePin = Amber.instance.settings.usePin
|
||||
if (usePin) {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(usePin = false)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
|
||||
LocalPreferences.savePinToEncryptedStorage(null)
|
||||
navController.navigate(Route.Security.route) {
|
||||
popUpTo(Route.Security.route) {
|
||||
inclusive = true
|
||||
}
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (usePin) {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(usePin = false)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
|
||||
LocalPreferences.savePinToEncryptedStorage(null)
|
||||
} else {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(usePin = true)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
|
||||
LocalPreferences.savePinToEncryptedStorage(pin)
|
||||
}
|
||||
} else {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(usePin = true)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
|
||||
LocalPreferences.savePinToEncryptedStorage(pin)
|
||||
navController.navigate(Route.Security.route) {
|
||||
popUpTo(Route.Security.route) {
|
||||
inclusive = true
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigate(Route.Security.route) {
|
||||
popUpTo(Route.Security.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-16
@@ -15,6 +15,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -36,6 +37,7 @@ fun BiometricAuthScreen(
|
||||
onAuth: (Boolean) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var showPinDialog by remember { mutableStateOf(false) }
|
||||
val keyguardLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||
@@ -117,22 +119,25 @@ fun BiometricAuthScreen(
|
||||
) {
|
||||
RandomPinInput(
|
||||
text = stringResource(R.string.enter_pin),
|
||||
onPinEntered = {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
if (it == pin) {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(
|
||||
lastBiometricsTime = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
|
||||
onAuth(true)
|
||||
showPinDialog = false
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.pin_does_not_match),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
onPinEntered = { enteredPin ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val pin = LocalPreferences.loadPinFromEncryptedStorage()
|
||||
scope.launch(Dispatchers.Main) {
|
||||
if (enteredPin == pin) {
|
||||
Amber.instance.settings = Amber.instance.settings.copy(
|
||||
lastBiometricsTime = System.currentTimeMillis(),
|
||||
)
|
||||
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
|
||||
onAuth(true)
|
||||
showPinDialog = false
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.pin_does_not_match),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user