diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt index 214548f..ea1e3eb 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt @@ -773,7 +773,7 @@ fun ColumnScope.AccountScreen( Text("Files", fontWeight = FontWeight.Medium, fontSize = 16.sp) Spacer(Modifier.height(Space.xs)) Text( - "Files encrypted on this phone, with a passphrase of their own. " + "Files encrypted on this phone, opened with your Flamenet passphrase. " + "Nothing is stored on a server.", fontSize = 13.sp, color = Palette.SecondaryText, diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/FilesScreen.kt b/app/src/main/kotlin/io/flamenet/messenger/app/FilesScreen.kt index 321833f..5fb7186 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/FilesScreen.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/FilesScreen.kt @@ -118,10 +118,22 @@ private fun ColumnScope.FilesSetUp( var passphrase by remember { mutableStateOf("") } var again by remember { mutableStateOf("") } + // 🔴 **"A passphrase of their own" told people to invent a second secret, + // and iOS never did.** iOS says "your passphrase" and takes whatever is + // typed, so a person naturally uses their Flamenet one and it simply + // works. Android's copy sent them off to make up something else, then + // asked for it again weeks later — and forgetting it is the one-way door + // described above this function. + // + // ⚠️ **Nothing about the derivation changes.** This is still a free-choice + // string protecting the identity record, so anyone who already set a + // different one keeps opening their files with it. Making the account + // passphrase the ACTUAL key would be a migration with permanent data loss + // on the far side of any mistake, and it is not what this fixes. Text( - "Files on this phone are encrypted with a passphrase of their own. " - + "Flamenet doesn't keep it, so nobody can open them by picking up " - + "your unlocked phone.", + "Use your Flamenet passphrase — the one you sign in with. Files on " + + "this phone are encrypted with it, and Flamenet keeps no copy, so " + + "nobody can open them by picking up your unlocked phone.", fontSize = 14.sp, color = Palette.SecondaryText, ) Spacer(Modifier.height(Space.sm)) @@ -134,7 +146,7 @@ private fun ColumnScope.FilesSetUp( OutlinedTextField( value = passphrase, onValueChange = { passphrase = it }, - label = { Text("Choose a passphrase") }, + label = { Text("Your Flamenet passphrase") }, visualTransformation = PasswordVisualTransformation(), singleLine = true, modifier = Modifier.fillMaxWidth().testTag("files-new-passphrase"), @@ -168,7 +180,10 @@ private fun ColumnScope.FilesUnlock( ) { var passphrase by remember { mutableStateOf("") } - Text("Enter your Files passphrase to open them.", + // ⚠️ Not "your Files passphrase". That name implied a second secret even + // for somebody who had used their Flamenet one, and left them hunting for + // a passphrase they never set. + Text("Enter your Flamenet passphrase to open them.", fontSize = 14.sp, color = Palette.SecondaryText) Spacer(Modifier.height(Space.md)) OutlinedTextField( diff --git a/app/src/test/kotlin/io/flamenet/messenger/app/FilesUsesTheAccountPassphraseTest.kt b/app/src/test/kotlin/io/flamenet/messenger/app/FilesUsesTheAccountPassphraseTest.kt new file mode 100644 index 0000000..30e666e --- /dev/null +++ b/app/src/test/kotlin/io/flamenet/messenger/app/FilesUsesTheAccountPassphraseTest.kt @@ -0,0 +1,74 @@ +package io.flamenet.messenger.app + +import java.io.File +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Files asks for the Flamenet passphrase, not a second secret. + * + * 🔴 **The screen used to say the files were encrypted "with a passphrase of + * their own", and iOS never said anything like it.** iOS says "your + * passphrase" and takes whatever is typed, so people use their Flamenet one + * and it works. Android's copy sent them off to invent something else — and + * forgetting it is a one-way door: the label keys are derived from the + * identity it protects and exist nowhere else, so every vaulted file becomes + * permanently unreadable. + * + * ⚠️ **This is about the words, and the words are the whole feature here.** + * Nothing in the derivation changed: it is still a free-choice string + * protecting the identity record, so anyone who already set a different one + * keeps opening their files with it. A guard on the copy is therefore the only + * guard there can be. + */ +class FilesUsesTheAccountPassphraseTest { + + private fun source(name: String): String { + var dir = File(System.getProperty("user.dir")) + while (!File(dir, "app/src/main/kotlin").isDirectory && dir.parentFile != null) { + dir = dir.parentFile + } + val f = File(dir, "app/src/main/kotlin/io/flamenet/messenger/app/$name") + assertTrue(f.isFile, "could not find $name — this guard is reading nothing") + return f.readText() + } + + /** Lines that are shown, not lines that explain. */ + private fun copyLines(body: String): List = + body.lines().filterNot { + val t = it.trimStart() + t.startsWith("//") || t.startsWith("*") || t.startsWith("/*") + } + + @Test + fun `the setup screen names the Flamenet passphrase`() { + val shown = copyLines(source("FilesScreen.kt")).joinToString("\n") + assertTrue( + shown.contains("Use your Flamenet passphrase"), + "the Files setup screen no longer tells people to use the passphrase " + + "they already have, which is the whole of android's parity gap with iOS", + ) + } + + /** + * ⚠️ The exact phrase that caused it. Kept as a literal because a person + * rewording this screen should have to read the comment above the string + * and decide deliberately, not discover the consequence from a user. + */ + @Test + fun `no screen tells somebody to invent a second secret`() { + for (name in listOf("FilesScreen.kt", "ChatScreens.kt")) { + val shown = copyLines(source(name)).joinToString("\n") + assertTrue( + !shown.contains("passphrase of their own"), + "$name tells people the files have a passphrase of their own — " + + "iOS asks for the one they already have, and forgetting a " + + "second one destroys every vaulted file", + ) + assertTrue( + !shown.contains("Choose a passphrase"), + "$name still asks somebody to choose a new passphrase for Files", + ) + } + } +}