🗝
summary refs log tree commit diff
path: root/src/server/admin.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/admin.rs')
-rw-r--r--src/server/admin.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/src/server/admin.rs b/src/server/admin.rs
new file mode 100644
index 0000000..54bdf12
--- /dev/null
+++ b/src/server/admin.rs
@@ -0,0 +1,93 @@
+use std::{future::Future, path::Path};
+
+use tap::Pipe;
+use tokio::{
+    io::{AsyncReadExt, AsyncWriteExt},
+    net::{UnixListener, UnixStream},
+};
+
+use crate::ipc::*;
+
+use super::store::Store;
+
+pub fn serve(
+    bind: &Path,
+    web_base: String,
+    store: Store,
+) -> eyre::Result<impl Future<Output = eyre::Result<()>>> {
+    if bind.exists() {
+        std::fs::remove_file(bind)?;
+    }
+    let listener = UnixListener::bind(bind)?;
+
+    Ok(async move {
+        loop {
+            match listener.accept().await {
+                Ok((stream, _)) => tokio::spawn({
+                    let store = store.clone();
+                    let web_base = web_base.clone();
+                    async move {
+                        if let Err(err) = handle(stream, store, &web_base).await {
+                            eprintln!("error handling admin connection: {err:?}");
+                        }
+                    }
+                }),
+                Err(err) => {
+                    eprintln!("error accepting admin connection: {err:?}");
+                    continue;
+                }
+            };
+        }
+    })
+}
+
+async fn handle(mut stream: UnixStream, store: Store, web_base: &str) -> eyre::Result<()> {
+    while let Ok(request_length) = stream.read_u16().await {
+        let mut request_buffer = vec![0u8; request_length as usize];
+        stream.read_exact(&mut request_buffer).await?;
+        let (request, _): (IPCRequest, _) =
+            bincode::decode_from_slice(&request_buffer, bincode::config::standard())?;
+        let response = match request {
+            IPCRequest::ListAccounts(_) => ListAccountsResponse {
+                names: store.list_accounts().await,
+            }
+            .into_response(),
+            IPCRequest::GetAccount(GetAccountRequest { name }) => {
+                IPCResponse::GetAccount(match store.get_account(&name).await {
+                    Some(account) => Ok(GetAccountResponse {
+                        scopes: account.scopes.clone(),
+                    }),
+                    None => Err(GetAccountError::NotFound),
+                })
+            }
+            IPCRequest::DeleteAccount(DeleteAccountRequest { name }) => {
+                store.delete_account(&name).await?;
+                DeleteAccountResponse {}.into_response()
+            }
+            IPCRequest::CreateInvite(_) => store
+                .create_invite()
+                .await?
+                .pipe(|invite| CreateInviteResponse {
+                    link: format!("{web_base}/invite/{invite}"),
+                })
+                .into_response(),
+            IPCRequest::UpdateScopes(UpdateScopesRequest { account, scopes }) => {
+                IPCResponse::UpdateScopes(
+                    match store
+                        .update_account(&account, |account| {
+                            account.scopes = scopes;
+                        })
+                        .await?
+                    {
+                        true => Ok(UpdateScopesResponse {}),
+                        false => Err(UpdateScopesError::NotFound),
+                    },
+                )
+            }
+        };
+        let response_buffer = bincode::encode_to_vec(response, bincode::config::standard())?;
+        stream.write_u16(response_buffer.len() as u16).await?;
+        stream.write_all(&response_buffer).await?;
+    }
+    Ok(())
+}