atagen

macltok

public
Star0
Watch0
6f1d6591c10d24ed02cad8602671b55d6cc21374
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;
713205a95af2atagen2026-09-26 09:24 UTC3
use std::{collections::HashMap, sync::Arc};
96fdf90aacd6atagen2026-09-08 08:24 UTC4
96fdf90aacd6atagen2026-09-08 08:24 UTC5
use matrix_sdk::{
6f1d6591c10datagen2026-09-26 09:52 UTC6
    Client, ClientBuilder, SendOutsideWasm, SessionMeta, SyncOutsideWasm, ThreadingSupport,
96fdf90aacd6atagen2026-09-08 08:24 UTC7
    authentication::{
96fdf90aacd6atagen2026-09-08 08:24 UTC8
        AuthSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC9
        matrix::MatrixSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC10
        oauth::{
96fdf90aacd6atagen2026-09-08 08:24 UTC11
            ClientId, ClientRegistrationData, CsrfToken, OAuthSession, UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC12
            registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
96fdf90aacd6atagen2026-09-08 08:24 UTC13
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC14
    },
62fe32759529atagen2026-09-26 08:50 UTC15
    cross_process_lock::CrossProcessLockConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC16
    encryption::{BackupDownloadStrategy, EncryptionSettings},
6f1d6591c10datagen2026-09-26 09:52 UTC17
    ruma::{OwnedDeviceId, OwnedUserId, serde::Raw},
96fdf90aacd6atagen2026-09-08 08:24 UTC18
};
96fdf90aacd6atagen2026-09-08 08:24 UTC19
use serde::{Deserialize, Serialize};
96fdf90aacd6atagen2026-09-08 08:24 UTC20
use thiserror::Error;
96fdf90aacd6atagen2026-09-08 08:24 UTC21
use url::Url;
96fdf90aacd6atagen2026-09-08 08:24 UTC22
713205a95af2atagen2026-09-26 09:24 UTC23
use crate::{
713205a95af2atagen2026-09-26 09:24 UTC24
    ClientHandle, Engine, EngineConfig, EngineError, EventStream, SessionStore, error::SourceError,
713205a95af2atagen2026-09-26 09:24 UTC25
};
96fdf90aacd6atagen2026-09-08 08:24 UTC26
96fdf90aacd6atagen2026-09-08 08:24 UTC27
pub struct Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC28
    client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC29
}
96fdf90aacd6atagen2026-09-08 08:24 UTC30
31ce2453c82fatagen2026-09-24 13:07 UTC31
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC32
pub struct PasswordLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC33
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC34
    pub username: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC35
    pub password: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC36
    pub device_display_name: Option<String>,
fa9114fcf75fatagen2026-09-24 12:51 UTC37
    /// set after a soft logout so the store's keys still belong to us
fa9114fcf75fatagen2026-09-24 12:51 UTC38
    pub device_id: Option<OwnedDeviceId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC39
}
96fdf90aacd6atagen2026-09-08 08:24 UTC40
31ce2453c82fatagen2026-09-24 13:07 UTC41
impl PasswordLogin {
31ce2453c82fatagen2026-09-24 13:07 UTC42
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC43
    pub fn new(
31ce2453c82fatagen2026-09-24 13:07 UTC44
        homeserver: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC45
        username: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC46
        password: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC47
    ) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC48
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC49
            homeserver: homeserver.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC50
            username: username.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC51
            password: password.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC52
            device_display_name: None,
31ce2453c82fatagen2026-09-24 13:07 UTC53
            device_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC54
        }
31ce2453c82fatagen2026-09-24 13:07 UTC55
    }
31ce2453c82fatagen2026-09-24 13:07 UTC56
31ce2453c82fatagen2026-09-24 13:07 UTC57
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC58
    pub fn with_device_display_name(mut self, name: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC59
        self.device_display_name = Some(name.into());
31ce2453c82fatagen2026-09-24 13:07 UTC60
        self
31ce2453c82fatagen2026-09-24 13:07 UTC61
    }
31ce2453c82fatagen2026-09-24 13:07 UTC62
31ce2453c82fatagen2026-09-24 13:07 UTC63
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC64
    pub fn with_device_id(mut self, device_id: OwnedDeviceId) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC65
        self.device_id = Some(device_id);
31ce2453c82fatagen2026-09-24 13:07 UTC66
        self
31ce2453c82fatagen2026-09-24 13:07 UTC67
    }
31ce2453c82fatagen2026-09-24 13:07 UTC68
}
31ce2453c82fatagen2026-09-24 13:07 UTC69
31ce2453c82fatagen2026-09-24 13:07 UTC70
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC71
pub struct OAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC72
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC73
    pub redirect_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC74
    pub client_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC75
    pub client_name: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC76
    pub client_id: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC77
    pub device_id: Option<OwnedDeviceId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC78
    pub application_type: OAuthApplicationType,
