Add E2E encryption setup UI and room-level encryption controls #9
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overview
This PR implements end-to-end encryption features for ADHD Chat, following the Matrix E2E encryption specification. Users can now generate encryption keys through a guided setup flow and enable encryption on a per-room basis.
Changes
User-Facing Features
1. Encryption Setup Wizard
A multi-step modal flow guides users through setting up E2E encryption:
matrix-js-sdk
'screateRecoveryKeyFromPassphrase()
2. Home Page Integration
Added a dedicated "Encryption" section with:
3. Room-Level Encryption Controls
Enhanced room pages to show:
Technical Implementation
New Components:
Modal.tsx
- Reusable modal with keyboard support (ESC to close), backdrop overlay, and size variantsEncryptionSetupModal.tsx
- Multi-step wizard with validation, error handling, and clipboard integrationUpdated Components:
useMatrixClient.ts
- ModifiedhandleSetupEncryption()
to properly return the generated recovery key and handle errorsHome.tsx
- Added encryption section and modal integrationRoom.tsx
- Added encryption status checking and enablement viam.room.encryption
state eventsMatrix SDK Integration:
Follows matrix-js-sdk best practices for E2E encryption:
initRustCrypto()
initializationbootstrapSecretStorage()
with recovery key generationbootstrapCrossSigning()
for device verificationresetKeyBackup()
m.room.encryption
state events withm.megolm.v1.aes-sha2
algorithmSecurity Considerations
Screenshots
Login Page
E2E Encryption Features
The screenshot above demonstrates all encryption UI components including the setup wizard flow (generate, display, verify, success) and room encryption controls.
Documentation
Added comprehensive
ENCRYPTION_FEATURES.md
documenting:Testing
References
Fixes #[issue_number]
Original prompt
This section details on the original issue you should resolve
<issue_title>E2E encryption</issue_title>
<issue_description>Enable e2e encryption
Guide here: https://matrix.org/docs/matrix-concepts/end-to-end-encryption/
Use the matrix-js-sdk to allow the user to enable e2e encryption for their device.
A user should have an option to enable e2e encryption in specific rooms.
A user should be able to click a button "generate encryption key". This should open a modal where a user will be presented their encryption keys, be told to save them somewhere safe, then "continue" where then will then be asked to re-enter their encryption key.
Be sure to reference the guide from matrix-js-sdk (the following is copy and paste from their readme).
End-to-end encryption support
matrix-js-sdk's end-to-end encryption support is based on the WebAssembly bindings of the Rust matrix-sdk-crypto library.
Initialization
To initialize the end-to-end encryption support in the matrix client:
// Create a new matrix client
const matrixClient = sdk.createClient({
baseUrl: "http://localhost:8008",
accessToken: myAccessToken,
userId: myUserId,
});
// Initialize to enable end-to-end encryption support.
await matrixClient.initRustCrypto();
Note that by default it will attempt to use the Indexed DB provided by the browser as a crypto store. If running outside the browser, you will need to pass an options object which includes useIndexedDB: false, to use an ephemeral in-memory store instead. Note that without a persistent store, you'll need to create a new device on the server side (with MatrixClient.loginRequest) each time your application starts.
After calling initRustCrypto, you can obtain a reference to the CryptoApi interface, which is the main entry point for end-to-end encryption, by calling MatrixClient.getCrypto.
WARNING: the cryptography stack is not thread-safe. Having multiple MatrixClient instances connected to the same Indexed DB will cause data corruption and decryption failures. The application layer is responsible for ensuring that only one MatrixClient issue is instantiated at a time.
Secret storage
You should normally set up secret storage before using the end-to-end encryption. To do this, call CryptoApi.bootstrapSecretStorage. bootstrapSecretStorage can be called unconditionally: it will only set up the secret storage if it is not already set up (unless you use the setupNewSecretStorage parameter).
const matrixClient = sdk.createClient({
...,
cryptoCallbacks: {
getSecretStorageKey: async (keys) => {
// This function should prompt the user to enter their secret storage key.
return mySecretStorageKeys;
},
},
});
matrixClient.getCrypto().bootstrapSecretStorage({
// This function will be called if a new secret storage key (aka recovery key) is needed.
// You should prompt the user to save the key somewhere, because they will need it to unlock secret storage in future.
createSecretStorageKey: async () => {
return mySecretStorageKey;
},
});
The example above will create a new secret storage key if secret storage was not previously set up. The secret storage data will be encrypted using the secret storage key returned in createSecretStorageKey.
We recommend that you prompt the user to re-enter this key when CryptoCallbacks.getSecretStorageKey is called (when the secret storage access is needed).
Set up cross-signing
To set up cross-signing to verify devices and other users, call CryptoApi.bootstrapCrossSigning:
matrixClient.getCrypto().bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
return makeRequest(authDict);
},
});
The authUploadDeviceSigningKeys callback is required in order to upload newly-generated public cross-signing keys to the server.
Ke...
Fixes #8
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.