Skip to content

OlmMachine constantly tries to upload keys when restoring session #5200

@MadLittleMods

Description

@MadLittleMods

Background

Here is how the code works:

  1. The Client continually asks OlmMachine.outgoing_requests() for requests to send (including OlmMachine.keys_for_upload(...)) and translates those AnyOutgoingRequest to actual outgoing requests
  2. When it gets a response, it should be calling OlmMachine.mark_request_as_sent(...) which is happening for Client.keys_upload(...)
  3. This trickles down as OlmMachine.mark_request_as_sent(...) -> OlmMachine.receive_keys_upload_response(...) -> Account.receive_keys_upload_response(...) -> which calls Account.mark_as_shared(), Account.mark_keys_as_published(), Account.update_key_counts(...)
  4. Then the next time the OlmMachine asks for the OlmMachine.keys_for_upload(...) request, it sees that the keys are already shared and returns None so we stop uploading more keys.

For new sessions, things work properly and Client.keys_upload(...) gets a 200 response and everything gets called as expected.

Problem:

But for existing sessions where we restore_session(...), Client.keys_upload(...) gets a 400 (keys already exists) error response from the homeserver because it's already uploaded those one-time-keys before (OTK) and mark_request_as_sent(...) never gets called and the keys are never marked as shared. So then we try the exact same thing on the next cycle of outgoing requests and get the same result every time. To be clear, we try to upload device_keys, one_time_keys, and fallback_keys continuously because nothing is marked as shared.

Synapse logs:

Shows off the 400 error response from /_matrix/client/v3/keys/upload: One time key signed_curve25519:AAAAAAAAAA0 already exists

2025-06-09 15:12:07,526 - synapse.rest.client.keys - 146 - INFO - POST-14 - asdf /keys/upload: user_id=@test-user-18-device-list-update2:my.synapse.linux.server, device_id=EYDNWNNLLA
2025-06-09 15:12:07,526 - synapse.handlers.e2e_keys - 913 - INFO - POST-14 - Updating device_keys for device 'EYDNWNNLLA' for user @test-user-18-device-list-update2:my.synapse.linux.server at 1749499927526
2025-06-09 15:12:07,531 - synapse.handlers.e2e_keys - 950 - INFO - POST-14- - Adding one_time_keys dict_keys(['signed_curve25519:AAAAAAAAAA0', 'signed_curve25519:AAAAAAAAAA4', 'signed_curve25519:AAAAAAAAAA8', 'signed_curve25519:AAAAAAAAAAA', 'signed_curve25519:AAAAAAAAAAE', 'signed_curve25519:AAAAAAAAAAI', 'signed_curve25519:AAAAAAAAAAM', 'signed_curve25519:AAAAAAAAAAQ', 'signed_curve25519:AAAAAAAAAAU', 'signed_curve25519:AAAAAAAAAAY', 'signed_curve25519:AAAAAAAAAAc', 'signed_curve25519:AAAAAAAAAAg', 'signed_curve25519:AAAAAAAAAAk', 'signed_curve25519:AAAAAAAAAAo', 'signed_curve25519:AAAAAAAAAAs', 'signed_curve25519:AAAAAAAAAAw', 'signed_curve25519:AAAAAAAAAB0', 'signed_curve25519:AAAAAAAAAB4', 'signed_curve25519:AAAAAAAAAB8', 'signed_curve25519:AAAAAAAAABA', 'signed_curve25519:AAAAAAAAABE', 'signed_curve25519:AAAAAAAAABI', 'signed_curve25519:AAAAAAAAABM', 'signed_curve25519:AAAAAAAAABQ', 'signed_curve25519:AAAAAAAAABU', 'signed_curve25519:AAAAAAAAABY', 'signed_curve25519:AAAAAAAAABc', 'signed_curve25519:AAAAAAAAABg', 'signed_curve25519:AAAAAAAAABk', 'signed_curve25519:AAAAAAAAABo', 'signed_curve25519:AAAAAAAAABs', 'signed_curve25519:AAAAAAAAABw', 'signed_curve25519:AAAAAAAAAC0', 'signed_curve25519:AAAAAAAAAC4', 'signed_curve25519:AAAAAAAAAC8', 'signed_curve25519:AAAAAAAAACA', 'signed_curve25519:AAAAAAAAACE', 'signed_curve25519:AAAAAAAAACI', 'signed_curve25519:AAAAAAAAACM', 'signed_curve25519:AAAAAAAAACQ', 'signed_curve25519:AAAAAAAAACU', 'signed_curve25519:AAAAAAAAACY', 'signed_curve25519:AAAAAAAAACc', 'signed_curve25519:AAAAAAAAACg', 'signed_curve25519:AAAAAAAAACk', 'signed_curve25519:AAAAAAAAACo', 'signed_curve25519:AAAAAAAAACs', 'signed_curve25519:AAAAAAAAACw', 'signed_curve25519:AAAAAAAAADA', 'signed_curve25519:AAAAAAAAADE']) for device 'EYDNWNNLLA' for user '@test-user-18-device-list-update2:my.synapse.linux.server' at 1749499927526
2025-06-09 15:12:07,536 - synapse.http.server - 130 - INFO - POST-14 - <XForwardedForRequest at 0x7f62fcf53890 method='POST' uri='/_matrix/client/v3/keys/upload' clientproto='HTTP/1.1' site='8008'> SynapseError: 400 - One time key signed_curve25519:AAAAAAAAAA0 already exists. Old key: {"key":"EGcRB4WrbDaRXEpHTBLmh0oygnGuWv82dzUL+uFNTTk","signatures":{"@test-user-18-device-list-update2:my.synapse.linux.server":{"ed25519:EYDNWNNLLA":"RxxsfpZpbtMdlC4UUrmTpCSHVvOXCDxd/nnkF1uRG9LAzWDciEv2WR+IGGKpTWwTmdx9LdmVU6JTNmDwYQrPDA"}}}; new key: {'key': '9lY+QpfmqmNv5+8jfb3qbgLyEhyV7SlUtp7VNm0Nc0I', 'signatures': {'@test-user-18-device-list-update2:my.synapse.linux.server': {'ed25519:EYDNWNNLLA': 'rpY9uVdqMN9GUPS50eWDidUq9foCQFvR4eB/8GSEb/qSxVHrKv1JEdsao6M0EArh/xrkx0wUmnDEB2aKgMfrBw'}}}
2025-06-09 15:12:07,537 - synapse.access.http.8008 - 508 - INFO - POST-14 - 127.0.0.1 - 8008 - {@test-user-18-device-list-update2:my.synapse.linux.server} Processed request: 0.011sec/0.000sec (0.001sec, 0.000sec) (0.000sec/0.008sec/4) 610B 400 "POST /_matrix/client/v3/keys/upload HTTP/1.1" "-" [0 dbevts]

