Add support for access tokens using MAS (#83)

* Add support for access tokens using MAS

* use 1.13.0

* Update dependencies

* Harden auth credential selection in matrix link init

Use the same non-empty access-token criterion for auth mode selection and bind the token directly from the branch condition.
Return explicit configuration errors for missing or empty `device_id`/`password` instead of panicking, so invalid auth config fails gracefully.

* Centralize and harden user auth config handling

Move authentication-mode resolution into typed config parsing with ConfigUserAuth,
so downstream login setup consumes validated credentials instead of re-checking raw optional fields.

Enforce explicit password-vs-token selection, validate token/device/user-id requirements in one place,
and normalize empty auth env overrides to unset values for consistent behavior across YAML and environment input.

* Add auth config unit tests

Move auth_config tests into a dedicated cfg test module file to keep production config code compact while preserving behavior coverage. The tests cover password/token mode selection, missing/both auth method rejection, missing device_id, and empty-value handling.

* Use conventional mxlink version requirement

Replace the unconventional wildcard lower-bound expression with a standard semver lower bound for readability and tooling consistency.

---------

Co-authored-by: Slavi Pantaleev <slavi@devture.com>
This commit is contained in:
Taylor Southwick
2026-03-07 00:26:40 -08:00
committed by GitHub
parent 8bd313f0d4
commit 4852d1fe92
9 changed files with 383 additions and 144 deletions

View File

@@ -25,7 +25,7 @@ use crate::agent::Manager as AgentManager;
use crate::entity::catch_up_marker::{
CatchUpMarker, CatchUpMarkerManager, DelayedCatchUpMarkerManager,
};
use crate::entity::cfg::{Avatar, Config};
use crate::entity::cfg::{Avatar, Config, ConfigUserAuth};
use crate::entity::globalconfig::{GlobalConfig, GlobalConfigurationManager};
use crate::entity::roomconfig::{RoomConfig, RoomConfigurationManager};
@@ -395,10 +395,22 @@ 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 user_auth = config.user.auth_config(&config.homeserver.server_name)?;
let login_creds = match user_auth {
ConfigUserAuth::UserPassword { username, password } => {
LoginCredentials::UserPassword(username, password)
}
ConfigUserAuth::AccessToken {
user_id,
device_id,
access_token,
} => LoginCredentials::AccessToken {
user_id,
device_id,
access_token,
},
};
let login_encryption = LoginEncryption::new(
config.user.encryption.recovery_passphrase.clone(),

View File

@@ -29,7 +29,15 @@ 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 = optional_non_empty(value);
}
cfg_env::BAIBOT_USER_ACCESS_TOKEN => {
config.user.access_token = optional_non_empty(value);
}
cfg_env::BAIBOT_USER_DEVICE_ID => {
config.user.device_id = optional_non_empty(value);
}
cfg_env::BAIBOT_USER_ENCRYPTION_RECOVERY_PASSPHRASE => {
config.user.encryption.recovery_passphrase = Some(value);
}
@@ -120,3 +128,7 @@ pub fn load() -> anyhow::Result<Config> {
Ok(config)
}
fn optional_non_empty(value: String) -> Option<String> {
if value.is_empty() { None } else { Some(value) }
}