From e43313210269b8e48fe35b17ac416c9ba88ae4f3 Mon Sep 17 00:00:00 2001 From: Vika Date: Sun, 18 Aug 2024 00:30:15 +0300 Subject: 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 --- src/lib.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 6 deletions(-) (limited to 'src/lib.rs') 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 where @@ -36,7 +38,63 @@ Q: JobQueue + Sized pub job_queue: Q, pub http: reqwest::Client, pub background_jobs: Arc>>, - pub cookie_key: Key + pub cookie_key: Key, + pub session_store: SessionStore +} + +pub type SessionStore = Arc>>; + +#[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 FromRequestParts for Session +where + SessionStore: FromRef, + Key: FromRef, + S: Send + Sync, +{ + type Rejection = NoSessionError; + + async fn from_request_parts(parts: &mut axum::http::request::Parts, state: &S) -> Result { + let jar = SignedCookieJar::::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 + Sized // have to repeat this magic invocation. impl FromRef> for FileAuthBackend -// where S: Storage, M: MediaStore where S: Storage, M: MediaStore, Q: JobQueue { fn from_ref(input: &AppState) -> Self { @@ -67,7 +124,6 @@ where S: Storage, M: MediaStore, Q: JobQueue } impl FromRef> for PostgresStorage -// where A: AuthBackend, M: MediaStore where A: AuthBackend, M: MediaStore, Q: JobQueue { fn from_ref(input: &AppState) -> Self { @@ -125,6 +181,14 @@ where A: AuthBackend, S: Storage, M: MediaStore } } +impl FromRef> for SessionStore +where A: AuthBackend, S: Storage, M: MediaStore, Q: JobQueue +{ + fn from_ref(input: &AppState) -> Self { + input.session_store.clone() + } +} + pub mod companion { use std::{collections::HashMap, sync::Arc}; use axum::{ @@ -229,6 +293,8 @@ M: MediaStore + 'static + FromRef, Q: kittybox_util::queue::JobQueue + FromRef, reqwest::Client: FromRef, Arc>>: FromRef, +crate::SessionStore: FromRef, +axum_extra::extract::cookie::Key: FromRef, St: Clone + Send + Sync + 'static { use axum::routing::get; @@ -241,6 +307,7 @@ St: Clone + Send + Sync + 'static .merge(crate::indieauth::router::()) .merge(crate::webmentions::router::()) .route("/.kittybox/health", get(health_check::)) + .nest("/.kittybox/login", crate::login::router::()) .route( "/.kittybox/static/:path", axum::routing::get(crate::frontend::statics) -- cgit 1.4.1