Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
110 changes: 110 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ futures = "0.3"
tokio = { version = "1.0", features = ["sync", "macros", "io-util", "rt", "time"] }
url = "2.4.1"
config = "0.15.11"
handlebars = "6.3.2"
8 changes: 8 additions & 0 deletions potsi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ server_url = "http://68.183.113.79:8000/openrtb2/auction"
counter_store = "jevans_synth_id_counter"
opid_store = "jevans_synth_id_opid"
secret_key = "potsi"
# Possible values
# - "client_ip"
# - "user_agent"
# - "first_party_id"
# - "auth_user_id"
# - "publisher_domain"
# - "accept_language"
template = "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}:{{ auth_user_id }}:{{ publisher_domain }}:{{ accept_language }}"
5 changes: 3 additions & 2 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub const SYNTH_HEADER_FRESH: &str = "X-Synthetic-Fresh";
pub const SYNTH_HEADER_POTSI: &str = "X-Synthetic-Potsi";
pub const SYNTHETIC_HEADER_FRESH: &str = "X-Synthetic-Fresh";
pub const SYNTHETIC_HEADER_POTSI: &str = "X-Synthetic-Potsi";
pub const SYNTHETIC_HEADER_PUB_USER_ID: &str = "X-Pub-User-ID";
16 changes: 8 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::env;

mod constants;
mod cookies;
use constants::{SYNTH_HEADER_FRESH, SYNTH_HEADER_POTSI};
use constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_POTSI};
mod models;
use models::AdResponse;
mod prebid;
Expand Down Expand Up @@ -60,7 +60,7 @@ fn handle_main_page(settings: &Settings, req: Request) -> Result<Response, Error

println!(
"Existing POTSI header: {:?}",
req.get_header(SYNTH_HEADER_POTSI)
req.get_header(SYNTHETIC_HEADER_POTSI)
);
println!("Generated Fresh ID: {}", fresh_id);
println!("Using POTSI ID: {}", synthetic_id);
Expand All @@ -69,8 +69,8 @@ fn handle_main_page(settings: &Settings, req: Request) -> Result<Response, Error
let mut response = Response::from_status(StatusCode::OK)
.with_body(HTML_TEMPLATE)
.with_header(header::CONTENT_TYPE, "text/html")
.with_header(SYNTH_HEADER_FRESH, &fresh_id) // Fresh ID always changes
.with_header(SYNTH_HEADER_POTSI, &synthetic_id); // POTSI ID remains stable
.with_header(SYNTHETIC_HEADER_FRESH, &fresh_id) // Fresh ID always changes
.with_header(SYNTHETIC_HEADER_POTSI, &synthetic_id); // POTSI ID remains stable

