about summary refs log tree commit diff
path: root/src/lib.rs
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-08-18 00:30:15 +0300
committerVika <vika@fireburn.ru>2024-08-18 00:30:15 +0300
commite43313210269b8e48fe35b17ac416c9ba88ae4f3 (patch)
tree51941c5149351bb32260fb8cbd4293eed80563e0 /src/lib.rs
parentcd8029a930b966225d0a57afb1ee29808fe2a409 (diff)
feat: logins!!
yes you can finally sign in

this is also supposed to show private posts intended for you!

maybe i can also reveal my email to those who sign in! :3
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs79
1 files changed, 73 insertions, 6 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 495591d..f1a563e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,13 +3,13 @@
 
 use std::sync::Arc;
 
-use axum::extract::FromRef;
-use axum_extra::extract::cookie::Key;
+use axum::{extract::{FromRef, FromRequestParts}, response::IntoResponse};
+use axum_extra::extract::{cookie::Key, SignedCookieJar};
 use database::{FileStorage, PostgresStorage, Storage};
 use indieauth::backend::{AuthBackend, FileBackend as FileAuthBackend};
 use kittybox_util::queue::JobQueue;
 use media::storage::{MediaStore, file::FileStore as FileMediaStore};
-use tokio::{sync::Mutex, task::JoinSet};
+use tokio::{sync::{Mutex, RwLock}, task::JoinSet};
 use webmentions::queue::PostgresJobQueue;
 
 /// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database.
@@ -22,6 +22,8 @@ pub mod webmentions;
 pub mod login;
 //pub mod admin;
 
+const OAUTH2_SOFTWARE_ID: &str = "6f2eee84-c22c-4c9e-b900-10d4e97273c8";
+
 #[derive(Clone)]
 pub struct AppState<A, S, M, Q>
 where
@@ -36,7 +38,63 @@ Q: JobQueue<webmentions::Webmention> + Sized
     pub job_queue: Q,
     pub http: reqwest::Client,
     pub background_jobs: Arc<Mutex<JoinSet<()>>>,
-    pub cookie_key: Key
+    pub cookie_key: Key,
+    pub session_store: SessionStore
+}
+
+pub type SessionStore = Arc<RwLock<std::collections::HashMap<uuid::Uuid, Session>>>;
+
+#[derive(Debug, Clone)]
+#[repr(transparent)]
+pub struct Session(kittybox_indieauth::ProfileUrl);
+
+impl std::ops::Deref for Session {
+    type Target = kittybox_indieauth::ProfileUrl;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+pub struct NoSessionError;
+impl axum::response::IntoResponse for NoSessionError {
+    fn into_response(self) -> axum::response::Response {
+        // TODO: prettier error message
+        (axum::http::StatusCode::UNAUTHORIZED, "You are not logged in, but this page requires a session.").into_response()
+    }
+}
+
+#[async_trait::async_trait]
+impl<S> FromRequestParts<S> for Session
+where
+    SessionStore: FromRef<S>,
+    Key: FromRef<S>,
+    S: Send + Sync,
+{
+    type Rejection = NoSessionError;
+
+    async fn from_request_parts(parts: &mut axum::http::request::Parts, state: &S) ->  Result<Self, Self::Rejection> {
+        let jar = SignedCookieJar::<Key>::from_request_parts(parts, state).await.unwrap();
+        let session_store = SessionStore::from_ref(state).read_owned().await;
+
+        tracing::debug!("Cookie jar: {:#?}", jar);
+        let cookie = match jar.get("session_id") {
+            Some(cookie) => {
+                tracing::debug!("Session ID cookie: {}", cookie);
+                cookie
+            },
+            None => { return Err(NoSessionError) }
+        };
+
+        session_store.get(
+            &dbg!(cookie.value_trimmed())
+                .parse()
+                .map_err(|err| {
+                    tracing::error!("Error parsing cookie: {}", err);
+                    NoSessionError
+                })?
+        ).cloned().ok_or(NoSessionError)
+    }
 }
 
 // This is really regrettable, but I can't write:
@@ -58,7 +116,6 @@ Q: JobQueue<webmentions::Webmention> + Sized
 // have to repeat this magic invocation.
 
 impl<S, M, Q> FromRef<AppState<Self, S, M, Q>> for FileAuthBackend
-// where S: Storage, M: MediaStore
 where S: Storage, M: MediaStore, Q: JobQueue<webmentions::Webmention>
 {
     fn from_ref(input: &AppState<Self, S, M, Q>) -> Self {
@@ -67,7 +124,6 @@ where S: Storage, M: MediaStore, Q: JobQueue<webmentions::Webmention>
 }
 
 impl<A, M, Q> FromRef<AppState<A, Self, M, Q>> for PostgresStorage
-// where A: AuthBackend, M: MediaStore
 where A: AuthBackend, M: MediaStore, Q: JobQueue<webmentions::Webmention>
 {
     fn from_ref(input: &AppState<A, Self, M, Q>) -> Self {
@@ -125,6 +181,14 @@ where A: AuthBackend, S: Storage, M: MediaStore
     }
 }
 
+impl<A, S, M, Q> FromRef<AppState<A, S, M, Q>> for SessionStore
+where A: AuthBackend, S: Storage, M: MediaStore, Q: JobQueue<webmentions::Webmention>
+{
+    fn from_ref(input: &AppState<A, S, M, Q>) -> Self {
+        input.session_store.clone()
+    }
+}
+
 pub mod companion {
     use std::{collections::HashMap, sync::Arc};
     use axum::{
@@ -229,6 +293,8 @@ M: MediaStore + 'static + FromRef<St>,
 Q: kittybox_util::queue::JobQueue<crate::webmentions::Webmention> + FromRef<St>,
 reqwest::Client: FromRef<St>,
 Arc<Mutex<JoinSet<()>>>: FromRef<St>,
+crate::SessionStore: FromRef<St>,
+axum_extra::extract::cookie::Key: FromRef<St>,
 St: Clone + Send + Sync + 'static
 {
     use axum::routing::get;
@@ -241,6 +307,7 @@ St: Clone + Send + Sync + 'static
         .merge(crate::indieauth::router::<St, A, S>())
         .merge(crate::webmentions::router::<St, Q>())
         .route("/.kittybox/health", get(health_check::<S>))
+        .nest("/.kittybox/login", crate::login::router::<St, S>())
         .route(
             "/.kittybox/static/:path",
             axum::routing::get(crate::frontend::statics)