Reproduction case

The example just uses the matrix-sdk to create a client, matrix_client.restore_session(...) if available (which means the first-run will just login/register the user), and setup a /sync loop, and keep it running until you press Enter.

main.rs
use std::time::Duration;

use anyhow::Context;
use matrix_sdk::reqwest;
use matrix_sdk::{LoopCtrl, config::SyncSettings, ruma::api::client::uiaa};
use serde::Deserialize;
use tokio_util::sync::CancellationToken;
use url::Url;

mod register;

/// Configuration for a given homeserver
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Homeserver {
    /// Homeserver name. Must be unique.
    ///
    /// Users on this homeserver should have MXID's like `@user:server_name`
    pub server_name: String,

    /// URL to access the client API.
    pub client_url: Url,

    /// Method to use to register new users
    pub user_registration: register::UserRegistration,

    /// Password to use for all users.
    pub user_password: String,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let use_existing_sessions = true;

    let homeserver = Homeserver {
        server_name: "my.synapse.server".to_string(),
        client_url: Url::parse("http://localhost:8008/").context("Invalid client URL")?,
        user_registration: register::UserRegistration::RegistrationSharedSecret(
            "xxx".to_string(),
        ),
        user_password: "secret_password".to_string(),
    };
    let user_id = format!(
        "@test-user-matrix-rust-sdk-key-upload1:{}",
        homeserver.server_name
    );

    let http_client = reqwest::Client::builder()
        .timeout(Duration::from_secs(30))
        .build()
        .context("Failed to build HTTP client")?;

    let matrix_client = matrix_sdk::Client::builder()
        .http_client(http_client.clone())
        .homeserver_url(&homeserver.client_url)
        .build()
        .await
        .context("Couldn't create matrix client")?;

    let session_path_string = format!("sessions/{}/{}.json", homeserver.server_name, user_id);
    let session_path = std::path::Path::new(&session_path_string);

    let (new_user, new_session) =
        match (use_existing_sessions, std::fs::read_to_string(session_path)) {
            (true, Ok(serialized_session)) => {
                // This is added to avoid type inference issues: `match the size for
                // values of type `str` cannot be known at compilation time`
                let serialized_session: String = serialized_session;

                // Try restoring from a previously persisted session first
                let session: matrix_sdk::authentication::matrix::MatrixSession =
                    serde_json::from_str(&serialized_session)?;
                matrix_client
                    .restore_session(session)
                    .await
                    .context("Failed restoring session for user")?;
                println!("Restored user session");

                (false, false)
            }
            _ => {
                println!("Registering user");
                // Register a user or login if the user already exists.
                let new_user = register::login_or_register_user(
                    http_client,
                    &matrix_client,
                    homeserver.user_registration.clone(),
                    user_id,
                    homeserver.user_password.clone(),
                )
                .await
                .context("Failed registering user")?;

                (new_user, true)
            }
        };

    // Ensure the user is logged in at this point
    let user_id = matrix_client
        .user_id()
        .context("Client is not logged in")?
        .to_owned();

    // Persist the session for future runs
    if let Some(session) = matrix_client.session() {
        match session {
            // We should get a `MatrixSession` because we're using the
            // "native Matrix authentication API".
            matrix_sdk::authentication::AuthSession::Matrix(matrix_session) => {
                let serialized_session = serde_json::to_string(&matrix_session)?;
                if let Some(session_path_directory) = session_path.parent() {
                    std::fs::create_dir_all(session_path_directory)?;
                }
                std::fs::write(session_path, serialized_session)?;
            }
            matrix_sdk::authentication::AuthSession::OAuth(_oauth_session) => {
                return Err(anyhow::anyhow!(
                    "Unable to persist OAuth session (not supported and unexpected)"
                ));
            }
            // This enum is non-exhaustive so we have to handle the `_` case
            // but it's not expected.
            _ => {
                return Err(anyhow::anyhow!(
                    "Unable to persist unknown session type (not supported): {:?}",
                    session
                ));
            }
        }
    } else {
        return Err(anyhow::anyhow!(
            "Client should be logged in by this point but has no session"
        ));
    }

    let cancelation_token = CancellationToken::new();

    // We need to bootstrap cross-signing for any new login session because each new
    // login creates a new device.
    if new_session {
        println!("Bootstrapping cross signing");
        if let Err(e) = matrix_client
            .encryption()
            .bootstrap_cross_signing_if_needed(None)
            .await
        {
            if let Some(response) = e.as_uiaa_response() {
                let mut password = uiaa::Password::new(
                    user_id.clone().into(),
                    homeserver.user_password.clone().to_owned(),
                );
                password.session.clone_from(&response.session);

                matrix_client
                    .encryption()
                    .bootstrap_cross_signing_if_needed(Some(uiaa::AuthData::Password(password)))
                    .await
                    .context("Couldn't bootstrap cross signing")?;
            } else {
                return Err(e).context("Couldn't bootstrap cross signing");
            }
        }
    }

    // Spawn the sync loop in the background with a cancellation token
    //
    // We care about syncing to mimic the real-world use case of a client listening
    // for new messages.
    println!("Starting sync loop");
    let sync_handle = tokio::spawn({
        let cancelation_token = cancelation_token.child_token();
        let client = matrix_client.clone();
        async move {
            client
                .sync_with_callback(SyncSettings::default(), |_| {
                    if cancelation_token.is_cancelled() {
                        std::future::ready(LoopCtrl::Break)
                    } else {
                        std::future::ready(LoopCtrl::Continue)
                    }
                })
                .await?;

            anyhow::Ok(())
        }
    });

    println!("Press Enter to continue...");
    let mut input = String::new();
    std::io::stdin()
        .read_line(&mut input)
        .expect("Failed to read line");

    // Stop all the agents
    cancelation_token.cancel();

    // Wait for the sync loop to finish
    if let Err(err) = sync_handle.await {
        println!("Something went wrong while waiting for the sync loop: {err}");
    }

    Ok(())
}
register.rs
use anyhow::{Context, ensure};
use hmac::{Hmac, Mac};
use http::{StatusCode, header};
use matrix_sdk::reqwest;
use matrix_sdk::ruma::api::client::{
    account::{check_registration_token_validity, register::v3::Request as RegistrationRequest},
    uiaa,
};
use serde::{Deserialize, Serialize};
use sha1::Sha1;

