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
8 changes: 4 additions & 4 deletions crates/common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ Behavior is covered by an extensive test suite in `crates/common/src/creative.rs

## Synthetic Identifier Propagation

- `synthetic.rs` generates a deterministic synthetic identifier per user request and exposes helpers:
- `generate_synthetic_id` — creates a fresh HMAC-based ID using request signals.
- `get_synthetic_id` — extracts an existing ID from the `x-psid-ts` header or `synthetic_id` cookie.
- `synthetic.rs` generates a synthetic identifier per user request and exposes helpers:
- `generate_synthetic_id` — creates a fresh HMAC-based ID using request signals and appends a short random suffix (format: `64hex.6alnum`).
- `get_synthetic_id` — extracts an existing ID from the `x-synthetic-id` header or `synthetic_id` cookie.
- `get_or_generate_synthetic_id` — reuses the existing ID when present, otherwise creates one.
- `publisher.rs::handle_publisher_request` stamps proxied origin responses with `X-Synthetic-Fresh`, `x-psid-ts`, and (when absent) issues the `synthetic_id` cookie so the browser keeps the identifier on subsequent requests.
- `publisher.rs::handle_publisher_request` stamps proxied origin responses with `x-synthetic-id`, and (when absent) issues the `synthetic_id` cookie so the browser keeps the identifier on subsequent requests.
- `proxy.rs::handle_first_party_proxy` replays the identifier to third-party creative origins by appending `synthetic_id=<value>` to the reconstructed target URL, follows redirects (301/302/303/307/308) up to four hops, and keeps downstream fetches linked to the same user scope.
- `proxy.rs::handle_first_party_click` adds `synthetic_id=<value>` to outbound click redirect URLs so analytics endpoints can associate clicks with impressions without third-party cookies.
6 changes: 3 additions & 3 deletions crates/common/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use http::header::HeaderName;

pub const HEADER_SYNTHETIC_FRESH: HeaderName = HeaderName::from_static("x-synthetic-fresh");
pub const HEADER_SYNTHETIC_PUB_USER_ID: HeaderName = HeaderName::from_static("x-pub-user-id");
pub const COOKIE_SYNTHETIC_ID: &str = "synthetic_id";

pub const HEADER_X_PUB_USER_ID: HeaderName = HeaderName::from_static("x-pub-user-id");
pub const HEADER_SYNTHETIC_TRUSTED_SERVER: HeaderName = HeaderName::from_static("x-psid-ts");
pub const HEADER_X_SYNTHETIC_ID: HeaderName = HeaderName::from_static("x-synthetic-id");
pub const HEADER_X_CONSENT_ADVERTISING: HeaderName =
HeaderName::from_static("x-consent-advertising");
pub const HEADER_X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
Expand Down
9 changes: 5 additions & 4 deletions crates/common/src/cookies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use error_stack::{Report, ResultExt};
use fastly::http::header;
use fastly::Request;

use crate::constants::COOKIE_SYNTHETIC_ID;
use crate::error::TrustedServerError;
use crate::settings::Settings;

Expand Down Expand Up @@ -65,8 +66,8 @@ pub fn handle_request_cookies(
#[must_use]
pub fn create_synthetic_cookie(settings: &Settings, synthetic_id: &str) -> String {
format!(
"synthetic_id={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}",
synthetic_id, settings.publisher.cookie_domain, COOKIE_MAX_AGE,
"{}={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}",
COOKIE_SYNTHETIC_ID, synthetic_id, settings.publisher.cookie_domain, COOKIE_MAX_AGE,
)
}

Expand Down Expand Up @@ -158,8 +159,8 @@ mod tests {
assert_eq!(
result,
format!(
"synthetic_id=12345; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}",
settings.publisher.cookie_domain, COOKIE_MAX_AGE,
"{}=12345; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}",
COOKIE_SYNTHETIC_ID, settings.publisher.cookie_domain, COOKIE_MAX_AGE,
)
);
}
Expand Down
99 changes: 17 additions & 82 deletions crates/common/src/integrations/prebid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::auction::types::{
AuctionContext, AuctionRequest, AuctionResponse, Bid as AuctionBid, MediaType,
};
use crate::backend::ensure_backend_from_url;
use crate::constants::{HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER};
use crate::constants::HEADER_X_SYNTHETIC_ID;
use crate::creative;
use crate::error::TrustedServerError;
use crate::geo::GeoInfo;
Expand All @@ -29,7 +29,7 @@ use crate::openrtb::{
};
use crate::request_signing::RequestSigner;
use crate::settings::{IntegrationConfig, Settings};
use crate::synthetic::{generate_synthetic_id, get_or_generate_synthetic_id};
use crate::synthetic::get_or_generate_synthetic_id;

const PREBID_INTEGRATION_ID: &str = "prebid";

Expand Down Expand Up @@ -384,22 +384,10 @@ async fn handle_prebid_auction(
)?;

let synthetic_id = get_or_generate_synthetic_id(settings, &req)?;
let fresh_id = generate_synthetic_id(settings, &req)?;

log::info!(
"Using synthetic ID: {}, fresh ID: {}",
synthetic_id,
fresh_id
);
log::info!("Using synthetic_id: {}", synthetic_id,);

enhance_openrtb_request(
&mut openrtb_request,
&synthetic_id,
&fresh_id,
settings,
&req,
config,
)?;
enhance_openrtb_request(&mut openrtb_request, &synthetic_id, settings, &req, config)?;

let mut pbs_req = Request::new(
Method::POST,
Expand Down Expand Up @@ -438,9 +426,7 @@ async fn handle_prebid_auction(

Ok(Response::from_status(StatusCode::OK)
.with_header(header::CONTENT_TYPE, "application/json")
.with_header("X-Synthetic-ID", &synthetic_id)
.with_header(HEADER_SYNTHETIC_FRESH, &fresh_id)
.with_header(HEADER_SYNTHETIC_TRUSTED_SERVER, &synthetic_id)
.with_header(HEADER_X_SYNTHETIC_ID, &synthetic_id)
.with_body(transformed_body))
}
Err(_) => Ok(Response::from_status(pbs_response.get_status())
Expand All @@ -456,7 +442,6 @@ async fn handle_prebid_auction(
fn enhance_openrtb_request(
request: &mut Json,
synthetic_id: &str,
fresh_id: &str,
settings: &Settings,
req: &Request,
config: &PrebidIntegrationConfig,
Expand All @@ -469,7 +454,6 @@ fn enhance_openrtb_request(
if !request["user"]["ext"].is_object() {
request["user"]["ext"] = json!({});
}
request["user"]["ext"]["synthetic_fresh"] = json!(fresh_id);

if req.get_header("Sec-GPC").is_some() {
if !request["regs"].is_object() {
Expand Down Expand Up @@ -1262,24 +1246,15 @@ mod tests {
});

let synthetic_id = "synthetic-123";
let fresh_id = "fresh-456";
let mut req = Request::new(Method::POST, "https://edge.example/auction");
req.set_header("Sec-GPC", "1");

let config = base_config();

enhance_openrtb_request(
&mut request_json,
synthetic_id,
fresh_id,
&settings,
&req,
&config,
)
.expect("should enhance request");
enhance_openrtb_request(&mut request_json, synthetic_id, &settings, &req, &config)
.expect("should enhance request");

assert_eq!(request_json["user"]["id"], synthetic_id);
assert_eq!(request_json["user"]["ext"]["synthetic_fresh"], fresh_id);
assert_eq!(
request_json["regs"]["ext"]["us_privacy"], "1YYN",
"GPC header should map to US privacy flag"
Expand All @@ -1305,21 +1280,13 @@ mod tests {
});

let synthetic_id = "synthetic-123";
let fresh_id = "fresh-456";
let req = Request::new(Method::POST, "https://edge.example/auction");

let mut config = base_config();
config.debug = true;

enhance_openrtb_request(
&mut request_json,
synthetic_id,
fresh_id,
&settings,
&req,
&config,
)
.expect("should enhance request");
enhance_openrtb_request(&mut request_json, synthetic_id, &settings, &req, &config)
.expect("should enhance request");

assert_eq!(
request_json["ext"]["prebid"]["debug"], true,
Expand All @@ -1335,21 +1302,13 @@ mod tests {
});

let synthetic_id = "synthetic-123";
let fresh_id = "fresh-456";
let req = Request::new(Method::POST, "https://edge.example/auction");

let mut config = base_config();
config.debug = false;

enhance_openrtb_request(
&mut request_json,
synthetic_id,
fresh_id,
&settings,
&req,
&config,
)
.expect("should enhance request");
enhance_openrtb_request(&mut request_json, synthetic_id, &settings, &req, &config)
.expect("should enhance request");

assert!(
request_json["ext"]["prebid"]["debug"].is_null(),
Expand Down Expand Up @@ -1554,7 +1513,6 @@ server_url = "https://prebid.example"

let req = Request::new(Method::GET, "https://example.com/test");
let synthetic_id = "test-synthetic-id";
let fresh_id = "test-fresh-id";

// Test with existing site.page
let mut request = json!({
Expand All @@ -1565,15 +1523,8 @@ server_url = "https://prebid.example"
}
});

enhance_openrtb_request(
&mut request,
synthetic_id,
fresh_id,
&settings,
&req,
&config,
)
.expect("should enhance request");
enhance_openrtb_request(&mut request, synthetic_id, &settings, &req, &config)
.expect("should enhance request");

let page = request["site"]["page"].as_str().unwrap();
assert_eq!(page, "https://example.com/page?kargo_debug=true");
Expand All @@ -1587,7 +1538,6 @@ server_url = "https://prebid.example"

let req = Request::new(Method::GET, "https://example.com/test");
let synthetic_id = "test-synthetic-id";
let fresh_id = "test-fresh-id";

// Test with existing query params in site.page
let mut request = json!({
Expand All @@ -1598,15 +1548,8 @@ server_url = "https://prebid.example"
}
});

enhance_openrtb_request(
&mut request,
synthetic_id,
fresh_id,
&settings,
&req,
&config,
)
.expect("should enhance request");
enhance_openrtb_request(&mut request, synthetic_id, &settings, &req, &config)
.expect("should enhance request");

let page = request["site"]["page"].as_str().unwrap();
assert_eq!(
Expand All @@ -1624,7 +1567,6 @@ server_url = "https://prebid.example"

let req = Request::new(Method::GET, "https://example.com/test");
let synthetic_id = "test-synthetic-id";
let fresh_id = "test-fresh-id";

// Test with URL that already has the debug params
let mut request = json!({
Expand All @@ -1635,15 +1577,8 @@ server_url = "https://prebid.example"
}
});

enhance_openrtb_request(
&mut request,
synthetic_id,
fresh_id,
&settings,
&req,
&config,
)
.expect("should enhance request");
enhance_openrtb_request(&mut request, synthetic_id, &settings, &req, &config)
.expect("should enhance request");

let page = request["site"]["page"].as_str().unwrap();
// Should still only have params once
Expand Down
Loading