atagen

macltok

public
Star0
Watch0
411bc1c78b65ac2d29c0a44b166993a7e76f1663
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/account.rs

62fe32759529atagen2026-09-26 08:50 UTC1
#[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC2
use std::path::PathBuf;
f12377bce9b9atagen2026-09-26 12:32 UTC3
#[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC4
use std::sync::atomic::{AtomicBool, Ordering};
713205a95af2atagen2026-09-26 09:24 UTC5
use std::{collections::HashMap, sync::Arc};
96fdf90aacd6atagen2026-09-08 08:24 UTC6
96fdf90aacd6atagen2026-09-08 08:24 UTC7
use matrix_sdk::{
f12377bce9b9atagen2026-09-26 12:32 UTC8
    Client, ClientBuilder, SendOutsideWasm, SessionChange, SessionMeta, SyncOutsideWasm,
f12377bce9b9atagen2026-09-26 12:32 UTC9
    ThreadingSupport,
96fdf90aacd6atagen2026-09-08 08:24 UTC10
    authentication::{
96fdf90aacd6atagen2026-09-08 08:24 UTC11
        AuthSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC12
        matrix::MatrixSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC13
        oauth::{
96fdf90aacd6atagen2026-09-08 08:24 UTC14
            ClientId, ClientRegistrationData, CsrfToken, OAuthSession, UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC15
            registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
96fdf90aacd6atagen2026-09-08 08:24 UTC16
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC17
    },
62fe32759529atagen2026-09-26 08:50 UTC18
    cross_process_lock::CrossProcessLockConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC19
    encryption::{BackupDownloadStrategy, EncryptionSettings},
6f1d6591c10datagen2026-09-26 09:52 UTC20
    ruma::{OwnedDeviceId, OwnedUserId, serde::Raw},
96fdf90aacd6atagen2026-09-08 08:24 UTC21
};
96fdf90aacd6atagen2026-09-08 08:24 UTC22
use serde::{Deserialize, Serialize};
96fdf90aacd6atagen2026-09-08 08:24 UTC23
use thiserror::Error;
f12377bce9b9atagen2026-09-26 12:32 UTC24
use tokio::sync::{broadcast, mpsc};
96fdf90aacd6atagen2026-09-08 08:24 UTC25
use url::Url;
96fdf90aacd6atagen2026-09-08 08:24 UTC26
713205a95af2atagen2026-09-26 09:24 UTC27
use crate::{
9a25b6ce66f4atagen2026-10-08 11:15 UTC28
    ClientHandle, Engine, EngineConfig, EngineError, ErrorKind, EventStream, ServerError,
9a25b6ce66f4atagen2026-10-08 11:15 UTC29
    SessionStore,
a250c7fa43fbatagen2026-09-26 13:35 UTC30
    engine::{Persistence, StoreLock},
a250c7fa43fbatagen2026-09-26 13:35 UTC31
    error::SourceError,
713205a95af2atagen2026-09-26 09:24 UTC32
};
96fdf90aacd6atagen2026-09-08 08:24 UTC33
f12377bce9b9atagen2026-09-26 12:32 UTC34
/// a logged in client and the `SessionStore` its record lives in. the engine it builds
f12377bce9b9atagen2026-09-26 12:32 UTC35
/// saves every token rotation there, unless told `without_persistence`
96fdf90aacd6atagen2026-09-08 08:24 UTC36
pub struct Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC37
    client: Client,
f12377bce9b9atagen2026-09-26 12:32 UTC38
    tracking: Tracking,
a250c7fa43fbatagen2026-09-26 13:35 UTC39
    store_lock: StoreLock,
f12377bce9b9atagen2026-09-26 12:32 UTC40
}
f12377bce9b9atagen2026-09-26 12:32 UTC41
f12377bce9b9atagen2026-09-26 12:32 UTC42
/// taken as soon as the client exists, since the sdk can refresh before there's an engine
f12377bce9b9atagen2026-09-26 12:32 UTC43
struct Tracking {
f12377bce9b9atagen2026-09-26 12:32 UTC44
    session_changes: broadcast::Receiver<SessionChange>,
f12377bce9b9atagen2026-09-26 12:32 UTC45
    persistence: Option<Persistence>,
f12377bce9b9atagen2026-09-26 12:32 UTC46
    #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC47
    inline: Arc<AtomicBool>,
96fdf90aacd6atagen2026-09-08 08:24 UTC48
}
96fdf90aacd6atagen2026-09-08 08:24 UTC49
31ce2453c82fatagen2026-09-24 13:07 UTC50
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC51
pub struct PasswordLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC52
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC53
    pub username: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC54
    pub password: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC55
    pub device_display_name: Option<String>,
fa9114fcf75fatagen2026-09-24 12:51 UTC56
    /// set after a soft logout so the store's keys still belong to us
fa9114fcf75fatagen2026-09-24 12:51 UTC57
    pub device_id: Option<OwnedDeviceId>,
9c05b1dd327aatagen2026-10-08 11:27 UTC58
    /// let plain http through, for loopback hosts only
9c05b1dd327aatagen2026-10-08 11:27 UTC59
    pub allow_insecure_loopback: bool,
96fdf90aacd6atagen2026-09-08 08:24 UTC60
}
96fdf90aacd6atagen2026-09-08 08:24 UTC61
31ce2453c82fatagen2026-09-24 13:07 UTC62
impl PasswordLogin {
31ce2453c82fatagen2026-09-24 13:07 UTC63
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC64
    pub fn new(
31ce2453c82fatagen2026-09-24 13:07 UTC65
        homeserver: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC66
        username: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC67
        password: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC68
    ) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC69
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC70
            homeserver: homeserver.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC71
            username: username.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC72
            password: password.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC73
            device_display_name: None,
31ce2453c82fatagen2026-09-24 13:07 UTC74
            device_id: None,
9c05b1dd327aatagen2026-10-08 11:27 UTC75
            allow_insecure_loopback: false,
31ce2453c82fatagen2026-09-24 13:07 UTC76
        }
31ce2453c82fatagen2026-09-24 13:07 UTC77
    }
31ce2453c82fatagen2026-09-24 13:07 UTC78
31ce2453c82fatagen2026-09-24 13:07 UTC79
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC80
    pub fn with_device_display_name(mut self, name: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC81
        self.device_display_name = Some(name.into());
31ce2453c82fatagen2026-09-24 13:07 UTC82
        self
31ce2453c82fatagen2026-09-24 13:07 UTC83
    }
31ce2453c82fatagen2026-09-24 13:07 UTC84
31ce2453c82fatagen2026-09-24 13:07 UTC85
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC86
    pub fn with_device_id(mut self, device_id: OwnedDeviceId) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC87
        self.device_id = Some(device_id);
31ce2453c82fatagen2026-09-24 13:07 UTC88
        self
31ce2453c82fatagen2026-09-24 13:07 UTC89
    }
9c05b1dd327aatagen2026-10-08 11:27 UTC90
9c05b1dd327aatagen2026-10-08 11:27 UTC91
    #[must_use]
9c05b1dd327aatagen2026-10-08 11:27 UTC92
    pub fn allow_insecure_loopback(mut self, allow: bool) -> Self {
9c05b1dd327aatagen2026-10-08 11:27 UTC93
        self.allow_insecure_loopback = allow;
9c05b1dd327aatagen2026-10-08 11:27 UTC94
        self
9c05b1dd327aatagen2026-10-08 11:27 UTC95
    }
31ce2453c82fatagen2026-09-24 13:07 UTC96
}
31ce2453c82fatagen2026-09-24 13:07 UTC97
31ce2453c82fatagen2026-09-24 13:07 UTC98
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC99
pub struct OAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC100
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC101
    pub redirect_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC102
    pub client_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC103
    pub client_name: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC104
    pub client_id: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC105
    pub device_id: Option<OwnedDeviceId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC106
    pub application_type: OAuthApplicationType,
9c05b1dd327aatagen2026-10-08 11:27 UTC107
    /// let plain http through, for loopback hosts only
9c05b1dd327aatagen2026-10-08 11:27 UTC108
    pub allow_insecure_loopback: bool,
96fdf90aacd6atagen2026-09-08 08:24 UTC109
}
96fdf90aacd6atagen2026-09-08 08:24 UTC110
31ce2453c82fatagen2026-09-24 13:07 UTC111
impl OAuthLogin {
31ce2453c82fatagen2026-09-24 13:07 UTC112
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC113
    pub fn new(
31ce2453c82fatagen2026-09-24 13:07 UTC114
        homeserver: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC115
        redirect_uri: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC116
        client_uri: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC117
        application_type: OAuthApplicationType,
31ce2453c82fatagen2026-09-24 13:07 UTC118
    ) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC119
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC120
            homeserver: homeserver.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC121
            redirect_uri: redirect_uri.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC122
            client_uri: client_uri.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC123
            client_name: None,
31ce2453c82fatagen2026-09-24 13:07 UTC124
            client_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC125
            device_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC126
            application_type,
9c05b1dd327aatagen2026-10-08 11:27 UTC127
            allow_insecure_loopback: false,
31ce2453c82fatagen2026-09-24 13:07 UTC128
        }