96fdf90aacd6atagen2026-09-08 08:24 UTC79
}
96fdf90aacd6atagen2026-09-08 08:24 UTC80
31ce2453c82fatagen2026-09-24 13:07 UTC81
impl OAuthLogin {
31ce2453c82fatagen2026-09-24 13:07 UTC82
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC83
    pub fn new(
31ce2453c82fatagen2026-09-24 13:07 UTC84
        homeserver: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC85
        redirect_uri: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC86
        client_uri: impl Into<String>,
31ce2453c82fatagen2026-09-24 13:07 UTC87
        application_type: OAuthApplicationType,
31ce2453c82fatagen2026-09-24 13:07 UTC88
    ) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC89
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC90
            homeserver: homeserver.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC91
            redirect_uri: redirect_uri.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC92
            client_uri: client_uri.into(),
31ce2453c82fatagen2026-09-24 13:07 UTC93
            client_name: None,
31ce2453c82fatagen2026-09-24 13:07 UTC94
            client_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC95
            device_id: None,
31ce2453c82fatagen2026-09-24 13:07 UTC96
            application_type,
31ce2453c82fatagen2026-09-24 13:07 UTC97
        }
31ce2453c82fatagen2026-09-24 13:07 UTC98
    }
31ce2453c82fatagen2026-09-24 13:07 UTC99
31ce2453c82fatagen2026-09-24 13:07 UTC100
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC101
    pub fn with_client_name(mut self, name: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC102
        self.client_name = Some(name.into());
31ce2453c82fatagen2026-09-24 13:07 UTC103
        self
31ce2453c82fatagen2026-09-24 13:07 UTC104
    }
31ce2453c82fatagen2026-09-24 13:07 UTC105
31ce2453c82fatagen2026-09-24 13:07 UTC106
    /// a statically registered client id, skipping dynamic registration
31ce2453c82fatagen2026-09-24 13:07 UTC107
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC108
    pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC109
        self.client_id = Some(client_id.into());
31ce2453c82fatagen2026-09-24 13:07 UTC110
        self
31ce2453c82fatagen2026-09-24 13:07 UTC111
    }
31ce2453c82fatagen2026-09-24 13:07 UTC112
31ce2453c82fatagen2026-09-24 13:07 UTC113
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC114
    pub fn with_device_id(mut self, device_id: OwnedDeviceId) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC115
        self.device_id = Some(device_id);
31ce2453c82fatagen2026-09-24 13:07 UTC116
        self
31ce2453c82fatagen2026-09-24 13:07 UTC117
    }
31ce2453c82fatagen2026-09-24 13:07 UTC118
}
31ce2453c82fatagen2026-09-24 13:07 UTC119
96fdf90aacd6atagen2026-09-08 08:24 UTC120
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC121
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC122
pub enum OAuthApplicationType {
96fdf90aacd6atagen2026-09-08 08:24 UTC123
    Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC124
    Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC125
}
96fdf90aacd6atagen2026-09-08 08:24 UTC126
96fdf90aacd6atagen2026-09-08 08:24 UTC127
pub struct PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC128
    client: Client,
713205a95af2atagen2026-09-26 09:24 UTC129
    sessions: Arc<dyn SessionStore>,
96fdf90aacd6atagen2026-09-08 08:24 UTC130
    authorization_url: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC131
    state: Option<CsrfToken>,
