diff --git a/main.lua b/main.lua
index 53d1ca7..cc7cc85 100644
--- a/main.lua
+++ b/main.lua
@@ -2,7 +2,7 @@ Server = "localhost"
 Username = "ErrorNoWatcher"
 HttpAddress = "127.0.0.1:8080"
 Owners = { "ErrorNoInternet" }
-MatrixOwners = { "@errornointernet:envs.net" }
+MatrixOptions = { owners = { "@errornointernet:envs.net" } }
 
 for _, module in ipairs({
 	"lib",
diff --git a/src/events.rs b/src/events.rs
index afb2f57..317f056 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -278,15 +278,10 @@ async fn lua_init(client: Client, state: &State, globals: &Table) -> Result<()>
 #[cfg(feature = "matrix")]
 fn matrix_init(client: &Client, state: State) {
     let globals = state.lua.globals();
-    if let Ok(homeserver_url) = globals.get::<String>("MatrixHomeserverUrl")
-        && let Ok(username) = globals.get::<String>("MatrixUsername")
-        && let Ok(password) = globals.get::<String>("MatrixPassword")
-    {
+    if let Ok(options) = globals.get::<Table>("MatrixOptions") {
         let name = client.username();
         tokio::spawn(async move {
-            if let Err(error) =
-                matrix::login(homeserver_url, username, &password, state, globals, name).await
-            {
+            if let Err(error) = matrix::login(state, options, globals, name).await {
                 error!("failed to log into matrix account: {error:?}");
             }
         });
diff --git a/src/matrix/bot.rs b/src/matrix/bot.rs
index 30c3e1a..1166185 100644
--- a/src/matrix/bot.rs
+++ b/src/matrix/bot.rs
@@ -28,15 +28,7 @@ pub async fn on_regular_room_message(
         return Ok(());
     };
 
-    if ctx
-        .state
-        .lua
-        .globals()
-        .get::<Vec<String>>("MatrixOwners")
-        .unwrap_or_default()
-        .contains(&event.sender.to_string())
-        && text_content.body.starts_with(&ctx.name)
-    {
+    if ctx.is_owner(&event.sender.to_string()) && text_content.body.starts_with(&ctx.name) {
         let body = text_content.body[ctx.name.len()..]
             .trim_start_matches(':')
             .trim();
@@ -101,13 +93,7 @@ pub async fn on_stripped_state_member(
 ) -> Result<()> {
     if let Some(user_id) = client.user_id()
         && member.state_key == user_id
-        && ctx
-            .state
-            .lua
-            .globals()
-            .get::<Vec<String>>("MatrixOwners")
-            .unwrap_or_default()
-            .contains(&member.sender.to_string())
+        && ctx.is_owner(&member.sender.to_string())
     {
         debug!("joining room {}", room.room_id());
         while let Err(error) = room.join().await {
diff --git a/src/matrix/mod.rs b/src/matrix/mod.rs
index 5b01556..eed37fd 100644
--- a/src/matrix/mod.rs
+++ b/src/matrix/mod.rs
@@ -20,6 +20,23 @@ pub struct Context {
     name: String,
 }
 
+impl Context {
+    fn is_owner(&self, name: &String) -> bool {
+        self.state
+            .lua
+            .globals()
+            .get::<Table>("MatrixOptions")
+            .ok()
+            .and_then(|options| {
+                options
+                    .get::<Vec<String>>("owners")
+                    .ok()
+                    .and_then(|owners| owners.contains(name).then_some(()))
+            })
+            .is_some()
+    }
+}
+
 #[derive(Clone, Serialize, Deserialize)]
 struct Session {
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -37,14 +54,13 @@ async fn persist_sync_token(
     Ok(())
 }
 
-pub async fn login(
-    homeserver_url: String,
-    username: String,
-    password: &str,
-    state: State,
-    globals: Table,
-    name: String,
-) -> Result<()> {
+pub async fn login(state: State, options: Table, globals: Table, name: String) -> Result<()> {
+    let (homeserver_url, username, password) = (
+        options.get::<String>("homeserver_url")?,
+        options.get::<String>("username")?,
+        &options.get::<String>("password")?,
+    );
+
     let root_dir = dirs::data_dir()
         .context("no data directory")?
         .join("errornowatcher")