31ce2453c82fatagen2026-09-24 13:07 UTC129
    }
31ce2453c82fatagen2026-09-24 13:07 UTC130
31ce2453c82fatagen2026-09-24 13:07 UTC131
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC132
    pub fn with_client_name(mut self, name: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC133
        self.client_name = Some(name.into());
31ce2453c82fatagen2026-09-24 13:07 UTC134
        self
31ce2453c82fatagen2026-09-24 13:07 UTC135
    }
31ce2453c82fatagen2026-09-24 13:07 UTC136
31ce2453c82fatagen2026-09-24 13:07 UTC137
    /// a statically registered client id, skipping dynamic registration
31ce2453c82fatagen2026-09-24 13:07 UTC138
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC139
    pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC140
        self.client_id = Some(client_id.into());
31ce2453c82fatagen2026-09-24 13:07 UTC141
        self
31ce2453c82fatagen2026-09-24 13:07 UTC142
    }
31ce2453c82fatagen2026-09-24 13:07 UTC143
31ce2453c82fatagen2026-09-24 13:07 UTC144
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC145
    pub fn with_device_id(mut self, device_id: OwnedDeviceId) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC146
        self.device_id = Some(device_id);
31ce2453c82fatagen2026-09-24 13:07 UTC147
        self
31ce2453c82fatagen2026-09-24 13:07 UTC148
    }
9c05b1dd327aatagen2026-10-08 11:27 UTC149
9c05b1dd327aatagen2026-10-08 11:27 UTC150
    #[must_use]
9c05b1dd327aatagen2026-10-08 11:27 UTC151
    pub fn allow_insecure_loopback(mut self, allow: bool) -> Self {
9c05b1dd327aatagen2026-10-08 11:27 UTC152
        self.allow_insecure_loopback = allow;
9c05b1dd327aatagen2026-10-08 11:27 UTC153
        self
9c05b1dd327aatagen2026-10-08 11:27 UTC154
    }
31ce2453c82fatagen2026-09-24 13:07 UTC155
}
31ce2453c82fatagen2026-09-24 13:07 UTC156
96fdf90aacd6atagen2026-09-08 08:24 UTC157
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC158
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC159
pub enum OAuthApplicationType {
96fdf90aacd6atagen2026-09-08 08:24 UTC160
    Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC161
    Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC162
}
96fdf90aacd6atagen2026-09-08 08:24 UTC163
96fdf90aacd6atagen2026-09-08 08:24 UTC164
pub struct PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC165
    client: Client,
713205a95af2atagen2026-09-26 09:24 UTC166
    sessions: Arc<dyn SessionStore>,
a250c7fa43fbatagen2026-09-26 13:35 UTC167
    tracking: Option<(Tracking, StoreLock)>,
96fdf90aacd6atagen2026-09-08 08:24 UTC168
    authorization_url: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC169
    state: Option<CsrfToken>,
