All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			
		
			
				
	
	
		
			172 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! # Authentication middleware
 | |
| 
 | |
| use std::future::{ready, Future, Ready};
 | |
| use std::pin::Pin;
 | |
| use std::rc::Rc;
 | |
| 
 | |
| use actix_identity::IdentityExt;
 | |
| use actix_web::body::EitherBody;
 | |
| use actix_web::http::{header, Method};
 | |
| use actix_web::{
 | |
|     dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
 | |
|     Error, HttpResponse,
 | |
| };
 | |
| 
 | |
| use crate::constants::{
 | |
|     ADMIN_ROUTES, AUTHENTICATED_ROUTES, AUTHORIZE_URI, TOKEN_URI, USERINFO_URI,
 | |
| };
 | |
| use crate::controllers::base_controller::{build_fatal_error_page, redirect_user_for_login};
 | |
| use crate::data::app_config::AppConfig;
 | |
| use crate::data::session_identity::{SessionIdentity, SessionIdentityData, SessionStatus};
 | |
| 
 | |
| // There are two steps in middleware processing.
 | |
| // 1. Middleware initialization, middleware factory gets called with
 | |
| //    next service in chain as parameter.
 | |
| // 2. Middleware's call method gets called with normal request.
 | |
| pub struct AuthMiddleware;
 | |
| 
 | |
| // Middleware factory is `Transform` trait
 | |
| // `S` - type of the next service
 | |
| // `B` - type of response's body
 | |
| impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
 | |
| where
 | |
|     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
 | |
|     S::Future: 'static,
 | |
|     B: 'static,
 | |
| {
 | |
|     type Response = ServiceResponse<EitherBody<B>>;
 | |
|     type Error = Error;
 | |
|     type Transform = AuthInnerMiddleware<S>;
 | |
|     type InitError = ();
 | |
|     type Future = Ready<Result<Self::Transform, Self::InitError>>;
 | |
| 
 | |
|     fn new_transform(&self, service: S) -> Self::Future {
 | |
|         ready(Ok(AuthInnerMiddleware {
 | |
|             service: Rc::new(service),
 | |
|         }))
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[derive(Debug)]
 | |
| enum ConnStatus {
 | |
|     SignedOut,
 | |
|     RegularUser,
 | |
|     Admin,
 | |
| }
 | |
| 
 | |
| impl ConnStatus {
 | |
|     pub fn is_auth(&self) -> bool {
 | |
|         !matches!(self, ConnStatus::SignedOut)
 | |
|     }
 | |
| 
 | |
|     pub fn is_admin(&self) -> bool {
 | |
|         matches!(self, ConnStatus::Admin)
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub struct AuthInnerMiddleware<S> {
 | |
|     service: Rc<S>,
 | |
| }
 | |
| 
 | |
| impl<S, B> Service<ServiceRequest> for AuthInnerMiddleware<S>
 | |
| where
 | |
|     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
 | |
|     S::Future: 'static,
 | |
|     B: 'static,
 | |
| {
 | |
|     type Response = ServiceResponse<EitherBody<B>>;
 | |
|     type Error = Error;
 | |
| 
 | |
|     #[allow(clippy::type_complexity)]
 | |
|     type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
 | |
| 
 | |
|     forward_ready!(service);
 | |
| 
 | |
|     fn call(&self, req: ServiceRequest) -> Self::Future {
 | |
|         let service = Rc::clone(&self.service);
 | |
| 
 | |
|         // Forward request
 | |
|         Box::pin(async move {
 | |
|             // Check if POST request comes from another website (block invalid origins)
 | |
|             let origin = req.headers().get(header::ORIGIN);
 | |
|             if req.method() == Method::POST && req.path() != TOKEN_URI && req.path() != USERINFO_URI
 | |
|             {
 | |
|                 if let Some(o) = origin {
 | |
|                     if !o
 | |
|                         .to_str()
 | |
|                         .unwrap_or("bad")
 | |
|                         .eq(&AppConfig::get().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") {
 | |
|                 return Ok(req.into_response(
 | |
|                     HttpResponse::Unauthorized()
 | |
|                         .body("Hey don't touch this!")
 | |
|                         .map_into_right_body(),
 | |
|                 ));
 | |
|             }
 | |
| 
 | |
|             let id = req.get_identity().ok().map(|r| r.id().unwrap_or_default());
 | |
|             let session_data = SessionIdentity::deserialize_session_data(id);
 | |
|             let session = match session_data {
 | |
|                 Some(SessionIdentityData {
 | |
|                     status: SessionStatus::SignedIn,
 | |
|                     is_admin: true,
 | |
|                     ..
 | |
|                 }) => ConnStatus::Admin,
 | |
|                 Some(SessionIdentityData {
 | |
|                     status: SessionStatus::SignedIn,
 | |
|                     ..
 | |
|                 }) => ConnStatus::RegularUser,
 | |
|                 _ => ConnStatus::SignedOut,
 | |
|             };
 | |
| 
 | |
|             log::trace!("Connection data: {:#?}", session_data);
 | |
|             log::debug!("Connection status: {:?}", session);
 | |
| 
 | |
|             // Redirect user to login page
 | |
|             if !session.is_auth()
 | |
|                 && (req.path().starts_with(ADMIN_ROUTES)
 | |
|                     || req.path().starts_with(AUTHENTICATED_ROUTES)
 | |
|                     || req.path().eq(AUTHORIZE_URI))
 | |
|             {
 | |
|                 log::debug!(
 | |
|                     "Redirect unauthenticated user from {} to authorization route.",
 | |
|                     req.path()
 | |
|                 );
 | |
| 
 | |
|                 let path = req.uri().to_string();
 | |
|                 return Ok(req
 | |
|                     .into_response(redirect_user_for_login(path))
 | |
|                     .map_into_right_body());
 | |
|             }
 | |
| 
 | |
|             // Restrict access to admin pages
 | |
|             if !session.is_admin() && req.path().starts_with(ADMIN_ROUTES) {
 | |
|                 return Ok(req
 | |
|                     .into_response(HttpResponse::Unauthorized().body(build_fatal_error_page(
 | |
|                         "You are not allowed to access this resource.",
 | |
|                     )))
 | |
|                     .map_into_right_body());
 | |
|             }
 | |
| 
 | |
|             service
 | |
|                 .call(req)
 | |
|                 .await
 | |
|                 .map(ServiceResponse::map_into_left_body)
 | |
|         })
 | |
|     }
 | |
| }
 |