authentication middleware handles redirects

This commit is contained in:
Aravinth Manivannan 2021-10-12 21:56:16 +05:30
parent f10c620040
commit 4158147f84
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
10 changed files with 294 additions and 50 deletions

7
Cargo.lock generated
View file

@ -2679,6 +2679,7 @@ dependencies = [
"sqlx",
"tokio",
"url",
"urlencoding",
"uuid",
"validator",
]
@ -2963,6 +2964,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "urlencoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821"
[[package]]
name = "utf-8"
version = "0.7.6"

View file

@ -54,6 +54,7 @@ log = "0.4"
lazy_static = "1.4"
url = "2.2"
urlencoding = "2.1.0"
rand = "0.8"
uuid = { version="0.8.2", features = ["v4"]}

View file

@ -25,12 +25,29 @@ use crate::errors::*;
use crate::AppData;
pub mod routes {
use crate::middleware::auth::GetLoginRoute;
use url::Url;
pub struct Auth {
pub logout: &'static str,
pub login: &'static str,
pub register: &'static str,
}
impl GetLoginRoute for Auth {
fn get_login_route(&self, src: Option<&str>) -> String {
if let Some(redirect_to) = src {
let mut url = Url::parse("http://x/").unwrap();
url.set_path(self.login);
url.query_pairs_mut()
.append_pair("redirect_to", redirect_to);
let path = format!("{}/?{}", url.path(), url.query().unwrap());
path
} else {
self.login.to_string()
}
}
}
impl Auth {
pub const fn new() -> Auth {
let login = "/api/v1/admin/signin";

View file

@ -51,7 +51,7 @@ pub mod runners {
let mut uuid;
let now = OffsetDateTime::now_utc();
payload.difficulties.sort();
payload.difficulties.sort_unstable();
loop {
uuid = get_uuid();
@ -121,11 +121,45 @@ async fn add(
#[cfg(test)]
mod tests {
use crate::api::v1::bench::{Bench, Submission};
use crate::api::v1::bench::Submission;
use crate::data::Data;
use crate::middleware::auth::GetLoginRoute;
use crate::tests::*;
use crate::*;
use actix_web::{http::header, test};
#[actix_rt::test]
async fn test_bench_register_works() {
let data = Data::new().await;
let app = get_app!(data).await;
let signin_resp = test::call_service(
&app,
test::TestRequest::get()
.uri(V1_API_ROUTES.benches.register)
.to_request(),
)
.await;
assert_eq!(signin_resp.status(), StatusCode::OK);
let redirect_to = Some("foo");
let signin_resp = test::call_service(
&app,
test::TestRequest::get()
.uri(&V1_API_ROUTES.benches.get_login_route(redirect_to))
.to_request(),
)
.await;
assert_eq!(signin_resp.status(), StatusCode::FOUND);
let headers = signin_resp.headers();
assert_eq!(
headers.get(header::LOCATION).unwrap(),
redirect_to.as_ref().unwrap()
)
}
#[actix_rt::test]
async fn test_add_campaign() {
const NAME: &str = "testadminuser";
@ -136,29 +170,6 @@ mod tests {
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
const THREADS: i32 = 4;
let benches = vec![
Bench {
difficulty: 1,
duration: 1.00,
},
Bench {
difficulty: 2,
duration: 2.00,
},
Bench {
difficulty: 3,
duration: 3.00,
},
Bench {
difficulty: 4,
duration: 4.00,
},
Bench {
difficulty: 5,
duration: 5.00,
},
];
{
let data = Data::new().await;
delete_user(NAME, &data).await;
@ -181,7 +192,7 @@ mod tests {
device_user_provided: DEVICE_USER_PROVIDED.into(),
device_software_recognised: DEVICE_SOFTWARE_RECOGNISED.into(),
threads: THREADS,
benches: benches.clone(),
benches: BENCHES.clone(),
};
let _proof =

View file

@ -30,8 +30,8 @@ pub fn services(cfg: &mut ServiceConfig) {
campaigns::services(cfg);
}
pub fn get_admin_check_login() -> crate::CheckLogin {
crate::CheckLogin::new(crate::V1_API_ROUTES.admin.auth.register)
pub fn get_admin_check_login() -> crate::CheckLogin<auth::routes::Auth> {
crate::CheckLogin::new(crate::V1_API_ROUTES.admin.auth)
}
pub mod routes {

View file

@ -18,7 +18,7 @@ use std::borrow::Cow;
use std::str::FromStr;
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use actix_web::{http, web, HttpResponse, Responder};
use futures::future::try_join_all;
use serde::{Deserialize, Serialize};
use sqlx::types::time::OffsetDateTime;
@ -29,6 +29,9 @@ use crate::errors::*;
use crate::AppData;
pub mod routes {
use crate::middleware::auth::GetLoginRoute;
pub struct Benches {
pub submit: &'static str,
pub register: &'static str,
@ -36,6 +39,25 @@ pub mod routes {
pub scope: &'static str,
}
impl GetLoginRoute for Benches {
fn get_login_route(&self, src: Option<&str>) -> String {
if let Some(redirect_to) = src {
// uri::Builder::new().path_and_query(
format!(
"{}?redirect_to={}",
self.register,
urlencoding::encode(redirect_to)
)
// let mut url: Uri = self.register.parse().unwrap();
// url.qu
// url.query_pairs_mut()
// .append_pair("redirect_to", redirect_to);
} else {
self.register.to_string()
}
}
}
impl Benches {
pub const fn new() -> Benches {
let submit = "/api/v1/benches/{campaign_id}/submit";
@ -50,10 +72,10 @@ pub mod routes {
}
}
pub fn submit_route(&self, campaign_id: &str) -> String {
self.submit.replace("{campaign_id}", &campaign_id)
self.submit.replace("{campaign_id}", campaign_id)
}
pub fn fetch_routes(&self, campaign_id: &str) -> String {
self.fetch.replace("{campaign_id}", &campaign_id)
self.fetch.replace("{campaign_id}", campaign_id)
}
}
}
@ -99,11 +121,27 @@ pub mod runners {
}
}
#[derive(Deserialize)]
pub struct Query {
pub redirect_to: Option<String>,
}
#[my_codegen::get(path = "crate::V1_API_ROUTES.benches.register")]
async fn register(data: AppData, id: Identity) -> ServiceResult<impl Responder> {
async fn register(
data: AppData,
id: Identity,
path: web::Query<Query>,
) -> ServiceResult<HttpResponse> {
let uuid = runners::register_runner(&data).await?;
id.remember(uuid.to_string());
Ok(HttpResponse::Ok())
let path = path.into_inner();
if let Some(redirect_to) = path.redirect_to {
Ok(HttpResponse::Found()
.insert_header((http::header::LOCATION, redirect_to))
.finish())
} else {
Ok(HttpResponse::Ok().into())
}
}
#[derive(Serialize, Deserialize, Clone)]
@ -126,8 +164,8 @@ pub struct SubmissionProof {
pub proof: String,
}
fn get_check_login() -> crate::CheckLogin {
crate::CheckLogin::new(crate::V1_API_ROUTES.benches.register)
fn get_check_login() -> crate::CheckLogin<routes::Benches> {
crate::CheckLogin::new(crate::V1_API_ROUTES.benches)
}
#[my_codegen::post(

View file

@ -16,6 +16,8 @@
*/
#![allow(clippy::type_complexity)]
use std::rc::Rc;
use actix_http::body::AnyBody;
use actix_identity::Identity;
use actix_service::{Service, Transform};
@ -24,43 +26,50 @@ use actix_web::{http, Error, FromRequest, HttpResponse};
use futures::future::{ok, Either, Ready};
pub struct CheckLogin {
login: &'static str,
pub trait GetLoginRoute {
fn get_login_route(&self, src: Option<&str>) -> String;
}
impl CheckLogin {
pub fn new(login: &'static str) -> Self {
pub struct CheckLogin<T: GetLoginRoute> {
login: Rc<T>,
}
impl<T: GetLoginRoute> CheckLogin<T> {
pub fn new(login: T) -> Self {
let login = Rc::new(login);
Self { login }
}
}
impl<S> Transform<S, ServiceRequest> for CheckLogin
impl<S, GT> Transform<S, ServiceRequest> for CheckLogin<GT>
where
S: Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error>,
S::Future: 'static,
GT: GetLoginRoute,
{
type Response = ServiceResponse<AnyBody>;
type Error = Error;
type Transform = CheckLoginMiddleware<S>;
type Transform = CheckLoginMiddleware<S, GT>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(CheckLoginMiddleware {
service,
login: self.login,
login: self.login.clone(),
})
}
}
pub struct CheckLoginMiddleware<S> {
pub struct CheckLoginMiddleware<S, GT> {
service: S,
login: &'static str,
login: Rc<GT>,
}
impl<S> Service<ServiceRequest> for CheckLoginMiddleware<S>
impl<S, GT> Service<ServiceRequest> for CheckLoginMiddleware<S, GT>
where
S: Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error>,
S::Future: 'static,
GT: GetLoginRoute,
{
type Response = ServiceResponse<AnyBody>;
type Error = Error;
@ -80,12 +89,134 @@ where
let req = ServiceRequest::from_parts(r, pl);
Either::Left(self.service.call(req))
} else {
let req = ServiceRequest::from_parts(r, pl); //.ok().unwrap();
let path = r.uri().path_and_query().map(|path| path.as_str());
let path = self.login.get_login_route(path);
let req = ServiceRequest::from_parts(r, pl);
Either::Right(ok(req.into_response(
HttpResponse::Found()
.insert_header((http::header::LOCATION, self.login))
.insert_header((http::header::LOCATION, path))
.finish(),
)))
}
}
}
#[cfg(test)]
mod tests {
use url::Url;
use crate::api::v1::bench::Submission;
use crate::data::Data;
use crate::middleware::auth::GetLoginRoute;
use crate::tests::*;
use crate::*;
use actix_web::{http::header, test};
#[actix_rt::test]
async fn auth_middleware_works() {
fn make_uri(path: &str, queries: &Option<Vec<(&str, &str)>>) -> String {
let mut url = Url::parse("http://x/").unwrap();
let final_path;
url.set_path(path);
if let Some(queries) = queries {
{
let mut query_pairs = url.query_pairs_mut();
queries.iter().for_each(|(k, v)| {
query_pairs.append_pair(k, v);
});
}
final_path = format!("{}?{}", url.path(), url.query().unwrap());
} else {
final_path = url.path().to_string();
}
final_path
}
const NAME: &str = "testmiddlewareuser";
const EMAIL: &str = "testuserupda@testmiddlewareuser.com";
const PASSWORD: &str = "longpassword2";
const DEVICE_USER_PROVIDED: &str = "foo";
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
const THREADS: i32 = 4;
let queries = Some(vec![
("foo", "bar"),
("src", "/x/y/z"),
("with_q", "/a/b/c/?goo=x"),
]);
{
let data = Data::new().await;
delete_user(NAME, &data).await;
}
let (data, _creds, 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(NAME, data.clone(), cookies.clone()).await;
let bench_submit_route =
V1_API_ROUTES.benches.submit_route(&campaign.campaign_id);
let bench_routes = vec![
(&bench_submit_route, queries.clone()),
(&bench_submit_route, None),
];
let app = get_app!(data).await;
// let campaign_routes = vec![
// (Some(V1_API_ROUTES.camp.submit), queries.clone()),
// (None, None),
// (Some(V1_API_ROUTES.benches.submit), None),
// ];
let bench_submit_payload = Submission {
device_user_provided: DEVICE_USER_PROVIDED.into(),
device_software_recognised: DEVICE_SOFTWARE_RECOGNISED.into(),
threads: THREADS,
benches: BENCHES.clone(),
};
for (from, query) in bench_routes.iter() {
let route = make_uri(from, query);
let signin_resp = test::call_service(
&app,
post_request!(&bench_submit_payload, &route).to_request(),
)
.await;
assert_eq!(signin_resp.status(), StatusCode::FOUND);
let redirect_to = V1_API_ROUTES.benches.get_login_route(Some(&route));
let headers = signin_resp.headers();
assert_eq!(headers.get(header::LOCATION).unwrap(), &redirect_to);
let add_feedback_resp = test::call_service(
&app,
post_request!(&bench_submit_payload, &route)
.cookie(survey_cookie.clone())
.to_request(),
)
.await;
assert_eq!(add_feedback_resp.status(), StatusCode::OK);
}
}
// let signin_resp = test::call_service(
// &app,
// test::TestRequest::get()
// .uri(V1_API_ROUTES.benches.get_login_route(redirect_to).as_ref().unwrap())
// .to_request(),
// )
// .await;
// assert_eq!(signin_resp.status(), StatusCode::FOUND);
// let headers = signin_resp.headers();
// assert_eq!(
// headers.get(header::LOCATION).unwrap(),
// redirect_to.as_ref().unwrap()
// )
//
}

View file

@ -126,8 +126,8 @@ mod tests {
for file in [
assets::LOGO.path,
assets::HEADSETS.path,
&*crate::JS,
&*crate::GLUE,
*crate::JS,
*crate::GLUE,
]
.iter()
{

View file

@ -20,6 +20,7 @@ use std::sync::Arc;
use actix_web::cookie::Cookie;
use actix_web::test;
use actix_web::{dev::ServiceResponse, error::ResponseError, http::StatusCode};
use lazy_static::lazy_static;
use serde::Serialize;
use uuid::Uuid;
@ -28,7 +29,7 @@ use crate::api::v1::admin::{
auth::runners::{Login, Register},
campaigns::{AddCapmaign, AddCapmaignResp},
};
use crate::api::v1::bench::{BenchConfig, Submission, SubmissionProof};
use crate::api::v1::bench::{Bench, BenchConfig, Submission, SubmissionProof};
use crate::data::Data;
use crate::errors::*;
use crate::V1_API_ROUTES;
@ -396,3 +397,28 @@ pub async fn submit_bench(
// assert_eq!(get_feedback_resp.status(), StatusCode::OK);
// test::read_body_json(get_feedback_resp).await
//}
lazy_static! {
pub static ref BENCHES: Vec<Bench> = vec![
Bench {
difficulty: 1,
duration: 1.00,
},
Bench {
difficulty: 2,
duration: 2.00,
},
Bench {
difficulty: 3,
duration: 3.00,
},
Bench {
difficulty: 4,
duration: 4.00,
},
Bench {
difficulty: 5,
duration: 5.00,
},
];
}

13
templates/vendor.ts Normal file
View file

@ -0,0 +1,13 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import * as glue from 'mcaptcha-glue'
glue.init()