d2b3473d34ed

fix: Clear receiver re-pair revocation (v2.5.21)

time
2026-07-27 18:55 UTC · 2 months ago
machine
koh2
agent
codex-gpt-5
session
peach-sync-e2e
parent
7b366ada2cea
5 files app/build.gradle+2 −2app/src/main/java/com/peachpasswords/sync/RelayAdoptionReceiver.kt+4 −0app/src/main/java/com/peachpasswords/sync/VaultAdopt.kt+6 −0app/src/main/java/com/peachpasswords/ui/pairing/AdoptVaultViewModel.kt+4 −0app/src/test/java/com/peachpasswords/sync/VaultAdoptTest.kt+6 −0
app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616         applicationId "com.peachpasswords"1717         minSdk 261818         targetSdk 3519-        versionCode 24012020-        versionName "2.5.20"19+        versionCode 24012120+        versionName "2.5.21"2121 2222         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323         vectorDrawables {

view file

app/src/main/java/com/peachpasswords/sync/RelayAdoptionReceiver.kt+4 −0
@@ -95,6 +95,10 @@9595             override suspend fun deriveLocalIdentity(): LocalDeviceIdentity =9696                 receiverDeps.deriveLocalIdentity()9797 98+            override suspend fun clearPairedDeviceRevocation(deviceId: String) {99+                receiverDeps.clearPairedDeviceRevocation(deviceId)100+            }101+98102             override suspend fun upsertPairedDevice(device: PairedDevice) {99103                 receiverDeps.upsertPairedDevice(device)100104             }

view file

app/src/main/java/com/peachpasswords/sync/VaultAdopt.kt+6 −0
@@ -91,6 +91,8 @@9191     /** Seal a fresh local vault under the receiver's password, reusing [rootKeyHex]. Returns the vault id. */9292     suspend fun createVaultFromRootKey(rootKeyHex: String): String9393     suspend fun deriveLocalIdentity(): LocalDeviceIdentity94+    /** Only called after a successful, SAS-confirmed direct pairing. */95+    suspend fun clearPairedDeviceRevocation(deviceId: String) {}9496     suspend fun upsertPairedDevice(device: PairedDevice)9597     suspend fun applyLicense(license: String) {}9698     val pairState: AdoptionPairState? get() = null@@ -215,6 +217,10 @@215217         relayUrl = deps.pairState?.relayUrl ?: grant.source.relayUrl,216218         pairedAt = nowMs(),217219     )220+    // A physically confirmed direct re-pair is the sole path allowed to lift221+    // terminal revocation. Clear before upsert so storage can never expose the222+    // source as both paired and revoked, even transiently.223+    deps.clearPairedDeviceRevocation(sourceDevice.deviceId)218224     deps.upsertPairedDevice(sourceDevice)219225     return sourceDevice220226 }

view file

app/src/main/java/com/peachpasswords/ui/pairing/AdoptVaultViewModel.kt+4 −0
@@ -219,6 +219,10 @@219219                 }220220             }221221 222+            override suspend fun clearPairedDeviceRevocation(deviceId: String) {223+                devices.clearRevocation(deviceId)224+            }225+222226             override suspend fun upsertPairedDevice(device: PairedDevice) {223227                 devices.upsert(device)224228             }

view file

app/src/test/java/com/peachpasswords/sync/VaultAdoptTest.kt+6 −0
@@ -60,6 +60,7 @@6060     ) : AdoptionReceiverDeps {6161         var createCalledWith: String? = null6262         var licenseApplied: String? = null63+        val revocationsCleared = mutableListOf<String>()6364         val upserted = mutableListOf<PairedDevice>()6465         val errors = mutableListOf<String>()6566         override fun generateDeviceId(): String = "receiver-ephemeral"@@ -70,6 +71,9 @@7071             return vaultId ?: error("no vault id")7172         }7273         override suspend fun deriveLocalIdentity(): LocalDeviceIdentity = derived ?: error("no identity")74+        override suspend fun clearPairedDeviceRevocation(deviceId: String) {75+            revocationsCleared += deviceId76+        }7377         override suspend fun upsertPairedDevice(device: PairedDevice) { upserted += device }7478         override suspend fun applyLicense(license: String) { licenseApplied = license }7579         override fun onError(error: String) { errors += error }@@ -116,6 +120,7 @@116120 117121         assertEquals(rootKeyHex, receiver.createCalledWith)118122         assertEquals("PPK-test", receiver.licenseApplied)123+        assertEquals(listOf("source-id"), receiver.revocationsCleared)119124         assertEquals("source-id", receiver.upserted.single().deviceId)120125         assertEquals("receiver-id", source.upserted.single().deviceId)121126         assertEquals("android", source.upserted.single().deviceType)@@ -207,6 +212,7 @@207212         assertNull(sourceJob.await())208213         assertFalse(source.rootRequested)209214         assertNull(receiver.createCalledWith)215+        assertTrue(receiver.revocationsCleared.isEmpty())210216         assertEquals(listOf("ADOPT_SAS_REJECTED"), receiver.errors)211217     }212218 

view file