Start to implement OpenID authentication
This commit is contained in:
		
							
								
								
									
										47
									
								
								remote_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										47
									
								
								remote_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -58,6 +58,22 @@ dependencies = [
 | 
				
			|||||||
 "zstd",
 | 
					 "zstd",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "actix-identity"
 | 
				
			||||||
 | 
					version = "0.7.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "f2c99b7a5614b72a78f04aa2021e5370fc1aef2475fffeffc0c1266b99007062"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "actix-service",
 | 
				
			||||||
 | 
					 "actix-session",
 | 
				
			||||||
 | 
					 "actix-utils",
 | 
				
			||||||
 | 
					 "actix-web",
 | 
				
			||||||
 | 
					 "derive_more",
 | 
				
			||||||
 | 
					 "futures-core",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "actix-macros"
 | 
					name = "actix-macros"
 | 
				
			||||||
version = "0.2.4"
 | 
					version = "0.2.4"
 | 
				
			||||||
@@ -130,6 +146,22 @@ dependencies = [
 | 
				
			|||||||
 "pin-project-lite",
 | 
					 "pin-project-lite",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "actix-session"
 | 
				
			||||||
 | 
					version = "0.9.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "b671404ec72194d8af58c2bdaf51e3c477a0595056bd5010148405870dda8df2"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "actix-service",
 | 
				
			||||||
 | 
					 "actix-utils",
 | 
				
			||||||
 | 
					 "actix-web",
 | 
				
			||||||
 | 
					 "anyhow",
 | 
				
			||||||
 | 
					 "derive_more",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_json",
 | 
				
			||||||
 | 
					 "tracing",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "actix-utils"
 | 
					name = "actix-utils"
 | 
				
			||||||
version = "3.0.1"
 | 
					version = "3.0.1"
 | 
				
			||||||
@@ -360,6 +392,12 @@ version = "0.2.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
 | 
					checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "base64"
 | 
				
			||||||
 | 
					version = "0.20.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "base64"
 | 
					name = "base64"
 | 
				
			||||||
version = "0.21.7"
 | 
					version = "0.21.7"
 | 
				
			||||||
@@ -565,7 +603,14 @@ version = "0.16.2"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
 | 
					checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "aes-gcm",
 | 
				
			||||||
 | 
					 "base64 0.20.0",
 | 
				
			||||||
 | 
					 "hkdf",
 | 
				
			||||||
 | 
					 "hmac",
 | 
				
			||||||
 "percent-encoding",
 | 
					 "percent-encoding",
 | 
				
			||||||
 | 
					 "rand",
 | 
				
			||||||
 | 
					 "sha2",
 | 
				
			||||||
 | 
					 "subtle",
 | 
				
			||||||
 "time",
 | 
					 "time",
 | 
				
			||||||
 "version_check",
 | 
					 "version_check",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
@@ -1646,12 +1691,14 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 | 
				
			|||||||
name = "remote_backend"
 | 
					name = "remote_backend"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "actix-identity",
 | 
				
			||||||
 "actix-remote-ip",
 | 
					 "actix-remote-ip",
 | 
				
			||||||
 "actix-web",
 | 
					 "actix-web",
 | 
				
			||||||
 "anyhow",
 | 
					 "anyhow",
 | 
				
			||||||
 "basic-jwt",
 | 
					 "basic-jwt",
 | 
				
			||||||
 "clap",
 | 
					 "clap",
 | 
				
			||||||
 "env_logger",
 | 
					 "env_logger",
 | 
				
			||||||
 | 
					 "futures-util",
 | 
				
			||||||
 "lazy_static",
 | 
					 "lazy_static",
 | 
				
			||||||
 "light-openid",
 | 
					 "light-openid",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,10 +12,12 @@ clap = { version = "4.5.4", features = ["derive", "env"] }
 | 
				
			|||||||
serde = { version = "1.0.198", features = ["derive"] }
 | 
					serde = { version = "1.0.198", features = ["derive"] }
 | 
				
			||||||
light-openid = { version = "1.0.2", features = ["crypto-wrapper"] }
 | 
					light-openid = { version = "1.0.2", features = ["crypto-wrapper"] }
 | 
				
			||||||
basic-jwt = "0.2.0"
 | 
					basic-jwt = "0.2.0"
 | 
				
			||||||
actix-remote-ip = "0.1.0"
 | 
					 | 
				
			||||||
lazy_static = "1.4.0"
 | 
					 | 
				
			||||||
actix-web = "4.5.1"
 | 
					actix-web = "4.5.1"
 | 
				
			||||||
 | 
					actix-remote-ip = "0.1.0"
 | 
				
			||||||
 | 
					actix-identity = "0.7.1"
 | 
				
			||||||
 | 
					lazy_static = "1.4.0"
 | 
				
			||||||
anyhow = "1.0.82"
 | 
					anyhow = "1.0.82"
 | 
				
			||||||
reqwest = { version = "0.12.4", features = ["json"] }
 | 
					reqwest = { version = "0.12.4", features = ["json"] }
 | 
				
			||||||
thiserror = "1.0.59"
 | 
					thiserror = "1.0.59"
 | 
				
			||||||
uuid = { version = "1.8.0", features = ["v4"] }
 | 
					uuid = { version = "1.8.0", features = ["v4"] }
 | 
				
			||||||
 | 
					futures-util = "0.3.30"
 | 
				
			||||||
@@ -33,9 +33,9 @@ pub struct AppConfig {
 | 
				
			|||||||
    )]
 | 
					    )]
 | 
				
			||||||
    pub oidc_configuration_url: String,
 | 
					    pub oidc_configuration_url: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Disable OpenID authentication
 | 
					    /// Disable authentication (for development purposes ONLY)
 | 
				
			||||||
    #[arg(long, env)]
 | 
					    #[arg(long, env)]
 | 
				
			||||||
    pub disable_oidc: bool,
 | 
					    pub unsecure_disable_login: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// OpenID client ID
 | 
					    /// OpenID client ID
 | 
				
			||||||
    #[arg(long, env, default_value = "foo")]
 | 
					    #[arg(long, env, default_value = "foo")]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								remote_backend/src/constants.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								remote_backend/src/constants.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					pub const ROUTES_WITHOUT_AUTH: [&str; 3] = [
 | 
				
			||||||
 | 
					    "/api/server/config",
 | 
				
			||||||
 | 
					    "/api/auth/start_oidc",
 | 
				
			||||||
 | 
					    "/api/auth/finish_oidc",
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
							
								
								
									
										97
									
								
								remote_backend/src/controllers/auth_controller.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								remote_backend/src/controllers/auth_controller.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					use actix_remote_ip::RemoteIP;
 | 
				
			||||||
 | 
					use actix_web::web::Data;
 | 
				
			||||||
 | 
					use actix_web::{web, HttpResponse, Responder};
 | 
				
			||||||
 | 
					use light_openid::basic_state_manager::BasicStateManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
 | 
					use crate::controllers::HttpResult;
 | 
				
			||||||
 | 
					use crate::extractors::auth_extractor::AuthExtractor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize)]
 | 
				
			||||||
 | 
					struct StartOIDCResponse {
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Start OIDC authentication
 | 
				
			||||||
 | 
					pub async fn start_oidc(sm: Data<BasicStateManager>, ip: RemoteIP) -> HttpResult {
 | 
				
			||||||
 | 
					    let prov = AppConfig::get().openid_provider();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let conf =
 | 
				
			||||||
 | 
					        light_openid::primitives::OpenIDConfig::load_from_url(prov.configuration_url).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(StartOIDCResponse {
 | 
				
			||||||
 | 
					        url: conf.gen_authorization_url(
 | 
				
			||||||
 | 
					            prov.client_id,
 | 
				
			||||||
 | 
					            &sm.gen_state(ip.0)?,
 | 
				
			||||||
 | 
					            &AppConfig::get().oidc_redirect_url(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct FinishOpenIDLoginQuery {
 | 
				
			||||||
 | 
					    code: String,
 | 
				
			||||||
 | 
					    state: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Finish OIDC authentication
 | 
				
			||||||
 | 
					pub async fn finish_oidc(
 | 
				
			||||||
 | 
					    sm: Data<BasicStateManager>,
 | 
				
			||||||
 | 
					    remote_ip: RemoteIP,
 | 
				
			||||||
 | 
					    req: web::Json<FinishOpenIDLoginQuery>,
 | 
				
			||||||
 | 
					    auth: AuthExtractor,
 | 
				
			||||||
 | 
					) -> HttpResult {
 | 
				
			||||||
 | 
					    if let Err(e) = sm.validate_state(remote_ip.0, &req.state) {
 | 
				
			||||||
 | 
					        log::error!("Failed to validate OIDC CB state! {e}");
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::BadRequest().json("Invalid state!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let prov = AppConfig::get().openid_provider();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let conf =
 | 
				
			||||||
 | 
					        light_openid::primitives::OpenIDConfig::load_from_url(prov.configuration_url).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let (token, _) = conf
 | 
				
			||||||
 | 
					        .request_token(
 | 
				
			||||||
 | 
					            prov.client_id,
 | 
				
			||||||
 | 
					            prov.client_secret,
 | 
				
			||||||
 | 
					            &req.code,
 | 
				
			||||||
 | 
					            &AppConfig::get().oidc_redirect_url(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					    let (user_info, _) = conf.request_user_info(&token).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if user_info.email_verified != Some(true) {
 | 
				
			||||||
 | 
					        log::error!("Email is not verified!");
 | 
				
			||||||
 | 
					        return Ok(HttpResponse::Unauthorized().json("Email unverified by IDP!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mail = match user_info.email {
 | 
				
			||||||
 | 
					        Some(m) => m,
 | 
				
			||||||
 | 
					        None => {
 | 
				
			||||||
 | 
					            return Ok(HttpResponse::Unauthorized().json("Email not provided by the IDP!"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auth.authenticate(mail);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().finish())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize)]
 | 
				
			||||||
 | 
					struct CurrentUser {
 | 
				
			||||||
 | 
					    id: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get current authenticated user
 | 
				
			||||||
 | 
					pub async fn current_user(auth: AuthExtractor) -> impl Responder {
 | 
				
			||||||
 | 
					    HttpResponse::Ok().json(CurrentUser {
 | 
				
			||||||
 | 
					        id: auth.id().unwrap_or_else(|| "Anonymous".to_string()),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Sign out
 | 
				
			||||||
 | 
					pub async fn sign_out(auth: AuthExtractor) -> impl Responder {
 | 
				
			||||||
 | 
					    auth.sign_out();
 | 
				
			||||||
 | 
					    HttpResponse::Ok().finish()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										89
									
								
								remote_backend/src/controllers/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								remote_backend/src/controllers/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					use actix_web::body::BoxBody;
 | 
				
			||||||
 | 
					use actix_web::http::StatusCode;
 | 
				
			||||||
 | 
					use actix_web::HttpResponse;
 | 
				
			||||||
 | 
					use std::error::Error;
 | 
				
			||||||
 | 
					use std::fmt::{Display, Formatter};
 | 
				
			||||||
 | 
					use std::io::ErrorKind;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod auth_controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Custom error to ease controller writing
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub enum HttpErr {
 | 
				
			||||||
 | 
					    Err(anyhow::Error),
 | 
				
			||||||
 | 
					    HTTPResponse(HttpResponse),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Display for HttpErr {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            HttpErr::Err(err) => Display::fmt(err, f),
 | 
				
			||||||
 | 
					            HttpErr::HTTPResponse(res) => {
 | 
				
			||||||
 | 
					                Display::fmt(&format!("HTTP RESPONSE {}", res.status().as_str()), f)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl actix_web::error::ResponseError for HttpErr {
 | 
				
			||||||
 | 
					    fn status_code(&self) -> StatusCode {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            HttpErr::Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
 | 
				
			||||||
 | 
					            HttpErr::HTTPResponse(r) => r.status(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fn error_response(&self) -> HttpResponse<BoxBody> {
 | 
				
			||||||
 | 
					        log::error!("Error while processing request! {}", self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        HttpResponse::InternalServerError().body("Failed to execute request!")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<anyhow::Error> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(err: anyhow::Error) -> HttpErr {
 | 
				
			||||||
 | 
					        HttpErr::Err(err)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<Box<dyn Error>> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: Box<dyn Error>) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<std::io::Error> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: std::io::Error) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::Err(value.into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<std::num::ParseIntError> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: std::num::ParseIntError) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::Err(value.into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<reqwest::Error> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: reqwest::Error) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::Err(value.into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<reqwest::header::ToStrError> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: reqwest::header::ToStrError) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::Err(value.into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<actix_web::Error> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: actix_web::Error) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::Err(std::io::Error::new(ErrorKind::Other, value.to_string()).into())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<HttpResponse> for HttpErr {
 | 
				
			||||||
 | 
					    fn from(value: HttpResponse) -> Self {
 | 
				
			||||||
 | 
					        HttpErr::HTTPResponse(value)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					pub type HttpResult = Result<HttpResponse, HttpErr>;
 | 
				
			||||||
							
								
								
									
										47
									
								
								remote_backend/src/extractors/auth_extractor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								remote_backend/src/extractors/auth_extractor.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					use actix_identity::Identity;
 | 
				
			||||||
 | 
					use actix_web::dev::Payload;
 | 
				
			||||||
 | 
					use actix_web::{Error, FromRequest, HttpMessage, HttpRequest};
 | 
				
			||||||
 | 
					use futures_util::future::{ready, Ready};
 | 
				
			||||||
 | 
					use std::fmt::Display;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct AuthExtractor {
 | 
				
			||||||
 | 
					    identity: Option<Identity>,
 | 
				
			||||||
 | 
					    request: HttpRequest,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AuthExtractor {
 | 
				
			||||||
 | 
					    /// Check whether the user is authenticated or not
 | 
				
			||||||
 | 
					    pub fn is_authenticated(&self) -> bool {
 | 
				
			||||||
 | 
					        self.identity.is_some()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Authenticate the user
 | 
				
			||||||
 | 
					    pub fn authenticate(&self, id: impl Display) {
 | 
				
			||||||
 | 
					        Identity::login(&self.request.extensions(), id.to_string())
 | 
				
			||||||
 | 
					            .expect("Unable to set authentication!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn id(&self) -> Option<String> {
 | 
				
			||||||
 | 
					        self.identity.as_ref().map(|i| i.id().unwrap())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn sign_out(self) {
 | 
				
			||||||
 | 
					        if let Some(i) = self.identity {
 | 
				
			||||||
 | 
					            i.logout()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromRequest for AuthExtractor {
 | 
				
			||||||
 | 
					    type Error = Error;
 | 
				
			||||||
 | 
					    type Future = Ready<Result<Self, Error>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
 | 
				
			||||||
 | 
					        let identity: Option<Identity> = Identity::from_request(req, payload).into_inner().ok();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ready(Ok(Self {
 | 
				
			||||||
 | 
					            identity,
 | 
				
			||||||
 | 
					            request: req.clone(),
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								remote_backend/src/extractors/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								remote_backend/src/extractors/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					pub mod auth_extractor;
 | 
				
			||||||
@@ -1,3 +1,7 @@
 | 
				
			|||||||
pub mod app_config;
 | 
					pub mod app_config;
 | 
				
			||||||
 | 
					pub mod constants;
 | 
				
			||||||
 | 
					pub mod controllers;
 | 
				
			||||||
 | 
					pub mod extractors;
 | 
				
			||||||
 | 
					pub mod middlewares;
 | 
				
			||||||
pub mod utils;
 | 
					pub mod utils;
 | 
				
			||||||
pub mod virtweb_client;
 | 
					pub mod virtweb_client;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
use actix_remote_ip::RemoteIPConfig;
 | 
					use actix_remote_ip::RemoteIPConfig;
 | 
				
			||||||
use actix_web::middleware::Logger;
 | 
					use actix_web::middleware::Logger;
 | 
				
			||||||
use actix_web::web::Data;
 | 
					use actix_web::web::Data;
 | 
				
			||||||
use actix_web::{App, HttpServer};
 | 
					use actix_web::{web, App, HttpServer};
 | 
				
			||||||
use light_openid::basic_state_manager::BasicStateManager;
 | 
					use light_openid::basic_state_manager::BasicStateManager;
 | 
				
			||||||
use remote_backend::app_config::AppConfig;
 | 
					use remote_backend::app_config::AppConfig;
 | 
				
			||||||
 | 
					use remote_backend::controllers::auth_controller;
 | 
				
			||||||
use remote_backend::virtweb_client;
 | 
					use remote_backend::virtweb_client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_web::main]
 | 
					#[actix_web::main]
 | 
				
			||||||
@@ -21,6 +22,22 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
            .app_data(Data::new(RemoteIPConfig {
 | 
					            .app_data(Data::new(RemoteIPConfig {
 | 
				
			||||||
                proxy: AppConfig::get().proxy_ip.clone(),
 | 
					                proxy: AppConfig::get().proxy_ip.clone(),
 | 
				
			||||||
            }))
 | 
					            }))
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/auth/start_oidc",
 | 
				
			||||||
 | 
					                web::get().to(auth_controller::start_oidc),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/auth/finish_oidc",
 | 
				
			||||||
 | 
					                web::post().to(auth_controller::finish_oidc),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/auth/user",
 | 
				
			||||||
 | 
					                web::get().to(auth_controller::current_user),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/auth/sign_out",
 | 
				
			||||||
 | 
					                web::get().to(auth_controller::sign_out),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .bind(&AppConfig::get().listen_address)?
 | 
					    .bind(&AppConfig::get().listen_address)?
 | 
				
			||||||
    .run()
 | 
					    .run()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										111
									
								
								remote_backend/src/middlewares/auth_middleware.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								remote_backend/src/middlewares/auth_middleware.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					use std::future::{ready, Ready};
 | 
				
			||||||
 | 
					use std::rc::Rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
 | 
					use crate::constants;
 | 
				
			||||||
 | 
					use crate::extractors::auth_extractor::AuthExtractor;
 | 
				
			||||||
 | 
					use actix_web::body::EitherBody;
 | 
				
			||||||
 | 
					use actix_web::dev::Payload;
 | 
				
			||||||
 | 
					use actix_web::{
 | 
				
			||||||
 | 
					    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
 | 
				
			||||||
 | 
					    Error, FromRequest, HttpResponse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use futures_util::future::LocalBoxFuture;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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.
 | 
				
			||||||
 | 
					#[derive(Default)]
 | 
				
			||||||
 | 
					pub struct AuthChecker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Middleware factory is `Transform` trait
 | 
				
			||||||
 | 
					// `S` - type of the next service
 | 
				
			||||||
 | 
					// `B` - type of response's body
 | 
				
			||||||
 | 
					impl<S, B> Transform<S, ServiceRequest> for AuthChecker
 | 
				
			||||||
 | 
					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 = AuthMiddleware<S>;
 | 
				
			||||||
 | 
					    type InitError = ();
 | 
				
			||||||
 | 
					    type Future = Ready<Result<Self::Transform, Self::InitError>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn new_transform(&self, service: S) -> Self::Future {
 | 
				
			||||||
 | 
					        ready(Ok(AuthMiddleware {
 | 
				
			||||||
 | 
					            service: Rc::new(service),
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct AuthMiddleware<S> {
 | 
				
			||||||
 | 
					    service: Rc<S>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
 | 
				
			||||||
 | 
					where
 | 
				
			||||||
 | 
					    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
 | 
				
			||||||
 | 
					    S::Future: 'static,
 | 
				
			||||||
 | 
					    B: 'static,
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    type Response = ServiceResponse<EitherBody<B>>;
 | 
				
			||||||
 | 
					    type Error = Error;
 | 
				
			||||||
 | 
					    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    forward_ready!(service);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn call(&self, req: ServiceRequest) -> Self::Future {
 | 
				
			||||||
 | 
					        let service = Rc::clone(&self.service);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Box::pin(async move {
 | 
				
			||||||
 | 
					            let remote_ip =
 | 
				
			||||||
 | 
					                actix_remote_ip::RemoteIP::from_request(req.request(), &mut Payload::None)
 | 
				
			||||||
 | 
					                    .await
 | 
				
			||||||
 | 
					                    .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check if no authentication is required
 | 
				
			||||||
 | 
					            if constants::ROUTES_WITHOUT_AUTH.contains(&req.path())
 | 
				
			||||||
 | 
					                || !req.path().starts_with("/api/")
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                log::trace!("No authentication is required")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // Check cookie authentication
 | 
				
			||||||
 | 
					            else if !&AppConfig::get().unsecure_disable_login {
 | 
				
			||||||
 | 
					                let auth = match AuthExtractor::from_request(req.request(), &mut Payload::None)
 | 
				
			||||||
 | 
					                    .into_inner()
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Ok(auth) => auth,
 | 
				
			||||||
 | 
					                    Err(e) => {
 | 
				
			||||||
 | 
					                        log::error!(
 | 
				
			||||||
 | 
					                            "Failed to extract authentication information from request! {e}"
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        return Ok(req
 | 
				
			||||||
 | 
					                            .into_response(HttpResponse::PreconditionFailed().finish())
 | 
				
			||||||
 | 
					                            .map_into_right_body());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if !auth.is_authenticated() {
 | 
				
			||||||
 | 
					                    log::error!(
 | 
				
			||||||
 | 
					                        "User attempted to access privileged route without authentication!"
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    return Ok(req
 | 
				
			||||||
 | 
					                        .into_response(
 | 
				
			||||||
 | 
					                            HttpResponse::PreconditionFailed().json("Please authenticate!"),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .map_into_right_body());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            log::info!("{} - {}", remote_ip.0, req.path());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            service
 | 
				
			||||||
 | 
					                .call(req)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .map(ServiceResponse::map_into_left_body)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								remote_backend/src/middlewares/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								remote_backend/src/middlewares/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					pub mod auth_middleware;
 | 
				
			||||||
		Reference in New Issue
	
	Block a user