M7: polish + E2E + docs (layout pass, theming, states, e2e driver, schema, setup.md, security)
This commit is contained in:
1 parent
0cc8b7aafe
commit
bf6bf7e8bd
26 files changed
+2225
-327
No files matched your search
@@ -1,16 +1,64 @@
|
||||
package iris.platform
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
import iris.data.SecureStore
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Android pairing storage. M1: SharedPreferences (dev). M5 moves the token
|
||||
* to EncryptedSharedPreferences per docs/10 §10.2.
|
||||
* Android pairing storage. M7: EncryptedSharedPreferences (MasterKey
|
||||
* AES256_GCM) per docs/09 §9.7. The M1/M5 plain "iris" SharedPreferences
|
||||
* values are migrated on first run (read old key, write encrypted, delete
|
||||
* old key) so an upgrade never loses the pairing.
|
||||
*/
|
||||
class AndroidSecureStore(context: Context) : SecureStore {
|
||||
private val prefs = context.applicationContext.getSharedPreferences("iris", Context.MODE_PRIVATE)
|
||||
private val appContext = context.applicationContext
|
||||
private val plainPrefs = appContext.getSharedPreferences(PLAIN_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
private val prefs: SharedPreferences = EncryptedSharedPreferences.create(
|
||||
appContext,
|
||||
SECURE_PREFS_NAME,
|
||||
MasterKey.Builder(appContext)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build(),
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
||||
)
|
||||
|
||||
init {
|
||||
migratePlainValues()
|
||||
}
|
||||
|
||||
/** One-time migration of the M1/M5 plain values into the encrypted store. */
|
||||
private fun migratePlainValues() {
|
||||
val editor = prefs.edit()
|
||||
var migrated = false
|
||||
for (key in listOf(KEY_URL, KEY_TOKEN, KEY_DEVICE_ID, KEY_FCM_TOKEN, KEY_NTFY_TOPIC, KEY_NTFY_SERVER)) {
|
||||
val old = plainPrefs.getString(key, null)
|
||||
if (old != null && !prefs.contains(key)) {
|
||||
editor.putString(key, old)
|
||||
migrated = true
|
||||
}
|
||||
}
|
||||
val oldCursor = plainPrefs.getLong(KEY_SYNC_CURSOR, 0L)
|
||||
if (oldCursor != 0L && !prefs.contains(KEY_SYNC_CURSOR)) {
|
||||
editor.putLong(KEY_SYNC_CURSOR, oldCursor)
|
||||
migrated = true
|
||||
}
|
||||
if (migrated) editor.apply()
|
||||
// The plain store must not keep a copy of any value.
|
||||
plainPrefs.edit()
|
||||
.remove(KEY_URL)
|
||||
.remove(KEY_TOKEN)
|
||||
.remove(KEY_DEVICE_ID)
|
||||
.remove(KEY_SYNC_CURSOR)
|
||||
.remove(KEY_FCM_TOKEN)
|
||||
.remove(KEY_NTFY_TOPIC)
|
||||
.remove(KEY_NTFY_SERVER)
|
||||
.apply()
|
||||
}
|
||||
|
||||
override var serverUrl: String
|
||||
get() = prefs.getString(KEY_URL, "").orEmpty()
|
||||
@@ -59,6 +107,8 @@ class AndroidSecureStore(context: Context) : SecureStore {
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PLAIN_PREFS_NAME = "iris"
|
||||
const val SECURE_PREFS_NAME = "iris_secure"
|
||||
const val KEY_URL = "server_url"
|
||||
const val KEY_TOKEN = "token"
|
||||
const val KEY_DEVICE_ID = "device_id"
|
||||
|
||||
Reference in new issue
Block a user