/// Method to use to register new users
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum UserRegistration {
    /// Synapse registration token
    ///
    /// Created via the Synapse admin API: `POST /_synapse/admin/v1/registration_tokens/new`.
    /// See https://github.com/element-hq/synapse/blob/develop/docs/usage/administration/admin_api/registration_tokens.md#create-token
    #[serde(rename = "admin_registration_token")]
    AdminRegistrationToken(String),

    /// Shared-Secret Registration
    ///
    /// As configured in the Synapse homeserver configuration `registration_shared_secret`
    #[serde(rename = "registration_shared_secret")]
    RegistrationSharedSecret(String),
}

/// `GET /_synapse/admin/v1/register`
#[derive(Deserialize, Debug)]
pub struct SharedSecretRegistrationNonceResponse {
    pub nonce: String,
}

#[allow(dead_code)]
pub enum UserType {
    /// Normal user
    Normal,
    /// Special designation in Synapse (see their docs)
    Bot,
    /// Special designation in Synapse (see their docs)
    Support,
}

/// Information necessary for a `POST /_synapse/admin/v1/register` request
pub struct SharedSecretRegistrationRequest {
    /// The `registgration_shared_secret` configured in Synapse
    pub registration_shared_secret: String,
    pub nonce: String,
    pub username: String,
    pub displayname: String,
    pub password: String,
    pub admin: bool,
    pub user_type: UserType,
}

