diff --git a/potsi.toml b/potsi.toml index 70abe9766..fcf2f38e6 100644 --- a/potsi.toml +++ b/potsi.toml @@ -1,3 +1,11 @@ +[ad_server] +ad_partner_url = "equativ_ad_api_2" +sync_url = "https://adapi-srv-eu.smartadserver.com/ac?pgid=2040327&fmtid=137675&synthetic_id={{synthetic_id}}" + +[prebid] +server_url = "http://68.183.113.79:8000/openrtb2/auction" + [synthetic] counter_store = "jevans_synth_id_counter" -opid_store = "jevans_synth_id_opid" \ No newline at end of file +opid_store = "jevans_synth_id_opid" +secret_key = "potsi" diff --git a/src/constants.rs b/src/constants.rs index b2462f439..1b99afe5b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,4 +1,2 @@ -pub const BACKEND2: &str = "equativ_ad_api_2"; -pub const SECRET_KEY: &[u8] = b"stackpop"; -pub const SYNTH_ID_COUNTER_STORE: &str = "jevans_synth_id_counter"; -pub const SYNTH_ID_OPID_STORE: &str = "jevans_synth_id_opid"; +pub const SYNTH_HEADER_FRESH: &str = "X-Synthetic-Fresh"; +pub const SYNTH_HEADER_POTSI: &str = "X-Synthetic-Potsi"; diff --git a/src/main.rs b/src/main.rs index 7d12ce6f7..e4ccefd35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use std::env; mod constants; mod cookies; -use constants::*; +use constants::{SYNTH_HEADER_FRESH, SYNTH_HEADER_POTSI}; mod models; use models::AdResponse; mod prebid; @@ -21,7 +21,9 @@ use templates::HTML_TEMPLATE; #[fastly::main] fn main(req: Request) -> Result { - let _settings = Settings::new(); + let settings = Settings::new().unwrap(); + println!("Settings {settings:?}"); + futures::executor::block_on(async { println!( "FASTLY_SERVICE_VERSION: {}", @@ -29,9 +31,9 @@ fn main(req: Request) -> Result { ); match (req.get_method(), req.get_path()) { - (&Method::GET, "/") => handle_main_page(req), - (&Method::GET, "/ad-creative") => handle_ad_request(req), - (&Method::GET, "/prebid-test") => handle_prebid_test(req).await, + (&Method::GET, "/") => handle_main_page(&settings, req), + (&Method::GET, "/ad-creative") => handle_ad_request(&settings, req), + (&Method::GET, "/prebid-test") => handle_prebid_test(&settings, req).await, _ => Ok(Response::from_status(StatusCode::NOT_FOUND) .with_body("Not Found") .with_header(header::CONTENT_TYPE, "text/plain")), @@ -39,26 +41,26 @@ fn main(req: Request) -> Result { }) } -fn handle_main_page(req: Request) -> Result { +fn handle_main_page(settings: &Settings, req: Request) -> Result { println!( - "Testing constants - BACKEND2: {}, SYNTH_ID_COUNTER_STORE: {}", - BACKEND2, SYNTH_ID_COUNTER_STORE + "Using ad_partner_url: {}, counter_store: {}", + settings.ad_server.ad_partner_url, settings.synthetic.counter_store, ); log_fastly::init_simple("mylogs", Info); // Calculate fresh ID first using the synthetic module - let fresh_id = synthetic::generate_synthetic_id(&req); + let fresh_id = synthetic::generate_synthetic_id(settings, &req); // Check for existing POTSI ID in this specific order: // 1. X-Synthetic-Potsi header // 2. Cookie // 3. Fall back to fresh ID - let synthetic_id = synthetic::get_or_generate_synthetic_id(&req); + let synthetic_id = synthetic::get_or_generate_synthetic_id(settings, &req); println!( "Existing POTSI header: {:?}", - req.get_header("X-Synthetic-Potsi") + req.get_header(SYNTH_HEADER_POTSI) ); println!("Generated Fresh ID: {}", fresh_id); println!("Using POTSI ID: {}", synthetic_id); @@ -67,8 +69,8 @@ fn handle_main_page(req: Request) -> Result { let mut response = Response::from_status(StatusCode::OK) .with_body(HTML_TEMPLATE) .with_header(header::CONTENT_TYPE, "text/html") - .with_header("X-Synthetic-Fresh", &fresh_id) // Fresh ID always changes - .with_header("X-Synthetic-Potsi", &synthetic_id); // POTSI ID remains stable + .with_header(SYNTH_HEADER_FRESH, &fresh_id) // Fresh ID always changes + .with_header(SYNTH_HEADER_POTSI, &synthetic_id); // POTSI ID remains stable // Always set the cookie with the synthetic ID response.set_header( @@ -94,7 +96,7 @@ fn handle_main_page(req: Request) -> Result { Ok(response) } -fn handle_ad_request(req: Request) -> Result { +fn handle_ad_request(settings: &Settings, req: Request) -> Result { // Log headers for debugging let client_ip = req .get_client_ip_addr() @@ -108,11 +110,11 @@ fn handle_ad_request(req: Request) -> Result { println!("X-Forwarded-For: {}", x_forwarded_for.unwrap_or("None")); // Generate synthetic ID - let synthetic_id = generate_synthetic_id(&req); + let synthetic_id = generate_synthetic_id(settings, &req); // Increment visit counter in KV store - println!("Opening KV store: {}", SYNTH_ID_COUNTER_STORE); - let store = match KVStore::open(SYNTH_ID_COUNTER_STORE) { + println!("Opening KV store: {}", settings.synthetic.counter_store); + let store = match KVStore::open(settings.synthetic.counter_store.as_str()) { Ok(Some(store)) => store, Ok(None) => { println!("KV store not found"); @@ -161,10 +163,7 @@ fn handle_ad_request(req: Request) -> Result { println!("Synthetic ID {} visit count: {}", synthetic_id, new_count); // Construct URL with synthetic ID - let ad_server_url = format!( - "https://adapi-srv-eu.smartadserver.com/ac?pgid=2040327&fmtid=137675&synthetic_id={}", - synthetic_id - ); + let ad_server_url = settings.ad_server.sync_url.replace("{{synthetic_id}}", &synthetic_id); println!("Sending request to backend: {}", ad_server_url); @@ -175,7 +174,7 @@ fn handle_ad_request(req: Request) -> Result { println!(" {}: {:?}", name, value); } - match req.send(BACKEND2) { + match req.send(settings.ad_server.ad_partner_url.as_str()) { Ok(mut res) => { println!( "Received response from backend with status: {}", @@ -236,8 +235,11 @@ fn handle_ad_request(req: Request) -> Result { println!("Found opid: {}", opid); // Store in opid KV store - println!("Attempting to open KV store: {}", SYNTH_ID_OPID_STORE); - match KVStore::open(SYNTH_ID_OPID_STORE) { + println!( + "Attempting to open KV store: {}", + settings.synthetic.opid_store + ); + match KVStore::open(settings.synthetic.opid_store.as_str()) { Ok(Some(store)) => { println!("Successfully opened KV store"); match store.insert(&synthetic_id, opid.as_bytes()) { @@ -251,12 +253,15 @@ fn handle_ad_request(req: Request) -> Result { } } Ok(None) => { - println!("KV store returned None: {}", SYNTH_ID_OPID_STORE); + println!( + "KV store returned None: {}", + settings.synthetic.opid_store + ); } Err(e) => { println!( "Error opening KV store '{}': {:?}", - SYNTH_ID_OPID_STORE, e + settings.synthetic.opid_store, e ); } }; @@ -293,29 +298,29 @@ fn handle_ad_request(req: Request) -> Result { } /// Handles the prebid test route with detailed error logging -async fn handle_prebid_test(mut req: Request) -> Result { +async fn handle_prebid_test(settings: &Settings, mut req: Request) -> Result { println!("Starting prebid test request handling"); // Calculate fresh ID - let fresh_id = synthetic::generate_synthetic_id(&req); + let fresh_id = synthetic::generate_synthetic_id(settings, &req); // Check for existing POTSI ID in same order as handle_main_page - let synthetic_id = synthetic::get_or_generate_synthetic_id(&req); + let synthetic_id = synthetic::get_or_generate_synthetic_id(settings, &req); println!( "Existing POTSI header: {:?}", - req.get_header("X-Synthetic-Potsi") + req.get_header(SYNTH_HEADER_POTSI) ); println!("Generated Fresh ID: {}", fresh_id); println!("Using POTSI ID: {}", synthetic_id); // Set both IDs as headers - req.set_header("X-Synthetic-Fresh", &fresh_id); - req.set_header("X-Synthetic-Potsi", &synthetic_id); + req.set_header(SYNTH_HEADER_FRESH, &fresh_id); + req.set_header(SYNTH_HEADER_POTSI, &synthetic_id); println!("Using POTSI ID: {}, Fresh ID: {}", synthetic_id, fresh_id); - let prebid_req = match PrebidRequest::new(&req) { + let prebid_req = match PrebidRequest::new(settings, &req) { Ok(req) => { println!( "Successfully created PrebidRequest with synthetic ID: {}", diff --git a/src/prebid.rs b/src/prebid.rs index 704b0ba8e..eb2e71a00 100644 --- a/src/prebid.rs +++ b/src/prebid.rs @@ -1,9 +1,12 @@ -use crate::synthetic::generate_synthetic_id; -use fastly::http::Method; +use fastly::http::{header, Method}; use fastly::{Error, Request, Response}; use serde_json::json; use url; +use crate::constants::{SYNTH_HEADER_FRESH, SYNTH_HEADER_POTSI}; +use crate::settings::Settings; +use crate::synthetic::generate_synthetic_id; + /// Represents a request to the Prebid Server with all necessary parameters pub struct PrebidRequest { /// Synthetic ID used for user identification across requests @@ -26,13 +29,13 @@ impl PrebidRequest { /// /// # Returns /// * `Result` - New PrebidRequest or error - pub fn new(req: &Request) -> Result { + pub fn new(settings: &Settings, req: &Request) -> Result { // Get the POTSI ID from header (which we just set in handle_prebid_test) let synthetic_id = req - .get_header("X-Synthetic-Potsi") + .get_header(SYNTH_HEADER_POTSI) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) - .unwrap_or_else(|| generate_synthetic_id(req)); + .unwrap_or_else(|| generate_synthetic_id(settings, req)); // Get the original client IP from Fastly headers let client_ip = req @@ -50,12 +53,12 @@ impl PrebidRequest { // Try to get domain from Referer or Origin headers, fallback to default let domain = req - .get_header("Referer") + .get_header(header::REFERER) .and_then(|h| h.to_str().ok()) .and_then(|r| url::Url::parse(r).ok()) .and_then(|u| u.host_str().map(|h| h.to_string())) .or_else(|| { - req.get_header("Origin") + req.get_header(header::ORIGIN) .and_then(|h| h.to_str().ok()) .and_then(|o| url::Url::parse(o).ok()) .and_then(|u| u.host_str().map(|h| h.to_string())) @@ -66,7 +69,7 @@ impl PrebidRequest { // Create origin with owned String let origin = req - .get_header("Origin") + .get_header(header::ORIGIN) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| format!("https://{}", domain)); @@ -92,7 +95,7 @@ impl PrebidRequest { // Get and store the POTSI ID value from the incoming request let potsi_id = incoming_req - .get_header("X-Synthetic-Potsi") + .get_header(SYNTH_HEADER_POTSI) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| self.synthetic_id.clone()); @@ -160,11 +163,11 @@ impl PrebidRequest { "at": 1 }); - req.set_header("Content-Type", "application/json"); + req.set_header(header::CONTENT_TYPE, "application/json"); req.set_header("X-Forwarded-For", &self.client_ip); - req.set_header("Origin", &self.origin); - req.set_header("X-Synthetic-Fresh", &self.synthetic_id); - req.set_header("X-Synthetic-Potsi", &potsi_id); + req.set_header(header::ORIGIN, &self.origin); + req.set_header(SYNTH_HEADER_FRESH, &self.synthetic_id); + req.set_header(SYNTH_HEADER_POTSI, &potsi_id); println!( "Sending prebid request with Fresh ID: {} and POTSI ID: {}", diff --git a/src/settings.rs b/src/settings.rs index 9b35e55e5..62acc4ec9 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -4,21 +4,37 @@ use std::str; #[derive(Debug, Deserialize)] #[allow(unused)] -struct Synthetic { - counter_store: String, - opid_store: String, +pub struct AdServer { + pub ad_partner_url: String, + pub sync_url: String, +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct Prebid { + pub server_url: String, +} + +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct Synthetic { + pub counter_store: String, + pub opid_store: String, + pub secret_key: String, } #[derive(Debug, Deserialize)] #[allow(unused)] pub(crate) struct Settings { - synthetic: Synthetic, + pub ad_server: AdServer, + pub prebid: Prebid, + pub synthetic: Synthetic, } impl Settings { pub(crate) fn new() -> Result { - let tom_bytes = include_bytes!("../potsi.toml"); - let toml_str = str::from_utf8(tom_bytes).unwrap(); + let toml_bytes = include_bytes!("../potsi.toml"); + let toml_str = str::from_utf8(toml_bytes).unwrap(); let s = Config::builder() .add_source(File::from_str(toml_str, FileFormat::Toml)) diff --git a/src/synthetic.rs b/src/synthetic.rs index 0c14a316f..4a05eb5ed 100644 --- a/src/synthetic.rs +++ b/src/synthetic.rs @@ -1,15 +1,17 @@ -use crate::constants::SECRET_KEY; -use crate::cookies::handle_request_cookies; use fastly::http::header; use fastly::Request; use hmac::{Hmac, Mac}; use log; use sha2::Sha256; +use crate::constants::SYNTH_HEADER_POTSI; +use crate::cookies::handle_request_cookies; +use crate::settings::Settings; + type HmacSha256 = Hmac; /// Generates a fresh synthetic_id based on request parameters -pub fn generate_synthetic_id(req: &Request) -> String { +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")); @@ -41,7 +43,8 @@ pub fn generate_synthetic_id(req: &Request) -> String { log::info!("Input string for fresh ID: {}", input_string); - let mut mac = HmacSha256::new_from_slice(SECRET_KEY).expect("HMAC can take key of any size"); + let mut mac = HmacSha256::new_from_slice(settings.synthetic.secret_key.as_bytes()) + .expect("HMAC can take key of any size"); mac.update(input_string.as_bytes()); let fresh_id = hex::encode(mac.finalize().into_bytes()); @@ -51,10 +54,10 @@ pub fn generate_synthetic_id(req: &Request) -> String { } /// Gets or creates a synthetic_id from the request -pub fn get_or_generate_synthetic_id(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("X-Synthetic-Potsi") + .get_header(SYNTH_HEADER_POTSI) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) { @@ -78,7 +81,7 @@ pub fn get_or_generate_synthetic_id(req: &Request) -> String { } // If no existing POTSI ID found, generate a fresh one - let fresh_id = generate_synthetic_id(req); + let fresh_id = generate_synthetic_id(settings, req); log::info!("No existing POTSI ID found, using fresh ID: {}", fresh_id); fresh_id } @@ -97,8 +100,26 @@ mod tests { req } + fn create_settings() -> Settings { + Settings { + ad_server: crate::settings::AdServer { + ad_partner_url: "https://example.com".to_string(), + sync_url: "https://example.com/synthetic_id={{synthetic_id}}".to_string(), + }, + prebid: crate::settings::Prebid { + server_url: "https://example.com".to_string(), + }, + synthetic: crate::settings::Synthetic { + counter_store: "https://example.com".to_string(), + opid_store: "https://example.com".to_string(), + secret_key: "secret_key".to_string(), + }, + } + } + #[test] fn test_generate_synthetic_id() { + let settings: Settings = create_settings(); let req = create_test_request(vec![ (&header::USER_AGENT.to_string(), "Mozilla/5.0"), (&header::COOKIE.to_string(), "pub_userid=12345"), @@ -107,37 +128,40 @@ mod tests { (&header::ACCEPT_LANGUAGE.to_string(), "en-US,en;q=0.9"), ]); - let synthetic_id = generate_synthetic_id(&req); + let synthetic_id = generate_synthetic_id(&settings, &req); assert_eq!( synthetic_id, - "5023f58a61668e5405a804d18662fc0b37518875cac551ed86e5e7223b541600" + "07cd73bb8c7db39753ab6b10198b10c3237a3f5a6d2232c6ce578f2c2a623e56" ) } #[test] fn test_get_or_generate_synthetic_id_with_header() { - let req = create_test_request(vec![("X-Synthetic-Potsi", "existing_potsi_id")]); + let settings = create_settings(); + let req = create_test_request(vec![(SYNTH_HEADER_POTSI, "existing_potsi_id")]); - let synthetic_id = get_or_generate_synthetic_id(&req); + let synthetic_id = get_or_generate_synthetic_id(&settings, &req); assert_eq!(synthetic_id, "existing_potsi_id"); } #[test] fn test_get_or_generate_synthetic_id_with_cookie() { + let settings = create_settings(); let req = create_test_request(vec![( &header::COOKIE.to_string(), "synthetic_id=existing_cookie_id", )]); - let synthetic_id = get_or_generate_synthetic_id(&req); + let synthetic_id = get_or_generate_synthetic_id(&settings, &req); assert_eq!(synthetic_id, "existing_cookie_id"); } #[test] fn test_get_or_generate_synthetic_id_generate_new() { + let settings = create_settings(); let req = create_test_request(vec![]); - let synthetic_id = get_or_generate_synthetic_id(&req); + let synthetic_id = get_or_generate_synthetic_id(&settings, &req); assert!(!synthetic_id.is_empty()); } }