authentication middleware handles redirects
This commit is contained in:
parent
f10c620040
commit
4158147f84
10 changed files with 294 additions and 50 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"]}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
// )
|
||||
//
|
||||
}
|
||||
|
|
|
@ -126,8 +126,8 @@ mod tests {
|
|||
for file in [
|
||||
assets::LOGO.path,
|
||||
assets::HEADSETS.path,
|
||||
&*crate::JS,
|
||||
&*crate::GLUE,
|
||||
*crate::JS,
|
||||
*crate::GLUE,
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
|
|
28
src/tests.rs
28
src/tests.rs
|
@ -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
13
templates/vendor.ts
Normal 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()
|
Loading…
Reference in a new issue