impl SharedSecretRegistrationRequest {
    /// Serialize the request body to a JSON string
    fn to_request_body(&self) -> anyhow::Result<SharedSecretRegistrationRequestBody> {
        Ok(SharedSecretRegistrationRequestBody {
            nonce: self.nonce.clone(),
            username: self.username.clone(),
            displayname: self.displayname.clone(),
            password: self.password.clone(),
            admin: self.admin,
            mac: self.generate_mac_digest_for_registration()?,
        })
    }

    /// Used for Shared-Secret Registration (`POST /_synapse/admin/v1/register`)
    ///
    /// Args:
    fn generate_mac_digest_for_registration(&self) -> anyhow::Result<String> {
        let mut mac = Hmac::<Sha1>::new_from_slice(self.registration_shared_secret.as_bytes())?;

        mac.update(self.nonce.as_bytes());
        mac.update(b"\0");
        mac.update(self.username.as_bytes());
        mac.update(b"\0");
        mac.update(self.password.as_bytes());
        mac.update(b"\0");
        mac.update(if self.admin { b"admin" } else { b"notadmin" });

        match self.user_type {
            UserType::Normal => (),
            UserType::Bot => {
                mac.update(b"\0");
                mac.update("bot".as_bytes());
            }
            UserType::Support => {
                mac.update(b"\0");
                mac.update("support".as_bytes());
            }
        };

        let result = mac.finalize();
        let code_bytes = result.into_bytes();
        Ok(hex::encode(code_bytes))
    }
}

/// Serialized request body for a `POST /_synapse/admin/v1/register` request
#[derive(Serialize, Deserialize, Debug)]
pub struct SharedSecretRegistrationRequestBody {
    pub nonce: String,
    pub username: String,
    pub displayname: String,
    pub password: String,
    pub admin: bool,
    pub mac: String,
}

/// `POST /_synapse/admin/v1/register`
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
pub struct SharedSecretRegistrationResponse {
    pub access_token: String,
    pub user_id: String,
    pub home_server: String,
    pub device_id: String,
}

