Add support for access token based authentication
* Extend the configuration format in order to allow access_token, user_id and device_id instead of username and password * Move the matrix login logic outside of login_and_sync for clarity * Add support for access token based session resuming instead of logging in every time (thus creating a new device each time the service starts up) * Delay the startup of feed reader loops until after the matrix module has had a chance to actually check authentication This change is quite involved and there are a few caveats, namely an intentional race condition between the feed reader loops and matrix authentication, as well as significantly different behaviors depending on which authentication scheme is being used: password based authentication requires an API call while resuming a session using an access token does not.
This commit is contained in:
@ -27,12 +27,26 @@ pub struct FeedConfig {
|
||||
pub delay: u64
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum AuthConfig {
|
||||
PasswordAuthConfig {
|
||||
username: String,
|
||||
password: String,
|
||||
},
|
||||
TokenAuthConfig {
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
access_token: String,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
pub default_room: String,
|
||||
pub feeds: Vec<FeedConfig>,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
#[serde(flatten)]
|
||||
pub auth: AuthConfig,
|
||||
pub homeserver_uri: String
|
||||
}
|
||||
|
||||
|
16
src/main.rs
16
src/main.rs
@ -24,8 +24,8 @@ mod state;
|
||||
|
||||
use tokio::{
|
||||
time::{sleep, Duration},
|
||||
sync::{Notify, broadcast},
|
||||
task::JoinHandle,
|
||||
sync::broadcast
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -55,17 +55,25 @@ async fn main() -> anyhow::Result<()> {
|
||||
panic!("Failed to initialize feed reader state db: {e:?}")
|
||||
});
|
||||
|
||||
// This message passing channel is used for sending messages to the matrix module,
|
||||
let matrix_ready = Arc::new(Notify::new());
|
||||
|
||||
// This channel is used for sending messages to the matrix module,
|
||||
// it holds a tuple with an HTML message and a list of rooms to post to
|
||||
let (bcast_tx, bcast_rx) = broadcast::channel::<(String, Vec<String>)>(1024);
|
||||
|
||||
let handles: Vec<JoinHandle<_>> = config.feeds.clone().into_iter().map(|feed_config| {
|
||||
let state_db = Arc::clone(&state_db);
|
||||
let bcast_tx = bcast_tx.clone();
|
||||
let matrix_ready = Arc::clone(&matrix_ready);
|
||||
let mut backoff: u64 = feed_config.delay;
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
||||
debug!("Waiting until matrix is ready");
|
||||
matrix_ready.notified().await;
|
||||
|
||||
debug!("Notified that matrix is ready, starting loop for {}", &feed_config.url);
|
||||
|
||||
loop {
|
||||
let delay = rand::random::<u64>() % (feed_config.delay / 5);
|
||||
|
||||
@ -135,9 +143,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
login_and_sync(
|
||||
config.homeserver_uri.clone().into(),
|
||||
&config.username,
|
||||
&config.password,
|
||||
&config.auth,
|
||||
&config.default_room,
|
||||
matrix_ready,
|
||||
bcast_rx
|
||||
).await?;
|
||||
|
||||
|
@ -18,23 +18,34 @@
|
||||
*/
|
||||
|
||||
use matrix_sdk::{
|
||||
matrix_auth::{
|
||||
MatrixAuth,
|
||||
MatrixSession,
|
||||
MatrixSessionTokens
|
||||
},
|
||||
SessionMeta,
|
||||
config::SyncSettings,
|
||||
ruma::events::room::{
|
||||
member::StrippedRoomMemberEvent,
|
||||
message::RoomMessageEventContent
|
||||
},
|
||||
ruma::RoomId,
|
||||
ruma::{RoomId, OwnedUserId, device_id},
|
||||
Client, Room
|
||||
};
|
||||
|
||||
use tokio::{
|
||||
time::{sleep, Duration},
|
||||
sync::broadcast
|
||||
sync::{broadcast, Notify},
|
||||
task
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tracing::{error, info};
|
||||
use std::error::Error;
|
||||
|
||||
use crate::config::AuthConfig;
|
||||
|
||||
async fn on_stripped_state_member(
|
||||
room_member: StrippedRoomMemberEvent,
|
||||
client: Client,
|
||||
@ -85,23 +96,45 @@ async fn send_to_room(
|
||||
};
|
||||
}
|
||||
|
||||
async fn login(
|
||||
auth_config: &AuthConfig,
|
||||
matrix_auth: MatrixAuth
|
||||
) -> anyhow::Result<()> {
|
||||
match auth_config {
|
||||
AuthConfig::PasswordAuthConfig{username, password} => {
|
||||
matrix_auth.login_username(username, password)
|
||||
.initial_device_display_name("bender v0.1.5").await?;
|
||||
},
|
||||
AuthConfig::TokenAuthConfig{user_id, device_id, access_token} => {
|
||||
matrix_auth.restore_session(
|
||||
MatrixSession {
|
||||
meta: SessionMeta {
|
||||
user_id: <OwnedUserId>::try_from(user_id.as_str())?,
|
||||
device_id: device_id!(device_id.as_str()).to_owned(),
|
||||
},
|
||||
tokens: MatrixSessionTokens {
|
||||
access_token: access_token.to_owned(),
|
||||
refresh_token: None,
|
||||
}
|
||||
}
|
||||
).await?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn login_and_sync(
|
||||
homeserver_url: String,
|
||||
username: &str,
|
||||
password: &str,
|
||||
auth_config: &AuthConfig,
|
||||
default_room_id: &str,
|
||||
ready: Arc<Notify>,
|
||||
mut rx: broadcast::Receiver<(String, Vec<String>)>
|
||||
) -> anyhow::Result<()> {
|
||||
// We are not reading encrypted messages, so we don't care about session persistence
|
||||
let client = Client::builder().homeserver_url(homeserver_url).build().await?;
|
||||
|
||||
client
|
||||
.matrix_auth()
|
||||
.login_username(username, password)
|
||||
.initial_device_display_name("bender v0.1.5")
|
||||
.await?;
|
||||
|
||||
info!("logged in as {username}");
|
||||
login(auth_config, client.matrix_auth()).await?;
|
||||
|
||||
client.add_event_handler(on_stripped_state_member);
|
||||
|
||||
@ -116,6 +149,15 @@ pub async fn login_and_sync(
|
||||
// we must pass that sync token to `sync`
|
||||
let settings = SyncSettings::default().token(sync_token);
|
||||
|
||||
// make sure all waiters have had a chance to register.
|
||||
// Ideally the notified futures would be created before spawning
|
||||
// the feed reader tasks but that would require the Notified structs to
|
||||
// live for 'static, which I dislike.
|
||||
task::yield_now().await;
|
||||
|
||||
info!("Matrix is ready, notifying waiters");
|
||||
ready.notify_waiters();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = client.sync(settings.clone()) => match res {
|
||||
|
Reference in New Issue
Block a user