Another account on the same install starts with an empty vault
Found on the emulator against a local relay: after a plain sign-out of
luke1009 and a sign-up as mira1009 on the same install, mira saw luke's
conversation with lina1009, carried andy1009's handle certificate from two
accounts earlier, and sent her first message to lina on luke's ratchet
session. The iPhone could not open it ("no session for envelope 95 from
a26ac1d7") and mira's first contact never arrived. Main has the same sign-in
path: a different account got a new device identity (android#22) and kept
every other record.
AccountSwitch.forgetPreviousAccountIfSwitching empties the vault when a
different account (another user id or relay) signs in, before its device is
made. The same account signing back in keeps everything, as before. The
last account's device identity was already being replaced at that point,
so nothing forgotten was still usable by it.
AnotherAccountStartsWithNothingTest: 2 red, then 4/4 green.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
69a7bd72c6
commit
8842479bb8
@@ -0,0 +1,28 @@
|
|||||||
|
package io.flamenet.messenger.app
|
||||||
|
|
||||||
|
import io.flamenet.e2e.Vault
|
||||||
|
|
||||||
|
/** What happens to the vault when somebody signs in. */
|
||||||
|
object AccountSwitch {
|
||||||
|
/**
|
||||||
|
* Before a different account takes over this install, forget the last
|
||||||
|
* one's records: its conversations, sessions, delivery keys, drafts and
|
||||||
|
* handle certificate. A plain sign-out keeps the vault so the SAME person
|
||||||
|
* can sign back in; another account on the same install got a new device
|
||||||
|
* but inherited the rest, saw the last account's conversations and sent
|
||||||
|
* on its ratchet sessions, which the far end could not open. The last
|
||||||
|
* account's device was already replaced at this point, so what is
|
||||||
|
* forgotten could not have been used by it again.
|
||||||
|
*
|
||||||
|
* True when something was forgotten.
|
||||||
|
*/
|
||||||
|
fun forgetPreviousAccountIfSwitching(
|
||||||
|
vault: Vault,
|
||||||
|
previous: Vault.Credential?,
|
||||||
|
sameAccount: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
if (previous == null || sameAccount) return false
|
||||||
|
vault.forgetEverything()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -620,6 +620,7 @@ class MessengerViewModel(
|
|||||||
(previous.userId == account.userId && previous.relayBaseUrl == config.baseUrl)
|
(previous.userId == account.userId && previous.relayBaseUrl == config.baseUrl)
|
||||||
val storedIdentity = if (sameAccount) vault.loadIdentity() else null
|
val storedIdentity = if (sameAccount) vault.loadIdentity() else null
|
||||||
if (!sameAccount) identity = null
|
if (!sameAccount) identity = null
|
||||||
|
AccountSwitch.forgetPreviousAccountIfSwitching(vault, previous, sameAccount)
|
||||||
|
|
||||||
val device =
|
val device =
|
||||||
identity
|
identity
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package io.flamenet.messenger.app
|
||||||
|
|
||||||
|
import io.flamenet.e2e.Vault
|
||||||
|
import java.io.File
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Found on the emulator against a local relay, 2026-10-09: a plain sign-out
|
||||||
|
* keeps the vault so the same person can sign back in, and a DIFFERENT
|
||||||
|
* account signing in on the same install got a new device identity but kept
|
||||||
|
* everything else. mira1009 saw luke1009's conversation with lina1009 and
|
||||||
|
* sent her first message on luke's ratchet session, which the iPhone could not open ("no session for
|
||||||
|
* envelope 95"). A different account now starts with an empty vault.
|
||||||
|
*/
|
||||||
|
class AnotherAccountStartsWithNothingTest {
|
||||||
|
private class MapStorage : Vault.Storage {
|
||||||
|
val records = mutableMapOf<String, ByteArray>()
|
||||||
|
|
||||||
|
override fun read(name: String) = records[name]
|
||||||
|
|
||||||
|
override fun write(
|
||||||
|
name: String,
|
||||||
|
bytes: ByteArray,
|
||||||
|
) {
|
||||||
|
records[name] = bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(name: String) {
|
||||||
|
records.remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun names() = records.keys.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun vault() = Vault(MapStorage(), KeyGenerator.getInstance("AES").apply { init(256) }.generateKey())
|
||||||
|
|
||||||
|
private fun credential(user: Long) = Vault.Credential("http://127.0.0.1:18810", "u$user", "pw", user)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a different account finds none of the last one's records`() {
|
||||||
|
val v = vault()
|
||||||
|
v.saveCredential(credential(3))
|
||||||
|
v.saveGroupName("g1", "luke's group")
|
||||||
|
v.saveDraft("user:4", "for lina")
|
||||||
|
assertTrue(AccountSwitch.forgetPreviousAccountIfSwitching(v, credential(3), sameAccount = false))
|
||||||
|
assertNull(v.groupName("g1"))
|
||||||
|
assertEquals(null, v.draft("user:4"))
|
||||||
|
assertNull(runCatching { v.loadCredential() }.getOrNull())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `the same account keeps everything`() {
|
||||||
|
val v = vault()
|
||||||
|
v.saveCredential(credential(3))
|
||||||
|
v.saveGroupName("g1", "luke's group")
|
||||||
|
assertFalse(AccountSwitch.forgetPreviousAccountIfSwitching(v, credential(3), sameAccount = true))
|
||||||
|
assertNotNull(v.groupName("g1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a first sign-in has nothing to forget`() {
|
||||||
|
assertFalse(AccountSwitch.forgetPreviousAccountIfSwitching(vault(), null, sameAccount = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `sign-in forgets before the new identity is saved`() {
|
||||||
|
val vm = File("src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt").readText()
|
||||||
|
val forget = vm.indexOf("AccountSwitch.forgetPreviousAccountIfSwitching(")
|
||||||
|
val newIdentity = vm.indexOf("DeviceIdentity.create().also {")
|
||||||
|
assertTrue(forget in 0 until newIdentity, "sign-in does not forget the last account before making this one's device")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user