mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-10 07:20:41 +01:00
chore(preferences): migration to multiplatform-settings and encryption support
This commit is contained in:
parent
ae99a7c2c2
commit
49706c603d
@ -30,10 +30,16 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
val androidMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.multiplatform.settings)
|
||||||
|
implementation(libs.androidx.security.crypto)
|
||||||
|
}
|
||||||
|
}
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.koin.core)
|
implementation(libs.koin.core)
|
||||||
implementation(libs.androidx.datastore)
|
implementation(libs.multiplatform.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
|
|
||||||
internal class AndroidKeyStoreFilePathProvider(
|
|
||||||
private val context: Context,
|
|
||||||
) {
|
|
||||||
fun get(): String = context.filesDir.resolve(DefaultTemporaryKeyStore.FILE_NAME).absolutePath
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
|
||||||
|
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
|
||||||
|
|
||||||
|
|
||||||
actual fun getKeyStoreFilePath(): String {
|
|
||||||
val provider: AndroidKeyStoreFilePathProvider by inject(
|
|
||||||
AndroidKeyStoreFilePathProvider::class.java
|
|
||||||
)
|
|
||||||
return provider.get()
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +1,51 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences.di
|
package com.github.diegoberaldin.raccoonforlemmy.core_preferences.di
|
||||||
|
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
import android.content.Context
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.AndroidKeyStoreFilePathProvider
|
import android.content.SharedPreferences
|
||||||
|
import androidx.security.crypto.EncryptedSharedPreferences
|
||||||
|
import androidx.security.crypto.MasterKeys
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.DefaultTemporaryKeyStore
|
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.DefaultTemporaryKeyStore
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||||
|
import com.russhwolf.settings.Settings
|
||||||
|
import com.russhwolf.settings.SharedPreferencesSettings
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
|
|
||||||
actual val corePreferencesModule = module {
|
actual val corePreferencesModule = module {
|
||||||
singleOf(::AndroidKeyStoreFilePathProvider)
|
singleOf(::SharedPreferencesProvider)
|
||||||
singleOf<TemporaryKeyStore>(::DefaultTemporaryKeyStore)
|
single<Settings> {
|
||||||
|
val sharedPreferencesProvider: SharedPreferencesProvider by inject()
|
||||||
|
SharedPreferencesSettings(
|
||||||
|
sharedPreferencesProvider.sharedPreferences,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
single<TemporaryKeyStore> {
|
||||||
|
DefaultTemporaryKeyStore(settings = get())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getTemporaryKeyStore(): TemporaryKeyStore {
|
actual fun getTemporaryKeyStore(): TemporaryKeyStore {
|
||||||
val res: TemporaryKeyStore by inject(TemporaryKeyStore::class.java)
|
val res by inject<TemporaryKeyStore>(TemporaryKeyStore::class.java)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SharedPreferencesProvider(
|
||||||
|
context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PREFERENCES_NAME = "secret_shared_prefs"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val masterKeyAlias: String = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||||
|
|
||||||
|
val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
|
||||||
|
PREFERENCES_NAME,
|
||||||
|
masterKeyAlias,
|
||||||
|
context,
|
||||||
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||||
|
)
|
||||||
|
}
|
@ -1,103 +1,42 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
import com.russhwolf.settings.Settings
|
||||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
import com.russhwolf.settings.get
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import com.russhwolf.settings.set
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.doublePreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.edit
|
|
||||||
import androidx.datastore.preferences.core.floatPreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
|
||||||
import okio.Path.Companion.toPath
|
|
||||||
|
|
||||||
internal expect fun getKeyStoreFilePath(): String
|
internal class DefaultTemporaryKeyStore(
|
||||||
|
private val settings: Settings,
|
||||||
|
) : TemporaryKeyStore {
|
||||||
|
|
||||||
internal class DefaultTemporaryKeyStore : TemporaryKeyStore {
|
override suspend fun containsKey(key: String): Boolean = settings.keys.contains(key)
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal const val FILE_NAME = "main.preferences_pb"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val scope = CoroutineScope(Dispatchers.Default)
|
|
||||||
private val dataStore: DataStore<Preferences> = createDataStore()
|
|
||||||
|
|
||||||
private fun createDataStore(): DataStore<Preferences> =
|
|
||||||
PreferenceDataStoreFactory.createWithPath(
|
|
||||||
corruptionHandler = null,
|
|
||||||
migrations = emptyList(),
|
|
||||||
scope = scope,
|
|
||||||
produceFile = { getKeyStoreFilePath().toPath() }
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun containsKey(key: String): Boolean = withTimeoutOrNull(500) {
|
|
||||||
dataStore.data.map {
|
|
||||||
it.asMap().keys.any { k -> k.name == key }
|
|
||||||
}.first()
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
override suspend fun save(key: String, value: Boolean) {
|
override suspend fun save(key: String, value: Boolean) {
|
||||||
dataStore.edit {
|
settings[key] = value
|
||||||
it[booleanPreferencesKey(key)] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(key: String, default: Boolean) = withTimeoutOrNull(500) {
|
override suspend fun get(key: String, default: Boolean): Boolean = settings[key, default]
|
||||||
dataStore.data.map {
|
|
||||||
it[booleanPreferencesKey(key)]
|
|
||||||
}.first()
|
|
||||||
} ?: default
|
|
||||||
|
|
||||||
override suspend fun save(key: String, value: String) {
|
override suspend fun save(key: String, value: String) {
|
||||||
dataStore.edit {
|
settings[key] = value
|
||||||
it[stringPreferencesKey(key)] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(key: String, default: String): String = withTimeoutOrNull(500) {
|
override suspend fun get(key: String, default: String): String = settings[key, default]
|
||||||
dataStore.data.map {
|
|
||||||
it[stringPreferencesKey(key)]
|
|
||||||
}.first()
|
|
||||||
} ?: default
|
|
||||||
|
|
||||||
override suspend fun save(key: String, value: Int) {
|
override suspend fun save(key: String, value: Int) {
|
||||||
dataStore.edit {
|
settings[key] = value
|
||||||
it[intPreferencesKey(key)] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(key: String, default: Int): Int = withTimeoutOrNull(500) {
|
override suspend fun get(key: String, default: Int): Int = settings[key, default]
|
||||||
dataStore.data.map {
|
|
||||||
it[intPreferencesKey(key)]
|
|
||||||
}.first()
|
|
||||||
} ?: default
|
|
||||||
|
|
||||||
override suspend fun save(key: String, value: Float) {
|
override suspend fun save(key: String, value: Float) {
|
||||||
dataStore.edit {
|
settings[key] = value
|
||||||
it[floatPreferencesKey(key)] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(key: String, default: Float): Float = withTimeoutOrNull(500) {
|
override suspend fun get(key: String, default: Float): Float = settings[key, default]
|
||||||
dataStore.data.map {
|
|
||||||
it[floatPreferencesKey(key)]
|
|
||||||
}.first()
|
|
||||||
} ?: default
|
|
||||||
|
|
||||||
override suspend fun save(key: String, value: Double) {
|
override suspend fun save(key: String, value: Double) {
|
||||||
dataStore.edit {
|
settings[key] = value
|
||||||
it[doublePreferencesKey(key)] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(key: String, default: Double): Double = withTimeoutOrNull(500) {
|
override suspend fun get(key: String, default: Double): Double = settings[key, default]
|
||||||
dataStore.data.map {
|
|
||||||
it[doublePreferencesKey(key)]
|
|
||||||
}.first()
|
|
||||||
} ?: default
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences
|
|
||||||
|
|
||||||
import platform.Foundation.NSDocumentDirectory
|
|
||||||
import platform.Foundation.NSFileManager
|
|
||||||
import platform.Foundation.NSURL
|
|
||||||
import platform.Foundation.NSUserDomainMask
|
|
||||||
|
|
||||||
actual fun getKeyStoreFilePath(): String {
|
|
||||||
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
|
|
||||||
directory = NSDocumentDirectory,
|
|
||||||
inDomain = NSUserDomainMask,
|
|
||||||
appropriateForURL = null,
|
|
||||||
create = false,
|
|
||||||
error = null,
|
|
||||||
)
|
|
||||||
return requireNotNull(documentDirectory).path + "/${DefaultTemporaryKeyStore.FILE_NAME}"
|
|
||||||
}
|
|
@ -1,20 +1,26 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core_preferences.di
|
package com.github.diegoberaldin.raccoonforlemmy.core_preferences.di
|
||||||
|
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.DefaultTemporaryKeyStore
|
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.DefaultTemporaryKeyStore
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core_preferences.TemporaryKeyStore
|
||||||
|
import com.russhwolf.settings.KeychainSettings
|
||||||
|
import com.russhwolf.settings.Settings
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import org.koin.core.module.dsl.singleOf
|
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
actual val corePreferencesModule = module {
|
actual val corePreferencesModule = module {
|
||||||
singleOf<TemporaryKeyStore>(::DefaultTemporaryKeyStore)
|
single<Settings> {
|
||||||
|
KeychainSettings(service = "secret_shared_prefs")
|
||||||
|
}
|
||||||
|
single<TemporaryKeyStore> {
|
||||||
|
DefaultTemporaryKeyStore(settings = get())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun getTemporaryKeyStore(): TemporaryKeyStore {
|
actual fun getTemporaryKeyStore(): TemporaryKeyStore {
|
||||||
return TemporaryKeyStoreHelper.keyStore
|
return TemporaryKeyStoreHelper.temporaryKeyStore
|
||||||
}
|
}
|
||||||
|
|
||||||
object TemporaryKeyStoreHelper : KoinComponent {
|
object TemporaryKeyStoreHelper : KoinComponent {
|
||||||
val keyStore: TemporaryKeyStore by inject()
|
val temporaryKeyStore: TemporaryKeyStore by inject()
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
androidx_activity_compose = "1.7.2"
|
androidx_activity_compose = "1.7.2"
|
||||||
androidx_datastore = "1.1.0-dev01"
|
androidx_crypto = "1.0.0"
|
||||||
android_gradle = "7.4.2"
|
android_gradle = "7.4.2"
|
||||||
compose = "1.4.3"
|
compose = "1.4.3"
|
||||||
compose_imageloader = "1.6.0"
|
compose_imageloader = "1.6.0"
|
||||||
@ -12,15 +12,17 @@ ktorfit_gradle = "1.0.0"
|
|||||||
ktorfit_lib = "1.4.3"
|
ktorfit_lib = "1.4.3"
|
||||||
markdown = "0.4.1"
|
markdown = "0.4.1"
|
||||||
moko_resources = "0.23.0"
|
moko_resources = "0.23.0"
|
||||||
|
multiplatform_settings = "1.0.0"
|
||||||
voyager = "1.0.0-rc05"
|
voyager = "1.0.0-rc05"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "androidx.activity.compose" }
|
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "androidx.activity.compose" }
|
||||||
androidx_datastore = { module = "androidx.datastore:datastore-preferences-core", version.ref = "androidx.datastore" }
|
androidx_security_crypto = { module = "androidx.security:security-crypto", version.ref = "androidx.crypto" }
|
||||||
|
|
||||||
compose_imageloader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "compose.imageloader" }
|
compose_imageloader = { module = "io.github.qdsfdhvh:image-loader", version.ref = "compose.imageloader" }
|
||||||
markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" }
|
markdown = { module = "org.jetbrains:markdown", version.ref = "markdown" }
|
||||||
|
multiplatform_settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatform.settings" }
|
||||||
|
|
||||||
koin_core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
koin_core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||||
koin_test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
|
koin_test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user