handle survey sessions with actix sessions
This commit is contained in:
parent
9ea9732fcd
commit
95dba20074
17 changed files with 176 additions and 82 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -13,3 +13,6 @@ coverage
|
|||
dist
|
||||
assets
|
||||
scripts/creds.py
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
|
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -31,7 +31,11 @@ pub fn services(cfg: &mut ServiceConfig) {
|
|||
}
|
||||
|
||||
pub fn get_admin_check_login() -> crate::CheckLogin<auth::routes::Auth> {
|
||||
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 {
|
||||
|
|
|
@ -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<RedirectQuery>,
|
||||
) -> ServiceResult<HttpResponse> {
|
||||
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<routes::Benches> {
|
||||
crate::CheckLogin::new(crate::V1_API_ROUTES.benches)
|
||||
pub fn get_check_login() -> crate::CheckLogin<routes::Benches> {
|
||||
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<routes::Benches> {
|
|||
)]
|
||||
async fn submit(
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
session: Session,
|
||||
payload: web::Json<Submission>,
|
||||
path: web::Path<String>,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let username = session.get::<String>(SURVEY_USER_ID).unwrap().unwrap();
|
||||
let path = path.into_inner();
|
||||
let campaign_id = Uuid::parse_str(&path).map_err(|_| ServiceError::NotAnId)?;
|
||||
|
||||
|
|
21
src/main.rs
21
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<CookieIdentityPolicy> {
|
||||
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))]
|
||||
|
|
|
@ -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<T: GetLoginRoute> {
|
||||
login: Rc<T>,
|
||||
session_type: AuthenticatedSession,
|
||||
}
|
||||
|
||||
impl<T: GetLoginRoute> CheckLogin<T> {
|
||||
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<S, GT> {
|
||||
service: S,
|
||||
login: Rc<GT>,
|
||||
session_type: AuthenticatedSession,
|
||||
}
|
||||
|
||||
impl<S, GT> Service<ServiceRequest> for CheckLoginMiddleware<S, GT>
|
||||
|
@ -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::<String>(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 {
|
||||
|
|
|
@ -31,7 +31,8 @@ pub fn services(cfg: &mut ServiceConfig) {
|
|||
}
|
||||
|
||||
pub fn get_page_check_login() -> crate::CheckLogin<auth::routes::Auth> {
|
||||
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);
|
||||
|
|
|
@ -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<String>) -> PageResult<impl Responder> {
|
||||
let path = path.into_inner();
|
||||
|
|
|
@ -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<Data>, 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,10 +14,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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::<String>("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())
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
<a
|
||||
class="link__btn"
|
||||
href="<.= crate::PAGES.panel.campaigns.get_bench_route(uuid) .>"
|
||||
target="_blank"
|
||||
>Get started</a
|
||||
>
|
||||
</main>
|
||||
|
|
Loading…
Reference in a new issue