/* * Copyright (C) 2022 Aravinth Manivannan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use actix_auth_middleware::*; use actix_web::web::ServiceConfig; use lazy_static::lazy_static; use rust_embed::RustEmbed; use serde::*; use tera::*; use crate::settings::Settings; use crate::static_assets::routes::ASSETS; use crate::{GIT_COMMIT_HASH, VERSION}; pub mod auth; pub mod errors; pub mod panel; pub mod routes; //pub use routes::get_auth_middleware; pub use routes::PAGES; pub struct TemplateFile { pub name: &'static str, pub path: &'static str, } impl TemplateFile { pub const fn new(name: &'static str, path: &'static str) -> Self { Self { name, path } } pub fn register(&self, t: &mut Tera) -> std::result::Result<(), tera::Error> { t.add_raw_template(self.name, &Templates::get_template(self).expect(self.name)) } #[cfg(test)] #[allow(dead_code)] pub fn register_from_file( &self, t: &mut Tera, ) -> std::result::Result<(), tera::Error> { use std::path::Path; t.add_template_file(Path::new("templates/").join(self.path), Some(self.name)) } } pub const PAYLOAD_KEY: &str = "payload"; pub const BASE: TemplateFile = TemplateFile::new("base", "components/base.html"); pub const FOOTER: TemplateFile = TemplateFile::new("footer", "components/footer/index.html"); pub const PANEL_NAV: TemplateFile = TemplateFile::new("panel_nav", "panel/nav/index.html"); pub const PUBLIC_NAV: TemplateFile = TemplateFile::new("pub_nav", "panel/nav/public.html"); lazy_static! { pub static ref TEMPLATES: Tera = { let mut tera = Tera::default(); for t in [BASE, FOOTER, PANEL_NAV, PUBLIC_NAV].iter() { t.register(&mut tera).unwrap(); } errors::register_templates(&mut tera); tera.autoescape_on(vec![".html", ".sql"]); auth::register_templates(&mut tera); panel::register_templates(&mut tera); tera }; } #[derive(RustEmbed)] #[folder = "templates/"] pub struct Templates; impl Templates { pub fn get_template(t: &TemplateFile) -> Option { match Self::get(t.path) { Some(file) => Some(String::from_utf8_lossy(&file.data).into_owned()), None => None, } } } pub fn context(s: &Settings, page_title: &str) -> Context { let mut ctx = Context::new(); let footer = Footer::new(s); ctx.insert("page_title", page_title); ctx.insert("page", &PAGES); ctx.insert("api", &crate::V1_API_ROUTES); ctx.insert("footer", &footer); ctx.insert("assets", &*ASSETS); ctx } pub fn auth_ctx(_username: Option<&str>, s: &Settings, page_title: &str) -> Context { let mut ctx = Context::new(); let footer = Footer::new(s); ctx.insert("page_title", page_title); ctx.insert("footer", &footer); ctx.insert("page", &PAGES); ctx.insert("api", &crate::V1_API_ROUTES); ctx.insert("assets", &*ASSETS); ctx } #[derive(Serialize)] pub struct Footer<'a> { version: &'a str, support_email: &'a str, source_code: &'a str, git_hash: &'a str, settings: &'a Settings, about: &'a str, privacy: &'a str, donate: &'a str, security: &'a str, thanks: &'a str, } impl<'a> Footer<'a> { pub fn new(settings: &'a Settings) -> Self { Self { version: VERSION, source_code: &settings.source_code, support_email: &settings.support_email, git_hash: &GIT_COMMIT_HASH[..8], about: settings.footer.about.as_str(), privacy: settings.footer.privacy.as_str(), thanks: settings.footer.thanks.as_str(), security: settings.footer.security.as_str(), donate: settings.footer.donate.as_str(), settings, } } } pub fn services(cfg: &mut ServiceConfig) { auth::services(cfg); panel::services(cfg); } pub fn get_page_check_login() -> Authentication { Authentication::with_identity(crate::PAGES.auth) } #[cfg(test)] mod terra_tests { #[test] fn templates_work_basic() { use super::*; use tera::Tera; let mut tera = Tera::default(); let mut tera2 = Tera::default(); for t in [ BASE, FOOTER, PANEL_NAV, auth::login::LOGIN, auth::join::REGISTER, errors::ERROR_TEMPLATE, ] .iter() { t.register_from_file(&mut tera2).unwrap(); t.register(&mut tera).unwrap(); } } } #[cfg(test)] mod http_page_tests { use actix_web::http::{header, StatusCode}; use actix_web::test; use crate::*; use super::PAGES; #[actix_rt::test] async fn templates_work() { use crate::tests::*; let data = get_test_data().await; let app = get_app!(data).await; for file in [PAGES.auth.login, PAGES.auth.join, PAGES.home].iter() { println!("[*] Testing route: {}", file); let resp = get_request!(&app, file); if file != &PAGES.home { assert_eq!(resp.status(), StatusCode::OK); } else { assert_eq!(resp.status(), StatusCode::FOUND); let headers = resp.headers(); let loc = headers.get(header::LOCATION).unwrap(); let resp = get_request!(&app, loc.to_str().unwrap()); assert_eq!(resp.status(), StatusCode::OK); } } } } #[cfg(not(tarpaulin_include))] #[cfg(test)] mod tests { use actix_web::http::{header, StatusCode}; use actix_web::test; use crate::tests::*; use crate::*; #[actix_rt::test] async fn protected_pages_templates_work() { const NAME: &str = "templateuser"; const PASSWORD: &str = "longpassword"; const EMAIL: &str = "templateuser@a.com"; const CAMPAIGN_NAME: &str = "delcappageusercamaping"; let data = get_test_data().await; { delete_user(NAME, &data).await; } let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await; let cookies = get_cookie!(signin_resp); let campaign = create_new_campaign(CAMPAIGN_NAME, data.clone(), cookies.clone()).await; let app = get_app!(data).await; let urls = vec![ PAGES.home.to_string(), PAGES.panel.campaigns.home.to_string(), PAGES.panel.campaigns.new.to_string(), // PAGES.panel.campaigns.get_feedback_route(&campaign.uuid), PAGES .panel .campaigns .get_delete_route(&campaign.campaign_id), ]; for url in urls.iter() { let resp = test::call_service(&app, test::TestRequest::get().uri(url).to_request()) .await; if resp.status() != StatusCode::FOUND { println!("Probably error url: {}", url); } assert_eq!(resp.status(), StatusCode::FOUND); let authenticated_resp = test::call_service( &app, test::TestRequest::get() .uri(url) .cookie(cookies.clone()) .to_request(), ) .await; if url == PAGES.home { assert_eq!(authenticated_resp.status(), StatusCode::FOUND); let headers = authenticated_resp.headers(); assert_eq!( headers.get(header::LOCATION).unwrap().to_str().unwrap(), PAGES .panel .campaigns .get_about_route(&data.settings.default_campaign) ); } else { assert_eq!(authenticated_resp.status(), StatusCode::OK); } } delete_user(NAME, &data).await; } #[actix_rt::test] async fn public_pages_tempaltes_work() { let data = get_test_data().await; let app = get_app!(data).await; let urls = vec![PAGES.auth.login, PAGES.auth.join]; //, PAGES.sitemap]; for url in urls.iter() { let resp = test::call_service(&app, test::TestRequest::get().uri(url).to_request()) .await; assert_eq!(resp.status(), StatusCode::OK); } } }