Migration from old to new preferences

This commit is contained in:
maxmoney21m
2023-03-12 13:24:51 +08:00
parent 992796a7bf
commit 269197a59d
5 changed files with 92 additions and 26 deletions

View File

@@ -6,13 +6,17 @@ import androidx.security.crypto.MasterKey
object EncryptedStorage { object EncryptedStorage {
private const val PREFERENCES_NAME = "secret_keeper" private const val PREFERENCES_NAME = "secret_keeper"
fun prefsFileName(npub: String? = null): String {
return if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
}
fun preferences(npub: String? = null): EncryptedSharedPreferences { fun preferences(npub: String? = null): EncryptedSharedPreferences {
val context = Amethyst.instance val context = Amethyst.instance
val masterKey: MasterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) val masterKey: MasterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build() .build()
val preferencesName = if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub" val preferencesName = prefsFileName(npub)
return EncryptedSharedPreferences.create( return EncryptedSharedPreferences.create(
context, context,

View File

@@ -11,17 +11,25 @@ import com.vitorpamplona.amethyst.model.toByteArray
import com.vitorpamplona.amethyst.service.model.ContactListEvent import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.Event import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
import fr.acinq.secp256k1.Hex
import nostr.postr.Persona import nostr.postr.Persona
import nostr.postr.toHex import nostr.postr.toHex
import nostr.postr.toNpub
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
// Release mode (!BuildConfig.DEBUG) always uses encrypted preferences // Release mode (!BuildConfig.DEBUG) always uses encrypted preferences
// To use plaintext SharedPreferences for debugging, set this to true // To use plaintext SharedPreferences for debugging, set this to true
// It will only apply in Debug builds // It will only apply in Debug builds
const val DEBUG_PLAINTEXT_PREFERENCES = true private const val DEBUG_PLAINTEXT_PREFERENCES = false
private const val OLD_PREFS_FILENAME = "secret_keeper"
data class AccountInfo(val npub: String, val current: Boolean, val displayName: String?, val profilePicture: String?) data class AccountInfo(
val npub: String,
val current: Boolean,
val displayName: String?,
val profilePicture: String?
)
private object PrefKeys { private object PrefKeys {
const val CURRENT_ACCOUNT = "currently_logged_in_account" const val CURRENT_ACCOUNT = "currently_logged_in_account"
@@ -57,6 +65,9 @@ object LocalPreferences {
private val savedAccounts: Set<String> private val savedAccounts: Set<String>
get() = encryptedPreferences().getStringSet(PrefKeys.SAVED_ACCOUNTS, null) ?: setOf() get() = encryptedPreferences().getStringSet(PrefKeys.SAVED_ACCOUNTS, null) ?: setOf()
private val prefsDirPath: String
get() = "${Amethyst.instance.filesDir.parent}/shared_prefs/"
private fun addAccount(npub: String) { private fun addAccount(npub: String) {
val accounts = savedAccounts.toMutableSet() val accounts = savedAccounts.toMutableSet()
accounts.add(npub) accounts.add(npub)
@@ -66,6 +77,12 @@ object LocalPreferences {
}.apply() }.apply()
} }
private fun setCurrentAccount(account: Account) {
val npub = account.userProfile().pubkeyNpub()
currentAccount = npub
addAccount(npub)
}
/** /**
* Removes the account from the app level shared preferences * Removes the account from the app level shared preferences
*/ */
@@ -82,8 +99,7 @@ object LocalPreferences {
* Deletes the npub-specific shared preference file * Deletes the npub-specific shared preference file
*/ */
private fun deleteUserPreferenceFile(npub: String) { private fun deleteUserPreferenceFile(npub: String) {
val context = Amethyst.instance val prefsDir = File(prefsDirPath)
val prefsDir = File("${context.filesDir.parent}/shared_prefs/")
prefsDir.list()?.forEach { prefsDir.list()?.forEach {
if (it.contains(npub)) { if (it.contains(npub)) {
File(prefsDir, it).delete() File(prefsDir, it).delete()
@@ -93,7 +109,7 @@ object LocalPreferences {
private fun encryptedPreferences(npub: String? = null): SharedPreferences { private fun encryptedPreferences(npub: String? = null): SharedPreferences {
return if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) { return if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) {
val preferenceFile = if (npub == null) "testing_only" else "testing_only_$npub" val preferenceFile = if (npub == null) "debug_prefs" else "debug_prefs_$npub"
Amethyst.instance.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE) Amethyst.instance.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE)
} else { } else {
return EncryptedStorage.preferences(npub) return EncryptedStorage.preferences(npub)
@@ -103,9 +119,13 @@ object LocalPreferences {
/** /**
* Clears the preferences for a given npub, deletes the preferences xml file, * Clears the preferences for a given npub, deletes the preferences xml file,
* and switches the user to the first account in the list if it exists * and switches the user to the first account in the list if it exists
*
* We need to use `commit()` to write changes to disk and release the file
* lock so that it can be deleted. If we use `apply()` there is a race
* condition and the file will probably not be deleted
*/ */
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
fun clearEncryptedStorage(npub: String) { fun updatePrefsForLogout(npub: String) {
val userPrefs = encryptedPreferences(npub) val userPrefs = encryptedPreferences(npub)
userPrefs.edit().clear().commit() userPrefs.edit().clear().commit()
removeAccount(npub) removeAccount(npub)
@@ -119,7 +139,12 @@ object LocalPreferences {
} }
} }
fun findAllLocalAccounts(): List<AccountInfo> { fun updatePrefsForLogin(account: Account) {
setCurrentAccount(account)
saveToEncryptedStorage(account)
}
fun allSavedAccounts(): List<AccountInfo> {
return savedAccounts.map { npub -> return savedAccounts.map { npub ->
val prefs = encryptedPreferences(npub) val prefs = encryptedPreferences(npub)
@@ -132,12 +157,6 @@ object LocalPreferences {
} }
} }
fun setCurrentAccount(account: Account) {
val npub = account.userProfile().pubkeyNpub()
currentAccount = npub
addAccount(npub)
}
fun saveToEncryptedStorage(account: Account) { fun saveToEncryptedStorage(account: Account) {
val prefs = encryptedPreferences(account.userProfile().pubkeyNpub()) val prefs = encryptedPreferences(account.userProfile().pubkeyNpub())
prefs.edit().apply { prefs.edit().apply {
@@ -157,11 +176,6 @@ object LocalPreferences {
}.apply() }.apply()
} }
fun login(account: Account) {
setCurrentAccount(account)
saveToEncryptedStorage(account)
}
fun loadFromEncryptedStorage(): Account? { fun loadFromEncryptedStorage(): Account? {
encryptedPreferences(currentAccount).apply { encryptedPreferences(currentAccount).apply {
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return null val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return null
@@ -183,7 +197,8 @@ object LocalPreferences {
val latestContactList = try { val latestContactList = try {
getString(PrefKeys.LATEST_CONTACT_LIST, null)?.let { getString(PrefKeys.LATEST_CONTACT_LIST, null)?.let {
Event.gson.fromJson(it, Event::class.java).getRefinedEvent(true) as ContactListEvent Event.gson.fromJson(it, Event::class.java)
.getRefinedEvent(true) as ContactListEvent
} }
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
@@ -192,7 +207,10 @@ object LocalPreferences {
val languagePreferences = try { val languagePreferences = try {
getString(PrefKeys.LANGUAGE_PREFS, null)?.let { getString(PrefKeys.LANGUAGE_PREFS, null)?.let {
gson.fromJson(it, object : TypeToken<Map<String, String>>() {}.type) as Map<String, String> gson.fromJson(
it,
object : TypeToken<Map<String, String>>() {}.type
) as Map<String, String>
} ?: mapOf() } ?: mapOf()
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
@@ -227,4 +245,45 @@ object LocalPreferences {
return getLong(PrefKeys.LAST_READ(route), 0) return getLong(PrefKeys.LAST_READ(route), 0)
} }
} }
fun migrateSingleUserPrefs() {
if (currentAccount != null) return
val pubkey = encryptedPreferences().getString(PrefKeys.NOSTR_PUBKEY, null) ?: return
val npub = Hex.decode(pubkey).toNpub()
val stringPrefs = listOf(
PrefKeys.NOSTR_PRIVKEY,
PrefKeys.NOSTR_PUBKEY,
PrefKeys.RELAYS,
PrefKeys.LANGUAGE_PREFS,
PrefKeys.TRANSLATE_TO,
PrefKeys.ZAP_AMOUNTS,
PrefKeys.LATEST_CONTACT_LIST
)
val stringSetPrefs = listOf(
PrefKeys.FOLLOWING_CHANNELS,
PrefKeys.HIDDEN_USERS,
PrefKeys.DONT_TRANSLATE_FROM
)
encryptedPreferences().apply {
val appPrefs = this
encryptedPreferences(npub).edit().apply {
val userPrefs = this
stringPrefs.forEach { userPrefs.putString(it, appPrefs.getString(it, null)) }
stringSetPrefs.forEach { userPrefs.putStringSet(it, appPrefs.getStringSet(it, null)) }
userPrefs.putBoolean(
PrefKeys.HIDE_DELETE_REQUEST_INFO,
appPrefs.getBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, false)
)
}.apply()
}
encryptedPreferences().edit().clear().apply()
addAccount(npub)
currentAccount = npub
}
} }

View File

@@ -14,6 +14,7 @@ import coil.ImageLoader
import coil.decode.GifDecoder import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder import coil.decode.SvgDecoder
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.nip19.Nip19
import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Client
@@ -48,6 +49,8 @@ class MainActivity : FragmentActivity() {
.build() .build()
} }
LocalPreferences.migrateSingleUserPrefs()
setContent { setContent {
AmethystTheme { AmethystTheme {
// A surface container using the 'background' color from the theme // A surface container using the 'background' color from the theme

View File

@@ -55,7 +55,7 @@ fun AccountSwitchBottomSheet(
accountStateViewModel: AccountStateViewModel accountStateViewModel: AccountStateViewModel
) { ) {
val context = LocalContext.current val context = LocalContext.current
val accounts = LocalPreferences.findAllLocalAccounts() val accounts = LocalPreferences.allSavedAccounts()
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return

View File

@@ -51,18 +51,18 @@ class AccountStateViewModel() : ViewModel() {
Account(Persona(Hex.decode(key))) Account(Persona(Hex.decode(key)))
} }
LocalPreferences.login(account) LocalPreferences.updatePrefsForLogin(account)
login(account) login(account)
} }
fun newKey() { fun newKey() {
val account = Account(Persona()) val account = Account(Persona())
LocalPreferences.login(account) LocalPreferences.updatePrefsForLogin(account)
login(account) login(account)
} }
fun login(account: Account) { fun login(account: Account) {
LocalPreferences.login(account) LocalPreferences.updatePrefsForLogin(account)
if (account.loggedIn.privKey != null) { if (account.loggedIn.privKey != null) {
_accountContent.update { AccountState.LoggedIn(account) } _accountContent.update { AccountState.LoggedIn(account) }
@@ -105,7 +105,7 @@ class AccountStateViewModel() : ViewModel() {
_accountContent.update { AccountState.LoggedOff } _accountContent.update { AccountState.LoggedOff }
LocalPreferences.clearEncryptedStorage(npub) LocalPreferences.updatePrefsForLogout(npub)
tryLoginExistingAccount() tryLoginExistingAccount()
} }
} }