feat: allow setting custom cors header on http trigger (#6786)

* feat: allow setting custom cors header on http trigger

* preflight

* headers one by one

* perf: optimize conditional_cors_middleware by checking existing headers first

Improves performance by iterating through existing headers once and using
flags to track which CORS headers need to be inserted, avoiding unnecessary
header lookups for the common case where headers are not present

Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>

* refactor: use not_insert flags in conditional_cors_middleware for clarity

Changed the conditional_cors_middleware logic to use not_insert_* flags
instead of needs_* flags as suggested, which better represents the intent
when iterating through existing headers first.

Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com>
This commit is contained in:
Alexander Petric
2025-10-09 19:41:54 +00:00
committed by GitHub
co-authored by claude[bot] Ruben Fiszel
parent 471e48b65b
commit 0910ec972d
@@ -33,7 +33,6 @@ use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use tower_http::cors::CorsLayer;
use windmill_audit::{audit_oss::audit_log, ActionKind};
use windmill_common::{
db::UserDB,
@@ -598,17 +597,62 @@ impl TriggerCrud for HttpTrigger {
}
}
async fn conditional_cors_middleware(
req: axum::extract::Request,
next: axum::middleware::Next,
) -> Response {
let mut response = next.run(req).await;
let headers = response.headers_mut();
// Check existing headers first to determine what not to insert
let mut not_insert_origin = false;
let mut not_insert_methods = false;
let mut not_insert_headers = false;
for key in headers.keys() {
if !not_insert_origin && key == http::header::ACCESS_CONTROL_ALLOW_ORIGIN {
not_insert_origin = true;
}
if !not_insert_methods && key == http::header::ACCESS_CONTROL_ALLOW_METHODS {
not_insert_methods = true;
}
if !not_insert_headers && key == http::header::ACCESS_CONTROL_ALLOW_HEADERS {
not_insert_headers = true;
}
// Early exit if all headers are already present
if not_insert_origin && not_insert_methods && not_insert_headers {
break;
}
}
// Insert only the missing headers
if !not_insert_origin {
headers.insert(
http::header::ACCESS_CONTROL_ALLOW_ORIGIN,
http::HeaderValue::from_static("*"),
);
}
if !not_insert_methods {
headers.insert(
http::header::ACCESS_CONTROL_ALLOW_METHODS,
http::HeaderValue::from_static("GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS"),
);
}
if !not_insert_headers {
headers.insert(
http::header::ACCESS_CONTROL_ALLOW_HEADERS,
http::HeaderValue::from_static("content-type, authorization"),
);
}
response
}
pub fn http_route_trigger_handler() -> Router {
let cors = CorsLayer::new()
.allow_methods([
http::Method::GET,
http::Method::POST,
http::Method::DELETE,
http::Method::PUT,
http::Method::PATCH,
])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
.allow_origin(tower_http::cors::Any);
Router::new()
.route(
"/*path",
@@ -617,9 +661,10 @@ pub fn http_route_trigger_handler() -> Router {
.delete(route_job)
.put(route_job)
.patch(route_job)
.head(|| async { "" }),
.head(|| async { "" })
.options(|| async { "" }),
)
.layer(cors)
.layer(axum::middleware::from_fn(conditional_cors_middleware))
}
async fn get_http_route_trigger(