diff --git a/.gitignore b/.gitignore index 4aa3436..daf4e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ coverage dist assets scripts/creds.py +__pycache__/ +*.py[cod] +*$py.class diff --git a/Cargo.lock b/Cargo.lock index 50641f9..34de1ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,6 +153,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "actix-session" +version = "0.5.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b853e383318e1074c1dc988871c33cd186b89bfadfd543758b2f7ffebb88f47" +dependencies = [ + "actix-service", + "actix-web", + "derive_more", + "futures-util", + "log", + "serde 1.0.130", + "serde_json", + "time 0.2.27", +] + [[package]] name = "actix-tls" version = "3.0.0-beta.5" @@ -2415,6 +2431,7 @@ dependencies = [ "actix-identity", "actix-rt", "actix-service", + "actix-session", "actix-web", "actix-web-codegen 0.5.0-beta.4 (git+https://github.com/realaravinth/actix-web)", "argon2-creds", diff --git a/Cargo.toml b/Cargo.toml index 87c0b96..43c3dd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ path = "./src/tests-migrate.rs" [dependencies] actix-web = "4.0.0-beta.9" actix-identity = "0.4.0-beta.2" +actix-session = "0.5.0-beta.2" actix-http = "3.0.0-beta.8" actix-rt = "2" actix-cors = "0.6.0-beta.2" diff --git a/config/default.toml b/config/default.toml index 048c7a9..0bb5d21 100644 --- a/config/default.toml +++ b/config/default.toml @@ -2,11 +2,13 @@ debug = true allow_registration = true source_code = "https://github.com/mcaptcha/survey" password = "password" +default_campaign = "b6b261fa-3ef9-4d7f-8852-339b8f81bb01" [server] # Please set a unique value, your kaizen instance's security depends on this being # unique cookie_secret = "8ce364dab188452ffa76c3e1869be5d40dcb9db4826b7b78a3e6ce1a8ca19d32" +cookie_secret2 = "408f276a8dec44992b14bdf1be9289a2c67547807e16c5ebcf5e904fcc916208" # The port at which you want authentication to listen to # takes a number, choose from 1000-10000 if you dont know what you are doing port = 7000 diff --git a/scripts/campaign.py b/scripts/campaign.py index a6b3442..6beed73 100755 --- a/scripts/campaign.py +++ b/scripts/campaign.py @@ -1,16 +1,16 @@ #!/bin/python # Copyright (C) 2021 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 . import requests @@ -18,32 +18,33 @@ import json from creds import COOKIE + def add_campaign(): """Add campaign""" - url = "localhost:7000/admin/api/v1/campaign/add" - payload = json.dumps({ - "name": "test_1", - "difficulties": [ - 50000, - 100000, - 150000, - 200000, - 250000, - 300000, - 350000, - 400000, - 450000 - ] - }) - headers = { - 'Content-Type': 'application/json', - 'Cookie': COOKIE - } + url = "http://localhost:7000/admin/api/v1/campaign/add" + payload = json.dumps( + { + "name": "test_1", + "difficulties": [ + 50000, + 100000, + 150000, + 200000, + 250000, + 300000, + 350000, + 400000, + 450000, + ], + } + ) + headers = {"Content-Type": "application/json", "Cookie": COOKIE} response = requests.request("POST", url, headers=headers, data=payload) data = response.json() - print('campaign ID: %s' % (data["campaign_id"])) + print("campaign ID: %s" % (data["campaign_id"])) + if __name__ == "__main__": add_campaign() diff --git a/src/api/v1/admin/campaigns.rs b/src/api/v1/admin/campaigns.rs index e8b4412..a6a4f9a 100644 --- a/src/api/v1/admin/campaigns.rs +++ b/src/api/v1/admin/campaigns.rs @@ -409,7 +409,6 @@ mod tests { let cookies = get_cookie!(signin_resp); let survey = get_survey_user(data.clone()).await; let survey_cookie = get_cookie!(survey); - // let app = get_app!(data).await; let campaign = create_new_campaign(NAME, data.clone(), cookies.clone()).await; let campaign_config = diff --git a/src/api/v1/admin/mod.rs b/src/api/v1/admin/mod.rs index 4d0cd0b..3dfed4a 100644 --- a/src/api/v1/admin/mod.rs +++ b/src/api/v1/admin/mod.rs @@ -31,7 +31,11 @@ pub fn services(cfg: &mut ServiceConfig) { } pub fn get_admin_check_login() -> crate::CheckLogin { - crate::CheckLogin::new(crate::V1_API_ROUTES.admin.auth) + use crate::middleware::auth::*; + CheckLogin::new( + crate::V1_API_ROUTES.admin.auth, + AuthenticatedSession::ActixIdentity, + ) } pub mod routes { diff --git a/src/api/v1/bench.rs b/src/api/v1/bench.rs index 9f8ece3..f4404f6 100644 --- a/src/api/v1/bench.rs +++ b/src/api/v1/bench.rs @@ -17,7 +17,7 @@ use std::borrow::Cow; use std::str::FromStr; -use actix_identity::Identity; +use actix_session::Session; use actix_web::{http, web, HttpResponse, Responder}; use futures::future::try_join_all; use serde::{Deserialize, Serialize}; @@ -28,6 +28,8 @@ use super::{get_uuid, RedirectQuery}; use crate::errors::*; use crate::AppData; +pub const SURVEY_USER_ID: &str = "survey_user_id"; + pub mod routes { use crate::middleware::auth::GetLoginRoute; @@ -124,11 +126,11 @@ pub mod runners { #[my_codegen::get(path = "crate::V1_API_ROUTES.benches.register")] async fn register( data: AppData, - id: Identity, + session: Session, path: web::Query, ) -> ServiceResult { let uuid = runners::register_runner(&data).await?; - id.remember(uuid.to_string()); + session.insert(SURVEY_USER_ID, uuid.to_string()).unwrap(); let path = path.into_inner(); if let Some(redirect_to) = path.redirect_to { Ok(HttpResponse::Found() @@ -159,8 +161,12 @@ pub struct SubmissionProof { pub proof: String, } -fn get_check_login() -> crate::CheckLogin { - crate::CheckLogin::new(crate::V1_API_ROUTES.benches) +pub fn get_check_login() -> crate::CheckLogin { + use crate::middleware::auth::*; + CheckLogin::new( + crate::V1_API_ROUTES.benches, + AuthenticatedSession::ActixSession, + ) } #[my_codegen::post( @@ -169,11 +175,11 @@ fn get_check_login() -> crate::CheckLogin { )] async fn submit( data: AppData, - id: Identity, + session: Session, payload: web::Json, path: web::Path, ) -> ServiceResult { - let username = id.identity().unwrap(); + let username = session.get::(SURVEY_USER_ID).unwrap().unwrap(); let path = path.into_inner(); let campaign_id = Uuid::parse_str(&path).map_err(|_| ServiceError::NotAnId)?; diff --git a/src/main.rs b/src/main.rs index db1deb6..c9b6fd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,7 +110,7 @@ async fn main() -> std::io::Result<()> { .header("Permissions-Policy", "interest-cohort=()"), ) .wrap(get_identity_service()) - .wrap(get_survey_identity_service()) + .wrap(get_survey_session()) .wrap(actix_middleware::NormalizePath::new( actix_middleware::TrailingSlash::Trim, )) @@ -132,16 +132,15 @@ pub fn get_json_err() -> JsonConfig { } #[cfg(not(tarpaulin_include))] -pub fn get_survey_identity_service() -> IdentityService { - let cookie_secret = &SETTINGS.server.cookie_secret; - IdentityService::new( - CookieIdentityPolicy::new(cookie_secret.as_bytes()) - .name("survey-id") - .path("/survey") - .max_age_secs(30 * 60) - .domain(&SETTINGS.server.domain) - .secure(false), - ) +pub fn get_survey_session() -> actix_session::CookieSession { + let cookie_secret = &SETTINGS.server.cookie_secret2; + actix_session::CookieSession::private(cookie_secret.as_bytes()) + .domain(&SETTINGS.server.domain) + .name("survey-id") + .path("/survey") + .max_age(30 * 60) + .domain(&SETTINGS.server.domain) + .secure(false) } #[cfg(not(tarpaulin_include))] diff --git a/src/middleware/auth.rs b/src/middleware/auth.rs index dd70d69..392fae4 100644 --- a/src/middleware/auth.rs +++ b/src/middleware/auth.rs @@ -18,12 +18,20 @@ use std::rc::Rc; +use crate::api::v1::bench::SURVEY_USER_ID; use actix_http::body::AnyBody; use actix_identity::Identity; use actix_service::{Service, Transform}; +use actix_session::Session; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::{http, Error, FromRequest, HttpResponse}; +#[derive(Clone)] +pub enum AuthenticatedSession { + ActixIdentity, + ActixSession, +} + use futures::future::{ok, Either, Ready}; pub trait GetLoginRoute { @@ -32,12 +40,16 @@ pub trait GetLoginRoute { pub struct CheckLogin { login: Rc, + session_type: AuthenticatedSession, } impl CheckLogin { - pub fn new(login: T) -> Self { + pub fn new(login: T, session_type: AuthenticatedSession) -> Self { let login = Rc::new(login); - Self { login } + Self { + login, + session_type, + } } } @@ -57,12 +69,14 @@ where ok(CheckLoginMiddleware { service, login: self.login.clone(), + session_type: self.session_type.clone(), }) } } pub struct CheckLoginMiddleware { service: S, login: Rc, + session_type: AuthenticatedSession, } impl Service for CheckLoginMiddleware @@ -79,13 +93,30 @@ where fn call(&self, req: ServiceRequest) -> Self::Future { let (r, mut pl) = req.into_parts(); + let mut is_authenticated = || match self.session_type { + AuthenticatedSession::ActixSession => { + if let Ok(Ok(Some(_))) = Session::from_request(&r, &mut pl) + .into_inner() + .map(|x| x.get::(SURVEY_USER_ID)) + { + true + } else { + false + } + } - // TODO investigate when the bellow statement will - // return error - if let Ok(Some(_)) = Identity::from_request(&r, &mut pl) - .into_inner() - .map(|x| x.identity()) - { + AuthenticatedSession::ActixIdentity => { + if let Ok(Some(_)) = Identity::from_request(&r, &mut pl) + .into_inner() + .map(|x| x.identity()) + { + true + } else { + false + } + } + }; + if is_authenticated() { let req = ServiceRequest::from_parts(r, pl); Either::Left(self.service.call(req)) } else { diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 370dced..4136c36 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -31,7 +31,8 @@ pub fn services(cfg: &mut ServiceConfig) { } pub fn get_page_check_login() -> crate::CheckLogin { - crate::CheckLogin::new(crate::PAGES.auth) + use crate::middleware::auth::*; + CheckLogin::new(crate::PAGES.auth, AuthenticatedSession::ActixIdentity) } #[cfg(not(tarpaulin_include))] @@ -97,7 +98,7 @@ mod tests { let headers = authenticated_resp.headers(); assert_eq!( headers.get(header::LOCATION).unwrap(), - PAGES.panel.campaigns.home + &*super::panel::DEFAULT_CAMPAIGN_ABOUT ); } else { assert_eq!(authenticated_resp.status(), StatusCode::OK); diff --git a/src/pages/panel/campaigns/bench.rs b/src/pages/panel/campaigns/bench.rs index 318fa11..0d5aea6 100644 --- a/src/pages/panel/campaigns/bench.rs +++ b/src/pages/panel/campaigns/bench.rs @@ -36,7 +36,7 @@ lazy_static! { #[get( path = "PAGES.panel.campaigns.bench", - wrap = "crate::pages::get_page_check_login()" + wrap = "crate::api::v1::bench::get_check_login()" )] pub async fn bench(path: web::Path) -> PageResult { let path = path.into_inner(); diff --git a/src/pages/panel/campaigns/mod.rs b/src/pages/panel/campaigns/mod.rs index aac65c3..17637c2 100644 --- a/src/pages/panel/campaigns/mod.rs +++ b/src/pages/panel/campaigns/mod.rs @@ -107,12 +107,37 @@ pub async fn home(data: AppData, id: Identity) -> impl Responder { #[cfg(test)] mod tests { + use actix_web::cookie::Cookie; use actix_web::http::StatusCode; use actix_web::test; use crate::tests::*; use crate::*; + async fn protect_urls_test(urls: &[String], data: Arc, cookie: Cookie<'_>) { + let app = get_app!(data).await; + 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(cookie.clone()) + .to_request(), + ) + .await; + + assert_eq!(authenticated_resp.status(), StatusCode::OK); + } + } + #[actix_rt::test] async fn survey_pages_work() { const NAME: &str = "surveyuserpages"; @@ -127,13 +152,15 @@ mod tests { let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await; let cookies = get_cookie!(signin_resp); + let survey = get_survey_user(data.clone()).await; + let survey_cookie = get_cookie!(survey); let campaign = create_new_campaign(CAMPAIGN_NAME, data.clone(), cookies.clone()).await; let app = get_app!(data).await; - let protected_urls = + let survey_protected_urls = vec![PAGES.panel.campaigns.get_bench_route(&campaign.campaign_id)]; let public_urls = @@ -149,25 +176,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } - for url in protected_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; - - assert_eq!(authenticated_resp.status(), StatusCode::OK); - } + protect_urls_test(&survey_protected_urls, data, survey_cookie).await; } } diff --git a/src/pages/panel/mod.rs b/src/pages/panel/mod.rs index cb99281..f7feb25 100644 --- a/src/pages/panel/mod.rs +++ b/src/pages/panel/mod.rs @@ -14,10 +14,9 @@ * You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use actix_web::{http, HttpResponse, Responder}; +use lazy_static::lazy_static; use my_codegen::get; -use super::get_page_check_login; - use crate::PAGES; mod campaigns; @@ -50,9 +49,18 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) { campaigns::services(cfg); } -#[get(path = "PAGES.panel.home", wrap = "get_page_check_login()")] +lazy_static! { + pub static ref DEFAULT_CAMPAIGN_ABOUT: String = PAGES + .panel + .campaigns + .get_about_route(&*crate::SETTINGS.default_campaign); +} + +#[get(path = "PAGES.panel.home")] pub async fn home() -> impl Responder { + let loc: &str = &*DEFAULT_CAMPAIGN_ABOUT; HttpResponse::Found() - .insert_header((http::header::LOCATION, PAGES.panel.campaigns.home)) + //.insert_header((http::header::LOCATION, PAGES.panel.campaigns.home)) + .insert_header((http::header::LOCATION, loc)) .finish() } diff --git a/src/settings.rs b/src/settings.rs index 9328893..7c4fd3f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -21,12 +21,14 @@ use config::{Config, ConfigError, Environment, File}; use log::{debug, warn}; use serde::Deserialize; use url::Url; +use uuid::Uuid; #[derive(Debug, Clone, Deserialize)] pub struct Server { pub port: u32, pub domain: String, pub cookie_secret: String, + pub cookie_secret2: String, pub ip: String, pub proxy_has_tls: bool, } @@ -80,6 +82,7 @@ pub struct Settings { pub server: Server, pub source_code: String, pub password: String, + pub default_campaign: String, } #[cfg(not(tarpaulin_include))] @@ -106,9 +109,10 @@ impl Settings { log::warn!("configuration file not found"); } - s.merge(Environment::with_prefix("MCAPTCHA").separator("_"))?; + s.merge(Environment::with_prefix("MCAPTCHA").separator("__"))?; check_url(&s); + check_uuid(&s); match env::var("PORT") { Ok(val) => { @@ -144,6 +148,17 @@ fn check_url(s: &Config) { Url::parse(&url).expect("Please enter a URL for source_code in settings"); } +#[cfg(not(tarpaulin_include))] +fn check_uuid(s: &Config) { + use std::str::FromStr; + + let id = s + .get::("default_campaign") + .expect("Couldn't access default_campaign"); + + Uuid::from_str(&id).expect("Please enter a UUID for default_campaign in settings"); +} + #[cfg(not(tarpaulin_include))] fn set_from_database_url(s: &mut Config, database_conf: &DatabaseBuilder) { s.set("database.username", database_conf.username.clone()) diff --git a/src/tests.rs b/src/tests.rs index a4e9e4c..60da5ff 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -88,7 +88,7 @@ macro_rules! get_app { actix_web::App::new() .app_data(crate::get_json_err()) .wrap(crate::get_identity_service()) - .wrap(get_survey_identity_service()) + .wrap(crate::get_survey_session()) .wrap(actix_web::middleware::NormalizePath::new( actix_web::middleware::TrailingSlash::Trim, )) diff --git a/templates/index.html b/templates/index.html index c6153f6..8169aff 100644 --- a/templates/index.html +++ b/templates/index.html @@ -78,7 +78,6 @@ Get started