Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2ff0ce9
Add PR7 design spec for geo lookup + client info extract-once
prk-Jr Mar 30, 2026
ead539c
Fix spec review issues in PR7 design doc
prk-Jr Mar 30, 2026
8bbfc74
Update PR7 spec to address all five agent review findings
prk-Jr Mar 30, 2026
b39cd79
Add PR7 implementation plan and address plan review findings
prk-Jr Mar 30, 2026
d6a624a
Fix three plan review findings and two open questions
prk-Jr Mar 30, 2026
986a1b2
Broaden two low-severity doc cleanup steps in PR7 plan
prk-Jr Mar 30, 2026
86079c5
Fix two remaining low findings in PR7 plan
prk-Jr Mar 30, 2026
a03a765
Fix count drift in Step 7: four → five locations
prk-Jr Mar 30, 2026
ac79961
Add client_info field to AuctionContext and fix all construction sites
prk-Jr Mar 30, 2026
b96aec0
Change RequestInfo::from_request to take &ClientInfo, thread services…
prk-Jr Mar 30, 2026
661e3df
Add Task 2 follow-up coverage and README route fixes
prk-Jr Mar 30, 2026
774a07f
Add services param to generate_synthetic_id, remove Fastly IP/geo cal…
prk-Jr Mar 30, 2026
95ce45e
Revert premature publisher geo change from Task 3
prk-Jr Mar 30, 2026
b10dcec
Replace deprecated GeoInfo::from_request in publisher.rs with service…
prk-Jr Mar 30, 2026
888170d
Remove Fastly IP extraction from Didomi copy_headers, use ClientInfo …
prk-Jr Mar 30, 2026
f856b68
Move IpAddr import to test module level in didomi.rs
prk-Jr Mar 30, 2026
eb12522
Apply rustfmt formatting to didomi.rs, publisher.rs, and synthetic.rs
prk-Jr Mar 30, 2026
7fcb3b4
Add test coverage for generate_synthetic_id with concrete client IP
prk-Jr Mar 31, 2026
1844290
Align geo lookup warn log format with codebase convention ({e} not {e…
prk-Jr Mar 31, 2026
0132a36
Apply Prettier formatting to PR7 plan and spec docs
prk-Jr Mar 31, 2026
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
2 changes: 1 addition & 1 deletion crates/trusted-server-adapter-fastly/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ async fn route_request(
path
);

match handle_publisher_request(settings, integration_registry, req) {
match handle_publisher_request(settings, integration_registry, runtime_services, req) {
Ok(response) => Ok(response),
Err(e) => {
log::error!("Failed to proxy to publisher origin: {:?}", e);
Expand Down
72 changes: 46 additions & 26 deletions crates/trusted-server-core/src/auction/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,18 +256,18 @@ The trusted-server handles several types of routes defined in `crates/trusted-se

| Route | Method | Handler | Purpose | Line |
|---------------------------|--------|--------------------------------|--------------------------------------------------|------|
| `/auction` | POST | `handle_auction()` | Main auction endpoint (Prebid.js/tsjs format) | 84 |
| `/first-party/proxy` | GET | `handle_first_party_proxy()` | Proxy creatives through first-party domain | 84 |
| `/first-party/click` | GET | `handle_first_party_click()` | Track clicks on ads | 85 |
| `/first-party/sign` | GET/POST | `handle_first_party_proxy_sign()` | Generate signed URLs for creatives | 86 |
| `/first-party/proxy-rebuild` | POST | `handle_first_party_proxy_rebuild()` | Rebuild creative HTML with new settings | 89 |
| `/static/tsjs=*` | GET | `handle_tsjs_dynamic()` | Serve tsjs library (Prebid.js alternative) | 66 |
| `/.well-known/ts.jwks.json` | GET | `handle_jwks_endpoint()` | Public key distribution for request signing | 71 |
| `/verify-signature` | POST | `handle_verify_signature()` | Verify signed requests | 74 |
| `/admin/keys/rotate` | POST | `handle_rotate_key()` | Rotate signing keys (admin only) | 77 |
| `/admin/keys/deactivate` | POST | `handle_deactivate_key()` | Deactivate signing keys (admin only) | 78 |
| `/integrations/*` | * | Integration Registry | Provider-specific endpoints (Prebid, etc.) | 92 |
| `*` (fallback) | * | `handle_publisher_request()` | Proxy to publisher origin | 108 |
| `/auction` | POST | `handle_auction()` | Main auction endpoint (Prebid.js/tsjs format) | 162 |
| `/first-party/proxy` | GET | `handle_first_party_proxy()` | Proxy creatives through first-party domain | 167 |
| `/first-party/click` | GET | `handle_first_party_click()` | Track clicks on ads | 170 |
| `/first-party/sign` | GET/POST | `handle_first_party_proxy_sign()` | Generate signed URLs for creatives | 173 |
| `/first-party/proxy-rebuild` | POST | `handle_first_party_proxy_rebuild()` | Rebuild creative HTML with new settings | 176 |
| `/static/tsjs=*` | GET | `handle_tsjs_dynamic()` | Serve tsjs library (Prebid.js alternative) | 145 |
| `/.well-known/trusted-server.json` | GET | `handle_trusted_server_discovery()` | Public key distribution for request signing | 149 |
| `/verify-signature` | POST | `handle_verify_signature()` | Verify signed requests | 154 |
| `/admin/keys/rotate` | POST | `handle_rotate_key()` | Rotate signing keys (admin only) | 158 |
| `/admin/keys/deactivate` | POST | `handle_deactivate_key()` | Deactivate signing keys (admin only) | 159 |
| `/integrations/*` | * | Integration Registry | Provider-specific endpoints (Prebid, etc.) | 179 |
| `*` (fallback) | * | `handle_publisher_request()` | Proxy to publisher origin | 195 |

### How Routing Works

Expand All @@ -276,20 +276,40 @@ The Fastly Compute entrypoint uses pattern matching on `(Method, path)` tuples:

```rust
let result = match (method, path.as_str()) {
// Auction endpoint
(Method::POST, "/auction") => handle_auction(&settings, req).await,

// First-party endpoints
(Method::GET, "/first-party/proxy") => handle_first_party_proxy(&settings, req).await,

// Integration registry (dynamic routes)
(m, path) if integration_registry.has_route(&m, path) => {
integration_registry.handle_proxy(&m, path, &settings, req).await
},

// Fallback to publisher origin
_ => handle_publisher_request(&settings, &integration_registry, req),
}
(Method::GET, path) if path.starts_with("/static/tsjs=") => {
handle_tsjs_dynamic(&req, integration_registry)
}
(Method::GET, "/.well-known/trusted-server.json") => {
handle_trusted_server_discovery(settings, runtime_services, req)
}
(Method::POST, "/verify-signature") => handle_verify_signature(settings, req),
(Method::POST, "/admin/keys/rotate") => handle_rotate_key(settings, req),
(Method::POST, "/admin/keys/deactivate") => handle_deactivate_key(settings, req),
(Method::POST, "/auction") => {
handle_auction(settings, orchestrator, runtime_services, req).await
}
(Method::GET, "/first-party/proxy") => {
handle_first_party_proxy(settings, runtime_services, req).await
}
(Method::GET, "/first-party/click") => {
handle_first_party_click(settings, runtime_services, req).await
}
(Method::GET, "/first-party/sign") | (Method::POST, "/first-party/sign") => {
handle_first_party_proxy_sign(settings, runtime_services, req).await
}
(Method::POST, "/first-party/proxy-rebuild") => {
handle_first_party_proxy_rebuild(settings, runtime_services, req).await
}
(m, path) if integration_registry.has_route(&m, path) => integration_registry
.handle_proxy(&m, path, settings, runtime_services, req)
.await
.unwrap_or_else(|| {
Err(Report::new(TrustedServerError::BadRequest {
message: format!("Unknown integration route: {path}"),
}))
}),
_ => handle_publisher_request(settings, integration_registry, runtime_services, req),
};
```

#### 2. Integration Registry (Dynamic Routes)
Expand Down
24 changes: 18 additions & 6 deletions crates/trusted-server-core/src/auction/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::auction::formats::AdRequest;
use crate::consent;
use crate::cookies::handle_request_cookies;
use crate::error::TrustedServerError;
use crate::geo::GeoInfo;
use crate::platform::RuntimeServices;
use crate::settings::Settings;
use crate::synthetic::get_or_generate_synthetic_id;
Expand Down Expand Up @@ -49,16 +48,21 @@ pub async fn handle_auction(

// Generate synthetic ID early so the consent pipeline can use it for
// KV Store fallback/write operations.
let synthetic_id = get_or_generate_synthetic_id(settings, &req).change_context(
let synthetic_id = get_or_generate_synthetic_id(settings, services, &req).change_context(
TrustedServerError::Auction {
message: "Failed to generate synthetic ID".to_string(),
},
)?;

// Extract consent from request cookies, headers, and geo.
let cookie_jar = handle_request_cookies(&req)?;
#[allow(deprecated)]
let geo = GeoInfo::from_request(&req);
let geo = services
.geo()
.lookup(services.client_info.client_ip)
.unwrap_or_else(|e| {
log::warn!("geo lookup failed: {e}");
None
});
let consent_context = consent::build_consent_context(&consent::ConsentPipelineInput {
jar: cookie_jar.as_ref(),
req: &req,
Expand All @@ -68,13 +72,21 @@ pub async fn handle_auction(
});

// Convert tsjs request format to auction request
let auction_request =
convert_tsjs_to_auction_request(&body, settings, &req, consent_context, &synthetic_id)?;
let auction_request = convert_tsjs_to_auction_request(
&body,
settings,
services,
&req,
consent_context,
&synthetic_id,
geo,
)?;

// Create auction context
let context = AuctionContext {
settings,
request: &req,
client_info: &services.client_info,
timeout_ms: settings.auction.timeout_ms,
provider_responses: None,
};
Expand Down
16 changes: 9 additions & 7 deletions crates/trusted-server-core/src/auction/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use crate::auction::context::ContextValue;
use crate::consent::ConsentContext;
use crate::creative;
use crate::error::TrustedServerError;
use crate::geo::GeoInfo;
use crate::openrtb::{to_openrtb_i32, OpenRtbBid, OpenRtbResponse, ResponseExt, SeatBid, ToExt};
use crate::platform::{GeoInfo, RuntimeServices};
use crate::settings::Settings;
use crate::synthetic::generate_synthetic_id;

Expand Down Expand Up @@ -82,15 +82,18 @@ pub struct BannerUnit {
pub fn convert_tsjs_to_auction_request(
body: &AdRequest,
settings: &Settings,
services: &RuntimeServices,
req: &Request,
consent: ConsentContext,
synthetic_id: &str,
geo: Option<GeoInfo>,
) -> Result<AuctionRequest, Report<TrustedServerError>> {
let synthetic_id = synthetic_id.to_owned();
let fresh_id =
generate_synthetic_id(settings, req).change_context(TrustedServerError::Auction {
let fresh_id = generate_synthetic_id(settings, services, req).change_context(
TrustedServerError::Auction {
message: "Failed to generate fresh ID".to_string(),
})?;
},
)?;

// Convert ad units to slots
let mut slots = Vec::new();
Expand Down Expand Up @@ -137,9 +140,8 @@ pub fn convert_tsjs_to_auction_request(
user_agent: req
.get_header_str("user-agent")
.map(std::string::ToString::to_string),
ip: req.get_client_ip_addr().map(|ip| ip.to_string()),
#[allow(deprecated)]
geo: GeoInfo::from_request(req),
ip: services.client_info.client_ip.map(|ip| ip.to_string()),
geo,
});

// Forward allowed config entries from the JS request into the context map.
Expand Down
14 changes: 13 additions & 1 deletion crates/trusted-server-core/src/auction/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ impl AuctionOrchestrator {
let mediator_context = AuctionContext {
settings: context.settings,
request: context.request,
client_info: context.client_info,
timeout_ms: remaining_ms,
provider_responses: Some(&provider_responses),
};
Expand Down Expand Up @@ -321,6 +322,7 @@ impl AuctionOrchestrator {
let provider_context = AuctionContext {
settings: context.settings,
request: context.request,
client_info: context.client_info,
timeout_ms: effective_timeout,
provider_responses: context.provider_responses,
};
Expand Down Expand Up @@ -673,10 +675,12 @@ mod tests {
fn create_test_context<'a>(
settings: &'a crate::settings::Settings,
req: &'a Request,
client_info: &'a crate::platform::ClientInfo,
) -> AuctionContext<'a> {
AuctionContext {
settings,
request: req,
client_info,
timeout_ms: 2000,
provider_responses: None,
}
Expand Down Expand Up @@ -769,7 +773,15 @@ mod tests {
let request = create_test_auction_request();
let settings = create_test_settings();
let req = Request::get("https://test.com/test");
let context = create_test_context(&settings, &req);
let context = create_test_context(
&settings,
&req,
&crate::platform::ClientInfo {
client_ip: None,
tls_protocol: None,
tls_cipher: None,
},
);

let result = orchestrator
.run_auction(&request, &context, &noop_services())
Expand Down
2 changes: 2 additions & 0 deletions crates/trusted-server-core/src/auction/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::collections::HashMap;

use crate::auction::context::ContextValue;
use crate::geo::GeoInfo;
use crate::platform::ClientInfo;
use crate::settings::Settings;

/// Represents a unified auction request across all providers.
Expand Down Expand Up @@ -102,6 +103,7 @@ pub struct SiteInfo {
pub struct AuctionContext<'a> {
pub settings: &'a Settings,
pub request: &'a Request,
pub client_info: &'a ClientInfo,
pub timeout_ms: u32,
/// Provider responses from the bidding phase, used by mediators.
/// This is `None` for regular bidders and `Some` when calling a mediator.
Expand Down
Loading
Loading