96fdf90aacd6atagen2026-09-08 08:24 UTC132
}
96fdf90aacd6atagen2026-09-08 08:24 UTC133
96fdf90aacd6atagen2026-09-08 08:24 UTC134
impl PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC135
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC136
    pub fn authorization_url(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC137
        &self.authorization_url
96fdf90aacd6atagen2026-09-08 08:24 UTC138
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC139
96fdf90aacd6atagen2026-09-08 08:24 UTC140
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC141
    pub fn registered_client_id(&self) -> String {
96fdf90aacd6atagen2026-09-08 08:24 UTC142
        self.client
96fdf90aacd6atagen2026-09-08 08:24 UTC143
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC144
            .client_id()
96fdf90aacd6atagen2026-09-08 08:24 UTC145
            .expect("OAuth client was registered before authorization")
96fdf90aacd6atagen2026-09-08 08:24 UTC146
            .as_str()
96fdf90aacd6atagen2026-09-08 08:24 UTC147
            .to_owned()
96fdf90aacd6atagen2026-09-08 08:24 UTC148
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC149
96fdf90aacd6atagen2026-09-08 08:24 UTC150
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC151
    pub const fn is_pending(&self) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC152
        self.state.is_some()
96fdf90aacd6atagen2026-09-08 08:24 UTC153
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC154
96fdf90aacd6atagen2026-09-08 08:24 UTC155
    pub async fn finish(&mut self, callback_url: &str) -> Result<Session, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC156
        let callback_url = Url::parse(callback_url)
96fdf90aacd6atagen2026-09-08 08:24 UTC157
            .map_err(|error| account_error("parsing the OAuth callback URL", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC158
        let state = self
96fdf90aacd6atagen2026-09-08 08:24 UTC159
            .state
96fdf90aacd6atagen2026-09-08 08:24 UTC160
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC161
            .ok_or(AccountError::OAuthCallbackAlreadySubmitted)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC162
        let oauth = self.client.oauth();
96fdf90aacd6atagen2026-09-08 08:24 UTC163
        if let Err(error) = oauth.finish_login(callback_url.into()).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC164
            oauth.abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC165
            return Err(account_error("OAuth login", error));
96fdf90aacd6atagen2026-09-08 08:24 UTC166
        }
713205a95af2atagen2026-09-26 09:24 UTC167
        Session::saved(self.client.clone(), &*self.sessions).await
96fdf90aacd6atagen2026-09-08 08:24 UTC168
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC169
96fdf90aacd6atagen2026-09-08 08:24 UTC170
    pub async fn abort(mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC171
        if let Some(state) = self.state.take() {
96fdf90aacd6atagen2026-09-08 08:24 UTC172
            self.client.oauth().abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC173
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC174
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC175
}
96fdf90aacd6atagen2026-09-08 08:24 UTC176
96fdf90aacd6atagen2026-09-08 08:24 UTC177
pub struct StoreConfig {
62fe32759529atagen2026-09-26 08:50 UTC178
    location: StoreLocation,
96fdf90aacd6atagen2026-09-08 08:24 UTC179
    passphrase: String,
62fe32759529atagen2026-09-26 08:50 UTC180
    lock_holder: Option<String>,
62fe32759529atagen2026-09-26 08:50 UTC181
}
62fe32759529atagen2026-09-26 08:50 UTC182
62fe32759529atagen2026-09-26 08:50 UTC183
enum StoreLocation {
62fe32759529atagen2026-09-26 08:50 UTC184
    #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC185
    Sqlite(PathBuf),
62fe32759529atagen2026-09-26 08:50 UTC186
    #[cfg(feature = "indexeddb")]
62fe32759529atagen2026-09-26 08:50 UTC187
    IndexedDb(String),
96fdf90aacd6atagen2026-09-08 08:24 UTC188
}
96fdf90aacd6atagen2026-09-08 08:24 UTC189
96fdf90aacd6atagen2026-09-08 08:24 UTC190
impl StoreConfig {
62fe32759529atagen2026-09-26 08:50 UTC191
    #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC192
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC193
    pub fn sqlite(path: impl Into<PathBuf>, passphrase: impl Into<String>) -> Self {
62fe32759529atagen2026-09-26 08:50 UTC194
        Self {
62fe32759529atagen2026-09-26 08:50 UTC195
            location: StoreLocation::Sqlite(path.into()),
62fe32759529atagen2026-09-26 08:50 UTC196
            passphrase: passphrase.into(),
62fe32759529atagen2026-09-26 08:50 UTC197
            lock_holder: None,
62fe32759529atagen2026-09-26 08:50 UTC198
        }
62fe32759529atagen2026-09-26 08:50 UTC199
    }
62fe32759529atagen2026-09-26 08:50 UTC200
62fe32759529atagen2026-09-26 08:50 UTC201
    #[cfg(feature = "indexeddb")]
96fdf90aacd6atagen2026-09-08 08:24 UTC202
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC203
    pub fn indexeddb(name: impl Into<String>, passphrase: impl Into<String>) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC204
        Self {
62fe32759529atagen2026-09-26 08:50 UTC205
            location: StoreLocation::IndexedDb(name.into()),
96fdf90aacd6atagen2026-09-08 08:24 UTC206
            passphrase: passphrase.into(),
62fe32759529atagen2026-09-26 08:50 UTC207
            lock_holder: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC208
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC209
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC210
62fe32759529atagen2026-09-26 08:50 UTC211
    /// the sdk names every client `main`, so two tabs on one store would both think they hold its lock
96fdf90aacd6atagen2026-09-08 08:24 UTC212
    #[must_use]
62fe32759529atagen2026-09-26 08:50 UTC213
    pub fn with_lock_holder(mut self, holder: impl Into<String>) -> Self {
62fe32759529atagen2026-09-26 08:50 UTC214
        self.lock_holder = Some(holder.into());
62fe32759529atagen2026-09-26 08:50 UTC215
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC216
    }
713205a95af2atagen2026-09-26 09:24 UTC217
713205a95af2atagen2026-09-26 09:24 UTC218
    /// delete the store's databases, once nothing has them open
713205a95af2atagen2026-09-26 09:24 UTC219
    pub async fn delete(&self) -> Result<(), AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC220
        match &self.location {
713205a95af2atagen2026-09-26 09:24 UTC221
            #[cfg(feature = "sqlite")]
713205a95af2atagen2026-09-26 09:24 UTC222
            StoreLocation::Sqlite(path) => crate::session_store::delete_sqlite_store(path)
713205a95af2atagen2026-09-26 09:24 UTC223
                .map_err(|error| account_error("deleting the Matrix store", error)),
713205a95af2atagen2026-09-26 09:24 UTC224
            #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC225
            StoreLocation::IndexedDb(name) => crate::session_store::delete_indexeddb_store(name)
713205a95af2atagen2026-09-26 09:24 UTC226
                .await
713205a95af2atagen2026-09-26 09:24 UTC227
                .map_err(|error| account_error("deleting the Matrix store", error)),
713205a95af2atagen2026-09-26 09:24 UTC228
        }
713205a95af2atagen2026-09-26 09:24 UTC229
    }
713205a95af2atagen2026-09-26 09:24 UTC230
713205a95af2atagen2026-09-26 09:24 UTC231
    pub(crate) fn passphrase(&self) -> &str {
713205a95af2atagen2026-09-26 09:24 UTC232
        &self.passphrase
713205a95af2atagen2026-09-26 09:24 UTC233
    }
713205a95af2atagen2026-09-26 09:24 UTC234
713205a95af2atagen2026-09-26 09:24 UTC235
    #[cfg(feature = "indexeddb")]
713205a95af2atagen2026-09-26 09:24 UTC236
    pub(crate) fn indexeddb_name(&self) -> &str {
713205a95af2atagen2026-09-26 09:24 UTC237
        let StoreLocation::IndexedDb(name) = &self.location;
713205a95af2atagen2026-09-26 09:24 UTC238
        name
713205a95af2atagen2026-09-26 09:24 UTC239
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC240
}
96fdf90aacd6atagen2026-09-08 08:24 UTC241
713205a95af2atagen2026-09-26 09:24 UTC242
/// Serializable authentication secrets. A `SessionStore` keeps it, and it must
713205a95af2atagen2026-09-26 09:24 UTC243
/// be replaced when `Event::SessionTokensChanged` arrives.
96fdf90aacd6atagen2026-09-08 08:24 UTC244
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC245
pub struct SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC246
    homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC247
    authentication: AuthenticationRecord,
96fdf90aacd6atagen2026-09-08 08:24 UTC248
}
96fdf90aacd6atagen2026-09-08 08:24 UTC249
96fdf90aacd6atagen2026-09-08 08:24 UTC250
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC251
#[serde(tag = "type", content = "session", rename_all = "snake_case")]
96fdf90aacd6atagen2026-09-08 08:24 UTC252
enum AuthenticationRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC253
    Matrix(MatrixSession),
96fdf90aacd6atagen2026-09-08 08:24 UTC254
    OAuth {
96fdf90aacd6atagen2026-09-08 08:24 UTC255
        client_id: ClientId,
96fdf90aacd6atagen2026-09-08 08:24 UTC256
        user: UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC257
    },
6f1d6591c10datagen2026-09-26 09:52 UTC258
    SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC259
        meta: SessionMeta,
6f1d6591c10datagen2026-09-26 09:52 UTC260
        client_id: Option<ClientId>,
6f1d6591c10datagen2026-09-26 09:52 UTC261
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC262
}
96fdf90aacd6atagen2026-09-08 08:24 UTC263
96fdf90aacd6atagen2026-09-08 08:24 UTC264
impl SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC265
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC266
    pub fn homeserver(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC267
        &self.homeserver
96fdf90aacd6atagen2026-09-08 08:24 UTC268
    }
6f1d6591c10datagen2026-09-26 09:52 UTC269
6f1d6591c10datagen2026-09-26 09:52 UTC270
    pub(crate) fn soft_logged_out(self) -> Self {
6f1d6591c10datagen2026-09-26 09:52 UTC271
        let authentication = match self.authentication {
6f1d6591c10datagen2026-09-26 09:52 UTC272
            AuthenticationRecord::Matrix(session) => AuthenticationRecord::SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC273
                meta: session.meta,
6f1d6591c10datagen2026-09-26 09:52 UTC274
                client_id: None,
6f1d6591c10datagen2026-09-26 09:52 UTC275
            },
6f1d6591c10datagen2026-09-26 09:52 UTC276
            AuthenticationRecord::OAuth { client_id, user } => {
6f1d6591c10datagen2026-09-26 09:52 UTC277
                AuthenticationRecord::SoftLoggedOut {
6f1d6591c10datagen2026-09-26 09:52 UTC278
                    meta: user.meta,
6f1d6591c10datagen2026-09-26 09:52 UTC279
                    client_id: Some(client_id),
6f1d6591c10datagen2026-09-26 09:52 UTC280
                }
6f1d6591c10datagen2026-09-26 09:52 UTC281
            }
6f1d6591c10datagen2026-09-26 09:52 UTC282
            soft @ AuthenticationRecord::SoftLoggedOut { .. } => soft,
6f1d6591c10datagen2026-09-26 09:52 UTC283
        };
6f1d6591c10datagen2026-09-26 09:52 UTC284
        Self {
6f1d6591c10datagen2026-09-26 09:52 UTC285
            homeserver: self.homeserver,
6f1d6591c10datagen2026-09-26 09:52 UTC286
            authentication,
6f1d6591c10datagen2026-09-26 09:52 UTC287
        }
6f1d6591c10datagen2026-09-26 09:52 UTC288
    }
6f1d6591c10datagen2026-09-26 09:52 UTC289
}
6f1d6591c10datagen2026-09-26 09:52 UTC290
6f1d6591c10datagen2026-09-26 09:52 UTC291
pub enum Restore {
6f1d6591c10datagen2026-09-26 09:52 UTC292
    Resumed(Session),
6f1d6591c10datagen2026-09-26 09:52 UTC293
    SoftLoggedOut(SoftLogout),
6f1d6591c10datagen2026-09-26 09:52 UTC294
}
6f1d6591c10datagen2026-09-26 09:52 UTC295
6f1d6591c10datagen2026-09-26 09:52 UTC296
/// who to log in again as. the store keeps this device's e2ee keys, so a login
6f1d6591c10datagen2026-09-26 09:52 UTC297
/// as any other device would orphan them
6f1d6591c10datagen2026-09-26 09:52 UTC298
#[derive(Clone, Debug)]
6f1d6591c10datagen2026-09-26 09:52 UTC299
#[non_exhaustive]
6f1d6591c10datagen2026-09-26 09:52 UTC300
pub struct SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC301
    pub homeserver: String,
6f1d6591c10datagen2026-09-26 09:52 UTC302
    pub user_id: OwnedUserId,
6f1d6591c10datagen2026-09-26 09:52 UTC303
    pub device_id: OwnedDeviceId,
6f1d6591c10datagen2026-09-26 09:52 UTC304
    /// set when the login was OAuth, for `OAuthLogin::with_client_id`
6f1d6591c10datagen2026-09-26 09:52 UTC305
    pub client_id: Option<String>,
6f1d6591c10datagen2026-09-26 09:52 UTC306
}
6f1d6591c10datagen2026-09-26 09:52 UTC307
6f1d6591c10datagen2026-09-26 09:52 UTC308
impl SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC309
    #[must_use]
