-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add key storage toggle to Encryption settings #29113
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
let setEnabledResolve: () => void; | ||
const setEnabledPromise = new Promise<void>((r) => { | ||
setEnabledResolve = r; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, you can use defer
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or Promise.withResolvers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really think this is ready to land as it is.
Although I don't have concerns from the crypto safety PoV, I do have concerns about the usability of the feature, and also the fact that this is code the crypto team will end up maintaining, and I think it needs work.
I'm particularly concerned about the fuzziness around what "enabled" means as regards key storage -- I think the code is making assumptions that do not hold.
Generally though, I'm concerned that this PR is too large to give a meaningful review to. I would urge you to find ways to shave bits off of it so that we can reduce the amount of state we need to keep in our heads at once.
Some random ideas might include:
- introducing DestructiveComponent separately
- making DeviceListener dependent on
m.org.matrix.custom.backup_disabled
separately - making the KeyStoragePanel read-only at first
), | ||
})} | ||
> | ||
<Root className="mx_KeyBackupPanel_toggleRow"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't the class here be about KeyStoragePanel
rather than KeyBackupPanel
?
test("should warn before turning off key storage", { tag: "@screenshot" }, async ({ page, app, util }) => { | ||
await verifySession(app, recoveryKey.encodedPrivateKey); | ||
await util.openEncryptionTab(); | ||
|
||
await page.getByRole("checkbox", { name: "Allow key storage" }).click(); | ||
|
||
await expect( | ||
page.getByRole("heading", { name: "Are you sure you want to turn off key storage and delete it?" }), | ||
).toBeVisible(); | ||
|
||
await expect(util.getEncryptionTabContent()).toMatchScreenshot("delete-key-storage-confirm.png"); | ||
|
||
await page.getByRole("button", { name: "Delete key storage" }).click(); | ||
|
||
await expect(page.getByRole("checkbox", { name: "Allow key storage" })).not.toBeChecked(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be good if we confirmed that the switch actually worked, rather than just exposing some nice UI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion on this, but having a .pcss
file which doesn't correspond to a specific component seems like a deviation from our normal way of doing things.
Wouldn't the normal way be to have a DestructiveEncryptionCard
or something?
* If the user needs to set up the encryption, the state will be set to "set_up_encryption". | ||
* If the user secrets are not cached, the state will be set to "secrets_not_cached". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment is now apparently incorrect?
@@ -124,6 +144,7 @@ export function EncryptionUserSettingsTab({ initialState = "loading" }: Encrypti | |||
* Hook to check if the user needs: | |||
* - to go through the SetupEncryption flow. | |||
* - to enter their recovery key, if the secrets are not cached locally. | |||
* ...and also whether key backup is enabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what, exactly, does "enabled" mean here? Is it the same or different to m.org.matrix.custom.backup_disabled
?
And are you sure that checking backupInfo?.version
matches that definition?
// the user just hasn't set up 4S yet: prompt them to do so | ||
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY); | ||
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to backups) | ||
const disabledEvent = cli.getAccountData("m.org.matrix.custom.backup_disabled"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we add a constant somewhere for this name, so that we don't need to hardcode it in 15 different places?
@@ -318,8 +318,11 @@ export default class DeviceListener { | |||
// prompt the user to enter their recovery key. | |||
showSetupEncryptionToast(SetupKind.KEY_STORAGE_OUT_OF_SYNC); | |||
} else if (defaultKeyId === null) { | |||
// the user just hasn't set up 4S yet: prompt them to do so | |||
showSetupEncryptionToast(SetupKind.SET_UP_RECOVERY); | |||
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to backups) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to backups) | |
// the user just hasn't set up 4S yet: prompt them to do so (unless they've explicitly said no to key storage) |
// Recheck the status if this account data has been updated as this implies it has changed | ||
if (type === "m.org.matrix.custom.backup_disabled") { | ||
checkEncryptionState(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why specifically this, rather than any of the other things that could cause the initial checks to be invalidated?
const type = event.getType(); | ||
// Recheck the status if this account data has been updated as this implies it has changed | ||
if (type === "m.org.matrix.custom.backup_disabled") { | ||
checkEncryptionState(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
won't this reset any flow that the user is in the middle of, causing a computer->window incident?
// also turn off 4S, since this is also storing keys on the server. | ||
// Delete the cross signing keys from secret storage | ||
await matrixClient.deleteAccountData("m.cross_signing.master"); | ||
await matrixClient.deleteAccountData("m.cross_signing.self_signing"); | ||
await matrixClient.deleteAccountData("m.cross_signing.user_signing"); | ||
// and the key backup key (we just turned it off anyway) | ||
await matrixClient.deleteAccountData("m.megolm_backup.v1"); | ||
|
||
// Delete the key information | ||
const defaultKey = await matrixClient.secretStorage.getDefaultKeyId(); | ||
if (defaultKey) { | ||
await matrixClient.deleteAccountData(`m.secret_storage.key.${defaultKey}`); | ||
|
||
// ...and the default key pointer | ||
await matrixClient.deleteAccountData("m.secret_storage.default_key"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to disable dehydration and delete the dehydrated device key here too.
This whole thing could usefully become crypto.deleteSecretStorage
.
mocked(useKeyStoragePanelViewModel).mockReturnValue({ | ||
setEnabled, | ||
isEnabled: true, | ||
loading: false, | ||
busy: false, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will carry over between tests: please can you jest.restoreAllMocks
in an afterEach
?
}); | ||
|
||
it("should call resetKeyBackup if there is no backup currently", async () => { | ||
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, please remember to restoreAllMocks
Closing in order to re-introduce as individual PRs. |
For links: this was part of #26468 |
Requires matrix-org/matrix-js-sdk#4661
Checklist
public
/exported
symbols have accurate TSDoc documentation.