96fdf90aacd6atagen2026-09-08 08:24 UTC170
}
96fdf90aacd6atagen2026-09-08 08:24 UTC171
96fdf90aacd6atagen2026-09-08 08:24 UTC172
impl PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC173
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC174
    pub fn authorization_url(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC175
        &self.authorization_url
96fdf90aacd6atagen2026-09-08 08:24 UTC176
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC177
96fdf90aacd6atagen2026-09-08 08:24 UTC178
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC179
    pub fn registered_client_id(&self) -> String {
96fdf90aacd6atagen2026-09-08 08:24 UTC180
        self.client
96fdf90aacd6atagen2026-09-08 08:24 UTC181
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC182
            .client_id()
96fdf90aacd6atagen2026-09-08 08:24 UTC183
            .expect("OAuth client was registered before authorization")
96fdf90aacd6atagen2026-09-08 08:24 UTC184
            .as_str()
96fdf90aacd6atagen2026-09-08 08:24 UTC185
            .to_owned()
96fdf90aacd6atagen2026-09-08 08:24 UTC186
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC187
96fdf90aacd6atagen2026-09-08 08:24 UTC188
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC189
    pub const fn is_pending(&self) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC190
        self.state.is_some()
96fdf90aacd6atagen2026-09-08 08:24 UTC191
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC192
96fdf90aacd6atagen2026-09-08 08:24 UTC193
    pub async fn finish(&mut self, callback_url: &str) -> Result<Session, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC194
        let callback_url = Url::parse(callback_url)
96fdf90aacd6atagen2026-09-08 08:24 UTC195
            .map_err(|error| account_error("parsing the OAuth callback URL", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC196
        let state = self
96fdf90aacd6atagen2026-09-08 08:24 UTC197
            .state
96fdf90aacd6atagen2026-09-08 08:24 UTC198
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC199
            .ok_or(AccountError::OAuthCallbackAlreadySubmitted)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC200
        let oauth = self.client.oauth();
96fdf90aacd6atagen2026-09-08 08:24 UTC201
        if let Err(error) = oauth.finish_login(callback_url.into()).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC202
            oauth.abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC203
            return Err(account_error("OAuth login", error));
96fdf90aacd6atagen2026-09-08 08:24 UTC204
        }
a250c7fa43fbatagen2026-09-26 13:35 UTC205
        let (tracking, store_lock) = self
f12377bce9b9atagen2026-09-26 12:32 UTC206
            .tracking
f12377bce9b9atagen2026-09-26 12:32 UTC207
            .take()
f12377bce9b9atagen2026-09-26 12:32 UTC208
            .ok_or(AccountError::OAuthCallbackAlreadySubmitted)?;
a250c7fa43fbatagen2026-09-26 13:35 UTC209
        Session::saved(
a250c7fa43fbatagen2026-09-26 13:35 UTC210
            self.client.clone(),
a250c7fa43fbatagen2026-09-26 13:35 UTC211
            self.sessions.clone(),
a250c7fa43fbatagen2026-09-26 13:35 UTC212
            tracking,
a250c7fa43fbatagen2026-09-26 13:35 UTC213
            store_lock,
a250c7fa43fbatagen2026-09-26 13:35 UTC214
        )
a250c7fa43fbatagen2026-09-26 13:35 UTC215
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC216
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC217
96fdf90aacd6atagen2026-09-08 08:24 UTC218
    pub async fn abort(mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC219
        if let Some(state) = self.state.take() {
96fdf90aacd6atagen2026-09-08 08:24 UTC220
            self.client.oauth().abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC221
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC222
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC223
}
96fdf90aacd6atagen2026-09-08 08:24 UTC224
96fdf90aacd6atagen2026-09-08 08:24 UTC225
pub struct StoreConfig {
62fe32759529atagen2026-09-26 08:50 UTC226
    location: StoreLocation,
96fdf90aacd6atagen2026-09-08 08:24 UTC227
    passphrase: String,
62fe32759529atagen2026-09-26 08:50 UTC228
}
62fe32759529atagen2026-09-26 08:50 UTC229
62fe32759529atagen2026-09-26 08:50 UTC230
enum StoreLocation {
62fe32759529atagen2026-09-26 08:50 UTC231
    #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC232
    Sqlite(PathBuf),
62fe32759529atagen2026-09-26 08:50 UTC233
    #[cfg(feature = "indexeddb")]
62fe32759529atagen2026-09-26 08:50 UTC234
    IndexedDb(String),
96fdf90aacd6atagen2026-09-08 08:24 UTC235
}
96fdf90aacd6atagen2026-09-08 08:24 UTC236
96fdf90aacd6atagen2026-09-08 08:24 UTC237
impl StoreConfig {
62fe32759529atagen2026-09-26 08:50 UTC238
    #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC239
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC240
    pub fn sqlite(path: impl Into<PathBuf>, passphrase: impl Into<String>) -> Self {
62fe32759529atagen2026-09-26 08:50 UTC241
        Self {
62fe32759529atagen2026-09-26 08:50 UTC242
            location: StoreLocation::Sqlite(path.into()),
62fe32759529atagen2026-09-26 08:50 UTC243
            passphrase: passphrase.into(),
62fe32759529atagen2026-09-26 08:50 UTC244
        }
62fe32759529atagen2026-09-26 08:50 UTC245
    }
62fe32759529atagen2026-09-26 08:50 UTC246
62fe32759529atagen2026-09-26 08:50 UTC247
    #[cfg(feature = "indexeddb")]
96fdf90aacd6atagen2026-09-08 08:24 UTC248
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC249
    pub fn indexeddb(name: impl Into<String>, passphrase: impl Into<String>) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC250
        Self {
62fe32759529atagen2026-09-26 08:50 UTC251
            location: StoreLocation::IndexedDb(name.into()),
96fdf90aacd6atagen2026-09-08 08:24 UTC252
            passphrase: passphrase.into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC253
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC254
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC255
713205a95af2atagen2026-09-26 09:24 UTC256
    /// delete the store's databases, once nothing has them open
713205a95af2atagen2026-09-26 09:24 UTC257
    pub async fn delete(&self) -> Result<(), AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC258
        match &self.location {
713205a95af2atagen2026-09-26 09:24 UTC259
            #[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC260
            StoreLocation::Sqlite(path) => crate::session_store::delete_sqlite_store(path)
713205a95af2atagen2026-09-26 09:24 UTC261
                .map_err(|error| account_error("deleting the Matrix store", error)),
713205a95af2atagen2026-09-26 09:24 UTC262
            #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC263
            StoreLocation::IndexedDb(name) => crate::session_store::delete_indexeddb_store(name)
713205a95af2atagen2026-09-26 09:24 UTC264
                .await
6db49c0ecf71atagen2026-09-26 12:41 UTC265
                .map_err(|error| error.into_account_error("deleting the Matrix store")),
713205a95af2atagen2026-09-26 09:24 UTC266
        }
713205a95af2atagen2026-09-26 09:24 UTC267
    }
713205a95af2atagen2026-09-26 09:24 UTC268
a250c7fa43fbatagen2026-09-26 13:35 UTC269
    /// taken before the client opens the store, and held until the engine and every
a250c7fa43fbatagen2026-09-26 13:35 UTC270
    /// `ClientHandle` are gone
a250c7fa43fbatagen2026-09-26 13:35 UTC271
    pub(crate) async fn lock(&self) -> Result<StoreLock, AccountError> {
a250c7fa43fbatagen2026-09-26 13:35 UTC272
        match &self.location {
a250c7fa43fbatagen2026-09-26 13:35 UTC273
            #[cfg(feature = "sqlite")]
a250c7fa43fbatagen2026-09-26 13:35 UTC274
            StoreLocation::Sqlite(path) => StoreLock::file(path),
a250c7fa43fbatagen2026-09-26 13:35 UTC275
            #[cfg(feature = "indexeddb")]
a250c7fa43fbatagen2026-09-26 13:35 UTC276
            StoreLocation::IndexedDb(name) => StoreLock::web(name).await,
a250c7fa43fbatagen2026-09-26 13:35 UTC277
        }
a250c7fa43fbatagen2026-09-26 13:35 UTC278
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC279
713205a95af2atagen2026-09-26 09:24 UTC280
    pub(crate) fn passphrase(&self) -> &str {
713205a95af2atagen2026-09-26 09:24 UTC281
        &self.passphrase
713205a95af2atagen2026-09-26 09:24 UTC282
    }
713205a95af2atagen2026-09-26 09:24 UTC283
713205a95af2atagen2026-09-26 09:24 UTC284
    #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC285
    pub(crate) fn indexeddb_name(&self) -> &str {
713205a95af2atagen2026-09-26 09:24 UTC286
        let StoreLocation::IndexedDb(name) = &self.location;
713205a95af2atagen2026-09-26 09:24 UTC287
        name
713205a95af2atagen2026-09-26 09:24 UTC288
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC289
}
96fdf90aacd6atagen2026-09-08 08:24 UTC290
f12377bce9b9atagen2026-09-26 12:32 UTC291
/// Serializable authentication secrets. A `SessionStore` keeps it, and the engine
f12377bce9b9atagen2026-09-26 12:32 UTC292
/// replaces it there on every token rotation.
96fdf90aacd6atagen2026-09-08 08:24 UTC293
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC294
pub struct SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC295
    homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC296
    authentication: AuthenticationRecord,
96fdf90aacd6atagen2026-09-08 08:24 UTC297
}
96fdf90aacd6atagen2026-09-08 08:24 UTC298
96fdf90aacd6atagen2026-09-08 08:24 UTC299
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC300
#[serde(tag = "type", content = "session", rename_all = "snake_case")]
96fdf90aacd6atagen2026-09-08 08:24 UTC301
enum AuthenticationRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC302
    Matrix(MatrixSession),
96fdf90aacd6atagen2026-09-08 08:24 UTC303
    OAuth {
96fdf90aacd6atagen2026-09-08 08:24 UTC304
        client_id: ClientId,
96fdf90aacd6atagen2026-09-08 08:24 UTC305
        user: UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC306
    },
6f1d6591c10datagen2026-09-26 09:52 UTC307
    SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC308
        meta: SessionMeta,
6f1d6591c10datagen2026-09-26 09:52 UTC309
        client_id: Option<ClientId>,
6f1d6591c10datagen2026-09-26 09:52 UTC310
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC311
}
96fdf90aacd6atagen2026-09-08 08:24 UTC312
96fdf90aacd6atagen2026-09-08 08:24 UTC313
impl SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC314
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC315
    pub fn homeserver(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC316
        &self.homeserver
96fdf90aacd6atagen2026-09-08 08:24 UTC317
    }
6f1d6591c10datagen2026-09-26 09:52 UTC318
cb0668d30c14atagen2026-10-08 11:34 UTC319
    pub(crate) fn user_id(&self) -> &OwnedUserId {
cb0668d30c14atagen2026-10-08 11:34 UTC320
        match &self.authentication {
cb0668d30c14atagen2026-10-08 11:34 UTC321
            AuthenticationRecord::Matrix(session) => &session.meta.user_id,
cb0668d30c14atagen2026-10-08 11:34 UTC322
            AuthenticationRecord::OAuth { user, .. } => &user.meta.user_id,
cb0668d30c14atagen2026-10-08 11:34 UTC323
            AuthenticationRecord::SoftLoggedOut { meta, .. } => &meta.user_id,
cb0668d30c14atagen2026-10-08 11:34 UTC324
        }
cb0668d30c14atagen2026-10-08 11:34 UTC325
    }
cb0668d30c14atagen2026-10-08 11:34 UTC326
cb0668d30c14atagen2026-10-08 11:34 UTC327
    pub(crate) fn soft_logout(&self) -> Option<SoftLogout> {
cb0668d30c14atagen2026-10-08 11:34 UTC328
        let AuthenticationRecord::SoftLoggedOut { meta, client_id } = &self.authentication else {
cb0668d30c14atagen2026-10-08 11:34 UTC329
            return None;
cb0668d30c14atagen2026-10-08 11:34 UTC330
        };
cb0668d30c14atagen2026-10-08 11:34 UTC331
        Some(SoftLogout {
cb0668d30c14atagen2026-10-08 11:34 UTC332
            homeserver: self.homeserver.clone(),
cb0668d30c14atagen2026-10-08 11:34 UTC333
            user_id: meta.user_id.clone(),
cb0668d30c14atagen2026-10-08 11:34 UTC334
            device_id: meta.device_id.clone(),
cb0668d30c14atagen2026-10-08 11:34 UTC335
            client_id: client_id
cb0668d30c14atagen2026-10-08 11:34 UTC336
                .as_ref()
cb0668d30c14atagen2026-10-08 11:34 UTC337
                .map(|client_id| client_id.as_str().to_owned()),
cb0668d30c14atagen2026-10-08 11:34 UTC338
        })
cb0668d30c14atagen2026-10-08 11:34 UTC339
    }
cb0668d30c14atagen2026-10-08 11:34 UTC340
6f1d6591c10datagen2026-09-26 09:52 UTC341
    pub(crate) fn soft_logged_out(self) -> Self {
6f1d6591c10datagen2026-09-26 09:52 UTC342
        let authentication = match self.authentication {
6f1d6591c10datagen2026-09-26 09:52 UTC343
            AuthenticationRecord::Matrix(session) => AuthenticationRecord::SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC344
                meta: session.meta,
6f1d6591c10datagen2026-09-26 09:52 UTC345
                client_id: None,
6f1d6591c10datagen2026-09-26 09:52 UTC346
            },
6f1d6591c10datagen2026-09-26 09:52 UTC347
            AuthenticationRecord::OAuth { client_id, user } => {
6f1d6591c10datagen2026-09-26 09:52 UTC348
                AuthenticationRecord::SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC349
                    meta: user.meta,
6f1d6591c10datagen2026-09-26 09:52 UTC350
                    client_id: Some(client_id),
6f1d6591c10datagen2026-09-26 09:52 UTC351
                }
6f1d6591c10datagen2026-09-26 09:52 UTC352
            }
6f1d6591c10datagen2026-09-26 09:52 UTC353
            soft @ AuthenticationRecord::SoftLoggedOut { .. } => soft,
6f1d6591c10datagen2026-09-26 09:52 UTC354
        };
6f1d6591c10datagen2026-09-26 09:52 UTC355
        Self {
6f1d6591c10datagen2026-09-26 09:52 UTC356
            homeserver: self.homeserver,
6f1d6591c10datagen2026-09-26 09:52 UTC357
            authentication,
6f1d6591c10datagen2026-09-26 09:52 UTC358
        }
6f1d6591c10datagen2026-09-26 09:52 UTC359
    }
6f1d6591c10datagen2026-09-26 09:52 UTC360
}
6f1d6591c10datagen2026-09-26 09:52 UTC361
6f1d6591c10datagen2026-09-26 09:52 UTC362
pub enum Restore {
6f1d6591c10datagen2026-09-26 09:52 UTC363
    Resumed(Session),
6f1d6591c10datagen2026-09-26 09:52 UTC364
    SoftLoggedOut(SoftLogout),
6f1d6591c10datagen2026-09-26 09:52 UTC365
}
6f1d6591c10datagen2026-09-26 09:52 UTC366
6f1d6591c10datagen2026-09-26 09:52 UTC367
/// who to log in again as. the store keeps this device's e2ee keys, so a login
6f1d6591c10datagen2026-09-26 09:52 UTC368
/// as any other device would orphan them
6f1d6591c10datagen2026-09-26 09:52 UTC369
#[derive(Clone, Debug)]
6f1d6591c10datagen2026-09-26 09:52 UTC370
#[non_exhaustive]
6f1d6591c10datagen2026-09-26 09:52 UTC371
pub struct SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC372
    pub homeserver: String,
6f1d6591c10datagen2026-09-26 09:52 UTC373
    pub user_id: OwnedUserId,
6f1d6591c10datagen2026-09-26 09:52 UTC374
    pub device_id: OwnedDeviceId,
6f1d6591c10datagen2026-09-26 09:52 UTC375
    /// set when the login was OAuth, for `OAuthLogin::with_client_id`
6f1d6591c10datagen2026-09-26 09:52 UTC376
    pub client_id: Option<String>,
6f1d6591c10datagen2026-09-26 09:52 UTC377
}
6f1d6591c10datagen2026-09-26 09:52 UTC378
6f1d6591c10datagen2026-09-26 09:52 UTC379
impl SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC380
    #[must_use]