6f1d6591c10datagen2026-09-26 09:52 UTC310
    pub fn password_login(&self, password: impl Into<String>) -> PasswordLogin {
6f1d6591c10datagen2026-09-26 09:52 UTC311
        PasswordLogin::new(self.homeserver.clone(), self.user_id.as_str(), password)
6f1d6591c10datagen2026-09-26 09:52 UTC312
            .with_device_id(self.device_id.clone())
6f1d6591c10datagen2026-09-26 09:52 UTC313
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC314
}
96fdf90aacd6atagen2026-09-08 08:24 UTC315
96fdf90aacd6atagen2026-09-08 08:24 UTC316
#[derive(Debug, Error)]
96fdf90aacd6atagen2026-09-08 08:24 UTC317
pub enum AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC318
    #[error("{context} failed")]
96fdf90aacd6atagen2026-09-08 08:24 UTC319
    Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC320
        context: &'static str,
96fdf90aacd6atagen2026-09-08 08:24 UTC321
        #[source]
62fe32759529atagen2026-09-26 08:50 UTC322
        source: Box<SourceError>,
96fdf90aacd6atagen2026-09-08 08:24 UTC323
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC324
    #[error("the homeserver returned no authenticated session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC325
    MissingSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC326
    #[error("the SDK returned an unsupported authentication session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC327
    UnsupportedAuthentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC328
    #[error("the OAuth callback was already submitted")]
