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
↓ peach-android-d2b3473d.tar.gzatlas at this save
files changed5 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 −0app/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 {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 }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 }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 }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