/// Register a user or login if the user already exists.
///
/// Returns whether the user was newly registered or not.
pub async fn login_or_register_user(
    http_client: reqwest::Client,
    matrix_client: &matrix_sdk::Client,
    user_registration_method: UserRegistration,
    user_id: String,
    password: String,
) -> anyhow::Result<bool> {
    let user_id = matrix_sdk::ruma::UserId::parse(user_id)?;
    let username = user_id.localpart().to_owned();
    let server_name = user_id.server_name().to_owned();

    // Sanity check that the matrix_client is for the correct server when possible.
    if let Some(matrix_client_server_name) = matrix_client.server() {
        ensure!(
            server_name == matrix_client_server_name.to_string(),
            "Server name in user_id ({}) doesn't match `matrix_client` server name ({})",
            user_id,
            server_name,
        );
    }

    let new_user = match user_registration_method {
        UserRegistration::AdminRegistrationToken(admin_registration_token) => {
            let request = check_registration_token_validity::v1::Request::new(
                admin_registration_token.clone(),
            );
            let is_registration_token_valid = matrix_client.send(request).await?;
            println!(
                "Registration token valid? {}",
                is_registration_token_valid.valid,
            );
            if !is_registration_token_valid.valid {
                return Err(anyhow::anyhow!(
                    "`registration_token` for homeserver ({:?}) is invalid",
                    server_name
                ));
            }

            let mut request: RegistrationRequest = RegistrationRequest::new();

            request.username = Some(username.clone());
            request.password = Some(password.to_owned());
            request.auth = Some(uiaa::AuthData::Password(uiaa::Password::new(
                uiaa::UserIdentifier::UserIdOrLocalpart(username.clone()),
                password.clone(),
            )));

            //matrix_client.send(Requ)
            match matrix_client.matrix_auth().register(request).await {
                Ok(_response) => true,
                Err(err) => {
                    if let Some(uiaa_info) = err.as_uiaa_response() {
                        let session = uiaa_info.session.clone();
                        println!("Failed registering with session {:?}: {:?}", session, err,);
                        let mut request = RegistrationRequest::new();

                        request.username = Some(username.clone());
                        request.password = Some(password.clone().to_owned());
                        let mut reg_token =
                            uiaa::RegistrationToken::new(admin_registration_token.clone());
                        reg_token.session = session.clone();
                        request.auth = Some(uiaa::AuthData::RegistrationToken(reg_token));
                        match matrix_client
                            .matrix_auth()
                            .register(request)
                            .await
                            .context("Failed to register user")
                        {
                            Ok(_response) => true,
                            Err(_err) => {
                                let mut request = RegistrationRequest::new();

                                request.username = Some(username.clone());
                                request.password = Some(password.clone().to_owned());
                                let mut password_register = uiaa::Dummy::new();
                                password_register.session = session;
                                request.auth = Some(uiaa::AuthData::Dummy(password_register));
                                match matrix_client
                                    .matrix_auth()
                                    .register(request)
                                    .await
                                    .context("Failed to register user")
                                {
                                    Ok(_response) => true,
                                    Err(_err) => {
                                        println!(
                                            "Failed registering user, attempting to login as existing user.",
                                        );
                                        matrix_client
                                            .matrix_auth()
                                            .login_username(username, &password.clone())
                                            .await
                                            .context("Failed logging in user")?;
                                        false
                                    }
                                }
                            }
                        }
                    } else {
                        println!(
                            "Failed getting session for user registration, attempting to login as existing user.",
                        );
                        matrix_client
                            .matrix_auth()
                            .login_username(username, &password.clone())
                            .await
                            .context("Failed logging in user")?;
                        false
                    }
                }
            }
        }
        UserRegistration::RegistrationSharedSecret(registration_shared_secret) => {
            let admin_register_url = matrix_client
                .homeserver()
                .join("/_synapse/admin/v1/register")?;

            let nonce_response = http_client
                .get(admin_register_url.clone())
                .send()
                .await?
                .error_for_status()?
                .json::<SharedSecretRegistrationNonceResponse>()
                .await?;

            let serialized_registration_request_body = serde_json::to_string(
                &SharedSecretRegistrationRequest {
                    registration_shared_secret,
                    nonce: nonce_response.nonce,
                    username: username.clone(),
                    displayname: username.clone(),
                    password: password.to_owned(),
                    admin: false,
                    user_type: UserType::Normal,
                }
                .to_request_body()?,
            )?;
            let registration_response = http_client
                .post(admin_register_url.clone())
                .header(header::CONTENT_TYPE, "application/json")
                .body(serialized_registration_request_body.clone())
                .send()
                .await?;

            let status = registration_response.status();
            match status {
                StatusCode::OK => {
                    let registration_response: SharedSecretRegistrationResponse =
                        registration_response.json().await?;

                    // Sanity check
                    ensure!(
                        registration_response.home_server == server_name,
                        "`home_server` ({}) in response doesn't match our expected server_name ({})",
                        registration_response.home_server,
                        server_name,
                    );
                    ensure!(
                        registration_response.user_id == user_id,
                        "`user_id` in response ({}) doesn't match expected user ID ({})",
                        registration_response.user_id,
                        user_id,
                    );

                    // Login the user for the `matrix_client`
                    matrix_client
                        .matrix_auth()
                        .login_username(username, &password.clone())
                        .await
                        .context("Failed logging in user")?;

                    // New user
                    true
                }
                StatusCode::BAD_REQUEST => {
                    let error_response: matrix_sdk::ruma::api::client::error::StandardErrorBody =
                        registration_response.json().await?;

                    match error_response.kind {
                        matrix_sdk::ruma::api::client::error::ErrorKind::UserInUse => {
                            // Login the user for the `matrix_client`
                            println!("User already exists, logging in.");
                            matrix_client
                                .matrix_auth()
                                .login_username(username, &password.clone())
                                .await
                                .context("Failed logging in user")?;

                            // Not a new user
                            false
                        }
                        _ => {
                            return Err(anyhow::anyhow!(
                                "Expected registration (POST {} with {}) to respond successfully (or `{}`) but encountered {} -> {:?}",
                                admin_register_url,
                                // Note: This does log senstive information but it is useful
                                // for debugging. We're assuming test users aren't important
                                // and/or in a test environment.
                                serialized_registration_request_body,
                                matrix_sdk::ruma::api::client::error::ErrorCode::UserInUse,
                                StatusCode::BAD_REQUEST,
                                error_response,
                            ));
                        }
                    }
                }
                status_code => {
                    return Err(anyhow::anyhow!(
                        "Expected registration (POST {} with {}) to respond successfully (or `{}`) but encountered {} -> {}",
                        admin_register_url,
                        // Note: This does log senstive information but it is useful
                        // for debugging. We're assuming test users aren't important
                        // and/or in a test environment.
                        serialized_registration_request_body,
                        matrix_sdk::ruma::api::client::error::ErrorCode::UserInUse,
                        status_code,
                        registration_response
                            .text()
                            .await
                            .unwrap_or("<unable to get response body>".to_string()),
                    ));
                }
            }
        }
    };

    Ok(new_user)
}
Cargo.toml
[package]
name = "matrix-sdk-keys-upload"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1.0.86"
hex = "0.4.3"
hmac = "0.12.1"
http = "1.3.1"
matrix-sdk = { version = "0.11.0", features = ["anyhow"] }
# matrix-sdk = { path = "/home/eric/Documents/github/element/matrix-rust-sdk/crates/matrix-sdk/", features = [
#   "anyhow",
# ] }
reqwest = { version = "0.12.15", default-features = false, features = ["json"] }
serde = { version = "1.0.202", features = ["derive", "rc"] }
serde_json = "1.0.140"
sha1 = "0.10.6"
tokio = { version = "1.37.0", features = ["full"] }
tokio-util = "0.7.11"
url = "2.5.4"