96fdf90aacd6atagen2026-09-08 08:24 UTC329
    OAuthCallbackAlreadySubmitted,
96fdf90aacd6atagen2026-09-08 08:24 UTC330
    #[error(transparent)]
96fdf90aacd6atagen2026-09-08 08:24 UTC331
    Engine(#[from] EngineError),
96fdf90aacd6atagen2026-09-08 08:24 UTC332
}
96fdf90aacd6atagen2026-09-08 08:24 UTC333
96fdf90aacd6atagen2026-09-08 08:24 UTC334
impl Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC335
    pub async fn login_password(
96fdf90aacd6atagen2026-09-08 08:24 UTC336
        login: PasswordLogin,
713205a95af2atagen2026-09-26 09:24 UTC337
        sessions: &dyn SessionStore,
96fdf90aacd6atagen2026-09-08 08:24 UTC338
    ) -> Result<Self, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC339
        let client = matrix_client_builder(sessions.store())
96fdf90aacd6atagen2026-09-08 08:24 UTC340
            .server_name_or_homeserver_url(login.homeserver.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC341
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC342
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC343
            .map_err(|error| account_error("building the Matrix client", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC344
        let mut request = client
96fdf90aacd6atagen2026-09-08 08:24 UTC345
            .matrix_auth()
96fdf90aacd6atagen2026-09-08 08:24 UTC346
            .login_username(login.username.trim(), &login.password)
96fdf90aacd6atagen2026-09-08 08:24 UTC347
            .request_refresh_token();
96fdf90aacd6atagen2026-09-08 08:24 UTC348
        if let Some(name) = login.device_display_name.as_deref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC349
            request = request.initial_device_display_name(name);
96fdf90aacd6atagen2026-09-08 08:24 UTC350
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC351
        if let Some(device_id) = login.device_id.as_deref() {
fa9114fcf75fatagen2026-09-24 12:51 UTC352
            request = request.device_id(device_id.as_str());
fa9114fcf75fatagen2026-09-24 12:51 UTC353
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC354
        request
96fdf90aacd6atagen2026-09-08 08:24 UTC355
            .send()
96fdf90aacd6atagen2026-09-08 08:24 UTC356
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC357
            .map_err(|error| account_error("password login", error))?;
713205a95af2atagen2026-09-26 09:24 UTC358
        Self::saved(client, sessions).await
96fdf90aacd6atagen2026-09-08 08:24 UTC359
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC360
96fdf90aacd6atagen2026-09-08 08:24 UTC361
    pub async fn begin_oauth(
96fdf90aacd6atagen2026-09-08 08:24 UTC362
        login: OAuthLogin,
713205a95af2atagen2026-09-26 09:24 UTC363
        sessions: Arc<dyn SessionStore>,
96fdf90aacd6atagen2026-09-08 08:24 UTC364
    ) -> Result<PendingOAuthLogin, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC365
        let redirect_uri = Url::parse(login.redirect_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC366
            .map_err(|error| account_error("parsing the OAuth redirect URI", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC367
        let client_uri = Url::parse(login.client_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC368
            .map_err(|error| account_error("parsing the OAuth client URI", error))?;
713205a95af2atagen2026-09-26 09:24 UTC369
        let client = matrix_client_builder(sessions.store())
96fdf90aacd6atagen2026-09-08 08:24 UTC370
            .server_name_or_homeserver_url(login.homeserver.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC371
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC372
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC373
            .map_err(|error| account_error("building the Matrix client", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC374
        let application_type = match login.application_type {
96fdf90aacd6atagen2026-09-08 08:24 UTC375
            OAuthApplicationType::Native => ApplicationType::Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC376
            OAuthApplicationType::Web => ApplicationType::Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC377
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC378
        let mut metadata = ClientMetadata::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC379
            application_type,
96fdf90aacd6atagen2026-09-08 08:24 UTC380
            vec![OAuthGrantType::AuthorizationCode {
96fdf90aacd6atagen2026-09-08 08:24 UTC381
                redirect_uris: vec![redirect_uri.clone()],
96fdf90aacd6atagen2026-09-08 08:24 UTC382
            }],
96fdf90aacd6atagen2026-09-08 08:24 UTC383
            Localized::new(client_uri, []),
96fdf90aacd6atagen2026-09-08 08:24 UTC384
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC385
        metadata.client_name = login.client_name.map(|name| Localized::new(name, []));
96fdf90aacd6atagen2026-09-08 08:24 UTC386
        let metadata = Raw::new(&metadata)
96fdf90aacd6atagen2026-09-08 08:24 UTC387
            .map_err(|error| account_error("serializing OAuth client metadata", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC388
        let mut registration = ClientRegistrationData::new(metadata);
96fdf90aacd6atagen2026-09-08 08:24 UTC389
        if let Some(client_id) = login.client_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC390
            registration.static_registrations = Some(HashMap::from([(
96fdf90aacd6atagen2026-09-08 08:24 UTC391
                client.homeserver(),
96fdf90aacd6atagen2026-09-08 08:24 UTC392
                ClientId::new(client_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC393
            )]));
96fdf90aacd6atagen2026-09-08 08:24 UTC394
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC395
        let authorization = client
96fdf90aacd6atagen2026-09-08 08:24 UTC396
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC397
            .login(redirect_uri, login.device_id, Some(registration), None)
96fdf90aacd6atagen2026-09-08 08:24 UTC398
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC399
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC400
            .map_err(|error| account_error("starting OAuth login", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC401
        Ok(PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC402
            client,
713205a95af2atagen2026-09-26 09:24 UTC403
            sessions,
96fdf90aacd6atagen2026-09-08 08:24 UTC404
            authorization_url: authorization.url.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC405
            state: Some(authorization.state),
96fdf90aacd6atagen2026-09-08 08:24 UTC406
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC407
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC408
713205a95af2atagen2026-09-26 09:24 UTC409
    /// Restore the record `sessions` holds, or `None` when it holds none.
96fdf90aacd6atagen2026-09-08 08:24 UTC410
    /// The caller must also handle refresh, rejection, and explicit logout.
6f1d6591c10datagen2026-09-26 09:52 UTC411
    pub async fn restore(sessions: &dyn SessionStore) -> Result<Option<Restore>, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC412
        let Some(record) = sessions.load().await? else {
713205a95af2atagen2026-09-26 09:24 UTC413
            return Ok(None);
713205a95af2atagen2026-09-26 09:24 UTC414
        };
713205a95af2atagen2026-09-26 09:24 UTC415
        let authentication = match record.authentication {
96fdf90aacd6atagen2026-09-08 08:24 UTC416
            AuthenticationRecord::Matrix(session) => AuthSession::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC417
            AuthenticationRecord::OAuth { client_id, user } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC418
                OAuthSession { client_id, user }.into()
96fdf90aacd6atagen2026-09-08 08:24 UTC419
            }
6f1d6591c10datagen2026-09-26 09:52 UTC420
            AuthenticationRecord::SoftLoggedOut { meta, client_id } => {
6f1d6591c10datagen2026-09-26 09:52 UTC421
                return Ok(Some(Restore::SoftLoggedOut(SoftLogout {
6f1d6591c10datagen2026-09-26 09:52 UTC422
                    homeserver: record.homeserver,
6f1d6591c10datagen2026-09-26 09:52 UTC423
                    user_id: meta.user_id,
6f1d6591c10datagen2026-09-26 09:52 UTC424
                    device_id: meta.device_id,
6f1d6591c10datagen2026-09-26 09:52 UTC425
                    client_id: client_id.map(|client_id| client_id.as_str().to_owned()),
6f1d6591c10datagen2026-09-26 09:52 UTC426
                })));
6f1d6591c10datagen2026-09-26 09:52 UTC427
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC428
        };
6f1d6591c10datagen2026-09-26 09:52 UTC429
        let client = matrix_client_builder(sessions.store())
6f1d6591c10datagen2026-09-26 09:52 UTC430
            .homeserver_url(&record.homeserver)
6f1d6591c10datagen2026-09-26 09:52 UTC431
            .build()
6f1d6591c10datagen2026-09-26 09:52 UTC432
            .await
6f1d6591c10datagen2026-09-26 09:52 UTC433
            .map_err(|error| account_error("reopening the Matrix store", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC434
        client
96fdf90aacd6atagen2026-09-08 08:24 UTC435
            .restore_session(authentication)
96fdf90aacd6atagen2026-09-08 08:24 UTC436
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC437
            .map_err(|error| account_error("restoring the authenticated session", error))?;
6f1d6591c10datagen2026-09-26 09:52 UTC438
        Ok(Some(Restore::Resumed(Self { client })))
96fdf90aacd6atagen2026-09-08 08:24 UTC439
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC440
96fdf90aacd6atagen2026-09-08 08:24 UTC441
    pub async fn build(
96fdf90aacd6atagen2026-09-08 08:24 UTC442
        self,
96fdf90aacd6atagen2026-09-08 08:24 UTC443
        config: EngineConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC444
    ) -> Result<(Engine, ClientHandle, EventStream), AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC445
        Engine::from_client(self.client, config)
96fdf90aacd6atagen2026-09-08 08:24 UTC446
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC447
            .map_err(AccountError::from)
96fdf90aacd6atagen2026-09-08 08:24 UTC448
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC449
713205a95af2atagen2026-09-26 09:24 UTC450
    async fn saved(client: Client, sessions: &dyn SessionStore) -> Result<Self, AccountError> {
713205a95af2atagen2026-09-26 09:24 UTC451
        sessions.save(&session_record(&client)?).await?;
713205a95af2atagen2026-09-26 09:24 UTC452
        Ok(Self { client })
96fdf90aacd6atagen2026-09-08 08:24 UTC453
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC454
}
96fdf90aacd6atagen2026-09-08 08:24 UTC455
96fdf90aacd6atagen2026-09-08 08:24 UTC456
pub(crate) fn session_record(client: &Client) -> Result<SessionRecord, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC457
    let authentication = match client.session().ok_or(AccountError::MissingSession)? {
96fdf90aacd6atagen2026-09-08 08:24 UTC458
        AuthSession::Matrix(session) => AuthenticationRecord::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC459
        AuthSession::OAuth(session) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC460
            let OAuthSession { client_id, user } = *session;
96fdf90aacd6atagen2026-09-08 08:24 UTC461
            AuthenticationRecord::OAuth { client_id, user }
96fdf90aacd6atagen2026-09-08 08:24 UTC462
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC463
        _ => return Err(AccountError::UnsupportedAuthentication),
96fdf90aacd6atagen2026-09-08 08:24 UTC464
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC465
    Ok(SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC466
        homeserver: client.homeserver().to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC467
        authentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC468
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC469
}
96fdf90aacd6atagen2026-09-08 08:24 UTC470
96fdf90aacd6atagen2026-09-08 08:24 UTC471
fn encryption_settings() -> EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC472
    EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC473
        auto_enable_cross_signing: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC474
        backup_download_strategy: BackupDownloadStrategy::OneShot,
96fdf90aacd6atagen2026-09-08 08:24 UTC475
        auto_enable_backups: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC476
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC477
}
96fdf90aacd6atagen2026-09-08 08:24 UTC478
96fdf90aacd6atagen2026-09-08 08:24 UTC479
fn matrix_client_builder(store: &StoreConfig) -> ClientBuilder {
62fe32759529atagen2026-09-26 08:50 UTC480
    let mut builder = Client::builder()
96fdf90aacd6atagen2026-09-08 08:24 UTC481
        .with_encryption_settings(encryption_settings())
96fdf90aacd6atagen2026-09-08 08:24 UTC482
        .with_threading_support(ThreadingSupport::Enabled {
96fdf90aacd6atagen2026-09-08 08:24 UTC483
            with_subscriptions: false,
96fdf90aacd6atagen2026-09-08 08:24 UTC484
        })
62fe32759529atagen2026-09-26 08:50 UTC485
        .handle_refresh_tokens();
62fe32759529atagen2026-09-26 08:50 UTC486
    if let Some(holder) = &store.lock_holder {
62fe32759529atagen2026-09-26 08:50 UTC487
        builder = builder.cross_process_store_config(CrossProcessLockConfig::multi_process(holder));
62fe32759529atagen2026-09-26 08:50 UTC488
    }
62fe32759529atagen2026-09-26 08:50 UTC489
    match &store.location {
62fe32759529atagen2026-09-26 08:50 UTC490
        #[cfg(feature = "sqlite")]
62fe32759529atagen2026-09-26 08:50 UTC491
        StoreLocation::Sqlite(path) => builder.sqlite_store(path, Some(&store.passphrase)),
62fe32759529atagen2026-09-26 08:50 UTC492
        #[cfg(feature = "indexeddb")]
62fe32759529atagen2026-09-26 08:50 UTC493
        StoreLocation::IndexedDb(name) => builder.indexeddb_store(name, Some(&store.passphrase)),
62fe32759529atagen2026-09-26 08:50 UTC494
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC495
}
96fdf90aacd6atagen2026-09-08 08:24 UTC496
713205a95af2atagen2026-09-26 09:24 UTC497
pub(crate) fn account_error(
96fdf90aacd6atagen2026-09-08 08:24 UTC498
    context: &'static str,
62fe32759529atagen2026-09-26 08:50 UTC499
    source: impl std::error::Error + SendOutsideWasm + SyncOutsideWasm + 'static,
96fdf90aacd6atagen2026-09-08 08:24 UTC500
) -> AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC501
    AccountError::Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC502
        context,
96fdf90aacd6atagen2026-09-08 08:24 UTC503
        source: Box::new(source),
96fdf90aacd6atagen2026-09-08 08:24 UTC504
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC505
}