diff --git a/crates/frontend/Cargo.toml b/crates/frontend/Cargo.toml index 26cc44d..d100300 100644 --- a/crates/frontend/Cargo.toml +++ b/crates/frontend/Cargo.toml @@ -1,15 +1,19 @@ [package] name = "rustical_frontend" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true +description.workspace = true +repository.workspace = true +publish = false [dependencies] -actix-web = "4.9" -askama = { version = "0.12", features = ["serde", "with-actix-web"] } -askama_actix = "0.14" -tokio = { version = "1.40", features = ["sync", "full"] } -rustical_store = { path = "../store/" } -anyhow = "1.0" -thiserror = "1.0" -actix-session = { version = "0.10", features = ["cookie-session"] } -serde = { version = "1.0", features = ["derive", "rc"] } +askama = { workspace = true } +askama_actix = { workspace = true } +actix-session = { workspace = true } +serde = { workspace = true } +anyhow = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +actix-web = { workspace = true } +rustical_store = { workspace = true } +actix-files = "0.6.6" diff --git a/crates/frontend/public/asd.html b/crates/frontend/public/asd.html new file mode 100644 index 0000000..21c82e4 --- /dev/null +++ b/crates/frontend/public/asd.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + ok wow! + + + diff --git a/crates/frontend/public/style.css b/crates/frontend/public/style.css new file mode 100644 index 0000000..e69de29 diff --git a/crates/frontend/src/config.rs b/crates/frontend/src/config.rs new file mode 100644 index 0000000..b03f3b1 --- /dev/null +++ b/crates/frontend/src/config.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct FrontendConfig { + secret_key: String, +} diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index 76eb90c..d02f48b 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -1,31 +1,93 @@ +use actix_session::{storage::CookieSessionStore, Session, SessionMiddleware}; use actix_web::{ + cookie::Key, get, http::Method, - web::{self, Data}, + web::{self, Data, Path}, + Responder, }; use askama::Template; -use rustical_store::CalendarStore; +use login::{route_get_login, route_post_login}; +use rustical_store::{ + auth::{AuthenticationMiddleware, AuthenticationProvider, User}, + model::Calendar, + CalendarStore, +}; use std::sync::Arc; use tokio::sync::RwLock; +mod config; +mod login; + +pub use config::FrontendConfig; + #[derive(Template)] #[template(path = "index.html")] struct IndexTemplate {} #[get("")] -async fn route_index() -> IndexTemplate { +async fn route_index(session: Session) -> IndexTemplate { + if let Some(user) = session.get::("user").unwrap() { + dbg!(user); + } else { + session.insert("user", "lennart").unwrap(); + } + dbg!(session.status()); IndexTemplate {} } -async fn route_user(store: Data>) -> IndexTemplate { - IndexTemplate {} +#[derive(Template)] +#[template(path = "components/calendar_list.html")] +struct CalendarList { + pub owner: String, + pub calendars: Vec, } -pub fn configure_frontend( +#[derive(Template)] +#[template(path = "layouts/default.html")] +struct DefaultTemplate { + pub body: Body, +} + +async fn route_user( + path: Path, + store: Data>, +) -> impl Responder { + let store = store.read().await; + let owner = path.into_inner(); + DefaultTemplate { + body: CalendarList { + owner: owner.to_owned(), + calendars: store.get_calendars(&owner).await.unwrap(), + }, + } +} + +pub fn configure_frontend( cfg: &mut web::ServiceConfig, + auth_provider: Arc, store: Arc>, ) { - cfg.app_data(Data::from(store.clone())) - .service(route_index) - .service(web::resource("/user/{user}").route(web::method(Method::GET).to(route_user::))); + cfg.service( + web::scope("") + .wrap(AuthenticationMiddleware::new(auth_provider.clone())) + .wrap( + SessionMiddleware::builder(CookieSessionStore::default(), Key::from(&[0; 64])) + .cookie_secure(true) + .cookie_content_security(actix_session::config::CookieContentSecurity::Private) + .build(), + ) + .app_data(Data::from(auth_provider)) + .app_data(Data::from(store.clone())) + .service(actix_files::Files::new("/public", "crates/frontend/public").prefer_utf8(true)) + .service(route_index) + .service( + web::resource("/user/{user}").route(web::method(Method::GET).to(route_user::)), + ) + .service( + web::resource("/login") + .route(web::method(Method::GET).to(route_get_login)) + .route(web::method(Method::POST).to(route_post_login::)), + ), + ); } diff --git a/crates/frontend/src/login.rs b/crates/frontend/src/login.rs new file mode 100644 index 0000000..7633f3f --- /dev/null +++ b/crates/frontend/src/login.rs @@ -0,0 +1,47 @@ +use actix_session::Session; +use actix_web::{ + error::ErrorUnauthorized, + web::{Data, Form, Redirect}, + HttpRequest, HttpResponse, Responder, +}; +use askama::Template; +use rustical_store::auth::AuthenticationProvider; +use serde::Deserialize; + +use crate::DefaultTemplate; + +#[derive(Template)] +#[template(path = "components/login.html")] +struct LoginForm; + +pub async fn route_get_login() -> impl Responder { + DefaultTemplate { body: LoginForm } +} + +#[derive(Deserialize)] +pub struct PostLoginForm { + username: String, + password: String, +} + +pub async fn route_post_login( + req: HttpRequest, + form: Form, + session: Session, + auth_provider: Data, +) -> HttpResponse { + // TODO: implement auth check + dbg!(&form.username, &form.password); + if let Ok(Some(user)) = auth_provider + .validate_user_token(&form.username, &form.password) + .await + { + session.insert("user", user).unwrap(); + Redirect::to(format!("/frontend/user/{}", &form.username)) + .see_other() + .respond_to(&req) + .map_into_boxed_body() + } else { + ErrorUnauthorized("Unauthorized").error_response() + } +} diff --git a/crates/frontend/templates/components/calendar_list.html b/crates/frontend/templates/components/calendar_list.html new file mode 100644 index 0000000..da88e35 --- /dev/null +++ b/crates/frontend/templates/components/calendar_list.html @@ -0,0 +1,12 @@ +

Welcome {{ owner }}!

+
    + {% for calendar in calendars %} +
  • + {% if let Some(name) = calendar.displayname %} + {{ name }} + {% else %} + {{ calendar.id }} + {% endif %} +
  • + {% endfor %} +
diff --git a/crates/frontend/templates/components/login.html b/crates/frontend/templates/components/login.html new file mode 100644 index 0000000..0f0112b --- /dev/null +++ b/crates/frontend/templates/components/login.html @@ -0,0 +1,7 @@ +
+ + + + + +
diff --git a/crates/frontend/templates/layouts/default.html b/crates/frontend/templates/layouts/default.html new file mode 100644 index 0000000..918a7ae --- /dev/null +++ b/crates/frontend/templates/layouts/default.html @@ -0,0 +1,15 @@ + + + + + + + RustiCal + + + + + {{ body|safe }} + + +