atagen

macltok

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