6f1d6591c10datagen2026-09-26 09:52 UTC381
    pub fn password_login(&self, password: impl Into<String>) -> PasswordLogin {
6f1d6591c10datagen2026-09-26 09:52 UTC382
        PasswordLogin::new(self.homeserver.clone(), self.user_id.as_str(), password)
6f1d6591c10datagen2026-09-26 09:52 UTC383
            .with_device_id(self.device_id.clone())
9c05b1dd327aatagen2026-10-08 11:27 UTC384
            .allow_insecure_loopback(true)
6f1d6591c10datagen2026-09-26 09:52 UTC385
    }
cb0668d30c14atagen2026-10-08 11:34 UTC386
cb0668d30c14atagen2026-10-08 11:34 UTC387
    #[must_use]
cb0668d30c14atagen2026-10-08 11:34 UTC388
    pub fn oauth_login(
cb0668d30c14atagen2026-10-08 11:34 UTC389
        &self,
cb0668d30c14atagen2026-10-08 11:34 UTC390
        redirect_uri: impl Into<String>,
cb0668d30c14atagen2026-10-08 11:34 UTC391
        client_uri: impl Into<String>,
cb0668d30c14atagen2026-10-08 11:34 UTC392
        application_type: OAuthApplicationType,
cb0668d30c14atagen2026-10-08 11:34 UTC393
    ) -> OAuthLogin {
cb0668d30c14atagen2026-10-08 11:34 UTC394
        let login = OAuthLogin::new(
cb0668d30c14atagen2026-10-08 11:34 UTC395
            self.homeserver.clone(),
cb0668d30c14atagen2026-10-08 11:34 UTC396
            redirect_uri,
cb0668d30c14atagen2026-10-08 11:34 UTC397
            client_uri,
cb0668d30c14atagen2026-10-08 11:34 UTC398
            application_type,
cb0668d30c14atagen2026-10-08 11:34 UTC399
        )
cb0668d30c14atagen2026-10-08 11:34 UTC400
        .with_device_id(self.device_id.clone())
cb0668d30c14atagen2026-10-08 11:34 UTC401
        .allow_insecure_loopback(true);
cb0668d30c14atagen2026-10-08 11:34 UTC402
        match &self.client_id {
cb0668d30c14atagen2026-10-08 11:34 UTC403
            Some(client_id) => login.with_client_id(client_id.clone()),
cb0668d30c14atagen2026-10-08 11:34 UTC404
            None => login,
cb0668d30c14atagen2026-10-08 11:34 UTC405
        }
cb0668d30c14atagen2026-10-08 11:34 UTC406
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC407
}
96fdf90aacd6atagen2026-09-08 08:24 UTC408
96fdf90aacd6atagen2026-09-08 08:24 UTC409
#[derive(Debug, Error)]
96fdf90aacd6atagen2026-09-08 08:24 UTC410
pub enum AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC411
    #[error("{context} failed")]
96fdf90aacd6atagen2026-09-08 08:24 UTC412
    Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC413
        context: &'static str,
9a25b6ce66f4atagen2026-10-08 11:15 UTC414
        kind: ErrorKind,
9a25b6ce66f4atagen2026-10-08 11:15 UTC415
        server: Option<ServerError>,
96fdf90aacd6atagen2026-09-08 08:24 UTC416
        #[source]
62fe32759529atagen2026-09-26 08:50 UTC417
        source: Box<SourceError>,
96fdf90aacd6atagen2026-09-08 08:24 UTC418
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC419
    #[error("the homeserver returned no authenticated session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC420
    MissingSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC421
    #[error("the SDK returned an unsupported authentication session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC422
    UnsupportedAuthentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC423
    #[error("the OAuth callback was already submitted")]
96fdf90aacd6atagen2026-09-08 08:24 UTC424
    OAuthCallbackAlreadySubmitted,
9c05b1dd327aatagen2026-10-08 11:27 UTC425
    /// what was typed can't name a homeserver, so nothing was sent anywhere
9c05b1dd327aatagen2026-10-08 11:27 UTC426
    #[error("that isn't a usable homeserver, {0}")]
