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
|
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)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub default_room: String,
|
pub default_room: String,
|
||||||
pub feeds: Vec<FeedConfig>,
|
pub feeds: Vec<FeedConfig>,
|
||||||
pub username: String,
|
#[serde(flatten)]
|
||||||
pub password: String,
|
pub auth: AuthConfig,
|
||||||
pub homeserver_uri: String
|
pub homeserver_uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
src/main.rs
16
src/main.rs
@ -24,8 +24,8 @@ mod state;
|
|||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
time::{sleep, Duration},
|
time::{sleep, Duration},
|
||||||
|
sync::{Notify, broadcast},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
sync::broadcast
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -55,17 +55,25 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
panic!("Failed to initialize feed reader state db: {e:?}")
|
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
|
// 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 (bcast_tx, bcast_rx) = broadcast::channel::<(String, Vec<String>)>(1024);
|
||||||
|
|
||||||
let handles: Vec<JoinHandle<_>> = config.feeds.clone().into_iter().map(|feed_config| {
|
let handles: Vec<JoinHandle<_>> = config.feeds.clone().into_iter().map(|feed_config| {
|
||||||
let state_db = Arc::clone(&state_db);
|
let state_db = Arc::clone(&state_db);
|
||||||
let bcast_tx = bcast_tx.clone();
|
let bcast_tx = bcast_tx.clone();
|
||||||
|
let matrix_ready = Arc::clone(&matrix_ready);
|
||||||
let mut backoff: u64 = feed_config.delay;
|
let mut backoff: u64 = feed_config.delay;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
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 {
|
loop {
|
||||||
let delay = rand::random::<u64>() % (feed_config.delay / 5);
|
let delay = rand::random::<u64>() % (feed_config.delay / 5);
|
||||||
|
|
||||||
@ -135,9 +143,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
login_and_sync(
|
login_and_sync(
|
||||||
config.homeserver_uri.clone().into(),
|
config.homeserver_uri.clone().into(),
|
||||||
&config.username,
|
&config.auth,
|
||||||
&config.password,
|
|
||||||
&config.default_room,
|
&config.default_room,
|
||||||
|
matrix_ready,
|
||||||
bcast_rx
|
bcast_rx
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
|
@ -18,23 +18,34 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
|
matrix_auth::{
|
||||||
|
MatrixAuth,
|
||||||
|
MatrixSession,
|
||||||
|
MatrixSessionTokens
|
||||||
|
},
|
||||||
|
SessionMeta,
|
||||||
config::SyncSettings,
|
config::SyncSettings,
|
||||||
ruma::events::room::{
|
ruma::events::room::{
|
||||||
member::StrippedRoomMemberEvent,
|
member::StrippedRoomMemberEvent,
|
||||||
message::RoomMessageEventContent
|
message::RoomMessageEventContent
|
||||||
},
|
},
|
||||||
ruma::RoomId,
|
ruma::{RoomId, OwnedUserId, device_id},
|
||||||
Client, Room
|
Client, Room
|
||||||
};
|
};
|
||||||
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
time::{sleep, Duration},
|
time::{sleep, Duration},
|
||||||
sync::broadcast
|
sync::{broadcast, Notify},
|
||||||
|
task
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
use crate::config::AuthConfig;
|
||||||
|
|
||||||
async fn on_stripped_state_member(
|
async fn on_stripped_state_member(
|
||||||
room_member: StrippedRoomMemberEvent,
|
room_member: StrippedRoomMemberEvent,
|
||||||
client: Client,
|
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(
|
pub async fn login_and_sync(
|
||||||
homeserver_url: String,
|
homeserver_url: String,
|
||||||
username: &str,
|
auth_config: &AuthConfig,
|
||||||
password: &str,
|
|
||||||
default_room_id: &str,
|
default_room_id: &str,
|
||||||
|
ready: Arc<Notify>,
|
||||||
mut rx: broadcast::Receiver<(String, Vec<String>)>
|
mut rx: broadcast::Receiver<(String, Vec<String>)>
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// We are not reading encrypted messages, so we don't care about session persistence
|
// We are not reading encrypted messages, so we don't care about session persistence
|
||||||
let client = Client::builder().homeserver_url(homeserver_url).build().await?;
|
let client = Client::builder().homeserver_url(homeserver_url).build().await?;
|
||||||
|
|
||||||
client
|
login(auth_config, client.matrix_auth()).await?;
|
||||||
.matrix_auth()
|
|
||||||
.login_username(username, password)
|
|
||||||
.initial_device_display_name("bender v0.1.5")
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("logged in as {username}");
|
|
||||||
|
|
||||||
client.add_event_handler(on_stripped_state_member);
|
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`
|
// we must pass that sync token to `sync`
|
||||||
let settings = SyncSettings::default().token(sync_token);
|
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 {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = client.sync(settings.clone()) => match res {
|
res = client.sync(settings.clone()) => match res {
|
||||||
|
Reference in New Issue
Block a user