// Always set the cookie with the synthetic ID
response.set_header(
Expand Down Expand Up @@ -312,14 +312,14 @@ async fn handle_prebid_test(settings: &Settings, mut req: Request) -> Result<Res

println!(
"Existing POTSI header: {:?}",
req.get_header(SYNTH_HEADER_POTSI)
req.get_header(SYNTHETIC_HEADER_POTSI)
);
println!("Generated Fresh ID: {}", fresh_id);
println!("Using POTSI ID: {}", synthetic_id);

// Set both IDs as headers
req.set_header(SYNTH_HEADER_FRESH, &fresh_id);
req.set_header(SYNTH_HEADER_POTSI, &synthetic_id);
req.set_header(SYNTHETIC_HEADER_FRESH, &fresh_id);
req.set_header(SYNTHETIC_HEADER_POTSI, &synthetic_id);

println!("Using POTSI ID: {}, Fresh ID: {}", synthetic_id, fresh_id);

Expand All @@ -344,7 +344,7 @@ async fn handle_prebid_test(settings: &Settings, mut req: Request) -> Result<Res

println!("Attempting to send bid request to Prebid Server at prebid_backend");

match prebid_req.send_bid_request(&req).await {
match prebid_req.send_bid_request(settings, &req).await {
// Pass the original request
Ok(mut prebid_response) => {
println!("Received response from Prebid Server");
Expand Down
18 changes: 11 additions & 7 deletions src/prebid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use fastly::http::{header, Method};
use fastly::{Error, Request, Response};
use serde_json::json;

use crate::constants::{SYNTH_HEADER_FRESH, SYNTH_HEADER_POTSI};
use crate::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_POTSI};
use crate::settings::Settings;
use crate::synthetic::generate_synthetic_id;

Expand Down Expand Up @@ -31,7 +31,7 @@ impl PrebidRequest {
pub fn new(settings: &Settings, req: &Request) -> Result<Self, Error> {
// Get the POTSI ID from header (which we just set in handle_prebid_test)
let synthetic_id = req
.get_header(SYNTH_HEADER_POTSI)
.get_header(SYNTHETIC_HEADER_POTSI)
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
.unwrap_or_else(|| generate_synthetic_id(settings, req));
Expand Down Expand Up @@ -89,12 +89,16 @@ impl PrebidRequest {
///
/// # Returns
/// * `Result<Response, Error>` - Prebid Server response or error
pub async fn send_bid_request(&self, incoming_req: &Request) -> Result<Response, Error> {
let mut req = Request::new(Method::POST, "http://68.183.113.79:8000/openrtb2/auction");
pub async fn send_bid_request(
&self,
settings: &Settings,
incoming_req: &Request,
) -> Result<Response, Error> {
let mut req = Request::new(Method::POST, settings.prebid.server_url.to_owned());

// Get and store the POTSI ID value from the incoming request
let potsi_id = incoming_req
.get_header(SYNTH_HEADER_POTSI)
.get_header(SYNTHETIC_HEADER_POTSI)
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
.unwrap_or_else(|| self.synthetic_id.clone());
Expand Down Expand Up @@ -165,8 +169,8 @@ impl PrebidRequest {
req.set_header(header::CONTENT_TYPE, "application/json");
req.set_header("X-Forwarded-For", &self.client_ip);
req.set_header(header::ORIGIN, &self.origin);
req.set_header(SYNTH_HEADER_FRESH, &self.synthetic_id);
req.set_header(SYNTH_HEADER_POTSI, &potsi_id);
req.set_header(SYNTHETIC_HEADER_FRESH, &self.synthetic_id);
req.set_header(SYNTHETIC_HEADER_POTSI, &potsi_id);

println!(
"Sending prebid request with Fresh ID: {} and POTSI ID: {}",
Expand Down
1 change: 1 addition & 0 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct Synthetic {
pub counter_store: String,
pub opid_store: String,
pub secret_key: String,
pub template: String,
}

#[derive(Debug, Deserialize)]
Expand Down
39 changes: 23 additions & 16 deletions src/synthetic.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use fastly::http::header;
use fastly::Request;
use handlebars::Handlebars;
use hmac::{Hmac, Mac};
use serde_json::json;
use sha2::Sha256;

use crate::constants::SYNTH_HEADER_POTSI;
use crate::constants::{SYNTHETIC_HEADER_POTSI, SYNTHETIC_HEADER_PUB_USER_ID};
use crate::cookies::handle_request_cookies;
use crate::settings::Settings;

Expand All @@ -13,34 +15,37 @@ type HmacSha256 = Hmac<Sha256>;
pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String {
let user_agent = req
.get_header(header::USER_AGENT)
.map(|h| h.to_str().unwrap_or("Unknown"));
.map(|h| h.to_str().unwrap_or("unknown"));
let first_party_id = handle_request_cookies(req).and_then(|jar| {
jar.get("pub_userid")
.map(|cookie| cookie.value().to_string())
});
let auth_user_id = req
.get_header("X-Pub-User-ID")
.get_header(SYNTHETIC_HEADER_PUB_USER_ID)
.map(|h| h.to_str().unwrap_or("anonymous"));
let publisher_domain = req
.get_header(header::HOST)
.map(|h| h.to_str().unwrap_or("unknown.com"));
.map(|h| h.to_str().unwrap_or("unknown"));
let client_ip = req.get_client_ip_addr().map(|ip| ip.to_string());
let accept_language = req
.get_header(header::ACCEPT_LANGUAGE)
.and_then(|h| h.to_str().ok())
.map(|lang| lang.split(',').next().unwrap_or("unknown"));

let input_string = format!(
"{}:{}:{}:{}:{}:{}",
client_ip.unwrap_or("unknown".to_string()),
user_agent.unwrap_or("unknown"),
first_party_id.unwrap_or("anonymous".to_string()),
auth_user_id.unwrap_or("anonymous"),
publisher_domain.unwrap_or("unknown.com"),
accept_language.unwrap_or("unknown")
);
let handlebars = Handlebars::new();
let data = &json!({
"client_ip": client_ip.unwrap_or("unknown".to_string()),
"user_agent": user_agent.unwrap_or("unknown"),
"first_party_id": first_party_id.unwrap_or("anonymous".to_string()),
"auth_user_id": auth_user_id.unwrap_or("anonymous"),
"publisher_domain": publisher_domain.unwrap_or("unknown.com"),
"accept_language": accept_language.unwrap_or("unknown")
});

log::info!("Input string for fresh ID: {}", input_string);
let input_string = handlebars
.render_template(&settings.synthetic.template, data)
.unwrap();
println!("Input string for fresh ID: {} {}", input_string, data);

let mut mac = HmacSha256::new_from_slice(settings.synthetic.secret_key.as_bytes())
.expect("HMAC can take key of any size");
Expand All @@ -56,7 +61,7 @@ pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String {
pub fn get_or_generate_synthetic_id(settings: &Settings, req: &Request) -> String {
// First try to get existing POTSI ID from header
if let Some(potsi) = req
.get_header(SYNTH_HEADER_POTSI)
.get_header(SYNTHETIC_HEADER_POTSI)
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
{
Expand Down Expand Up @@ -112,6 +117,7 @@ mod tests {
counter_store: "https://example.com".to_string(),
opid_store: "https://example.com".to_string(),
secret_key: "secret_key".to_string(),
template: "{{ client_ip }}:{{ user_agent }}:{{ first_party_id }}:{{ auth_user_id }}:{{ publisher_domain }}:{{ accept_language }}".to_string(),
},
}
}
Expand All @@ -128,6 +134,7 @@ mod tests {
]);

let synthetic_id = generate_synthetic_id(&settings, &req);
print!("Generated synthetic ID: {}", synthetic_id);
assert_eq!(
synthetic_id,
"07cd73bb8c7db39753ab6b10198b10c3237a3f5a6d2232c6ce578f2c2a623e56"
Expand All @@ -137,7 +144,7 @@ mod tests {
#[test]
fn test_get_or_generate_synthetic_id_with_header() {
let settings = create_settings();
let req = create_test_request(vec![(SYNTH_HEADER_POTSI, "existing_potsi_id")]);
let req = create_test_request(vec![(SYNTHETIC_HEADER_POTSI, "existing_potsi_id")]);

let synthetic_id = get_or_generate_synthetic_id(&settings, &req);
assert_eq!(synthetic_id, "existing_potsi_id");
Expand Down