From 3237f8b4cee27aec2d72d78dcb989986c765889f Mon Sep 17 00:00:00 2001 From: TommyLike Date: Tue, 4 Jul 2023 15:54:58 +0800 Subject: [PATCH 1/4] Disable configuration file reload due to large cpu consumptiop --- src/application/datakey.rs | 4 ++-- src/control_server_entrypoint.rs | 3 ++- src/data_server_entrypoint.rs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/application/datakey.rs b/src/application/datakey.rs index c86800f..37a730b 100644 --- a/src/application/datakey.rs +++ b/src/application/datakey.rs @@ -155,8 +155,8 @@ impl DBKeyService if data.key_type == X509EE && parent_key.key_type != X509ICA { return Err(Error::ActionsNotAllowedError("only ICA key is allowed for creating End Entity Key".to_string())); } - if data.key_type == X509CA { - return Err(Error::ActionsNotAllowedError("CA key is not allowed to specify parent key".to_string())); + if data.key_type == X509CA || data.key_type == OpenPGP { + return Err(Error::ActionsNotAllowedError("CA key or openPGP is not allowed to specify parent key".to_string())); } Ok(()) } diff --git a/src/control_server_entrypoint.rs b/src/control_server_entrypoint.rs index 82a77f5..ba81c92 100644 --- a/src/control_server_entrypoint.rs +++ b/src/control_server_entrypoint.rs @@ -74,7 +74,8 @@ lazy_static! { let path = app.config.unwrap_or(format!("{}/{}", env::current_dir().expect("current dir not found").display(), "config/server.toml")); let server_config = util::config::ServerConfig::new(path); - server_config.watch(CANCEL_TOKEN.clone()).expect("failed to watch configure file"); + //TODO: Enable watch configure will 100 percent cpu consumption, fix it later + //server_config.watch(CANCEL_TOKEN.clone()).expect("failed to watch configure file"); server_config.config }; } diff --git a/src/data_server_entrypoint.rs b/src/data_server_entrypoint.rs index fa1638f..cd19c9f 100644 --- a/src/data_server_entrypoint.rs +++ b/src/data_server_entrypoint.rs @@ -76,7 +76,8 @@ lazy_static! { let path = app.config.unwrap_or(format!("{}/{}", env::current_dir().expect("current dir not found").display(), "config/server.toml")); let server_config = util::config::ServerConfig::new(path); - server_config.watch(CANCEL_TOKEN.clone()).expect("failed to watch configure file"); + //TODO: Enable watch configure will 100 percent cpu consumption, fix it later + //server_config.watch(CANCEL_TOKEN.clone()).expect("failed to watch configure file"); server_config.config }; } -- Gitee From cb9203924c2df8c37e0f2c19ab49bab1616bab03 Mon Sep 17 00:00:00 2001 From: TommyLike Date: Tue, 4 Jul 2023 15:59:22 +0800 Subject: [PATCH 2/4] Fix disable notes --- src/control_server_entrypoint.rs | 2 +- src/data_server_entrypoint.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/control_server_entrypoint.rs b/src/control_server_entrypoint.rs index ba81c92..dc2f89b 100644 --- a/src/control_server_entrypoint.rs +++ b/src/control_server_entrypoint.rs @@ -74,7 +74,7 @@ lazy_static! { let path = app.config.unwrap_or(format!("{}/{}", env::current_dir().expect("current dir not found").display(), "config/server.toml")); let server_config = util::config::ServerConfig::new(path); - //TODO: Enable watch configure will 100 percent cpu consumption, fix it later + //TODO: Enable watch configure file will lead to 100 percent cpu consumption, fix it later //server_config.watch(CANCEL_TOKEN.clone()).expect("failed to watch configure file"); server_config.config }; diff --git a/src/data_server_entrypoint.rs b/src/data_server_entrypoint.rs index cd19c9f..a00e8a2 100644 --- a/src/data_server_entrypoint.rs +++ b/src/data_server_entrypoint.rs @@ -76,7 +76,7 @@ lazy_static! { let path = app.config.unwrap_or(format!("{}/{}", env::current_dir().expect("current dir not found").display(), "config/server.toml")); let server_config = util::config::ServerConfig::new(path); - //TODO: Enable watch configure will 100 percent cpu consumption, fix it later + //TODO: Enable watch configure file will lead to 100 percent cpu consumption, fix it later //server_config.watch(CANCEL_TOKEN.clone()).expect("failed to watch configure file"); server_config.config }; -- Gitee From 59f01475c76283bc4d688c025ad65e77cea64ca9 Mon Sep 17 00:00:00 2001 From: TommyLike Date: Wed, 5 Jul 2023 23:58:56 +0800 Subject: [PATCH 3/4] Support CSRF token for broswer visit --- Cargo.toml | 3 + src/application/user.rs | 13 +- src/control_admin_entrypoint.rs | 2 +- .../handler/control/model/user/dto.rs | 152 +++++++++++++++--- .../handler/control/user_handler.rs | 7 +- src/presentation/server/control_server.rs | 15 +- src/util/error.rs | 32 ++++ src/util/key.rs | 11 ++ 8 files changed, 199 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index afd2f02..70f21ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ clap = { version = "4.0.22", features = ["derive", "env"] } config = "0.13.3" lazy_static = "1.4.0" actix-web = { version = "4.3.0", features = ["openssl"]} +actix-web-lab = "0.19.1" tonic = {version = "0.8.2", features = ["tls", "tls-roots", "transport", "channel"]} prost = "0.11.0" signal-hook = "0.3.14" @@ -75,6 +76,8 @@ utoipa = { version = "3", features = ["actix_extras"] } utoipa-swagger-ui = { version ="3.1.3", features = ["actix-web"]} efi_signer = "0.2.4" regex = "1" +csrf= "0.4.1" +data-encoding= "2.4.0" [build-dependencies] tonic-build = "0.8.4" diff --git a/src/application/user.rs b/src/application/user.rs index 139ee61..f04e653 100644 --- a/src/application/user.rs +++ b/src/application/user.rs @@ -27,7 +27,7 @@ use tokio::sync::RwLock as AsyncRwLock; use chrono::Utc; use serde::{Deserialize}; use config::Config; -use reqwest::{header, Client}; +use reqwest::{header, Client, StatusCode}; use crate::presentation::handler::control::model::user::dto::UserIdentity; use openidconnect::{ Scope, @@ -143,13 +143,18 @@ impl DBUserService async fn get_access_token(&self, code: &str) -> Result { match Client::builder().build() { Ok(client) => { - let token: AccessToken = client.post(&self.oidc_config.token_url).query(&[ + let response= client.post(&self.oidc_config.token_url).query(&[ ("client_id", self.oidc_config.client_id.as_str()), ("client_secret", self.oidc_config.client_secret.as_str()), ("code", code), ("redirect_uri", self.oidc_config.redirect_uri.as_str()), - ("grant_type", "authorization_code")]).send().await?.json().await?; - Ok(token) + ("grant_type", "authorization_code")]).send().await?; + if response.status() != StatusCode::OK { + Err(Error::AuthError(format!("failed to get access token {}", response.text().await?))) + } else { + let resp: AccessToken = response.json().await?; + Ok(resp) + } } Err(err) => { Err(Error::AuthError(err.to_string())) diff --git a/src/control_admin_entrypoint.rs b/src/control_admin_entrypoint.rs index 4ab080e..9f98efb 100644 --- a/src/control_admin_entrypoint.rs +++ b/src/control_admin_entrypoint.rs @@ -188,7 +188,7 @@ async fn main() -> Result<()> { } key.validate()?; - let keys = control_server.create_keys(&mut DataKey::create_from(key, UserIdentity::from(user))?).await?; + let keys = control_server.create_keys(&mut DataKey::create_from(key, UserIdentity::from_user(user))?).await?; info!("[Result]: Keys {} type {} has been successfully generated", &keys.name, &generate_keys.key_type) } None => {} diff --git a/src/presentation/handler/control/model/user/dto.rs b/src/presentation/handler/control/model/user/dto.rs index 738db01..a3f3fa7 100644 --- a/src/presentation/handler/control/model/user/dto.rs +++ b/src/presentation/handler/control/model/user/dto.rs @@ -1,21 +1,122 @@ -use actix_web::{Result, HttpRequest, FromRequest, dev::Payload}; -use crate::util::error::{Error}; - +use actix_web::{Result, HttpRequest, FromRequest, dev::Payload, dev::ServiceRequest, body::MessageBody,dev::ServiceResponse}; +use crate::util::error::{Error, Result as SignatrustResult}; +use std::convert::TryInto; use actix_identity::Identity; +use actix_web_lab::middleware::Next; use actix_web::web; use std::pin::Pin; use futures::Future; use serde::{Deserialize, Serialize}; use std::convert::From; +use std::str::FromStr; +use actix_web::http::header::HeaderName; use crate::application::user::UserService; use crate::domain::user::entity::User; use utoipa::{IntoParams, ToSchema}; use validator::Validate; +use csrf::{AesGcmCsrfProtection, CsrfProtection}; +use data_encoding::BASE64; +use reqwest::header::HeaderValue; +use reqwest::StatusCode; +use secstr::SecVec; +use crate::util::error::Error::GeneratingKeyError; +use crate::util::key::generate_csrf_parent_token; + +const CSRF_HEADER_NAME: &str = "Xsrf-Token"; +const AUTH_HEADER_NAME: &str = "Authorization"; +const SET_COOKIE_HEADER: &str = "set-cookie"; + #[derive(Debug, Deserialize, Serialize, ToSchema)] pub struct UserIdentity { pub email: String, pub id: i32, + //these two only exist when calling from OIDC login + pub csrf_generation_token: Option>, + pub csrf_token: Option +} + +impl UserIdentity { + pub fn from_user(id: User) -> Self { + UserIdentity { + id: id.id, + email: id.email, + csrf_token: None, + csrf_generation_token: None + } + } + + pub fn from_user_with_csrf_token(id: User, protect_key: [u8; 32]) -> SignatrustResult { + let protect = AesGcmCsrfProtection::from_key(protect_key); + let random_token = generate_csrf_parent_token(); + let random_token_array = random_token.clone().try_into()?; + //we don't use cookie here + let token = protect.generate_token(&random_token_array)?; + Ok(UserIdentity { + id: id.id, + email: id.email, + csrf_generation_token: Some(random_token.to_vec()), + csrf_token: Some(token.b64_string()) + }) + } + + pub fn generate_new_csrf_cookie(&self, protect_key: [u8; 32], ttl_seconds: i64) -> SignatrustResult { + if self.csrf_generation_token.is_none() || self.csrf_token.is_none() { + return Err(GeneratingKeyError("csrf token is empty, cannot generate new csrf cookie".to_string())); + } + let protect = AesGcmCsrfProtection::from_key(protect_key); + let generation_token: [u8; 64] = self.csrf_generation_token.clone().unwrap().try_into()?; + let cookie = protect.generate_cookie( + &generation_token, ttl_seconds)?; + Ok(cookie.b64_string()) + } + + pub fn csrf_token_valid(&self, protect_key: [u8; 32], value: &str) -> SignatrustResult { + let protect = AesGcmCsrfProtection::from_key(protect_key); + let csrf = BASE64.decode(value.as_bytes())?; + let token = BASE64.decode(self.csrf_token.clone().unwrap().as_bytes())?; + Ok(protect.verify_token_pair(&protect.parse_token(&token)?, &protect.parse_cookie(&csrf)?)) + } + + pub async fn append_csrf_token(req: ServiceRequest, next: Next) -> core::result::Result, actix_web::error::Error> { + let mut response = next.call(req).await?; + let http_req = response.request(); + if let Ok(identity) = Identity::from_request(http_req, &mut Payload::None).into_inner() { + if let Ok(user_json) = identity.id() { + if let Ok(user) = serde_json::from_str::(&user_json) { + if response.status() == StatusCode::UNAUTHORIZED { + //only append csrf token in authorized response + return Ok(response); + } + //generate csrf token based on user token + if let Some(protect_key) = http_req.app_data::>>() { + if let Ok(protect_key_array) = protect_key.clone().unsecure().try_into() { + if let Ok(csrf_token) = user.generate_new_csrf_cookie(protect_key_array, 600) { + let http_header = response.headers_mut(); + http_header.insert( + HeaderName::from_static(SET_COOKIE_HEADER), + HeaderValue::from_str(&format!("{}={}; Secure; Path=/; Max-Age=600", CSRF_HEADER_NAME, csrf_token)).unwrap(), + ); + } else { + warn!("failed to generate csrf token in middleware"); + } + } + } + } + } + } + + Ok(response) + } +} + +impl From for User { + fn from(id: UserIdentity) -> Self { + User { + id: id.id, + email: id.email + } + } } impl FromRequest for UserIdentity { @@ -24,7 +125,7 @@ impl FromRequest for UserIdentity { fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future { let mut login: Option = None; - //fetch from session + //fetch id from session if let Ok(identity) = Identity::from_request(&req.clone(), pl).into_inner() { if let Ok(user_json) = identity.id() { if let Ok(user) = serde_json::from_str(&user_json) { @@ -35,13 +136,15 @@ impl FromRequest for UserIdentity { let req = req.clone(); Box::pin(async move { match login { - //fetch valid token + // if API is invoked with API token, + // we need to fetch user identity from database + // and check whether the token is expired None => { - if let Some(value) = req.headers().get("Authorization") { + if let Some(value) = req.headers().get(AUTH_HEADER_NAME) { if let Some(user_service) = req.app_data::>() { if let Ok(token) = user_service.get_ref().get_valid_token(value.to_str().unwrap()).await { if let Ok(user) = user_service.get_ref().get_user_by_id(token.user_id).await { - return Ok(UserIdentity::from(user)); + return Ok(UserIdentity::from_user(user)); } } else { warn!("unable to find token record"); @@ -52,34 +155,33 @@ impl FromRequest for UserIdentity { } Err(Error::UnauthorizedError) } + // or we have to check both the token and csrf value. Some(user) => { - Ok(user) + if let Some(protect_key) = req.app_data::>>() { + if let Some(header) = req.headers().get(CSRF_HEADER_NAME) { + if let Ok(protect_key_array) = protect_key.clone().unsecure().try_into() { + if let Ok(true) = user.csrf_token_valid(protect_key_array, header.to_str().unwrap()) { + return Ok(user) + } else { + warn!("csrf header is invalid"); + } + } + } else { + warn!("unable to find csrf cookie"); + } + } else { + warn!("unable to find csrf protect key"); + } + Err(Error::UnauthorizedError) } } }) } } -impl From for User { - fn from(id: UserIdentity) -> Self { - User { - id: id.id, - email: id.email - } - } -} - #[derive(Deserialize, IntoParams, Validate, ToSchema)] pub struct Code { #[validate(length(min = 1))] pub code: String, } -impl From for UserIdentity { - fn from(id: User) -> Self { - UserIdentity { - id: id.id, - email: id.email, - } - } -} \ No newline at end of file diff --git a/src/presentation/handler/control/user_handler.rs b/src/presentation/handler/control/user_handler.rs index 1da32cf..7a3fcdf 100644 --- a/src/presentation/handler/control/user_handler.rs +++ b/src/presentation/handler/control/user_handler.rs @@ -18,6 +18,7 @@ use actix_web::{HttpResponse, Responder, Result, web, Scope, HttpRequest, HttpMe use crate::util::error::Error; use super::model::user::dto::UserIdentity; use actix_identity::Identity; +use secstr::SecVec; use validator::Validate; use crate::application::user::UserService; @@ -103,9 +104,11 @@ async fn logout(id: Identity) -> Result { (status = 500, description = "Server internal error", body = ErrorMessage) ) )] -async fn callback(req: HttpRequest, user_service: web::Data, code: web::Query) -> Result { +async fn callback(req: HttpRequest, user_service: web::Data, code: web::Query, protect_key: web::Data>) -> Result { code.validate()?; - let user_entity:UserIdentity = UserIdentity::from(user_service.into_inner().validate_user(&code.code).await?); + //generate csrf token and cookie + let protect_key_array:[u8; 32] = protect_key.unsecure().try_into()?; + let user_entity = UserIdentity::from_user_with_csrf_token(user_service.validate_user(&code.code).await?, protect_key_array)?; match Identity::login(&req.extensions(), serde_json::to_string(&user_entity)?) { Ok(_) => { Ok(HttpResponse::Found().insert_header(("Location", "/")).finish()) diff --git a/src/presentation/server/control_server.rs b/src/presentation/server/control_server.rs index 7a245c9..e7b8fe6 100644 --- a/src/presentation/server/control_server.rs +++ b/src/presentation/server/control_server.rs @@ -29,11 +29,14 @@ use actix_session::{config::PersistentSession, storage::RedisSessionStore, Sessi use actix_limitation::{Limiter, RateLimiter}; use time::Duration as timeDuration; use std::time::Duration; +use actix_web_lab::middleware::{from_fn}; use crate::infra::database::model::datakey::repository as datakeyRepository; use crate::infra::database::pool::{create_pool, get_db_pool}; use crate::presentation::handler::control::*; use actix_web::{dev::ServiceRequest}; +use actix_web::cookie::SameSite; +use secstr::SecVec; use tokio_util::sync::CancellationToken; use crate::util::error::Result; @@ -46,7 +49,8 @@ use crate::domain::datakey::entity::DataKey; use crate::domain::token::entity::Token; use crate::domain::user::entity::User; use crate::presentation::handler::control::model::token::dto::{CreateTokenDTO}; -use crate::presentation::handler::control::model::user::dto::UserIdentity; +use crate::presentation::handler::control::model::user::dto::{UserIdentity}; +use crate::util::key::truncate_string_to_protect_key; pub struct ControlServer { server_config: Arc>, @@ -190,9 +194,11 @@ impl ControlServer { ); let openapi = ControlApiDoc::openapi(); + let csrf_protect_key = web::Data::new(SecVec::new(truncate_string_to_protect_key(&key).to_vec())); let http_server = HttpServer::new(move || { App::new() + .wrap(from_fn(UserIdentity::append_csrf_token)) .wrap(middleware::Logger::default()) .wrap(IdentityMiddleware::default()) //rate limiter handler @@ -203,12 +209,13 @@ impl ControlServer { store.clone(), Key::from(key.as_bytes())) .session_lifecycle(PersistentSession::default().session_ttl(timeDuration::hours(1))) .cookie_name("Signatrust".to_owned()) - .cookie_secure(false) + .cookie_secure(true) .cookie_domain(None) + .cookie_same_site(SameSite::Strict) .cookie_path("/".to_owned()) .build(), ) - // enable logger + .app_data(csrf_protect_key.clone()) .app_data(key_service.clone()) .app_data(user_service.clone()) .app_data(limiter.clone()) @@ -249,7 +256,7 @@ impl ControlServer { pub async fn create_user_token(&self, user: User) -> Result { let user = self.user_service.save(user).await?; self.user_service.generate_token( - &UserIdentity::from(user.clone()), + &UserIdentity::from_user(user.clone()), CreateTokenDTO::new("default admin token".to_owned())).await } diff --git a/src/util/error.rs b/src/util/error.rs index 69d8d4a..270b76c 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -14,6 +14,8 @@ * */ +use std::array::TryFromSliceError; +use std::convert::Infallible; use config::ConfigError; use pgp::composed::key::SecretKeyParamsBuilderError; use pgp::errors::Error as PGPError; @@ -40,6 +42,7 @@ use openidconnect::url::ParseError as OIDCParseError; use openidconnect::ConfigurationError; use openidconnect::UserInfoError; use anyhow::Error as AnyhowError; +use csrf::CsrfError; use utoipa::{ToSchema}; use efi_signer::error::Error as EFIError; @@ -96,6 +99,8 @@ pub enum Error { UnprivilegedError, #[error("operation disallowed: {0}")] ActionsNotAllowedError(String), + #[error("framework error: {0}")] + FrameworkError(String), //client error #[error("file extension {0} not supported for file {1}")] @@ -363,3 +368,30 @@ impl From for Error { } } +impl From for Error { + fn from(error: CsrfError) -> Self { + Error::FrameworkError(error.to_string()) + } +} + +impl From for Error { + fn from(error: actix_web::Error) -> Self { Error::FrameworkError(error.to_string()) } +} + +impl From for Error { + fn from(error: data_encoding::DecodeError) -> Self { Error::FrameworkError(error.to_string()) } +} + +impl From for Error { + fn from(error: Infallible) -> Self { Error::FrameworkError(error.to_string()) } +} + +impl From for Error { + fn from(error: TryFromSliceError) -> Self { Error::FrameworkError(error.to_string()) } +} + +impl From> for Error { + fn from(error: Vec) -> Self { Error::KeyParseError(format!("original vec {:?}", error)) } +} + + diff --git a/src/util/key.rs b/src/util/key.rs index 41314c0..4ccb9c8 100644 --- a/src/util/key.rs +++ b/src/util/key.rs @@ -36,6 +36,17 @@ pub fn generate_api_token() -> String { thread_rng().sample_iter(&Alphanumeric).take(40).map(char::from).collect() } +pub fn generate_csrf_parent_token() -> Vec { + let number: Vec = (0..64).map(|_| thread_rng().gen::()).collect(); + number +} +pub fn truncate_string_to_protect_key(s: &str) -> [u8; 32] { + let truncated = &s.as_bytes()[..32].to_owned(); + let mut result = [0u8; 32]; + result[..truncated.len()].copy_from_slice(truncated); + result +} + pub fn get_token_hash(real_token: &str) -> String { let mut hasher = Sha256::default(); hasher.update(real_token); -- Gitee From 098bd067920064de054d3ed8f3e4675db36b285e Mon Sep 17 00:00:00 2001 From: TommyLike Date: Thu, 6 Jul 2023 11:57:02 +0800 Subject: [PATCH 4/4] Disable generating csrf token per request --- .../handler/control/model/user/dto.rs | 28 +++++++++---------- .../handler/control/user_handler.rs | 11 ++++++-- src/presentation/server/control_server.rs | 7 +++-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/presentation/handler/control/model/user/dto.rs b/src/presentation/handler/control/model/user/dto.rs index a3f3fa7..a1f366c 100644 --- a/src/presentation/handler/control/model/user/dto.rs +++ b/src/presentation/handler/control/model/user/dto.rs @@ -8,7 +8,6 @@ use std::pin::Pin; use futures::Future; use serde::{Deserialize, Serialize}; use std::convert::From; -use std::str::FromStr; use actix_web::http::header::HeaderName; use crate::application::user::UserService; use crate::domain::user::entity::User; @@ -22,9 +21,9 @@ use secstr::SecVec; use crate::util::error::Error::GeneratingKeyError; use crate::util::key::generate_csrf_parent_token; -const CSRF_HEADER_NAME: &str = "Xsrf-Token"; -const AUTH_HEADER_NAME: &str = "Authorization"; -const SET_COOKIE_HEADER: &str = "set-cookie"; +pub const CSRF_HEADER_NAME: &str = "Xsrf-Token"; +pub const AUTH_HEADER_NAME: &str = "Authorization"; +pub const SET_COOKIE_HEADER: &str = "set-cookie"; #[derive(Debug, Deserialize, Serialize, ToSchema)] @@ -71,25 +70,26 @@ impl UserIdentity { Ok(cookie.b64_string()) } - pub fn csrf_token_valid(&self, protect_key: [u8; 32], value: &str) -> SignatrustResult { + pub fn csrf_cookie_valid(&self, protect_key: [u8; 32], value: &str) -> SignatrustResult { let protect = AesGcmCsrfProtection::from_key(protect_key); - let csrf = BASE64.decode(value.as_bytes())?; - let token = BASE64.decode(self.csrf_token.clone().unwrap().as_bytes())?; - Ok(protect.verify_token_pair(&protect.parse_token(&token)?, &protect.parse_cookie(&csrf)?)) + Ok(protect.verify_token_pair( + &protect.parse_token( + &BASE64.decode(self.csrf_token.clone().unwrap().as_bytes())?)?, + &protect.parse_cookie( + &BASE64.decode(value.as_bytes())?)?)) } - pub async fn append_csrf_token(req: ServiceRequest, next: Next) -> core::result::Result, actix_web::error::Error> { + pub async fn append_csrf_cookie(req: ServiceRequest, next: Next) -> core::result::Result, actix_web::error::Error> { let mut response = next.call(req).await?; - let http_req = response.request(); - if let Ok(identity) = Identity::from_request(http_req, &mut Payload::None).into_inner() { + if let Ok(identity) = Identity::from_request(response.request(), &mut Payload::None).into_inner() { if let Ok(user_json) = identity.id() { if let Ok(user) = serde_json::from_str::(&user_json) { if response.status() == StatusCode::UNAUTHORIZED { //only append csrf token in authorized response return Ok(response); } - //generate csrf token based on user token - if let Some(protect_key) = http_req.app_data::>>() { + //generate csrf cookie based on user token + if let Some(protect_key) = response.request().app_data::>>() { if let Ok(protect_key_array) = protect_key.clone().unsecure().try_into() { if let Ok(csrf_token) = user.generate_new_csrf_cookie(protect_key_array, 600) { let http_header = response.headers_mut(); @@ -160,7 +160,7 @@ impl FromRequest for UserIdentity { if let Some(protect_key) = req.app_data::>>() { if let Some(header) = req.headers().get(CSRF_HEADER_NAME) { if let Ok(protect_key_array) = protect_key.clone().unsecure().try_into() { - if let Ok(true) = user.csrf_token_valid(protect_key_array, header.to_str().unwrap()) { + if let Ok(true) = user.csrf_cookie_valid(protect_key_array, header.to_str().unwrap()) { return Ok(user) } else { warn!("csrf header is invalid"); diff --git a/src/presentation/handler/control/user_handler.rs b/src/presentation/handler/control/user_handler.rs index 7a3fcdf..8ca7d63 100644 --- a/src/presentation/handler/control/user_handler.rs +++ b/src/presentation/handler/control/user_handler.rs @@ -18,12 +18,13 @@ use actix_web::{HttpResponse, Responder, Result, web, Scope, HttpRequest, HttpMe use crate::util::error::Error; use super::model::user::dto::UserIdentity; use actix_identity::Identity; +use actix_web::cookie::Cookie; use secstr::SecVec; use validator::Validate; use crate::application::user::UserService; use crate::presentation::handler::control::model::token::dto::{CreateTokenDTO, TokenDTO}; -use crate::presentation::handler::control::model::user::dto::Code; +use crate::presentation::handler::control::model::user::dto::{Code, CSRF_HEADER_NAME}; /// Start the login OIDC login process /// @@ -111,7 +112,13 @@ async fn callback(req: HttpRequest, user_service: web::Data, co let user_entity = UserIdentity::from_user_with_csrf_token(user_service.validate_user(&code.code).await?, protect_key_array)?; match Identity::login(&req.extensions(), serde_json::to_string(&user_entity)?) { Ok(_) => { - Ok(HttpResponse::Found().insert_header(("Location", "/")).finish()) + let cookie = Cookie::build(CSRF_HEADER_NAME, user_entity.generate_new_csrf_cookie(protect_key_array, 3600)?) + .path("/") + .secure(true) + .same_site(actix_web::cookie::SameSite::Strict) + .expires(time::OffsetDateTime::now_utc() + time::Duration::seconds(3600)) + .finish(); + Ok(HttpResponse::Found().cookie(cookie).insert_header(("Location", "/")).finish()) } Err(err) => { Err(Error::AuthError(format!("failed to get oidc token {}", err))) diff --git a/src/presentation/server/control_server.rs b/src/presentation/server/control_server.rs index e7b8fe6..1d3e5b8 100644 --- a/src/presentation/server/control_server.rs +++ b/src/presentation/server/control_server.rs @@ -29,7 +29,6 @@ use actix_session::{config::PersistentSession, storage::RedisSessionStore, Sessi use actix_limitation::{Limiter, RateLimiter}; use time::Duration as timeDuration; use std::time::Duration; -use actix_web_lab::middleware::{from_fn}; use crate::infra::database::model::datakey::repository as datakeyRepository; use crate::infra::database::pool::{create_pool, get_db_pool}; @@ -198,7 +197,11 @@ impl ControlServer { let http_server = HttpServer::new(move || { App::new() - .wrap(from_fn(UserIdentity::append_csrf_token)) + //NOTE: csrf protect,following the suggestion from https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html + //there is no need to update csrf cookie for every request + //in the case of signed double submit cookie ,disable updating csrf token in middleware automatically + //now and open it if we have to. + //.wrap(from_fn(UserIdentity::append_csrf_cookie)) .wrap(middleware::Logger::default()) .wrap(IdentityMiddleware::default()) //rate limiter handler -- Gitee