Block POST requests from unknown origins

This commit is contained in:
Pierre HUBERT 2022-04-03 15:48:45 +02:00
parent 9f5fdd65ab
commit 9236b91f12
3 changed files with 35 additions and 10 deletions

View File

@ -5,7 +5,7 @@ use clap::Parser;
use crate::constants::USERS_LIST_FILE; use crate::constants::USERS_LIST_FILE;
/// Basic OIDC provider /// Basic OIDC provider
#[derive(Parser, Debug)] #[derive(Parser, Debug, Clone)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
pub struct AppConfig { pub struct AppConfig {
/// Listen address /// Listen address
@ -20,12 +20,16 @@ pub struct AppConfig {
#[clap(short, long, env, default_value = "")] #[clap(short, long, env, default_value = "")]
pub token_key: String, pub token_key: String,
/// Should the auth cookie be secure /// Website origin
#[clap(long, env)] #[clap(short, long, env, default_value = "http://localhost:8000")]
pub secure_auth_cookie: bool, pub website_origin: String,
} }
impl AppConfig { impl AppConfig {
pub fn secure_cookie(&self) -> bool {
self.website_origin.starts_with("https:")
}
pub fn storage_path(&self) -> &Path { pub fn storage_path(&self) -> &Path {
self.storage_path.as_ref() self.storage_path.as_ref()
} }

View File

@ -62,11 +62,12 @@ async fn main() -> std::io::Result<()> {
let users_actor = UsersActor::new(users).start(); let users_actor = UsersActor::new(users).start();
log::info!("Server will listen on {}", config.listen_address); log::info!("Server will listen on {}", config.listen_address);
let listen_address = config.listen_address.to_string();
HttpServer::new(move || { HttpServer::new(move || {
let policy = CookieIdentityPolicy::new(config.token_key.as_bytes()) let policy = CookieIdentityPolicy::new(config.token_key.as_bytes())
.name(SESSION_COOKIE_NAME) .name(SESSION_COOKIE_NAME)
.secure(config.secure_auth_cookie) .secure(config.secure_cookie())
.visit_deadline(Duration::seconds(MAX_INACTIVITY_DURATION)) .visit_deadline(Duration::seconds(MAX_INACTIVITY_DURATION))
.login_deadline(Duration::seconds(MAX_SESSION_DURATION)) .login_deadline(Duration::seconds(MAX_SESSION_DURATION))
.same_site(SameSite::Strict); .same_site(SameSite::Strict);
@ -74,6 +75,7 @@ async fn main() -> std::io::Result<()> {
App::new() App::new()
.app_data(web::Data::new(users_actor.clone())) .app_data(web::Data::new(users_actor.clone()))
.app_data(web::Data::new(config.clone()))
.wrap(Logger::default()) .wrap(Logger::default())
.wrap(AuthMiddleware {}) .wrap(AuthMiddleware {})
@ -92,7 +94,7 @@ async fn main() -> std::io::Result<()> {
// Logout page // Logout page
.route("/logout", web::get().to(logout_route)) .route("/logout", web::get().to(logout_route))
}) })
.bind(config.listen_address)? .bind(listen_address)?
.run() .run()
.await .await
} }

View File

@ -5,12 +5,14 @@ use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use actix_identity::RequestIdentity; use actix_identity::RequestIdentity;
use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error, HttpResponse}; use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error, HttpResponse, web};
use actix_web::body::EitherBody; use actix_web::body::EitherBody;
use actix_web::http::{header, Method};
use askama::Template; use askama::Template;
use crate::constants::{ADMIN_ROUTES, AUTHENTICATED_ROUTES}; use crate::constants::{ADMIN_ROUTES, AUTHENTICATED_ROUTES};
use crate::controllers::base_controller::redirect_user_for_login; use crate::controllers::base_controller::redirect_user_for_login;
use crate::data::app_config::AppConfig;
use crate::data::session_identity::{SessionIdentity, SessionIdentityData, SessionStatus}; use crate::data::session_identity::{SessionIdentity, SessionIdentityData, SessionStatus};
// There are two steps in middleware processing. // There are two steps in middleware processing.
@ -83,6 +85,23 @@ impl<S, B> Service<ServiceRequest> for AuthInnerMiddleware<S>
// Forward request // Forward request
Box::pin(async move { Box::pin(async move {
let config: &web::Data<AppConfig> = req.app_data().expect("AppData undefined!");
// Check if POST request comes from another website (block invalid origins)
let origin = req.headers().get(header::ORIGIN);
if req.method() == Method::POST {
if let Some(o) = origin {
if !o.to_str().unwrap_or("bad").eq(&config.website_origin) {
log::warn!("Blocked POST request from invalid origin! Origin given {:?}", o);
return Ok(req.into_response(
HttpResponse::Unauthorized()
.body("POST request from invalid origin!")
.map_into_right_body()
));
}
}
}
if req.path().starts_with("/.git") { if req.path().starts_with("/.git") {
return Ok(req.into_response( return Ok(req.into_response(
HttpResponse::Unauthorized() HttpResponse::Unauthorized()
@ -91,14 +110,14 @@ impl<S, B> Service<ServiceRequest> for AuthInnerMiddleware<S>
)); ));
} }
let identity = match SessionIdentity::deserialize_session_data(req.get_identity()) { let session = match SessionIdentity::deserialize_session_data(req.get_identity()) {
Some(SessionIdentityData { status: SessionStatus::SignedIn, is_admin: true, .. }) => ConnStatus::Admin, Some(SessionIdentityData { status: SessionStatus::SignedIn, is_admin: true, .. }) => ConnStatus::Admin,
Some(SessionIdentityData { status: SessionStatus::SignedIn, .. }) => ConnStatus::RegularUser, Some(SessionIdentityData { status: SessionStatus::SignedIn, .. }) => ConnStatus::RegularUser,
_ => ConnStatus::SignedOut, _ => ConnStatus::SignedOut,
}; };
// Redirect user to login page // Redirect user to login page
if !identity.is_auth() && (req.path().starts_with(ADMIN_ROUTES) || if !session.is_auth() && (req.path().starts_with(ADMIN_ROUTES) ||
req.path().starts_with(AUTHENTICATED_ROUTES)) { req.path().starts_with(AUTHENTICATED_ROUTES)) {
let path = req.path().to_string(); let path = req.path().to_string();
return Ok(req.into_response(redirect_user_for_login(path)) return Ok(req.into_response(redirect_user_for_login(path))
@ -106,7 +125,7 @@ impl<S, B> Service<ServiceRequest> for AuthInnerMiddleware<S>
} }
// Restrict access to admin pages // Restrict access to admin pages
if !identity.is_admin() && req.path().starts_with(ADMIN_ROUTES) { if !session.is_admin() && req.path().starts_with(ADMIN_ROUTES) {
return Ok(req.into_response(HttpResponse::Unauthorized() return Ok(req.into_response(HttpResponse::Unauthorized()
.body(AccessDeniedTemplate {}.render().unwrap())) .body(AccessDeniedTemplate {}.render().unwrap()))
.map_into_right_body()); .map_into_right_body());