Workaround

Currently, it seems like the matrix-sdk assumes you're defining a client store_config. Things appear to work once you start using one but the Rust SDK should gracefully handle key conflicts without this.

    let matrix_client = matrix_sdk::Client::builder()
        .http_client(http_client.clone())
        .homeserver_url(&homeserver.client_url)
        // Works when a client store is defined (`indexeddb_store()` and `store_config()` also available)
        .sqlite_store(client_session_store_path, None)
        .build()
        .await
        .context("Couldn't create matrix client")?;

See examples/persist_session/src/main.rs as a full example.

Definition of done

Tasks that have spawned from the discussion below:

  • Update doc comments for Client::restore_session() to point out the need to also setup persistent storage on the client
  • Update Restoring a client section in the encryption.md docs on to more explicitly point out the need to setup persistent storage on the client
  • Detect when the persistent storage isn't configured (i.e. no keys in the crypto storage) and someone uses Client::restore_session(); make sure error is thrown when the e2e-encryption feature is enabled.
  • (needs buy-in): Detect key conflict on /_matrix/client/v3/keys/upload scenarios and back-off on trying to upload keys (this is a last resort as usually we should be able to detect a mismatch between the device ID from user session/CryptoStore and throw an error then instead)
  • (needs buy-in): Better API (still being discussed)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions