Add support for access tokens using MAS

This commit is contained in:
Taylor Southwick
2026-03-05 18:57:00 +00:00
parent afc5572d6a
commit 1d8f2b6890
5 changed files with 67 additions and 13 deletions

View File

@@ -5,8 +5,17 @@ homeserver:
user:
mxid_localpart: baibot
# Authentication: set EITHER password OR access_token + device_id.
#
# Password-based login (traditional homeservers):
password: baibot
# Access token login (for MAS/OIDC-enabled homeservers):
# Generate a token via: mas-cli manage issue-compatibility-token <username> [device_id]
# access_token: null
# device_id: null
# The name the bot uses as a display name and when it refers to itself.
# Leave empty to use the default (baibot).
name: baibot

View File

@@ -6,7 +6,7 @@ use mxlink::matrix_sdk::Room;
use mxlink::matrix_sdk::media::{MediaFormat, MediaRequestParameters};
use mxlink::matrix_sdk::ruma::api::client::profile::{AvatarUrl, DisplayName};
use mxlink::matrix_sdk::ruma::{
MilliSecondsSinceUnixEpoch, OwnedUserId, events::room::MediaSource,
MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, events::room::MediaSource,
};
use mxlink::{
@@ -395,10 +395,25 @@ async fn create_matrix_link(config: &Config) -> anyhow::Result<MatrixLink> {
let session_encryption_key = config.persistence.session_encryption_key()?;
let db_dir_path: std::path::PathBuf = config.persistence.db_dir_path()?;
let login_creds = LoginCredentials::UserPassword(
config.user.mxid_localpart.to_owned(),
config.user.password.to_owned(),
);
let login_creds = if let Some(access_token) = &config.user.access_token {
let server_name = &config.homeserver.server_name;
let localpart = &config.user.mxid_localpart;
let user_id = OwnedUserId::try_from(format!("@{localpart}:{server_name}"))
.map_err(|e| anyhow::anyhow!("Invalid user ID: {e}"))?;
let device_id = OwnedDeviceId::from(
config.user.device_id.as_deref().expect("device_id must be set for access token auth"),
);
LoginCredentials::AccessToken {
user_id,
device_id,
access_token: access_token.to_owned(),
}
} else {
LoginCredentials::UserPassword(
config.user.mxid_localpart.to_owned(),
config.user.password.as_deref().expect("password must be set if access_token is not").to_owned(),
)
};
let login_encryption = LoginEncryption::new(
config.user.encryption.recovery_passphrase.clone(),

View File

@@ -29,7 +29,9 @@ pub fn load() -> anyhow::Result<Config> {
cfg_env::BAIBOT_HOMESERVER_SERVER_NAME => config.homeserver.server_name = value,
cfg_env::BAIBOT_HOMESERVER_URL => config.homeserver.url = value,
cfg_env::BAIBOT_USER_MXID_LOCALPART => config.user.mxid_localpart = value,
cfg_env::BAIBOT_USER_PASSWORD => config.user.password = value,
cfg_env::BAIBOT_USER_PASSWORD => config.user.password = Some(value),
cfg_env::BAIBOT_USER_ACCESS_TOKEN => config.user.access_token = Some(value),
cfg_env::BAIBOT_USER_DEVICE_ID => config.user.device_id = Some(value),
cfg_env::BAIBOT_USER_ENCRYPTION_RECOVERY_PASSPHRASE => {
config.user.encryption.recovery_passphrase = Some(value);
}

View File

@@ -127,7 +127,15 @@ impl Avatar {
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigUser {
pub mxid_localpart: String,
pub password: String,
#[serde(default)]
pub password: Option<String>,
#[serde(default)]
pub access_token: Option<String>,
#[serde(default)]
pub device_id: Option<String>,
#[serde(default = "super::defaults::name")]
pub name: String,
@@ -148,12 +156,7 @@ impl ConfigUser {
));
}
if self.password.is_empty() {
return Err(anyhow::anyhow!(
"The user.password ({}) configuration must be set",
super::env::BAIBOT_USER_PASSWORD
));
}
self.validate_auth()?;
if self.name.is_empty() {
return Err(anyhow::anyhow!(
@@ -166,6 +169,29 @@ impl ConfigUser {
Ok(())
}
fn validate_auth(&self) -> anyhow::Result<()> {
let has_password = self.password.as_deref().is_some_and(|p| !p.is_empty());
let has_access_token = self.access_token.as_deref().is_some_and(|t| !t.is_empty());
let has_device_id = self.device_id.as_deref().is_some_and(|d| !d.is_empty());
if !has_password && !has_access_token {
return Err(anyhow::anyhow!(
"Either user.password ({}) or user.access_token ({}) must be set",
super::env::BAIBOT_USER_PASSWORD,
super::env::BAIBOT_USER_ACCESS_TOKEN
));
}
if has_access_token && !has_device_id {
return Err(anyhow::anyhow!(
"user.device_id ({}) must be set when using access token authentication",
super::env::BAIBOT_USER_DEVICE_ID
));
}
Ok(())
}
}
#[derive(Debug, Default, Serialize, Deserialize)]

View File

@@ -5,6 +5,8 @@ pub const BAIBOT_HOMESERVER_URL: &str = "BAIBOT_HOMESERVER_URL";
pub const BAIBOT_USER_MXID_LOCALPART: &str = "BAIBOT_USER_MXID_LOCALPART";
pub const BAIBOT_USER_PASSWORD: &str = "BAIBOT_USER_PASSWORD";
pub const BAIBOT_USER_ACCESS_TOKEN: &str = "BAIBOT_USER_ACCESS_TOKEN";
pub const BAIBOT_USER_DEVICE_ID: &str = "BAIBOT_USER_DEVICE_ID";
pub const BAIBOT_USER_NAME: &str = "BAIBOT_USER_NAME";
pub const BAIBOT_USER_AVATAR: &str = "BAIBOT_USER_AVATAR";
pub const BAIBOT_USER_ENCRYPTION_RECOVERY_PASSPHRASE: &str =