Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 46 additions & 4 deletions crates/trusted-server-adapter-fastly/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use trusted_server_core::constants::{
HEADER_X_TS_ENV, HEADER_X_TS_VERSION,
};
use trusted_server_core::ec::admin::handle_register_partner;
use trusted_server_core::ec::finalize::ec_finalize_response;
use trusted_server_core::ec::kv::KvIdentityGraph;
use trusted_server_core::ec::partner::PartnerStore;
use trusted_server_core::ec::EcContext;
use trusted_server_core::error::TrustedServerError;
use trusted_server_core::geo::GeoInfo;
use trusted_server_core::http_util::sanitize_forwarded_headers;
Expand Down Expand Up @@ -80,10 +83,29 @@ async fn route_request(
// clients are untrusted and can hijack URL rewriting (see #409).
sanitize_forwarded_headers(&mut req);

// Extract geo info before auth check or routing consumes the request
// Extract geo info before auth check or routing consumes the request.
let geo_info = GeoInfo::from_request(&req);

let mut ec_context =
match EcContext::read_from_request_with_geo(settings, &req, geo_info.as_ref()) {
Ok(context) => context,
Err(err) => {
let mut response = to_error_response(&err);
finalize_response(settings, geo_info.as_ref(), &mut response);
return Ok(response);
}
};

let kv_graph = maybe_identity_graph(settings);

if let Some(mut response) = enforce_basic_auth(settings, &req) {
ec_finalize_response(
settings,
geo_info.as_ref(),
&ec_context,
kv_graph.as_ref(),
&mut response,
);
finalize_response(settings, geo_info.as_ref(), &mut response);
return Ok(response);
}
Expand Down Expand Up @@ -116,7 +138,9 @@ async fn route_request(
}

// Unified auction endpoint (returns creative HTML inline)
(Method::POST, "/auction") => handle_auction(settings, orchestrator, req).await,
(Method::POST, "/auction") => {
handle_auction(settings, orchestrator, kv_graph.as_ref(), &ec_context, req).await
}

// tsjs endpoints
(Method::GET, "/first-party/proxy") => handle_first_party_proxy(settings, req).await,
Expand All @@ -128,7 +152,7 @@ async fn route_request(
handle_first_party_proxy_rebuild(settings, req).await
}
(m, path) if integration_registry.has_route(&m, path) => integration_registry
.handle_proxy(&m, path, settings, req)
.handle_proxy(&m, path, settings, kv_graph.as_ref(), &mut ec_context, req)
.await
.unwrap_or_else(|| {
Err(Report::new(TrustedServerError::BadRequest {
Expand All @@ -143,7 +167,13 @@ async fn route_request(
path
);

match handle_publisher_request(settings, integration_registry, req) {
match handle_publisher_request(
settings,
integration_registry,
kv_graph.as_ref(),
&mut ec_context,
req,
) {
Ok(response) => Ok(response),
Err(e) => {
log::error!("Failed to proxy to publisher origin: {:?}", e);
Expand All @@ -156,11 +186,23 @@ async fn route_request(
// Convert any errors to HTTP error responses
let mut response = result.unwrap_or_else(|e| to_error_response(&e));

ec_finalize_response(
settings,
geo_info.as_ref(),
&ec_context,
kv_graph.as_ref(),
&mut response,
);

finalize_response(settings, geo_info.as_ref(), &mut response);

Ok(response)
}

fn maybe_identity_graph(settings: &Settings) -> Option<KvIdentityGraph> {
settings.ec.ec_store.as_ref().map(KvIdentityGraph::new)
}

/// Applies all standard response headers: geo, version, staging, and configured headers.
///
/// Called from every response path (including auth early-returns) so that all
Expand Down
17 changes: 5 additions & 12 deletions crates/trusted-server-core/src/auction/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use error_stack::{Report, ResultExt};
use fastly::{Request, Response};

use crate::auction::formats::AdRequest;
use crate::ec::kv::KvIdentityGraph;
use crate::ec::EcContext;
use crate::error::TrustedServerError;
use crate::settings::Settings;
Expand All @@ -28,20 +29,10 @@ use super::AuctionOrchestrator;
pub async fn handle_auction(
settings: &Settings,
orchestrator: &AuctionOrchestrator,
_kv: Option<&KvIdentityGraph>,
ec_context: &EcContext,
mut req: Request,
) -> Result<Response, Report<TrustedServerError>> {
// Read EC state before consuming the request body.
let mut ec_context = EcContext::read_from_request(settings, &req).change_context(
TrustedServerError::Auction {
message: "Failed to read EC context".to_string(),
},
)?;

// Auction is an organic handler — generate EC if needed.
if let Err(err) = ec_context.generate_if_needed(settings) {
log::warn!("EC generation failed for auction: {err:?}");
}

// Parse request body
let body: AdRequest = serde_json::from_slice(&req.take_body_bytes()).change_context(
TrustedServerError::Auction {
Expand All @@ -54,6 +45,8 @@ pub async fn handle_auction(
body.ad_units.len()
);

// Story 5 middleware contract: auction is a read-only EC route.
// It must not generate EC IDs; it only consumes pre-routed context.
// Only forward the EC ID to auction partners when consent allows it.
// A returning user may still have a ts-ec cookie but have since
// withdrawn consent — forwarding that revoked ID to bidders would
Expand Down
Loading
Loading