9c05b1dd327aatagen2026-10-08 11:27 UTC427
    InvalidHomeserver(&'static str),
cb0668d30c14atagen2026-10-08 11:34 UTC428
    /// `Accounts` already has a live login for them. a second would be another device
cb0668d30c14atagen2026-10-08 11:34 UTC429
    #[error("{0} is already logged in")]
cb0668d30c14atagen2026-10-08 11:34 UTC430
    AlreadyLoggedIn(OwnedUserId),
6db49c0ecf71atagen2026-09-26 12:41 UTC431
    /// an IndexedDB delete is waiting on the store's other connections. it goes through
6db49c0ecf71atagen2026-09-26 12:41 UTC432
    /// once they close, and opening the store again waits behind it
6db49c0ecf71atagen2026-09-26 12:41 UTC433
    #[error("the store is still open elsewhere, it will be deleted once that closes")]
6db49c0ecf71atagen2026-09-26 12:41 UTC434
    StoreDeletePending,
a250c7fa43fbatagen2026-09-26 13:35 UTC435
    /// another engine, in this process or another, has the store. only one runs per store
a250c7fa43fbatagen2026-09-26 13:35 UTC436
    #[error("another engine is using this store")]
a250c7fa43fbatagen2026-09-26 13:35 UTC437
    StoreInUse,
a250c7fa43fbatagen2026-09-26 13:35 UTC438
    /// the login worked but its first record didn't save. the server already has the
a250c7fa43fbatagen2026-09-26 13:35 UTC439
    /// device, so retry the save rather than log in again
a250c7fa43fbatagen2026-09-26 13:35 UTC440
    #[error("saving the new login's record failed")]
a250c7fa43fbatagen2026-09-26 13:35 UTC441
    Unsaved {
a250c7fa43fbatagen2026-09-26 13:35 UTC442
        session: Box<UnsavedSession>,
a250c7fa43fbatagen2026-09-26 13:35 UTC443
        #[source]
a250c7fa43fbatagen2026-09-26 13:35 UTC444
        source: Box<AccountError>,
a250c7fa43fbatagen2026-09-26 13:35 UTC445
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC446
    #[error(transparent)]
96fdf90aacd6atagen2026-09-08 08:24 UTC447
    Engine(#[from] EngineError),
96fdf90aacd6atagen2026-09-08 08:24 UTC448
}
96fdf90aacd6atagen2026-09-08 08:24 UTC449
9a25b6ce66f4atagen2026-10-08 11:15 UTC450
impl AccountError {
9a25b6ce66f4atagen2026-10-08 11:15 UTC451
    /// what the homeserver said, when it refused
9a25b6ce66f4atagen2026-10-08 11:15 UTC452
    #[must_use]
9a25b6ce66f4atagen2026-10-08 11:15 UTC453
    pub fn server(&self) -> Option<&ServerError> {
9a25b6ce66f4atagen2026-10-08 11:15 UTC454
        match self {
9a25b6ce66f4atagen2026-10-08 11:15 UTC455
            Self::Operation { server, .. } => server.as_ref(),
9a25b6ce66f4atagen2026-10-08 11:15 UTC456
            Self::Unsaved { source, .. } => source.server(),
9a25b6ce66f4atagen2026-10-08 11:15 UTC457
            _ => None,
9a25b6ce66f4atagen2026-10-08 11:15 UTC458
        }
9a25b6ce66f4atagen2026-10-08 11:15 UTC459
    }
9a25b6ce66f4atagen2026-10-08 11:15 UTC460
}
9a25b6ce66f4atagen2026-10-08 11:15 UTC461
a250c7fa43fbatagen2026-09-26 13:35 UTC462
/// a live login whose record never reached its `SessionStore`
a250c7fa43fbatagen2026-09-26 13:35 UTC463
pub struct UnsavedSession {
a250c7fa43fbatagen2026-09-26 13:35 UTC464
    session: Session,
a250c7fa43fbatagen2026-09-26 13:35 UTC465
    sessions: Arc<dyn SessionStore>,
a250c7fa43fbatagen2026-09-26 13:35 UTC466
}
a250c7fa43fbatagen2026-09-26 13:35 UTC467
a250c7fa43fbatagen2026-09-26 13:35 UTC468
impl UnsavedSession {
a250c7fa43fbatagen2026-09-26 13:35 UTC469
    pub async fn save(self) -> Result<Session, AccountError> {
a250c7fa43fbatagen2026-09-26 13:35 UTC470
        let saved = match session_record(&self.session.client) {
a250c7fa43fbatagen2026-09-26 13:35 UTC471
            Ok(record) => self.sessions.save(&record).await,
a250c7fa43fbatagen2026-09-26 13:35 UTC472
            Err(error) => Err(error),
a250c7fa43fbatagen2026-09-26 13:35 UTC473
        };
a250c7fa43fbatagen2026-09-26 13:35 UTC474
        match saved {
a250c7fa43fbatagen2026-09-26 13:35 UTC475
            Ok(()) => Ok(self.session),
a250c7fa43fbatagen2026-09-26 13:35 UTC476
            Err(source) => Err(AccountError::Unsaved {
a250c7fa43fbatagen2026-09-26 13:35 UTC477
                session: Box::new(self),
a250c7fa43fbatagen2026-09-26 13:35 UTC478
                source: Box::new(source),
a250c7fa43fbatagen2026-09-26 13:35 UTC479
            }),
a250c7fa43fbatagen2026-09-26 13:35 UTC480
        }
a250c7fa43fbatagen2026-09-26 13:35 UTC481
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC482
a250c7fa43fbatagen2026-09-26 13:35 UTC483
    /// the record to keep somewhere else, if the store won't take it
a250c7fa43fbatagen2026-09-26 13:35 UTC484
    pub fn record(&self) -> Result<SessionRecord, AccountError> {
a250c7fa43fbatagen2026-09-26 13:35 UTC485
        session_record(&self.session.client)
a250c7fa43fbatagen2026-09-26 13:35 UTC486
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC487
}
a250c7fa43fbatagen2026-09-26 13:35 UTC488
a250c7fa43fbatagen2026-09-26 13:35 UTC489
impl std::fmt::Debug for UnsavedSession {
a250c7fa43fbatagen2026-09-26 13:35 UTC490
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
a250c7fa43fbatagen2026-09-26 13:35 UTC491
        formatter
a250c7fa43fbatagen2026-09-26 13:35 UTC492
            .debug_struct("UnsavedSession")
a250c7fa43fbatagen2026-09-26 13:35 UTC493
            .finish_non_exhaustive()
a250c7fa43fbatagen2026-09-26 13:35 UTC494
    }
a250c7fa43fbatagen2026-09-26 13:35 UTC495
}
a250c7fa43fbatagen2026-09-26 13:35 UTC496
96fdf90aacd6atagen2026-09-08 08:24 UTC497
impl Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC498
    pub async fn login_password(
96fdf90aacd6atagen2026-09-08 08:24 UTC499
        login: PasswordLogin,
f12377bce9b9atagen2026-09-26 12:32 UTC500
        sessions: Arc<dyn SessionStore>,
96fdf90aacd6atagen2026-09-08 08:24 UTC501
    ) -> Result<Self, AccountError> {
cb0668d30c14atagen2026-10-08 11:34 UTC502
        homeserver_input(&login.homeserver, login.allow_insecure_loopback)?;
a250c7fa43fbatagen2026-09-26 13:35 UTC503
        let store_lock = sessions.store().lock().await?;
cb0668d30c14atagen2026-10-08 11:34 UTC504
        Self::login_password_locked(login, sessions, store_lock).await
cb0668d30c14atagen2026-10-08 11:34 UTC505
    }
cb0668d30c14atagen2026-10-08 11:34 UTC506
cb0668d30c14atagen2026-10-08 11:34 UTC507
    pub(crate) async fn login_password_locked(
cb0668d30c14atagen2026-10-08 11:34 UTC508
        login: PasswordLogin,
cb0668d30c14atagen2026-10-08 11:34 UTC509
        sessions: Arc<dyn SessionStore>,
cb0668d30c14atagen2026-10-08 11:34 UTC510
        store_lock: StoreLock,
cb0668d30c14atagen2026-10-08 11:34 UTC511
    ) -> Result<Self, AccountError> {
cb0668d30c14atagen2026-10-08 11:34 UTC512
        let homeserver = homeserver_input(&login.homeserver, login.allow_insecure_loopback)?;
713205a95af2atagen2026-09-26 09:24 UTC513
        let client = matrix_client_builder(sessions.store())
9c05b1dd327aatagen2026-10-08 11:27 UTC514
            .server_name_or_homeserver_url(homeserver)
96fdf90aacd6atagen2026-09-08 08:24 UTC515
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC516
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC517
            .map_err(|error| account_error("building the Matrix client", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC518
        let tracking = Tracking::start(&client, sessions.clone())?;
96fdf90aacd6atagen2026-09-08 08:24 UTC519
        let mut request = client
96fdf90aacd6atagen2026-09-08 08:24 UTC520
            .matrix_auth()
96fdf90aacd6atagen2026-09-08 08:24 UTC521
            .login_username(login.username.trim(), &login.password)
96fdf90aacd6atagen2026-09-08 08:24 UTC522
            .request_refresh_token();
96fdf90aacd6atagen2026-09-08 08:24 UTC523
        if let Some(name) = login.device_display_name.as_deref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC524
            request = request.initial_device_display_name(name);
96fdf90aacd6atagen2026-09-08 08:24 UTC525
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC526
        if let Some(device_id) = login.device_id.as_deref() {
fa9114fcf75fatagen2026-09-24 12:51 UTC527
            request = request.device_id(device_id.as_str());
fa9114fcf75fatagen2026-09-24 12:51 UTC528
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC529
        request
96fdf90aacd6atagen2026-09-08 08:24 UTC530
            .send()
96fdf90aacd6atagen2026-09-08 08:24 UTC531
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC532
            .map_err(|error| account_error("password login", error))?;
a250c7fa43fbatagen2026-09-26 13:35 UTC533
        Self::saved(client, sessions, tracking, store_lock).await
96fdf90aacd6atagen2026-09-08 08:24 UTC534
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC535
a250c7fa43fbatagen2026-09-26 13:35 UTC536
    /// the pending login lives only in this process, so the one that begins it has to finish it
96fdf90aacd6atagen2026-09-08 08:24 UTC537
    pub async fn begin_oauth(
96fdf90aacd6atagen2026-09-08 08:24 UTC538
        login: OAuthLogin,
713205a95af2atagen2026-09-26 09:24 UTC539
        sessions: Arc<dyn SessionStore>,
96fdf90aacd6atagen2026-09-08 08:24 UTC540
    ) -> Result<PendingOAuthLogin, AccountError> {
cb0668d30c14atagen2026-10-08 11:34 UTC541
        homeserver_input(&login.homeserver, login.allow_insecure_loopback)?;
cb0668d30c14atagen2026-10-08 11:34 UTC542
        let store_lock = sessions.store().lock().await?;
cb0668d30c14atagen2026-10-08 11:34 UTC543
        Self::begin_oauth_locked(login, sessions, store_lock).await
cb0668d30c14atagen2026-10-08 11:34 UTC544
    }
cb0668d30c14atagen2026-10-08 11:34 UTC545
cb0668d30c14atagen2026-10-08 11:34 UTC546
    pub(crate) async fn begin_oauth_locked(
cb0668d30c14atagen2026-10-08 11:34 UTC547
        login: OAuthLogin,
cb0668d30c14atagen2026-10-08 11:34 UTC548
        sessions: Arc<dyn SessionStore>,
cb0668d30c14atagen2026-10-08 11:34 UTC549
        store_lock: StoreLock,
cb0668d30c14atagen2026-10-08 11:34 UTC550
    ) -> Result<PendingOAuthLogin, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC551
        let redirect_uri = Url::parse(login.redirect_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC552
            .map_err(|error| account_error("parsing the OAuth redirect URI", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC553
        let client_uri = Url::parse(login.client_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC554
            .map_err(|error| account_error("parsing the OAuth client URI", error))?;
9c05b1dd327aatagen2026-10-08 11:27 UTC555
        let homeserver = homeserver_input(&login.homeserver, login.allow_insecure_loopback)?;
713205a95af2atagen2026-09-26 09:24 UTC556
        let client = matrix_client_builder(sessions.store())
9c05b1dd327aatagen2026-10-08 11:27 UTC557
            .server_name_or_homeserver_url(homeserver)
96fdf90aacd6atagen2026-09-08 08:24 UTC558
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC559
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC560
            .map_err(|error| account_error("building the Matrix client", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC561
        let tracking = Tracking::start(&client, sessions.clone())?;
96fdf90aacd6atagen2026-09-08 08:24 UTC562
        let application_type = match login.application_type {
96fdf90aacd6atagen2026-09-08 08:24 UTC563
            OAuthApplicationType::Native => ApplicationType::Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC564
            OAuthApplicationType::Web => ApplicationType::Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC565
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC566
        let mut metadata = ClientMetadata::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC567
            application_type,
96fdf90aacd6atagen2026-09-08 08:24 UTC568
            vec![OAuthGrantType::AuthorizationCode {
96fdf90aacd6atagen2026-09-08 08:24 UTC569
                redirect_uris: vec![redirect_uri.clone()],
96fdf90aacd6atagen2026-09-08 08:24 UTC570
            }],
96fdf90aacd6atagen2026-09-08 08:24 UTC571
            Localized::new(client_uri, []),
96fdf90aacd6atagen2026-09-08 08:24 UTC572
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC573
        metadata.client_name = login.client_name.map(|name| Localized::new(name, []));
96fdf90aacd6atagen2026-09-08 08:24 UTC574
        let metadata = Raw::new(&metadata)
96fdf90aacd6atagen2026-09-08 08:24 UTC575
            .map_err(|error| account_error("serializing OAuth client metadata", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC576
        let mut registration = ClientRegistrationData::new(metadata);
96fdf90aacd6atagen2026-09-08 08:24 UTC577
        if let Some(client_id) = login.client_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC578
            registration.static_registrations = Some(HashMap::from([(
96fdf90aacd6atagen2026-09-08 08:24 UTC579
                client.homeserver(),
96fdf90aacd6atagen2026-09-08 08:24 UTC580
                ClientId::new(client_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC581
            )]));
96fdf90aacd6atagen2026-09-08 08:24 UTC582
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC583
        let authorization = client
96fdf90aacd6atagen2026-09-08 08:24 UTC584
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC585
            .login(redirect_uri, login.device_id, Some(registration), None)
96fdf90aacd6atagen2026-09-08 08:24 UTC586
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC587
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC588
            .map_err(|error| account_error("starting OAuth login", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC589
        Ok(PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC590
            client,
713205a95af2atagen2026-09-26 09:24 UTC591
            sessions,
a250c7fa43fbatagen2026-09-26 13:35 UTC592
            tracking: Some((tracking, store_lock)),
96fdf90aacd6atagen2026-09-08 08:24 UTC593
            authorization_url: authorization.url.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC594
            state: Some(authorization.state),
96fdf90aacd6atagen2026-09-08 08:24 UTC595
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC596
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC597
713205a95af2atagen2026-09-26 09:24 UTC598
    /// Restore the record `sessions` holds, or `None` when it holds none.
f12377bce9b9atagen2026-09-26 12:32 UTC599
    /// The caller must also handle rejection and explicit logout.
f12377bce9b9atagen2026-09-26 12:32 UTC600
    pub async fn restore(sessions: Arc<dyn SessionStore>) -> Result<Option<Restore>, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC601
        let Some(record) = sessions.load().await? else {
713205a95af2atagen2026-09-26 09:24 UTC602
            return Ok(None);
713205a95af2atagen2026-09-26 09:24 UTC603
        };
713205a95af2atagen2026-09-26 09:24 UTC604
        let authentication = match record.authentication {
96fdf90aacd6atagen2026-09-08 08:24 UTC605
            AuthenticationRecord::Matrix(session) => AuthSession::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC606
            AuthenticationRecord::OAuth { client_id, user } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC607
                OAuthSession { client_id, user }.into()
96fdf90aacd6atagen2026-09-08 08:24 UTC608
            }
6f1d6591c10datagen2026-09-26 09:52 UTC609
            AuthenticationRecord::SoftLoggedOut { meta, client_id } => {
6f1d6591c10datagen2026-09-26 09:52 UTC610
                return Ok(Some(Restore::SoftLoggedOut(SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC611
                    homeserver: record.homeserver,
6f1d6591c10datagen2026-09-26 09:52 UTC612
                    user_id: meta.user_id,
6f1d6591c10datagen2026-09-26 09:52 UTC613
                    device_id: meta.device_id,
6f1d6591c10datagen2026-09-26 09:52 UTC614
                    client_id: client_id.map(|client_id| client_id.as_str().to_owned()),
6f1d6591c10datagen2026-09-26 09:52 UTC615
                })));
6f1d6591c10datagen2026-09-26 09:52 UTC616
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC617
        };
a250c7fa43fbatagen2026-09-26 13:35 UTC618
        let store_lock = sessions.store().lock().await?;
6f1d6591c10datagen2026-09-26 09:52 UTC619
        let client = matrix_client_builder(sessions.store())
6f1d6591c10datagen2026-09-26 09:52 UTC620
            .homeserver_url(&record.homeserver)
6f1d6591c10datagen2026-09-26 09:52 UTC621
            .build()
6f1d6591c10datagen2026-09-26 09:52 UTC622
            .await
6f1d6591c10datagen2026-09-26 09:52 UTC623
            .map_err(|error| account_error("reopening the Matrix store", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC624
        let tracking = Tracking::start(&client, sessions)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC625
        client
96fdf90aacd6atagen2026-09-08 08:24 UTC626
            .restore_session(authentication)
96fdf90aacd6atagen2026-09-08 08:24 UTC627
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC628
            .map_err(|error| account_error("restoring the authenticated session", error))?;
a250c7fa43fbatagen2026-09-26 13:35 UTC629
        Ok(Some(Restore::Resumed(Self {
a250c7fa43fbatagen2026-09-26 13:35 UTC630
            client,
a250c7fa43fbatagen2026-09-26 13:35 UTC631
            tracking,
a250c7fa43fbatagen2026-09-26 13:35 UTC632
            store_lock,
a250c7fa43fbatagen2026-09-26 13:35 UTC633
        })))
f12377bce9b9atagen2026-09-26 12:32 UTC634
    }
f12377bce9b9atagen2026-09-26 12:32 UTC635
f12377bce9b9atagen2026-09-26 12:32 UTC636
    /// leave the record to the host, which then saves `ClientHandle::session_record()`
f12377bce9b9atagen2026-09-26 12:32 UTC637
    /// on every `SessionTokensChanged`
f12377bce9b9atagen2026-09-26 12:32 UTC638
    #[must_use]
f12377bce9b9atagen2026-09-26 12:32 UTC639
    pub fn without_persistence(mut self) -> Self {
f12377bce9b9atagen2026-09-26 12:32 UTC640
        self.tracking.persistence = None;
f12377bce9b9atagen2026-09-26 12:32 UTC641
        #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC642
        self.tracking.inline.store(false, Ordering::Release);
f12377bce9b9atagen2026-09-26 12:32 UTC643
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC644
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC645
96fdf90aacd6atagen2026-09-08 08:24 UTC646
    pub async fn build(
96fdf90aacd6atagen2026-09-08 08:24 UTC647
        self,
96fdf90aacd6atagen2026-09-08 08:24 UTC648
        config: EngineConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC649
    ) -> Result<(Engine, ClientHandle, EventStream), AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC650
        Engine::with_persistence(
f12377bce9b9atagen2026-09-26 12:32 UTC651
            self.client,
f12377bce9b9atagen2026-09-26 12:32 UTC652
            config,
f12377bce9b9atagen2026-09-26 12:32 UTC653
            self.tracking.session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC654
            self.tracking.persistence,
a250c7fa43fbatagen2026-09-26 13:35 UTC655
            Some(self.store_lock),
f12377bce9b9atagen2026-09-26 12:32 UTC656
        )
f12377bce9b9atagen2026-09-26 12:32 UTC657
        .await
f12377bce9b9atagen2026-09-26 12:32 UTC658
        .map_err(AccountError::from)
96fdf90aacd6atagen2026-09-08 08:24 UTC659
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC660
f12377bce9b9atagen2026-09-26 12:32 UTC661
    async fn saved(
f12377bce9b9atagen2026-09-26 12:32 UTC662
        client: Client,
a250c7fa43fbatagen2026-09-26 13:35 UTC663
        sessions: Arc<dyn SessionStore>,
f12377bce9b9atagen2026-09-26 12:32 UTC664
        tracking: Tracking,
a250c7fa43fbatagen2026-09-26 13:35 UTC665
        store_lock: StoreLock,
f12377bce9b9atagen2026-09-26 12:32 UTC666
    ) -> Result<Self, AccountError> {
a250c7fa43fbatagen2026-09-26 13:35 UTC667
        UnsavedSession {
a250c7fa43fbatagen2026-09-26 13:35 UTC668
            session: Self {
a250c7fa43fbatagen2026-09-26 13:35 UTC669
                client,
a250c7fa43fbatagen2026-09-26 13:35 UTC670
                tracking,
a250c7fa43fbatagen2026-09-26 13:35 UTC671
                store_lock,
a250c7fa43fbatagen2026-09-26 13:35 UTC672
            },
a250c7fa43fbatagen2026-09-26 13:35 UTC673
            sessions,
a250c7fa43fbatagen2026-09-26 13:35 UTC674
        }
a250c7fa43fbatagen2026-09-26 13:35 UTC675
        .save()
a250c7fa43fbatagen2026-09-26 13:35 UTC676
        .await
f12377bce9b9atagen2026-09-26 12:32 UTC677
    }
f12377bce9b9atagen2026-09-26 12:32 UTC678
}
f12377bce9b9atagen2026-09-26 12:32 UTC679
f12377bce9b9atagen2026-09-26 12:32 UTC680
impl Tracking {
f12377bce9b9atagen2026-09-26 12:32 UTC681
    fn start(client: &Client, sessions: Arc<dyn SessionStore>) -> Result<Self, AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC682
        let session_changes = client.subscribe_to_session_changes();
f12377bce9b9atagen2026-09-26 12:32 UTC683
        #[cfg_attr(target_family = "wasm", allow(unused_variables))]
f12377bce9b9atagen2026-09-26 12:32 UTC684
        let (results, inline_results) = mpsc::unbounded_channel();
f12377bce9b9atagen2026-09-26 12:32 UTC685
        #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC686
        let inline = save_inline(client, sessions.clone(), results)?;
f12377bce9b9atagen2026-09-26 12:32 UTC687
        Ok(Self {
f12377bce9b9atagen2026-09-26 12:32 UTC688
            session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC689
            persistence: Some(Persistence {
f12377bce9b9atagen2026-09-26 12:32 UTC690
                save: Box::new(move |client| {
f12377bce9b9atagen2026-09-26 12:32 UTC691
                    let sessions = sessions.clone();
f12377bce9b9atagen2026-09-26 12:32 UTC692
                    Box::pin(async move {
f12377bce9b9atagen2026-09-26 12:32 UTC693
                        let record = session_record(&client).map_err(save_failed)?;
f12377bce9b9atagen2026-09-26 12:32 UTC694
                        sessions.save(&record).await.map_err(save_failed)
f12377bce9b9atagen2026-09-26 12:32 UTC695
                    })
f12377bce9b9atagen2026-09-26 12:32 UTC696
                }),
f12377bce9b9atagen2026-09-26 12:32 UTC697
                inline: inline_results,
f12377bce9b9atagen2026-09-26 12:32 UTC698
            }),
f12377bce9b9atagen2026-09-26 12:32 UTC699
            #[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC700
            inline,
f12377bce9b9atagen2026-09-26 12:32 UTC701
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC702
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC703
}
96fdf90aacd6atagen2026-09-08 08:24 UTC704
f12377bce9b9atagen2026-09-26 12:32 UTC705
/// the sdk calls this inside its refresh, after it has the new tokens and before it uses
f12377bce9b9atagen2026-09-26 12:32 UTC706
/// them, so a store that writes there and then can't lose a refresh token to a crash
f12377bce9b9atagen2026-09-26 12:32 UTC707
#[cfg(not(target_family = "wasm"))]
f12377bce9b9atagen2026-09-26 12:32 UTC708
fn save_inline(
f12377bce9b9atagen2026-09-26 12:32 UTC709
    client: &Client,
f12377bce9b9atagen2026-09-26 12:32 UTC710
    sessions: Arc<dyn SessionStore>,
f12377bce9b9atagen2026-09-26 12:32 UTC711
    results: mpsc::UnboundedSender<Result<(), crate::Error>>,
f12377bce9b9atagen2026-09-26 12:32 UTC712
) -> Result<Arc<AtomicBool>, AccountError> {
f12377bce9b9atagen2026-09-26 12:32 UTC713
    let enabled = Arc::new(AtomicBool::new(true));
f12377bce9b9atagen2026-09-26 12:32 UTC714
    let saving = enabled.clone();
f12377bce9b9atagen2026-09-26 12:32 UTC715
    client
f12377bce9b9atagen2026-09-26 12:32 UTC716
        .set_session_callbacks(
f12377bce9b9atagen2026-09-26 12:32 UTC717
            // only asked for with the cross process refresh lock, which we never enable
f12377bce9b9atagen2026-09-26 12:32 UTC718
            Box::new(|client| {
f12377bce9b9atagen2026-09-26 12:32 UTC719
                client
f12377bce9b9atagen2026-09-26 12:32 UTC720
                    .session_tokens()
f12377bce9b9atagen2026-09-26 12:32 UTC721
                    .ok_or_else(|| AccountError::MissingSession.into())
f12377bce9b9atagen2026-09-26 12:32 UTC722
            }),
f12377bce9b9atagen2026-09-26 12:32 UTC723
            Box::new(move |client| {
f12377bce9b9atagen2026-09-26 12:32 UTC724
                if !saving.load(Ordering::Acquire) {
f12377bce9b9atagen2026-09-26 12:32 UTC725
                    return Ok(());
f12377bce9b9atagen2026-09-26 12:32 UTC726
                }
f12377bce9b9atagen2026-09-26 12:32 UTC727
                let record = session_record(&client)?;
f12377bce9b9atagen2026-09-26 12:32 UTC728
                if let Some(result) = sessions.save_blocking(&record) {
f12377bce9b9atagen2026-09-26 12:32 UTC729
                    let _ = results.send(result.map_err(save_failed));
f12377bce9b9atagen2026-09-26 12:32 UTC730
                }
f12377bce9b9atagen2026-09-26 12:32 UTC731
                Ok(())
f12377bce9b9atagen2026-09-26 12:32 UTC732
            }),
f12377bce9b9atagen2026-09-26 12:32 UTC733
        )
f12377bce9b9atagen2026-09-26 12:32 UTC734
        .map_err(|error| account_error("registering the session callbacks", error))?;
f12377bce9b9atagen2026-09-26 12:32 UTC735
    Ok(enabled)
f12377bce9b9atagen2026-09-26 12:32 UTC736
}
f12377bce9b9atagen2026-09-26 12:32 UTC737
f12377bce9b9atagen2026-09-26 12:32 UTC738
fn save_failed(error: AccountError) -> crate::Error {
f12377bce9b9atagen2026-09-26 12:32 UTC739
    let message = format!("saving the session record failed, {error}");
f12377bce9b9atagen2026-09-26 12:32 UTC740
    crate::Error::with_source(ErrorKind::Store, message, error)
f12377bce9b9atagen2026-09-26 12:32 UTC741
}
f12377bce9b9atagen2026-09-26 12:32 UTC742
96fdf90aacd6atagen2026-09-08 08:24 UTC743
pub(crate) fn session_record(client: &Client) -> Result<SessionRecord, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC744
    let authentication = match client.session().ok_or(AccountError::MissingSession)? {
96fdf90aacd6atagen2026-09-08 08:24 UTC745
        AuthSession::Matrix(session) => AuthenticationRecord::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC746
        AuthSession::OAuth(session) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC747
            let OAuthSession { client_id, user } = *session;
96fdf90aacd6atagen2026-09-08 08:24 UTC748
            AuthenticationRecord::OAuth { client_id, user }
96fdf90aacd6atagen2026-09-08 08:24 UTC749
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC750
        _ => return Err(AccountError::UnsupportedAuthentication),
96fdf90aacd6atagen2026-09-08 08:24 UTC751
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC752
    Ok(SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC753
        homeserver: client.homeserver().to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC754
        authentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC755
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC756
}
96fdf90aacd6atagen2026-09-08 08:24 UTC757
96fdf90aacd6atagen2026-09-08 08:24 UTC758
fn encryption_settings() -> EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC759
    EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC760
        auto_enable_cross_signing: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC761
        backup_download_strategy: BackupDownloadStrategy::OneShot,
96fdf90aacd6atagen2026-09-08 08:24 UTC762
        auto_enable_backups: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC763
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC764
}
96fdf90aacd6atagen2026-09-08 08:24 UTC765
a250c7fa43fbatagen2026-09-26 13:35 UTC766
/// `StoreLock` already keeps every other client off the store, and the sdk's lease would cost
a250c7fa43fbatagen2026-09-26 13:35 UTC767
/// a store write per locked section and a task renewing it every 50ms
96fdf90aacd6atagen2026-09-08 08:24 UTC768
fn matrix_client_builder(store: &StoreConfig) -> ClientBuilder {
a250c7fa43fbatagen2026-09-26 13:35 UTC769
    let builder = Client::builder()
96fdf90aacd6atagen2026-09-08 08:24 UTC770
        .with_encryption_settings(encryption_settings())
96fdf90aacd6atagen2026-09-08 08:24 UTC771
        .with_threading_support(ThreadingSupport::Enabled {
96fdf90aacd6atagen2026-09-08 08:24 UTC772
            with_subscriptions: false,
96fdf90aacd6atagen2026-09-08 08:24 UTC773
        })
a250c7fa43fbatagen2026-09-26 13:35 UTC774
        .handle_refresh_tokens()
a250c7fa43fbatagen2026-09-26 13:35 UTC775
        .cross_process_store_config(CrossProcessLockConfig::SingleProcess);
62fe32759529atagen2026-09-26 08:50 UTC776
    match &store.location {
62fe32759529atagen2026-09-26 08:50 UTC777
        #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC778
        StoreLocation::Sqlite(path) => builder.sqlite_store(path, Some(&store.passphrase)),
62fe32759529atagen2026-09-26 08:50 UTC779
        #[cfg(feature = "indexeddb")]
62fe32759529atagen2026-09-26 08:50 UTC780
        StoreLocation::IndexedDb(name) => builder.indexeddb_store(name, Some(&store.passphrase)),
62fe32759529atagen2026-09-26 08:50 UTC781
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC782
}
96fdf90aacd6atagen2026-09-08 08:24 UTC783
9c05b1dd327aatagen2026-10-08 11:27 UTC784
/// a bare server name goes through untouched, since that's what makes the sdk try `.well-known`
cb0668d30c14atagen2026-10-08 11:34 UTC785
pub(crate) fn homeserver_input(
cb0668d30c14atagen2026-10-08 11:34 UTC786
    input: &str,
cb0668d30c14atagen2026-10-08 11:34 UTC787
    allow_insecure_loopback: bool,
cb0668d30c14atagen2026-10-08 11:34 UTC788
) -> Result<&str, AccountError> {
9c05b1dd327aatagen2026-10-08 11:27 UTC789
    let input = input.trim();
9c05b1dd327aatagen2026-10-08 11:27 UTC790
    let invalid = |reason| Err(AccountError::InvalidHomeserver(reason));
9c05b1dd327aatagen2026-10-08 11:27 UTC791
    if input.is_empty() {
9c05b1dd327aatagen2026-10-08 11:27 UTC792
        return invalid("it's empty");
9c05b1dd327aatagen2026-10-08 11:27 UTC793
    }
9c05b1dd327aatagen2026-10-08 11:27 UTC794
    if input.contains(char::is_whitespace) {
9c05b1dd327aatagen2026-10-08 11:27 UTC795
        return invalid("it has spaces in it");
9c05b1dd327aatagen2026-10-08 11:27 UTC796
    }
9c05b1dd327aatagen2026-10-08 11:27 UTC797
    if !input.contains("://") {
9c05b1dd327aatagen2026-10-08 11:27 UTC798
        return match <&ruma::ServerName>::try_from(input) {
9c05b1dd327aatagen2026-10-08 11:27 UTC799
            Ok(_) => Ok(input),
9c05b1dd327aatagen2026-10-08 11:27 UTC800
            Err(_) => invalid("it's neither a server name nor a url"),
9c05b1dd327aatagen2026-10-08 11:27 UTC801
        };
9c05b1dd327aatagen2026-10-08 11:27 UTC802
    }
9c05b1dd327aatagen2026-10-08 11:27 UTC803
    let Ok(url) = Url::parse(input) else {
9c05b1dd327aatagen2026-10-08 11:27 UTC804
        return invalid("it isn't a valid url");
9c05b1dd327aatagen2026-10-08 11:27 UTC805
    };
9c05b1dd327aatagen2026-10-08 11:27 UTC806
    if !url.username().is_empty() || url.password().is_some() {
9c05b1dd327aatagen2026-10-08 11:27 UTC807
        return invalid("it has a username in it");
9c05b1dd327aatagen2026-10-08 11:27 UTC808
    }
9c05b1dd327aatagen2026-10-08 11:27 UTC809
    let loopback = match url.host() {
9c05b1dd327aatagen2026-10-08 11:27 UTC810
        None => return invalid("it has no host"),
9c05b1dd327aatagen2026-10-08 11:27 UTC811
        Some(url::Host::Domain(domain)) => domain.eq_ignore_ascii_case("localhost"),
9c05b1dd327aatagen2026-10-08 11:27 UTC812
        Some(url::Host::Ipv4(address)) => address.is_loopback(),
9c05b1dd327aatagen2026-10-08 11:27 UTC813
        Some(url::Host::Ipv6(address)) => address.is_loopback(),
9c05b1dd327aatagen2026-10-08 11:27 UTC814
    };
9c05b1dd327aatagen2026-10-08 11:27 UTC815
    match url.scheme() {
9c05b1dd327aatagen2026-10-08 11:27 UTC816
        "https" => Ok(input),
9c05b1dd327aatagen2026-10-08 11:27 UTC817
        "http" if allow_insecure_loopback && loopback => Ok(input),
9c05b1dd327aatagen2026-10-08 11:27 UTC818
        "http" if allow_insecure_loopback => invalid("plain http only reaches loopback hosts"),
9c05b1dd327aatagen2026-10-08 11:27 UTC819
        "http" => invalid("it isn't https"),
9c05b1dd327aatagen2026-10-08 11:27 UTC820
        _ => invalid("only http and https are spoken"),
9c05b1dd327aatagen2026-10-08 11:27 UTC821
    }
9c05b1dd327aatagen2026-10-08 11:27 UTC822
}
9c05b1dd327aatagen2026-10-08 11:27 UTC823
713205a95af2atagen2026-09-26 09:24 UTC824
pub(crate) fn account_error(
96fdf90aacd6atagen2026-09-08 08:24 UTC825
    context: &'static str,
62fe32759529atagen2026-09-26 08:50 UTC826
    source: impl std::error::Error + SendOutsideWasm + SyncOutsideWasm + 'static,
96fdf90aacd6atagen2026-09-08 08:24 UTC827
) -> AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC828
    AccountError::Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC829
        context,
9a25b6ce66f4atagen2026-10-08 11:15 UTC830
        kind: crate::error::classify(&source),
9a25b6ce66f4atagen2026-10-08 11:15 UTC831
        server: ServerError::find(&source),
96fdf90aacd6atagen2026-09-08 08:24 UTC832
        source: Box::new(source),
96fdf90aacd6atagen2026-09-08 08:24 UTC833
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC834
}