atagen

macltok

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

Blame: src/account.rs

96fdf90aacd6atagen2026-09-08 08:24 UTC1
use std::{
96fdf90aacd6atagen2026-09-08 08:24 UTC2
    collections::HashMap,
96fdf90aacd6atagen2026-09-08 08:24 UTC3
    path::{Path, PathBuf},
96fdf90aacd6atagen2026-09-08 08:24 UTC4
};
96fdf90aacd6atagen2026-09-08 08:24 UTC5
96fdf90aacd6atagen2026-09-08 08:24 UTC6
use matrix_sdk::{
96fdf90aacd6atagen2026-09-08 08:24 UTC7
    Client, ClientBuilder, ThreadingSupport,
96fdf90aacd6atagen2026-09-08 08:24 UTC8
    authentication::{
96fdf90aacd6atagen2026-09-08 08:24 UTC9
        AuthSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC10
        matrix::MatrixSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC11
        oauth::{
96fdf90aacd6atagen2026-09-08 08:24 UTC12
            ClientId, ClientRegistrationData, CsrfToken, OAuthSession, UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC13
            registration::{ApplicationType, ClientMetadata, Localized, OAuthGrantType},
96fdf90aacd6atagen2026-09-08 08:24 UTC14
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC15
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC16
    encryption::{BackupDownloadStrategy, EncryptionSettings},
96fdf90aacd6atagen2026-09-08 08:24 UTC17
    ruma::{OwnedDeviceId, 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
96fdf90aacd6atagen2026-09-08 08:24 UTC23
use crate::{ClientHandle, Engine, EngineConfig, EngineError, EventStream};
96fdf90aacd6atagen2026-09-08 08:24 UTC24
96fdf90aacd6atagen2026-09-08 08:24 UTC25
pub struct Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC26
    client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC27
    record: SessionRecord,
96fdf90aacd6atagen2026-09-08 08:24 UTC28
}
96fdf90aacd6atagen2026-09-08 08:24 UTC29
96fdf90aacd6atagen2026-09-08 08:24 UTC30
pub struct PasswordLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC31
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC32
    pub username: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC33
    pub password: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC34
    pub device_display_name: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC35
}
96fdf90aacd6atagen2026-09-08 08:24 UTC36
96fdf90aacd6atagen2026-09-08 08:24 UTC37
pub struct OAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC38
    pub homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC39
    pub redirect_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC40
    pub client_uri: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC41
    pub client_name: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC42
    pub client_id: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC43
    pub device_id: Option<OwnedDeviceId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC44
    pub application_type: OAuthApplicationType,
96fdf90aacd6atagen2026-09-08 08:24 UTC45
}
96fdf90aacd6atagen2026-09-08 08:24 UTC46
96fdf90aacd6atagen2026-09-08 08:24 UTC47
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96fdf90aacd6atagen2026-09-08 08:24 UTC48
pub enum OAuthApplicationType {
96fdf90aacd6atagen2026-09-08 08:24 UTC49
    Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC50
    Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC51
}
96fdf90aacd6atagen2026-09-08 08:24 UTC52
96fdf90aacd6atagen2026-09-08 08:24 UTC53
pub struct PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC54
    client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC55
    authorization_url: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC56
    state: Option<CsrfToken>,
96fdf90aacd6atagen2026-09-08 08:24 UTC57
}
96fdf90aacd6atagen2026-09-08 08:24 UTC58
96fdf90aacd6atagen2026-09-08 08:24 UTC59
impl PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC60
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC61
    pub fn authorization_url(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC62
        &self.authorization_url
96fdf90aacd6atagen2026-09-08 08:24 UTC63
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC64
96fdf90aacd6atagen2026-09-08 08:24 UTC65
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC66
    pub fn registered_client_id(&self) -> String {
96fdf90aacd6atagen2026-09-08 08:24 UTC67
        self.client
96fdf90aacd6atagen2026-09-08 08:24 UTC68
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC69
            .client_id()
96fdf90aacd6atagen2026-09-08 08:24 UTC70
            .expect("OAuth client was registered before authorization")
96fdf90aacd6atagen2026-09-08 08:24 UTC71
            .as_str()
96fdf90aacd6atagen2026-09-08 08:24 UTC72
            .to_owned()
96fdf90aacd6atagen2026-09-08 08:24 UTC73
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC74
96fdf90aacd6atagen2026-09-08 08:24 UTC75
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC76
    pub const fn is_pending(&self) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC77
        self.state.is_some()
96fdf90aacd6atagen2026-09-08 08:24 UTC78
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC79
96fdf90aacd6atagen2026-09-08 08:24 UTC80
    pub async fn finish(&mut self, callback_url: &str) -> Result<Session, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC81
        let callback_url = Url::parse(callback_url)
96fdf90aacd6atagen2026-09-08 08:24 UTC82
            .map_err(|error| account_error("parsing the OAuth callback URL", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC83
        let state = self
96fdf90aacd6atagen2026-09-08 08:24 UTC84
            .state
96fdf90aacd6atagen2026-09-08 08:24 UTC85
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC86
            .ok_or(AccountError::OAuthCallbackAlreadySubmitted)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC87
        let oauth = self.client.oauth();
96fdf90aacd6atagen2026-09-08 08:24 UTC88
        if let Err(error) = oauth.finish_login(callback_url.into()).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC89
            oauth.abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC90
            return Err(account_error("OAuth login", error));
96fdf90aacd6atagen2026-09-08 08:24 UTC91
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC92
        Session::from_client(self.client.clone())
96fdf90aacd6atagen2026-09-08 08:24 UTC93
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC94
96fdf90aacd6atagen2026-09-08 08:24 UTC95
    pub async fn abort(mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC96
        if let Some(state) = self.state.take() {
96fdf90aacd6atagen2026-09-08 08:24 UTC97
            self.client.oauth().abort_login(&state).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC98
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC99
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC100
}
96fdf90aacd6atagen2026-09-08 08:24 UTC101
96fdf90aacd6atagen2026-09-08 08:24 UTC102
pub struct StoreConfig {
96fdf90aacd6atagen2026-09-08 08:24 UTC103
    path: PathBuf,
96fdf90aacd6atagen2026-09-08 08:24 UTC104
    passphrase: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC105
}
96fdf90aacd6atagen2026-09-08 08:24 UTC106
96fdf90aacd6atagen2026-09-08 08:24 UTC107
impl StoreConfig {
96fdf90aacd6atagen2026-09-08 08:24 UTC108
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC109
    pub fn new(path: impl Into<PathBuf>, passphrase: impl Into<String>) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC110
        Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC111
            path: path.into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC112
            passphrase: passphrase.into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC113
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC114
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC115
96fdf90aacd6atagen2026-09-08 08:24 UTC116
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC117
    pub fn path(&self) -> &Path {
96fdf90aacd6atagen2026-09-08 08:24 UTC118
        &self.path
96fdf90aacd6atagen2026-09-08 08:24 UTC119
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC120
}
96fdf90aacd6atagen2026-09-08 08:24 UTC121
96fdf90aacd6atagen2026-09-08 08:24 UTC122
/// Serializable authentication secrets. Store this separately from the
96fdf90aacd6atagen2026-09-08 08:24 UTC123
/// encrypted SQLite store in protected, preferably encrypted storage.
96fdf90aacd6atagen2026-09-08 08:24 UTC124
/// A saved record must be replaced when `Event::SessionTokensChanged` arrives.
96fdf90aacd6atagen2026-09-08 08:24 UTC125
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC126
pub struct SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC127
    homeserver: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC128
    authentication: AuthenticationRecord,
96fdf90aacd6atagen2026-09-08 08:24 UTC129
}
96fdf90aacd6atagen2026-09-08 08:24 UTC130
96fdf90aacd6atagen2026-09-08 08:24 UTC131
#[derive(Clone, Deserialize, Serialize)]
96fdf90aacd6atagen2026-09-08 08:24 UTC132
#[serde(tag = "type", content = "session", rename_all = "snake_case")]
96fdf90aacd6atagen2026-09-08 08:24 UTC133
enum AuthenticationRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC134
    Matrix(MatrixSession),
96fdf90aacd6atagen2026-09-08 08:24 UTC135
    OAuth {
96fdf90aacd6atagen2026-09-08 08:24 UTC136
        client_id: ClientId,
96fdf90aacd6atagen2026-09-08 08:24 UTC137
        user: UserSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC138
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC139
}
96fdf90aacd6atagen2026-09-08 08:24 UTC140
96fdf90aacd6atagen2026-09-08 08:24 UTC141
impl SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC142
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC143
    pub fn homeserver(&self) -> &str {
96fdf90aacd6atagen2026-09-08 08:24 UTC144
        &self.homeserver
96fdf90aacd6atagen2026-09-08 08:24 UTC145
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC146
}
96fdf90aacd6atagen2026-09-08 08:24 UTC147
96fdf90aacd6atagen2026-09-08 08:24 UTC148
#[derive(Debug, Error)]
96fdf90aacd6atagen2026-09-08 08:24 UTC149
pub enum AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC150
    #[error("{context} failed")]
96fdf90aacd6atagen2026-09-08 08:24 UTC151
    Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC152
        context: &'static str,
96fdf90aacd6atagen2026-09-08 08:24 UTC153
        #[source]
96fdf90aacd6atagen2026-09-08 08:24 UTC154
        source: Box<dyn std::error::Error + Send + Sync>,
96fdf90aacd6atagen2026-09-08 08:24 UTC155
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC156
    #[error("the homeserver returned no authenticated session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC157
    MissingSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC158
    #[error("the SDK returned an unsupported authentication session")]
96fdf90aacd6atagen2026-09-08 08:24 UTC159
    UnsupportedAuthentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC160
    #[error("the OAuth callback was already submitted")]
96fdf90aacd6atagen2026-09-08 08:24 UTC161
    OAuthCallbackAlreadySubmitted,
96fdf90aacd6atagen2026-09-08 08:24 UTC162
    #[error(transparent)]
96fdf90aacd6atagen2026-09-08 08:24 UTC163
    Engine(#[from] EngineError),
96fdf90aacd6atagen2026-09-08 08:24 UTC164
}
96fdf90aacd6atagen2026-09-08 08:24 UTC165
96fdf90aacd6atagen2026-09-08 08:24 UTC166
impl Session {
96fdf90aacd6atagen2026-09-08 08:24 UTC167
    pub async fn login_password(
96fdf90aacd6atagen2026-09-08 08:24 UTC168
        login: PasswordLogin,
96fdf90aacd6atagen2026-09-08 08:24 UTC169
        store: StoreConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC170
    ) -> Result<Self, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC171
        let client = matrix_client_builder(&store)
96fdf90aacd6atagen2026-09-08 08:24 UTC172
            .server_name_or_homeserver_url(login.homeserver.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC173
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC174
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC175
            .map_err(|error| account_error("building the Matrix client", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC176
        let mut request = client
96fdf90aacd6atagen2026-09-08 08:24 UTC177
            .matrix_auth()
96fdf90aacd6atagen2026-09-08 08:24 UTC178
            .login_username(login.username.trim(), &login.password)
96fdf90aacd6atagen2026-09-08 08:24 UTC179
            .request_refresh_token();
96fdf90aacd6atagen2026-09-08 08:24 UTC180
        if let Some(name) = login.device_display_name.as_deref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC181
            request = request.initial_device_display_name(name);
96fdf90aacd6atagen2026-09-08 08:24 UTC182
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC183
        request
96fdf90aacd6atagen2026-09-08 08:24 UTC184
            .send()
96fdf90aacd6atagen2026-09-08 08:24 UTC185
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC186
            .map_err(|error| account_error("password login", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC187
        Self::from_client(client)
96fdf90aacd6atagen2026-09-08 08:24 UTC188
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC189
96fdf90aacd6atagen2026-09-08 08:24 UTC190
    pub async fn begin_oauth(
96fdf90aacd6atagen2026-09-08 08:24 UTC191
        login: OAuthLogin,
96fdf90aacd6atagen2026-09-08 08:24 UTC192
        store: StoreConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC193
    ) -> Result<PendingOAuthLogin, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC194
        let redirect_uri = Url::parse(login.redirect_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC195
            .map_err(|error| account_error("parsing the OAuth redirect URI", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC196
        let client_uri = Url::parse(login.client_uri.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC197
            .map_err(|error| account_error("parsing the OAuth client URI", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC198
        let client = matrix_client_builder(&store)
96fdf90aacd6atagen2026-09-08 08:24 UTC199
            .server_name_or_homeserver_url(login.homeserver.trim())
96fdf90aacd6atagen2026-09-08 08:24 UTC200
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC201
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC202
            .map_err(|error| account_error("building the Matrix client", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC203
        let application_type = match login.application_type {
96fdf90aacd6atagen2026-09-08 08:24 UTC204
            OAuthApplicationType::Native => ApplicationType::Native,
96fdf90aacd6atagen2026-09-08 08:24 UTC205
            OAuthApplicationType::Web => ApplicationType::Web,
96fdf90aacd6atagen2026-09-08 08:24 UTC206
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC207
        let mut metadata = ClientMetadata::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC208
            application_type,
96fdf90aacd6atagen2026-09-08 08:24 UTC209
            vec![OAuthGrantType::AuthorizationCode {
96fdf90aacd6atagen2026-09-08 08:24 UTC210
                redirect_uris: vec![redirect_uri.clone()],
96fdf90aacd6atagen2026-09-08 08:24 UTC211
            }],
96fdf90aacd6atagen2026-09-08 08:24 UTC212
            Localized::new(client_uri, []),
96fdf90aacd6atagen2026-09-08 08:24 UTC213
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC214
        metadata.client_name = login.client_name.map(|name| Localized::new(name, []));
96fdf90aacd6atagen2026-09-08 08:24 UTC215
        let metadata = Raw::new(&metadata)
96fdf90aacd6atagen2026-09-08 08:24 UTC216
            .map_err(|error| account_error("serializing OAuth client metadata", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC217
        let mut registration = ClientRegistrationData::new(metadata);
96fdf90aacd6atagen2026-09-08 08:24 UTC218
        if let Some(client_id) = login.client_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC219
            registration.static_registrations = Some(HashMap::from([(
96fdf90aacd6atagen2026-09-08 08:24 UTC220
                client.homeserver(),
96fdf90aacd6atagen2026-09-08 08:24 UTC221
                ClientId::new(client_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC222
            )]));
96fdf90aacd6atagen2026-09-08 08:24 UTC223
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC224
        let authorization = client
96fdf90aacd6atagen2026-09-08 08:24 UTC225
            .oauth()
96fdf90aacd6atagen2026-09-08 08:24 UTC226
            .login(redirect_uri, login.device_id, Some(registration), None)
96fdf90aacd6atagen2026-09-08 08:24 UTC227
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC228
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC229
            .map_err(|error| account_error("starting OAuth login", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC230
        Ok(PendingOAuthLogin {
96fdf90aacd6atagen2026-09-08 08:24 UTC231
            client,
96fdf90aacd6atagen2026-09-08 08:24 UTC232
            authorization_url: authorization.url.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC233
            state: Some(authorization.state),
96fdf90aacd6atagen2026-09-08 08:24 UTC234
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC235
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC236
96fdf90aacd6atagen2026-09-08 08:24 UTC237
    /// Restore a saved record with the same store location and passphrase.
96fdf90aacd6atagen2026-09-08 08:24 UTC238
    /// The caller must also handle refresh, rejection, and explicit logout.
96fdf90aacd6atagen2026-09-08 08:24 UTC239
    pub async fn restore(record: SessionRecord, store: StoreConfig) -> Result<Self, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC240
        let client = matrix_client_builder(&store)
96fdf90aacd6atagen2026-09-08 08:24 UTC241
            .homeserver_url(&record.homeserver)
96fdf90aacd6atagen2026-09-08 08:24 UTC242
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC243
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC244
            .map_err(|error| account_error("reopening the Matrix store", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC245
        let authentication = match record.authentication.clone() {
96fdf90aacd6atagen2026-09-08 08:24 UTC246
            AuthenticationRecord::Matrix(session) => AuthSession::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC247
            AuthenticationRecord::OAuth { client_id, user } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC248
                OAuthSession { client_id, user }.into()
96fdf90aacd6atagen2026-09-08 08:24 UTC249
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC250
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC251
        client
96fdf90aacd6atagen2026-09-08 08:24 UTC252
            .restore_session(authentication)
96fdf90aacd6atagen2026-09-08 08:24 UTC253
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC254
            .map_err(|error| account_error("restoring the authenticated session", error))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC255
        Ok(Self { client, record })
96fdf90aacd6atagen2026-09-08 08:24 UTC256
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC257
96fdf90aacd6atagen2026-09-08 08:24 UTC258
    /// Initial login credentials only. This snapshot does not track later
96fdf90aacd6atagen2026-09-08 08:24 UTC259
    /// token rotations; use `ClientHandle::session_record` after building.
96fdf90aacd6atagen2026-09-08 08:24 UTC260
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC261
    pub fn record(&self) -> SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC262
        self.record.clone()
96fdf90aacd6atagen2026-09-08 08:24 UTC263
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC264
96fdf90aacd6atagen2026-09-08 08:24 UTC265
    pub async fn build(
96fdf90aacd6atagen2026-09-08 08:24 UTC266
        self,
96fdf90aacd6atagen2026-09-08 08:24 UTC267
        config: EngineConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC268
    ) -> Result<(Engine, ClientHandle, EventStream), AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC269
        Engine::from_client(self.client, config)
96fdf90aacd6atagen2026-09-08 08:24 UTC270
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC271
            .map_err(AccountError::from)
96fdf90aacd6atagen2026-09-08 08:24 UTC272
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC273
96fdf90aacd6atagen2026-09-08 08:24 UTC274
    fn from_client(client: Client) -> Result<Self, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC275
        let record = session_record(&client)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC276
        Ok(Self { client, record })
96fdf90aacd6atagen2026-09-08 08:24 UTC277
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC278
}
96fdf90aacd6atagen2026-09-08 08:24 UTC279
96fdf90aacd6atagen2026-09-08 08:24 UTC280
pub(crate) fn session_record(client: &Client) -> Result<SessionRecord, AccountError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC281
    let authentication = match client.session().ok_or(AccountError::MissingSession)? {
96fdf90aacd6atagen2026-09-08 08:24 UTC282
        AuthSession::Matrix(session) => AuthenticationRecord::Matrix(session),
96fdf90aacd6atagen2026-09-08 08:24 UTC283
        AuthSession::OAuth(session) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC284
            let OAuthSession { client_id, user } = *session;
96fdf90aacd6atagen2026-09-08 08:24 UTC285
            AuthenticationRecord::OAuth { client_id, user }
96fdf90aacd6atagen2026-09-08 08:24 UTC286
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC287
        _ => return Err(AccountError::UnsupportedAuthentication),
96fdf90aacd6atagen2026-09-08 08:24 UTC288
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC289
    Ok(SessionRecord {
96fdf90aacd6atagen2026-09-08 08:24 UTC290
        homeserver: client.homeserver().to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC291
        authentication,
96fdf90aacd6atagen2026-09-08 08:24 UTC292
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC293
}
96fdf90aacd6atagen2026-09-08 08:24 UTC294
96fdf90aacd6atagen2026-09-08 08:24 UTC295
fn encryption_settings() -> EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC296
    EncryptionSettings {
96fdf90aacd6atagen2026-09-08 08:24 UTC297
        auto_enable_cross_signing: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC298
        backup_download_strategy: BackupDownloadStrategy::OneShot,
96fdf90aacd6atagen2026-09-08 08:24 UTC299
        auto_enable_backups: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC300
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC301
}
96fdf90aacd6atagen2026-09-08 08:24 UTC302
96fdf90aacd6atagen2026-09-08 08:24 UTC303
fn matrix_client_builder(store: &StoreConfig) -> ClientBuilder {
96fdf90aacd6atagen2026-09-08 08:24 UTC304
    Client::builder()
96fdf90aacd6atagen2026-09-08 08:24 UTC305
        .with_encryption_settings(encryption_settings())
96fdf90aacd6atagen2026-09-08 08:24 UTC306
        .with_threading_support(ThreadingSupport::Enabled {
96fdf90aacd6atagen2026-09-08 08:24 UTC307
            with_subscriptions: false,
96fdf90aacd6atagen2026-09-08 08:24 UTC308
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC309
        .handle_refresh_tokens()
96fdf90aacd6atagen2026-09-08 08:24 UTC310
        .sqlite_store(&store.path, Some(&store.passphrase))
96fdf90aacd6atagen2026-09-08 08:24 UTC311
}
96fdf90aacd6atagen2026-09-08 08:24 UTC312
96fdf90aacd6atagen2026-09-08 08:24 UTC313
fn account_error(
96fdf90aacd6atagen2026-09-08 08:24 UTC314
    context: &'static str,
96fdf90aacd6atagen2026-09-08 08:24 UTC315
    source: impl std::error::Error + Send + Sync + 'static,
96fdf90aacd6atagen2026-09-08 08:24 UTC316
) -> AccountError {
96fdf90aacd6atagen2026-09-08 08:24 UTC317
    AccountError::Operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC318
        context,
96fdf90aacd6atagen2026-09-08 08:24 UTC319
        source: Box::new(source),
96fdf90aacd6atagen2026-09-08 08:24 UTC320
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC321
}