mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-29 18:02:43 +02:00
Migration from old to new preferences
This commit is contained in:
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user