From 4ca744e364ccab46ccd91b6828705f9284206fe6 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Wed, 21 May 2025 11:41:01 -0700 Subject: [PATCH 1/6] Refactor away from Fastly --- Cargo.lock | 5 ++- Cargo.toml | 3 ++ crates/common/Cargo.toml | 4 +- crates/common/src/constants.rs | 11 +++-- crates/common/src/cookies.rs | 33 +++++++++++--- crates/common/src/gdpr.rs | 32 +++++++++---- crates/common/src/http_wrapper.rs | 75 +++++++++++++++++++++++++++++++ crates/common/src/lib.rs | 1 + crates/common/src/prebid.rs | 17 ++++--- crates/common/src/synthetic.rs | 49 +++++++++++--------- crates/fastly/src/http_wrapper.rs | 49 ++++++++++++++++++++ crates/fastly/src/main.rs | 14 +++--- 12 files changed, 233 insertions(+), 60 deletions(-) create mode 100644 crates/common/src/http_wrapper.rs create mode 100644 crates/fastly/src/http_wrapper.rs diff --git a/Cargo.lock b/Cargo.lock index 4ee937e..647c1e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -715,9 +715,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -1533,6 +1533,7 @@ dependencies = [ "handlebars", "hex", "hmac", + "http", "log", "log-fastly", "serde", diff --git a/Cargo.toml b/Cargo.toml index e0632e5..8e3663a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,6 @@ members = [ "crates/common", "crates/fastly", ] + +[profile.release] +debug = 1 diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 9dd7b7e..8a60258 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -8,9 +8,6 @@ edition = "2021" publish = false license = "Apache-2.0" -[profile.release] -debug = 1 - [dependencies] chrono = "0.4" config = "0.15.11" @@ -20,6 +17,7 @@ futures = "0.3" handlebars = "6.3.2" hex = "0.4.3" hmac = "0.12.1" +http = "1.3.1" log = "0.4.20" log-fastly = "0.10.0" serde = { version = "1.0", features = ["derive"] } diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index de5edba..69f7b74 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -1,3 +1,8 @@ -pub const SYNTHETIC_HEADER_FRESH: &str = "X-Synthetic-Fresh"; -pub const SYNTHETIC_HEADER_TRUSTED_SERVER: &str = "X-Synthetic-Trusted-Server"; -pub const SYNTHETIC_HEADER_PUB_USER_ID: &str = "X-Pub-User-ID"; +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 HEADER_SYNTHETIC_TRUSTED_SERVER: HeaderName = + HeaderName::from_static("x-synthetic-trusted-server"); +pub const HEADER_X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); +pub const HEADER_X_SUBJECT_ID: HeaderName = HeaderName::from_static("x-subject-id"); diff --git a/crates/common/src/cookies.rs b/crates/common/src/cookies.rs index 3ec61fa..a84ad81 100644 --- a/crates/common/src/cookies.rs +++ b/crates/common/src/cookies.rs @@ -1,6 +1,7 @@ use cookie::{Cookie, CookieJar}; -use fastly::http::header; -use fastly::Request; +use http::header; + +use crate::http_wrapper::RequestWrapper; const COOKIE_MAX_AGE: i32 = 365 * 24 * 60 * 60; // 1 year @@ -17,7 +18,7 @@ pub fn parse_cookies_to_jar(s: &str) -> CookieJar { jar } -pub fn handle_request_cookies(req: &Request) -> Option { +pub fn handle_request_cookies(req: &T) -> Option { match req.get_header(header::COOKIE) { Some(header_value) => { let header_value_str: &str = header_value.to_str().unwrap_or(""); @@ -42,6 +43,9 @@ pub fn create_synthetic_cookie(synthetic_id: &str) -> String { mod tests { use super::*; + use crate::http_wrapper::tests::HttpRequestWrapper; + use http::request; + #[test] fn test_parse_cookies_to_jar() { let header_value = "c1=v1; c2=v2"; @@ -79,7 +83,11 @@ mod tests { #[test] fn test_handle_request_cookies() { - let req = Request::get("http://example.com").with_header(header::COOKIE, "c1=v1;c2=v2"); + let builder = request::Builder::new() + .method("GET") + .uri("http://example.com") + .header(header::COOKIE, "c1=v1; c2=v2"); + let req = HttpRequestWrapper::new(builder); let jar = handle_request_cookies(&req).unwrap(); assert!(jar.iter().count() == 2); @@ -89,7 +97,11 @@ mod tests { #[test] fn test_handle_request_cookies_with_empty_cookie() { - let req = Request::get("http://example.com").with_header(header::COOKIE, ""); + let builder = request::Builder::new() + .method("GET") + .uri("http://example.com") + .header(header::COOKIE, ""); + let req = HttpRequestWrapper::new(builder); let jar = handle_request_cookies(&req).unwrap(); assert!(jar.iter().count() == 0); @@ -97,7 +109,10 @@ mod tests { #[test] fn test_handle_request_cookies_no_cookie_header() { - let req: Request = Request::get("https://example.com"); + let builder = request::Builder::new() + .method("GET") + .uri("http://example.com"); + let req = HttpRequestWrapper::new(builder); let jar = handle_request_cookies(&req); assert!(jar.is_none()); @@ -105,7 +120,11 @@ mod tests { #[test] fn test_handle_request_cookies_invalid_cookie_header() { - let req = Request::get("http://example.com").with_header(header::COOKIE, "invalid"); + let builder = request::Builder::new() + .method("GET") + .uri("http://example.com") + .header(header::COOKIE, "invalid"); + let req = HttpRequestWrapper::new(builder); let jar = handle_request_cookies(&req).unwrap(); assert!(jar.iter().count() == 0); diff --git a/crates/common/src/gdpr.rs b/crates/common/src/gdpr.rs index bb92a1e..436f65b 100644 --- a/crates/common/src/gdpr.rs +++ b/crates/common/src/gdpr.rs @@ -1,9 +1,17 @@ +use std::collections::HashMap; + +use http::header; +use http::method::Method; +use http::status::StatusCode; + +use fastly::{Error, Response}; + +use serde::{Deserialize, Serialize}; + +use crate::constants::HEADER_X_SUBJECT_ID; use crate::cookies; +use crate::http_wrapper::RequestWrapper; use crate::settings::Settings; -use fastly::http::{header, Method, StatusCode}; -use fastly::{Error, Request, Response}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct GdprConsent { @@ -45,7 +53,7 @@ impl Default for UserData { } } -pub fn get_consent_from_request(req: &Request) -> Option { +pub fn get_consent_from_request(req: &T) -> Option { if let Some(jar) = cookies::handle_request_cookies(req) { if let Some(consent_cookie) = jar.get("gdpr_consent") { if let Ok(consent) = serde_json::from_str(consent_cookie.value()) { @@ -63,7 +71,10 @@ pub fn create_consent_cookie(consent: &GdprConsent) -> String { ) } -pub fn handle_consent_request(_settings: &Settings, req: Request) -> Result { +pub fn handle_consent_request( + _settings: &Settings, + req: T, +) -> Result { match *req.get_method() { Method::GET => { // Return current consent status @@ -89,11 +100,14 @@ pub fn handle_consent_request(_settings: &Settings, req: Request) -> Result Result { +pub fn handle_data_subject_request( + _settings: &Settings, + req: T, +) -> Result { match *req.get_method() { Method::GET => { // Handle data access request - if let Some(synthetic_id) = req.get_header("X-Subject-ID") { + if let Some(synthetic_id) = req.get_header(HEADER_X_SUBJECT_ID) { // Create a HashMap to store all user-related data let mut data: HashMap = HashMap::new(); @@ -110,7 +124,7 @@ pub fn handle_data_subject_request(_settings: &Settings, req: Request) -> Result } Method::DELETE => { // Handle right to erasure (right to be forgotten) - if let Some(_synthetic_id) = req.get_header("X-Subject-ID") { + if let Some(_synthetic_id) = req.get_header(HEADER_X_SUBJECT_ID) { // TODO: Implement data deletion from KV store Ok(Response::from_status(StatusCode::OK) .with_body("Data deletion request processed")) diff --git a/crates/common/src/http_wrapper.rs b/crates/common/src/http_wrapper.rs new file mode 100644 index 0000000..ce70c13 --- /dev/null +++ b/crates/common/src/http_wrapper.rs @@ -0,0 +1,75 @@ +use http::header::{HeaderName, HeaderValue}; +use http::Method; +use std::net::IpAddr; + +pub trait RequestWrapper { + fn get_client_ip_addr(&self) -> Option; + + fn get_header(&self, name: HeaderName) -> Option<&HeaderValue>; + + fn get_headers(&self) -> impl Iterator; + + fn get_method(&self) -> &Method; + + fn get_path(&self) -> &str; + + fn into_body_bytes(&self) -> Vec; + + fn set_header(&mut self, name: HeaderName, value: HeaderValue); +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + use http::request::Builder; + + #[derive(Debug)] + pub struct HttpRequestWrapper { + builder: Builder, + } + + impl HttpRequestWrapper { + pub fn new(builder: Builder) -> Self { + HttpRequestWrapper { builder } + } + } + + impl RequestWrapper for HttpRequestWrapper { + #[inline(always)] + fn get_client_ip_addr(&self) -> Option { + Some(IpAddr::from([127, 0, 0, 1])) // Placeholder for testing + } + + #[inline(always)] + fn get_header(&self, name: HeaderName) -> Option<&HeaderValue> { + self.builder.headers_ref().unwrap().get(name) + } + + #[inline(always)] + fn get_headers(&self) -> impl Iterator { + self.builder.headers_ref().unwrap().iter() + } + + #[inline(always)] + fn get_method(&self) -> &Method { + self.builder.method_ref().unwrap() + } + + #[inline(always)] + fn get_path(&self) -> &str { + self.builder.uri_ref().unwrap().path() + } + + fn into_body_bytes(&self) -> Vec { + // TODO: Implement the actual value extraction logic + vec![] + } + + #[inline(always)] + fn set_header(&mut self, _name: HeaderName, _value: HeaderValue) { + // TODO: Implement the actual header setting logic + // self.builder = self.builder.header(name, value); + } + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 7dff572..ce55b64 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,6 +1,7 @@ pub mod constants; pub mod cookies; pub mod gdpr; +pub mod http_wrapper; pub mod models; pub mod prebid; pub mod privacy; diff --git a/crates/common/src/prebid.rs b/crates/common/src/prebid.rs index 1d707ea..a97ca2f 100644 --- a/crates/common/src/prebid.rs +++ b/crates/common/src/prebid.rs @@ -2,7 +2,10 @@ use fastly::http::{header, Method}; use fastly::{Error, Request, Response}; use serde_json::json; -use crate::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_TRUSTED_SERVER}; +use crate::constants::{ + HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER, HEADER_X_FORWARDED_FOR, +}; +use crate::http_wrapper::RequestWrapper; use crate::settings::Settings; use crate::synthetic::generate_synthetic_id; @@ -28,10 +31,10 @@ impl PrebidRequest { /// /// # Returns /// * `Result` - New PrebidRequest or error - pub fn new(settings: &Settings, req: &Request) -> Result { + pub fn new(settings: &Settings, req: &T) -> Result { // Get the Trusted Server ID from header (which we just set in handle_prebid_test) let synthetic_id = req - .get_header(SYNTHETIC_HEADER_TRUSTED_SERVER) + .get_header(HEADER_SYNTHETIC_TRUSTED_SERVER) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| generate_synthetic_id(settings, req)); @@ -41,7 +44,7 @@ impl PrebidRequest { .get_client_ip_addr() .map(|ip| ip.to_string()) .unwrap_or_else(|| { - req.get_header("X-Forwarded-For") + req.get_header(HEADER_X_FORWARDED_FOR) .and_then(|h| h.to_str().ok()) .unwrap_or("") .split(',') // X-Forwarded-For can be a comma-separated list @@ -98,7 +101,7 @@ impl PrebidRequest { // Get and store the POTSI ID value from the incoming request let id: String = incoming_req - .get_header(SYNTHETIC_HEADER_TRUSTED_SERVER) + .get_header(HEADER_SYNTHETIC_TRUSTED_SERVER) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| self.synthetic_id.clone()); @@ -169,8 +172,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(SYNTHETIC_HEADER_FRESH, &self.synthetic_id); - req.set_header(SYNTHETIC_HEADER_TRUSTED_SERVER, &id); + req.set_header(HEADER_SYNTHETIC_FRESH, &self.synthetic_id); + req.set_header(HEADER_SYNTHETIC_TRUSTED_SERVER, &id); println!( "Sending prebid request with Fresh ID: {} and Trusted Server ID: {}", diff --git a/crates/common/src/synthetic.rs b/crates/common/src/synthetic.rs index a22610c..f262373 100644 --- a/crates/common/src/synthetic.rs +++ b/crates/common/src/synthetic.rs @@ -1,18 +1,18 @@ -use fastly::http::header; -use fastly::Request; use handlebars::Handlebars; use hmac::{Hmac, Mac}; +use http::header; use serde_json::json; use sha2::Sha256; -use crate::constants::{SYNTHETIC_HEADER_PUB_USER_ID, SYNTHETIC_HEADER_TRUSTED_SERVER}; +use crate::constants::{HEADER_SYNTHETIC_PUB_USER_ID, HEADER_SYNTHETIC_TRUSTED_SERVER}; use crate::cookies::handle_request_cookies; +use crate::http_wrapper::RequestWrapper; use crate::settings::Settings; type HmacSha256 = Hmac; /// Generates a fresh synthetic_id based on request parameters -pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String { +pub fn generate_synthetic_id(settings: &Settings, req: &T) -> String { let user_agent = req .get_header(header::USER_AGENT) .map(|h| h.to_str().unwrap_or("unknown")); @@ -21,7 +21,7 @@ pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String { .map(|cookie| cookie.value().to_string()) }); let auth_user_id = req - .get_header(SYNTHETIC_HEADER_PUB_USER_ID) + .get_header(HEADER_SYNTHETIC_PUB_USER_ID) .map(|h| h.to_str().unwrap_or("anonymous")); let publisher_domain = req .get_header(header::HOST) @@ -58,10 +58,10 @@ pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String { } /// Gets or creates a synthetic_id from the request -pub fn get_or_generate_synthetic_id(settings: &Settings, req: &Request) -> String { +pub fn get_or_generate_synthetic_id(settings: &Settings, req: &T) -> String { // First try to get existing Trusted Server ID from header if let Some(synthetic_id) = req - .get_header(SYNTHETIC_HEADER_TRUSTED_SERVER) + .get_header(HEADER_SYNTHETIC_TRUSTED_SERVER) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) { @@ -96,14 +96,22 @@ pub fn get_or_generate_synthetic_id(settings: &Settings, req: &Request) -> Strin #[cfg(test)] mod tests { use super::*; - use fastly::http::HeaderValue; - fn create_test_request(headers: Vec<(&str, &str)>) -> Request { - let mut req = Request::new("GET", "http://example.com"); + use http::request; + use http::HeaderName; + + use crate::http_wrapper::tests::HttpRequestWrapper; + + fn create_test_request(headers: Vec<(HeaderName, &str)>) -> HttpRequestWrapper { + let mut builder = request::Builder::new() + .method("GET") + .uri("http://example.com"); + for (key, value) in headers { - req.set_header(key, HeaderValue::from_str(value).unwrap()); + builder = builder.header(key, value); } + let req = HttpRequestWrapper::new(builder); req } @@ -129,18 +137,18 @@ mod tests { fn test_generate_synthetic_id() { let settings: Settings = create_settings(); let req = create_test_request(vec![ - (header::USER_AGENT.as_ref(), "Mozilla/5.0"), - (header::COOKIE.as_ref(), "pub_userid=12345"), - ("X-Pub-User-ID", "67890"), - (header::HOST.as_ref(), "example.com"), - (header::ACCEPT_LANGUAGE.as_ref(), "en-US,en;q=0.9"), + (header::USER_AGENT, "Mozilla/5.0"), + (header::COOKIE, "pub_userid=12345"), + (HEADER_SYNTHETIC_PUB_USER_ID, "67890"), + (header::HOST, "example.com"), + (header::ACCEPT_LANGUAGE, "en-US,en;q=0.9"), ]); let synthetic_id = generate_synthetic_id(&settings, &req); print!("Generated synthetic ID: {}", synthetic_id); assert_eq!( synthetic_id, - "07cd73bb8c7db39753ab6b10198b10c3237a3f5a6d2232c6ce578f2c2a623e56" + "69c0591bbcb53cc97d10ab0105a8410f7aa0b81b1a1ae31df457b7c238940e23" ) } @@ -148,7 +156,7 @@ mod tests { fn test_get_or_generate_synthetic_id_with_header() { let settings = create_settings(); let req = create_test_request(vec![( - SYNTHETIC_HEADER_TRUSTED_SERVER, + HEADER_SYNTHETIC_TRUSTED_SERVER, "existing_synthetic_id", )]); @@ -159,10 +167,7 @@ mod tests { #[test] fn test_get_or_generate_synthetic_id_with_cookie() { let settings = create_settings(); - let req = create_test_request(vec![( - header::COOKIE.as_ref(), - "synthetic_id=existing_cookie_id", - )]); + let req = create_test_request(vec![(header::COOKIE, "synthetic_id=existing_cookie_id")]); let synthetic_id = get_or_generate_synthetic_id(&settings, &req); assert_eq!(synthetic_id, "existing_cookie_id"); diff --git a/crates/fastly/src/http_wrapper.rs b/crates/fastly/src/http_wrapper.rs new file mode 100644 index 0000000..9093bbc --- /dev/null +++ b/crates/fastly/src/http_wrapper.rs @@ -0,0 +1,49 @@ +use fastly::Request as FastlyRequest; +use http::header::{HeaderName, HeaderValue}; +use http::Method; +use std::net::IpAddr; + +use trusted_server_common::http_wrapper::RequestWrapper; + +#[derive(Debug)] +pub struct FastlyRequestWrapper { + request: FastlyRequest, +} + +impl FastlyRequestWrapper { + pub fn new(request: FastlyRequest) -> Self { + FastlyRequestWrapper { request } + } +} + +impl RequestWrapper for FastlyRequestWrapper { + #[inline(always)] + fn get_client_ip_addr(&self) -> Option { + self.request.get_client_ip_addr() + } + + #[inline(always)] + fn get_header(&self, name: HeaderName) -> Option<&HeaderValue> { + self.request.get_header(name) + } + + #[inline(always)] + fn get_headers(&self) -> impl Iterator { + self.request.get_headers() + } + + #[inline(always)] + fn get_method(&self) -> &Method { + self.request.get_method() + } + + #[inline(always)] + fn get_path(&self) -> &str { + self.request.get_path() + } + + #[inline(always)] + fn set_header(&mut self, name: HeaderName, value: HeaderValue) { + self.request.set_header(name, value) + } +} diff --git a/crates/fastly/src/main.rs b/crates/fastly/src/main.rs index bd7dc84..2d7940d 100644 --- a/crates/fastly/src/main.rs +++ b/crates/fastly/src/main.rs @@ -6,7 +6,7 @@ use log::LevelFilter::Info; use serde_json::json; use std::env; -use trusted_server_common::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_TRUSTED_SERVER}; +use trusted_server_common::constants::{HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER}; use trusted_server_common::cookies::create_synthetic_cookie; use trusted_server_common::gdpr::{ get_consent_from_request, handle_consent_request, handle_data_subject_request, @@ -142,7 +142,7 @@ fn handle_main_page(settings: &Settings, mut req: Request) -> Result Result Result Date: Wed, 21 May 2025 20:26:50 -0700 Subject: [PATCH 2/6] Fixed compilation errors` --- Cargo.lock | 1 + crates/common/src/constants.rs | 9 +++ crates/common/src/gdpr.rs | 2 +- crates/common/src/http_wrapper.rs | 8 ++- crates/common/src/prebid.rs | 13 ++-- crates/common/src/synthetic.rs | 4 +- crates/fastly/Cargo.toml | 2 + crates/fastly/src/http_wrapper.rs | 15 +++-- crates/fastly/src/main.rs | 104 ++++++++++++++++++------------ 9 files changed, 100 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 647c1e5..6d0b3aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1549,6 +1549,7 @@ version = "0.1.0" dependencies = [ "fastly 0.11.2", "futures", + "http", "log", "log-fastly", "serde", diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 69f7b74..3ec5f3f 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -4,5 +4,14 @@ pub const HEADER_SYNTHETIC_FRESH: HeaderName = HeaderName::from_static("x-synthe pub const HEADER_SYNTHETIC_PUB_USER_ID: HeaderName = HeaderName::from_static("x-pub-user-id"); pub const HEADER_SYNTHETIC_TRUSTED_SERVER: HeaderName = HeaderName::from_static("x-synthetic-trusted-server"); +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"); +pub const HEADER_X_GEO_CITY: HeaderName = HeaderName::from_static("x-geo-city"); +pub const HEADER_X_GEO_CONTINENT: HeaderName = HeaderName::from_static("x-geo-continent"); +pub const HEADER_X_GEO_COORDINATES: HeaderName = HeaderName::from_static("x-geo-coordinates"); +pub const HEADER_X_GEO_COUNTRY: HeaderName = HeaderName::from_static("x-geo-country"); +pub const HEADER_X_GEO_INFO_AVAILABLE: HeaderName = HeaderName::from_static("x-geo-info-available"); +pub const HEADER_X_GEO_METRO_CODE: HeaderName = HeaderName::from_static("x-geo-metro-code"); +pub const HEADER_X_GEO_REGION: HeaderName = HeaderName::from_static("x-geo-region"); pub const HEADER_X_SUBJECT_ID: HeaderName = HeaderName::from_static("x-subject-id"); diff --git a/crates/common/src/gdpr.rs b/crates/common/src/gdpr.rs index 436f65b..9825669 100644 --- a/crates/common/src/gdpr.rs +++ b/crates/common/src/gdpr.rs @@ -73,7 +73,7 @@ pub fn create_consent_cookie(consent: &GdprConsent) -> String { pub fn handle_consent_request( _settings: &Settings, - req: T, + mut req: T, ) -> Result { match *req.get_method() { Method::GET => { diff --git a/crates/common/src/http_wrapper.rs b/crates/common/src/http_wrapper.rs index ce70c13..3af73e0 100644 --- a/crates/common/src/http_wrapper.rs +++ b/crates/common/src/http_wrapper.rs @@ -13,9 +13,13 @@ pub trait RequestWrapper { fn get_path(&self) -> &str; - fn into_body_bytes(&self) -> Vec; + fn into_body_bytes(&mut self) -> Vec; fn set_header(&mut self, name: HeaderName, value: HeaderValue); + + fn set_header_str(&mut self, name: HeaderName, value: &str) { + self.set_header(name, HeaderValue::from_str(value).unwrap()) + } } #[cfg(test)] @@ -61,7 +65,7 @@ pub(crate) mod tests { self.builder.uri_ref().unwrap().path() } - fn into_body_bytes(&self) -> Vec { + fn into_body_bytes(&mut self) -> Vec { // TODO: Implement the actual value extraction logic vec![] } diff --git a/crates/common/src/prebid.rs b/crates/common/src/prebid.rs index a97ca2f..32fa9e4 100644 --- a/crates/common/src/prebid.rs +++ b/crates/common/src/prebid.rs @@ -1,5 +1,6 @@ -use fastly::http::{header, Method}; use fastly::{Error, Request, Response}; +use http::header; +use http::method::Method; use serde_json::json; use crate::constants::{ @@ -24,10 +25,10 @@ pub struct PrebidRequest { } impl PrebidRequest { - /// Creates a new PrebidRequest from an incoming Fastly request + /// Creates a new PrebidRequest from an incoming request /// /// # Arguments - /// * `req` - The incoming Fastly request + /// * `req` - The incoming request /// /// # Returns /// * `Result` - New PrebidRequest or error @@ -39,7 +40,7 @@ impl PrebidRequest { .map(|s| s.to_string()) .unwrap_or_else(|| generate_synthetic_id(settings, req)); - // Get the original client IP from Fastly headers + // Get the original client IP from headers let client_ip = req .get_client_ip_addr() .map(|ip| ip.to_string()) @@ -92,10 +93,10 @@ impl PrebidRequest { /// /// # Returns /// * `Result` - Prebid Server response or error - pub async fn send_bid_request( + pub async fn send_bid_request( &self, settings: &Settings, - incoming_req: &Request, + incoming_req: &T, ) -> Result { let mut req = Request::new(Method::POST, settings.prebid.server_url.to_owned()); diff --git a/crates/common/src/synthetic.rs b/crates/common/src/synthetic.rs index f262373..180b2b1 100644 --- a/crates/common/src/synthetic.rs +++ b/crates/common/src/synthetic.rs @@ -111,8 +111,8 @@ mod tests { builder = builder.header(key, value); } - let req = HttpRequestWrapper::new(builder); - req + + HttpRequestWrapper::new(builder) } fn create_settings() -> Settings { diff --git a/crates/fastly/Cargo.toml b/crates/fastly/Cargo.toml index 5e478e2..0bcf059 100644 --- a/crates/fastly/Cargo.toml +++ b/crates/fastly/Cargo.toml @@ -2,10 +2,12 @@ name = "trusted-server-fastly" version = "0.1.0" edition = "2021" +publish = false [dependencies] fastly = "0.11.2" futures = "0.3" +http = "1.3.1" log = "0.4.20" log-fastly = "0.10.0" serde = { version = "1.0", features = ["derive"] } diff --git a/crates/fastly/src/http_wrapper.rs b/crates/fastly/src/http_wrapper.rs index 9093bbc..77d1e01 100644 --- a/crates/fastly/src/http_wrapper.rs +++ b/crates/fastly/src/http_wrapper.rs @@ -6,17 +6,17 @@ use std::net::IpAddr; use trusted_server_common::http_wrapper::RequestWrapper; #[derive(Debug)] -pub struct FastlyRequestWrapper { - request: FastlyRequest, +pub struct FastlyRequestWrapper<'a> { + request: &'a mut FastlyRequest, } -impl FastlyRequestWrapper { - pub fn new(request: FastlyRequest) -> Self { +impl<'a> FastlyRequestWrapper<'a> { + pub fn new(request: &'a mut FastlyRequest) -> Self { FastlyRequestWrapper { request } } } -impl RequestWrapper for FastlyRequestWrapper { +impl<'a> RequestWrapper for FastlyRequestWrapper<'a> { #[inline(always)] fn get_client_ip_addr(&self) -> Option { self.request.get_client_ip_addr() @@ -42,6 +42,11 @@ impl RequestWrapper for FastlyRequestWrapper { self.request.get_path() } + #[inline(always)] + fn into_body_bytes(&mut self) -> Vec { + self.request.take_body_bytes() + } + #[inline(always)] fn set_header(&mut self, name: HeaderName, value: HeaderValue) { self.request.set_header(name, value) diff --git a/crates/fastly/src/main.rs b/crates/fastly/src/main.rs index 2d7940d..369c9b1 100644 --- a/crates/fastly/src/main.rs +++ b/crates/fastly/src/main.rs @@ -1,16 +1,28 @@ +use std::env; + +use http::header; +use http::Method; +use http::status::StatusCode; + use fastly::geo::geo_lookup; -use fastly::http::{header, Method, StatusCode}; use fastly::KVStore; -use fastly::{Error, Request, Response}; +use fastly::{Error, Request as FastlyRequest, Response}; use log::LevelFilter::Info; use serde_json::json; -use std::env; -use trusted_server_common::constants::{HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER}; +pub mod http_wrapper; +use crate::http_wrapper::FastlyRequestWrapper; + +use trusted_server_common::constants::{ + HEADER_SYNTHETIC_FRESH, HEADER_SYNTHETIC_TRUSTED_SERVER, HEADER_X_CONSENT_ADVERTISING, + HEADER_X_FORWARDED_FOR, HEADER_X_GEO_CITY, HEADER_X_GEO_CONTINENT, HEADER_X_GEO_COORDINATES, + HEADER_X_GEO_COUNTRY, HEADER_X_GEO_INFO_AVAILABLE, HEADER_X_GEO_METRO_CODE, +}; use trusted_server_common::cookies::create_synthetic_cookie; use trusted_server_common::gdpr::{ get_consent_from_request, handle_consent_request, handle_data_subject_request, }; +use trusted_server_common::http_wrapper::RequestWrapper; use trusted_server_common::models::AdResponse; use trusted_server_common::prebid::PrebidRequest; use trusted_server_common::privacy::PRIVACY_TEMPLATE; @@ -20,7 +32,7 @@ use trusted_server_common::templates::HTML_TEMPLATE; use trusted_server_common::why::WHY_TEMPLATE; #[fastly::main] -fn main(req: Request) -> Result { +fn main(mut fastly_req: FastlyRequest) -> Result { let settings = Settings::new().unwrap(); println!("Settings {settings:?}"); @@ -30,6 +42,8 @@ fn main(req: Request) -> Result { std::env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| String::new()) ); + let req = FastlyRequestWrapper::new(&mut fastly_req); + match (req.get_method(), req.get_path()) { (&Method::GET, "/") => handle_main_page(&settings, req), (&Method::GET, "/ad-creative") => handle_ad_request(&settings, req), @@ -54,7 +68,7 @@ fn main(req: Request) -> Result { }) } -fn get_dma_code(req: &mut Request) -> Option { +fn get_dma_code(req: &mut T) -> Option { // Debug: Check if we're running in Fastly environment println!("Fastly Environment Check:"); println!( @@ -72,30 +86,30 @@ fn get_dma_code(req: &mut Request) -> Option { // Set all available geo information in headers let city = geo.city(); - req.set_header("X-Geo-City", city); + req.set_header_str(HEADER_X_GEO_CITY, city); println!(" City: {}", city); let country = geo.country_code(); - req.set_header("X-Geo-Country", country); + req.set_header_str(HEADER_X_GEO_COUNTRY, country); println!(" Country: {}", country); - req.set_header("X-Geo-Continent", format!("{:?}", geo.continent())); + req.set_header_str(HEADER_X_GEO_CONTINENT, &format!("{:?}", geo.continent())); println!(" Continent: {:?}", geo.continent()); - req.set_header( - "X-Geo-Coordinates", - format!("{},{}", geo.latitude(), geo.longitude()), + req.set_header_str( + HEADER_X_GEO_COORDINATES, + &format!("{},{}", geo.latitude(), geo.longitude()), ); println!(" Location: ({}, {})", geo.latitude(), geo.longitude()); // Get and set the metro code (DMA) let metro_code = geo.metro_code(); - req.set_header("X-Geo-Metro-Code", metro_code.to_string()); + req.set_header_str(HEADER_X_GEO_METRO_CODE, &metro_code.to_string()); println!("Found DMA/Metro code: {}", metro_code); return Some(metro_code.to_string()); } else { println!("No geo information available for the request"); - req.set_header("X-Geo-Info-Available", "false"); + req.set_header_str(HEADER_X_GEO_INFO_AVAILABLE, "false"); } // If no metro code is found, log all request headers for debugging @@ -107,7 +121,7 @@ fn get_dma_code(req: &mut Request) -> Option { None } -fn handle_main_page(settings: &Settings, mut req: Request) -> Result { +fn handle_main_page(settings: &Settings, mut req: T) -> Result { println!( "Using ad_partner_url: {}, counter_store: {}", settings.ad_server.ad_partner_url, settings.synthetic.counter_store, @@ -161,16 +175,16 @@ fn handle_main_page(settings: &Settings, mut req: Request) -> Result Result Result { +fn handle_ad_request( + settings: &Settings, + mut req: T, +) -> Result { // Check GDPR consent to determine if we should serve personalized or non-personalized ads let _consent = get_consent_from_request(&req).unwrap_or_default(); let advertising_consent = req - .get_header("X-Consent-Advertising") + .get_header(HEADER_X_CONSENT_ADVERTISING) .and_then(|h| h.to_str().ok()) .map(|v| v == "true") .unwrap_or(false); @@ -217,7 +234,7 @@ fn handle_ad_request(settings: &Settings, mut req: Request) -> Result Result Result Result Result { +async fn handle_prebid_test( + settings: &Settings, + mut req: T, +) -> Result { println!("Starting prebid test request handling"); // Check consent status from headers let advertising_consent = req - .get_header("X-Consent-Advertising") + .get_header(HEADER_X_CONSENT_ADVERTISING) .and_then(|h| h.to_str().ok()) .map(|v| v == "true") .unwrap_or(false); @@ -480,10 +500,10 @@ async fn handle_prebid_test(settings: &Settings, mut req: Request) -> Result Date: Thu, 22 May 2025 12:53:11 -0700 Subject: [PATCH 3/6] Fixed format --- crates/common/src/synthetic.rs | 1 - crates/fastly/src/main.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/common/src/synthetic.rs b/crates/common/src/synthetic.rs index 180b2b1..af647a1 100644 --- a/crates/common/src/synthetic.rs +++ b/crates/common/src/synthetic.rs @@ -111,7 +111,6 @@ mod tests { builder = builder.header(key, value); } - HttpRequestWrapper::new(builder) } diff --git a/crates/fastly/src/main.rs b/crates/fastly/src/main.rs index 369c9b1..ab1b78b 100644 --- a/crates/fastly/src/main.rs +++ b/crates/fastly/src/main.rs @@ -1,8 +1,8 @@ use std::env; use http::header; -use http::Method; use http::status::StatusCode; +use http::Method; use fastly::geo::geo_lookup; use fastly::KVStore; From fb2cba95bd02cf845a97f10fef127ccab6522a62 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 22 May 2025 15:40:04 -0700 Subject: [PATCH 4/6] Fixed clippy --- crates/common/src/http_wrapper.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/common/src/http_wrapper.rs b/crates/common/src/http_wrapper.rs index 3af73e0..80994b3 100644 --- a/crates/common/src/http_wrapper.rs +++ b/crates/common/src/http_wrapper.rs @@ -13,6 +13,7 @@ pub trait RequestWrapper { fn get_path(&self) -> &str; + #[allow(clippy::wrong_self_convention)] fn into_body_bytes(&mut self) -> Vec; fn set_header(&mut self, name: HeaderName, value: HeaderValue); From 5050d896fe3a83e8a1ff2c4f72065096ae5c579f Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:05:03 -0700 Subject: [PATCH 5/6] Added common logger --- .github/workflows/security.yml | 47 +++++ Cargo.lock | 55 +++++- crates/common/Cargo.toml | 2 + crates/common/src/constants.rs | 1 + crates/common/src/cookies.rs | 41 ++++- crates/common/src/error.rs | 33 ++++ crates/common/src/gdpr.rs | 14 +- crates/common/src/geo.rs | 25 +++ crates/common/src/ip.rs | 73 ++++++++ crates/common/src/lib.rs | 5 + crates/common/src/logging.rs | 21 +++ crates/common/src/prebid.rs | 9 +- crates/common/src/request_id.rs | 30 ++++ crates/common/src/settings.rs | 31 +++- crates/common/src/synthetic.rs | 6 +- crates/fastly/Cargo.toml | 1 + crates/fastly/src/main.rs | 295 ++++++++++++++++++-------------- trusted-server.toml | 4 + 18 files changed, 538 insertions(+), 155 deletions(-) create mode 100644 .github/workflows/security.yml create mode 100644 crates/common/src/error.rs create mode 100644 crates/common/src/geo.rs create mode 100644 crates/common/src/ip.rs create mode 100644 crates/common/src/logging.rs create mode 100644 crates/common/src/request_id.rs diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..7727150 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,47 @@ +name: Security Audit + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + schedule: + - cron: "0 0 * * 0" # Weekly on Sunday at midnight + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache Cargo dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Retrieve Rust version + id: rust-version + run: echo "rust-version=$(grep '^rust ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + shell: bash + + - name: Set up Rust tool chain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ steps.rust-version.outputs.rust-version }} + + - name: Install cargo-audit + run: cargo install cargo-audit + + - name: Run security audit + run: cargo audit + + - name: Check for known vulnerabilities in dependencies + uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index ef6368a..35db30b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,7 +199,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -649,7 +649,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1149,6 +1161,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "regex" version = "1.11.1" @@ -1539,14 +1557,17 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", + "thiserror 1.0.69", "tokio", "url", + "uuid", ] [[package]] name = "trusted-server-fastly" version = "0.1.0" dependencies = [ + "chrono", "fastly 0.11.2", "futures", "http", @@ -1604,6 +1625,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" @@ -1616,6 +1649,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1806,6 +1848,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 782f585..a0d32c8 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -24,4 +24,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.91" sha2 = "0.10.6" tokio = { version = "1.43", features = ["sync", "macros", "io-util", "rt", "time"] } +thiserror = "1.0" url = "2.4.1" +uuid = { version = "1.0", features = ["v4", "serde"] } diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 3ec5f3f..359377c 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -15,3 +15,4 @@ pub const HEADER_X_GEO_INFO_AVAILABLE: HeaderName = HeaderName::from_static("x-g pub const HEADER_X_GEO_METRO_CODE: HeaderName = HeaderName::from_static("x-geo-metro-code"); pub const HEADER_X_GEO_REGION: HeaderName = HeaderName::from_static("x-geo-region"); pub const HEADER_X_SUBJECT_ID: HeaderName = HeaderName::from_static("x-subject-id"); +pub const HEADER_X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id"); diff --git a/crates/common/src/cookies.rs b/crates/common/src/cookies.rs index a84ad81..44a6c09 100644 --- a/crates/common/src/cookies.rs +++ b/crates/common/src/cookies.rs @@ -2,6 +2,7 @@ use cookie::{Cookie, CookieJar}; use http::header; use crate::http_wrapper::RequestWrapper; +use crate::settings::Settings; const COOKIE_MAX_AGE: i32 = 365 * 24 * 60 * 60; // 1 year @@ -32,10 +33,10 @@ pub fn handle_request_cookies(req: &T) -> Option { } } -pub fn create_synthetic_cookie(synthetic_id: &str) -> String { +pub fn create_synthetic_cookie(synthetic_id: &str, settings: &Settings) -> String { format!( - "synthetic_id={}; Domain=.auburndao.com; Path=/; Secure; SameSite=Lax; Max-Age={}", - synthetic_id, COOKIE_MAX_AGE, + "synthetic_id={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age={}", + synthetic_id, settings.server.cookie_domain, COOKIE_MAX_AGE, ) } @@ -132,10 +133,40 @@ mod tests { #[test] fn test_create_synthetic_cookie() { - let result = create_synthetic_cookie("12345"); + // Create a test settings + let settings_toml = r#" +[server] +domain = "example.com" +cookie_domain = ".example.com" + +[ad_server] +ad_partner_url = "test" +sync_url = "test" + +[prebid] +server_url = "test" + +[synthetic] +counter_store = "test" +opid_store = "test" +secret_key = "test-key" +template = "test" + "#; + + let settings: Settings = config::Config::builder() + .add_source(config::File::from_str( + settings_toml, + config::FileFormat::Toml, + )) + .build() + .unwrap() + .try_deserialize() + .unwrap(); + + let result = create_synthetic_cookie("12345", &settings); assert_eq!( result, - "synthetic_id=12345; Domain=.auburndao.com; Path=/; Secure; SameSite=Lax; Max-Age=31536000" + "synthetic_id=12345; Domain=.example.com; Path=/; Secure; SameSite=Lax; Max-Age=31536000" ); } } diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs new file mode 100644 index 0000000..0072982 --- /dev/null +++ b/crates/common/src/error.rs @@ -0,0 +1,33 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TrustedServerError { + #[error("Configuration error: {0}")] + Config(#[from] config::ConfigError), + + #[error("Template rendering error: {0}")] + Template(#[from] handlebars::RenderError), + + #[error("Invalid UTF-8: {0}")] + Utf8(#[from] std::str::Utf8Error), + + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + + #[error("HTTP error: {0}")] + Http(String), + + #[error("KV store error: {0}")] + KvStore(String), + + #[error("Invalid request: {0}")] + InvalidRequest(String), + + #[error("Security error: {0}")] + Security(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), +} + +pub type Result = std::result::Result; diff --git a/crates/common/src/gdpr.rs b/crates/common/src/gdpr.rs index 9825669..ac192a6 100644 --- a/crates/common/src/gdpr.rs +++ b/crates/common/src/gdpr.rs @@ -64,15 +64,16 @@ pub fn get_consent_from_request(req: &T) -> Option String { +pub fn create_consent_cookie(consent: &GdprConsent, settings: &Settings) -> String { format!( - "gdpr_consent={}; Domain=.auburndao.com; Path=/; Secure; SameSite=Lax; Max-Age=31536000", - serde_json::to_string(consent).unwrap_or_default() + "gdpr_consent={}; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age=31536000", + serde_json::to_string(consent).unwrap_or_default(), + settings.server.cookie_domain ) } pub fn handle_consent_request( - _settings: &Settings, + settings: &Settings, mut req: T, ) -> Result { match *req.get_method() { @@ -90,7 +91,10 @@ pub fn handle_consent_request( .with_header(header::CONTENT_TYPE, "application/json") .with_body(serde_json::to_string(&consent)?); - response.set_header(header::SET_COOKIE, create_consent_cookie(&consent)); + response.set_header( + header::SET_COOKIE, + create_consent_cookie(&consent, settings), + ); Ok(response) } _ => { diff --git a/crates/common/src/geo.rs b/crates/common/src/geo.rs new file mode 100644 index 0000000..3eee723 --- /dev/null +++ b/crates/common/src/geo.rs @@ -0,0 +1,25 @@ +use fastly::Response; + +use crate::constants::{ + HEADER_X_GEO_CITY, HEADER_X_GEO_CONTINENT, HEADER_X_GEO_COORDINATES, HEADER_X_GEO_COUNTRY, + HEADER_X_GEO_INFO_AVAILABLE, HEADER_X_GEO_METRO_CODE, +}; +use crate::http_wrapper::RequestWrapper; + +/// Copy all geo headers from request to response +pub fn copy_geo_headers(req: &T, response: &mut Response) { + let geo_headers = &[ + HEADER_X_GEO_CITY, + HEADER_X_GEO_COUNTRY, + HEADER_X_GEO_CONTINENT, + HEADER_X_GEO_COORDINATES, + HEADER_X_GEO_METRO_CODE, + HEADER_X_GEO_INFO_AVAILABLE, + ]; + + for header_name in geo_headers { + if let Some(value) = req.get_header(header_name.clone()) { + response.set_header(header_name, value); + } + } +} diff --git a/crates/common/src/ip.rs b/crates/common/src/ip.rs new file mode 100644 index 0000000..0dc90d9 --- /dev/null +++ b/crates/common/src/ip.rs @@ -0,0 +1,73 @@ +use std::net::IpAddr; + +/// Parse client IP from X-Forwarded-For header value. +/// Takes the first IP in the comma-separated list to prevent spoofing. +/// Validates that the IP is a valid IP address format. +pub fn parse_client_ip(forwarded_for: &str) -> Option { + forwarded_for + .split(',') + .next() + .and_then(|ip| ip.trim().parse::().ok()) +} + +/// Extract the client IP from the request, handling X-Forwarded-For header +pub fn get_client_ip(forwarded_for: Option<&str>) -> String { + forwarded_for + .and_then(parse_client_ip) + .map(|ip| ip.to_string()) + .unwrap_or_else(|| "Unknown".to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_client_ip_single_ipv4() { + let ip = parse_client_ip("192.168.1.1"); + assert_eq!(ip.unwrap().to_string(), "192.168.1.1"); + } + + #[test] + fn test_parse_client_ip_multiple_ips() { + let ip = parse_client_ip("192.168.1.1, 10.0.0.1, 172.16.0.1"); + assert_eq!(ip.unwrap().to_string(), "192.168.1.1"); + } + + #[test] + fn test_parse_client_ip_with_spaces() { + let ip = parse_client_ip(" 192.168.1.1 , 10.0.0.1 "); + assert_eq!(ip.unwrap().to_string(), "192.168.1.1"); + } + + #[test] + fn test_parse_client_ip_ipv6() { + let ip = parse_client_ip("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + assert_eq!(ip.unwrap().to_string(), "2001:db8:85a3::8a2e:370:7334"); + } + + #[test] + fn test_parse_client_ip_invalid() { + assert!(parse_client_ip("not-an-ip").is_none()); + assert!(parse_client_ip("192.168.1.256").is_none()); + assert!(parse_client_ip("").is_none()); + } + + #[test] + fn test_get_client_ip_with_valid_header() { + let ip = get_client_ip(Some("192.168.1.1, 10.0.0.1")); + assert_eq!(ip, "192.168.1.1"); + } + + #[test] + fn test_get_client_ip_with_invalid_header() { + let ip = get_client_ip(Some("invalid-ip")); + assert_eq!(ip, "Unknown"); + } + + #[test] + fn test_get_client_ip_without_header() { + let ip = get_client_ip(None); + assert_eq!(ip, "Unknown"); + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index ce55b64..9c24083 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,10 +1,15 @@ pub mod constants; pub mod cookies; +pub mod error; pub mod gdpr; +pub mod geo; pub mod http_wrapper; +pub mod ip; +pub mod logging; pub mod models; pub mod prebid; pub mod privacy; +pub mod request_id; pub mod settings; pub mod synthetic; pub mod templates; diff --git a/crates/common/src/logging.rs b/crates/common/src/logging.rs new file mode 100644 index 0000000..943cd66 --- /dev/null +++ b/crates/common/src/logging.rs @@ -0,0 +1,21 @@ +use log::LevelFilter; + +/// Initialize logging for the application +/// Should be called once at the start of main() +pub fn init_logging() { + // Initialize Fastly logging if available, otherwise use env_logger for tests + #[cfg(target_arch = "wasm32")] + { + log_fastly::init_simple("trusted-server", LevelFilter::Info); + } + + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::builder().filter_level(LevelFilter::Info).init(); + } +} + +/// Log level helper to determine if debug logging is enabled +pub fn is_debug_enabled() -> bool { + log::log_enabled!(log::Level::Debug) +} diff --git a/crates/common/src/prebid.rs b/crates/common/src/prebid.rs index 32fa9e4..88a834b 100644 --- a/crates/common/src/prebid.rs +++ b/crates/common/src/prebid.rs @@ -68,7 +68,7 @@ impl PrebidRequest { }) .unwrap_or_else(|| "auburndao.com".to_string()); - println!("Detected domain: {}", domain); + log::debug!("Detected domain: {}", domain); // Create origin with owned String let origin = req @@ -107,7 +107,7 @@ impl PrebidRequest { .map(|s| s.to_string()) .unwrap_or_else(|| self.synthetic_id.clone()); - println!("Found Truted Server ID from incoming request: {}", id); + log::info!("Found Trusted Server ID from incoming request: {}", id); // Construct the OpenRTB2 bid request let prebid_body = json!({ @@ -176,9 +176,10 @@ impl PrebidRequest { req.set_header(HEADER_SYNTHETIC_FRESH, &self.synthetic_id); req.set_header(HEADER_SYNTHETIC_TRUSTED_SERVER, &id); - println!( + log::info!( "Sending prebid request with Fresh ID: {} and Trusted Server ID: {}", - self.synthetic_id, id + self.synthetic_id, + id ); req.set_body_json(&prebid_body)?; diff --git a/crates/common/src/request_id.rs b/crates/common/src/request_id.rs new file mode 100644 index 0000000..2fe683e --- /dev/null +++ b/crates/common/src/request_id.rs @@ -0,0 +1,30 @@ +use crate::constants::HEADER_X_REQUEST_ID; +use crate::http_wrapper::RequestWrapper; +use fastly::Response; +use uuid::Uuid; + +/// Generate a new request ID +pub fn generate_request_id() -> String { + Uuid::new_v4().to_string() +} + +/// Get request ID from headers or generate a new one +pub fn get_or_generate_request_id(req: &T) -> String { + req.get_header(HEADER_X_REQUEST_ID) + .and_then(|h| h.to_str().ok()) + .map(|s| s.to_string()) + .unwrap_or_else(generate_request_id) +} + +/// Add request ID to response headers +pub fn add_request_id_to_response(response: &mut Response, request_id: &str) { + response.set_header(HEADER_X_REQUEST_ID, request_id); +} + +/// Log with request ID context +#[macro_export] +macro_rules! log_with_request_id { + ($level:ident, $request_id:expr, $($arg:tt)+) => { + log::$level!("[{}] {}", $request_id, format!($($arg)+)); + }; +} diff --git a/crates/common/src/settings.rs b/crates/common/src/settings.rs index a3b70c3..4e3d0a8 100644 --- a/crates/common/src/settings.rs +++ b/crates/common/src/settings.rs @@ -1,4 +1,4 @@ -use config::{Config, ConfigError, File, FileFormat}; +use config::{Config, ConfigError, Environment, File, FileFormat}; use serde::Deserialize; use std::str; @@ -24,9 +24,17 @@ pub struct Synthetic { pub template: String, } +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct Server { + pub domain: String, + pub cookie_domain: String, +} + #[derive(Debug, Deserialize)] #[allow(unused)] pub struct Settings { + pub server: Server, pub ad_server: AdServer, pub prebid: Prebid, pub synthetic: Synthetic, @@ -35,13 +43,24 @@ pub struct Settings { impl Settings { pub fn new() -> Result { let toml_bytes = include_bytes!("../../../trusted-server.toml"); - let toml_str = str::from_utf8(toml_bytes).unwrap(); + let toml_str = str::from_utf8(toml_bytes) + .map_err(|e| ConfigError::Message(format!("Invalid UTF-8 in config: {}", e)))?; - let s = Config::builder() + let builder = Config::builder() .add_source(File::from_str(toml_str, FileFormat::Toml)) - .build()?; + .add_source(Environment::with_prefix("TRUSTED_SERVER").separator("__")); + + let config = builder.build()?; + + // Validate that secret key is not the default + if let Ok(secret_key) = config.get_string("synthetic.secret_key") { + if secret_key == "trusted-server" { + return Err(ConfigError::Message( + "Secret key must be changed from default. Set TRUSTED_SERVER__SYNTHETIC__SECRET_KEY environment variable.".into() + )); + } + } - // You can deserialize (and thus freeze) the entire configuration as - s.try_deserialize() + config.try_deserialize() } } diff --git a/crates/common/src/synthetic.rs b/crates/common/src/synthetic.rs index af647a1..f7e2319 100644 --- a/crates/common/src/synthetic.rs +++ b/crates/common/src/synthetic.rs @@ -45,7 +45,7 @@ pub fn generate_synthetic_id(settings: &Settings, req: &T) -> let input_string = handlebars .render_template(&settings.synthetic.template, data) .unwrap(); - println!("Input string for fresh ID: {} {}", input_string, data); + log::debug!("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"); @@ -116,6 +116,10 @@ mod tests { fn create_settings() -> Settings { Settings { + server: crate::settings::Server { + domain: "example.com".to_string(), + cookie_domain: ".example.com".to_string(), + }, ad_server: crate::settings::AdServer { ad_partner_url: "https://example.com".to_string(), sync_url: "https://example.com/synthetic_id={{synthetic_id}}".to_string(), diff --git a/crates/fastly/Cargo.toml b/crates/fastly/Cargo.toml index 0bcf059..7df67f0 100644 --- a/crates/fastly/Cargo.toml +++ b/crates/fastly/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" publish = false [dependencies] +chrono = "0.4" fastly = "0.11.2" futures = "0.3" http = "1.3.1" diff --git a/crates/fastly/src/main.rs b/crates/fastly/src/main.rs index ab1b78b..48b41ee 100644 --- a/crates/fastly/src/main.rs +++ b/crates/fastly/src/main.rs @@ -7,7 +7,6 @@ use http::Method; use fastly::geo::geo_lookup; use fastly::KVStore; use fastly::{Error, Request as FastlyRequest, Response}; -use log::LevelFilter::Info; use serde_json::json; pub mod http_wrapper; @@ -22,10 +21,13 @@ use trusted_server_common::cookies::create_synthetic_cookie; use trusted_server_common::gdpr::{ get_consent_from_request, handle_consent_request, handle_data_subject_request, }; +use trusted_server_common::geo::copy_geo_headers; use trusted_server_common::http_wrapper::RequestWrapper; +use trusted_server_common::ip::get_client_ip; use trusted_server_common::models::AdResponse; use trusted_server_common::prebid::PrebidRequest; use trusted_server_common::privacy::PRIVACY_TEMPLATE; +use trusted_server_common::request_id::{add_request_id_to_response, get_or_generate_request_id}; use trusted_server_common::settings::Settings; use trusted_server_common::synthetic::{generate_synthetic_id, get_or_generate_synthetic_id}; use trusted_server_common::templates::HTML_TEMPLATE; @@ -33,19 +35,36 @@ use trusted_server_common::why::WHY_TEMPLATE; #[fastly::main] fn main(mut fastly_req: FastlyRequest) -> Result { - let settings = Settings::new().unwrap(); - println!("Settings {settings:?}"); + // Initialize logging first + trusted_server_common::logging::init_logging(); + + let settings = match Settings::new() { + Ok(s) => s, + Err(e) => { + log::error!("Configuration error: {}", e); + return Ok(Response::from_status(StatusCode::INTERNAL_SERVER_ERROR) + .with_body(format!("Configuration error: {}", e)) + .with_header(header::CONTENT_TYPE, "text/plain")); + } + }; + log::debug!("Settings loaded: {:?}", settings); futures::executor::block_on(async { - println!( - "FASTLY_SERVICE_VERSION: {}", + let req = FastlyRequestWrapper::new(&mut fastly_req); + let request_id = get_or_generate_request_id(&req); + + log::info!( + "[{}] Request: {} {} - FASTLY_SERVICE_VERSION: {}", + request_id, + req.get_method(), + req.get_path(), std::env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| String::new()) ); - let req = FastlyRequestWrapper::new(&mut fastly_req); - - match (req.get_method(), req.get_path()) { + let start = std::time::Instant::now(); + let mut response = match (req.get_method(), req.get_path()) { (&Method::GET, "/") => handle_main_page(&settings, req), + (&Method::GET, "/health") => handle_health_check(), (&Method::GET, "/ad-creative") => handle_ad_request(&settings, req), (&Method::GET, "/prebid-test") => handle_prebid_test(&settings, req).await, (&Method::GET, "/gdpr/consent") => handle_consent_request(&settings, req), @@ -64,74 +83,106 @@ fn main(mut fastly_req: FastlyRequest) -> Result { .with_body("Not Found") .with_header(header::CONTENT_TYPE, "text/plain") .with_header("x-compress-hint", "on")), - } + }?; + + // Add request ID to response + add_request_id_to_response(&mut response, &request_id); + + // Log request completion + let elapsed = start.elapsed(); + log::info!( + "[{}] Response: {} - Duration: {:?}", + request_id, + response.get_status(), + elapsed + ); + + Ok(response) }) } +fn handle_health_check() -> Result { + let health_status = json!({ + "status": "healthy", + "service": "trusted-server", + "version": env!("CARGO_PKG_VERSION"), + "timestamp": chrono::Utc::now().to_rfc3339(), + "fastly": { + "pop": env::var("FASTLY_POP").unwrap_or_else(|_| "unknown".into()), + "region": env::var("FASTLY_REGION").unwrap_or_else(|_| "unknown".into()), + "service_version": env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| "unknown".into()), + } + }); + + Ok(Response::from_status(StatusCode::OK) + .with_header(header::CONTENT_TYPE, "application/json") + .with_header(header::CACHE_CONTROL, "no-store, no-cache, must-revalidate") + .with_body(serde_json::to_string(&health_status)?)) +} + fn get_dma_code(req: &mut T) -> Option { // Debug: Check if we're running in Fastly environment - println!("Fastly Environment Check:"); - println!( + log::debug!("Fastly Environment Check:"); + log::debug!( " FASTLY_POP: {}", - std::env::var("FASTLY_POP").unwrap_or_else(|_| "not in Fastly".to_string()) + std::env::var("FASTLY_POP").unwrap_or_else(|_| "not in Fastly".into()) ); - println!( + log::debug!( " FASTLY_REGION: {}", - std::env::var("FASTLY_REGION").unwrap_or_else(|_| "not in Fastly".to_string()) + std::env::var("FASTLY_REGION").unwrap_or_else(|_| "not in Fastly".into()) ); // Get detailed geo information using geo_lookup if let Some(geo) = req.get_client_ip_addr().and_then(geo_lookup) { - println!("Geo Information Found:"); + log::debug!("Geo Information Found:"); // Set all available geo information in headers let city = geo.city(); req.set_header_str(HEADER_X_GEO_CITY, city); - println!(" City: {}", city); + log::debug!(" City: {}", city); let country = geo.country_code(); req.set_header_str(HEADER_X_GEO_COUNTRY, country); - println!(" Country: {}", country); + log::debug!(" Country: {}", country); req.set_header_str(HEADER_X_GEO_CONTINENT, &format!("{:?}", geo.continent())); - println!(" Continent: {:?}", geo.continent()); + log::debug!(" Continent: {:?}", geo.continent()); req.set_header_str( HEADER_X_GEO_COORDINATES, &format!("{},{}", geo.latitude(), geo.longitude()), ); - println!(" Location: ({}, {})", geo.latitude(), geo.longitude()); + log::debug!(" Location: ({}, {})", geo.latitude(), geo.longitude()); // Get and set the metro code (DMA) let metro_code = geo.metro_code(); req.set_header_str(HEADER_X_GEO_METRO_CODE, &metro_code.to_string()); - println!("Found DMA/Metro code: {}", metro_code); + log::info!("Found DMA/Metro code: {}", metro_code); return Some(metro_code.to_string()); } else { - println!("No geo information available for the request"); + log::debug!("No geo information available for the request"); req.set_header_str(HEADER_X_GEO_INFO_AVAILABLE, "false"); } // If no metro code is found, log all request headers for debugging - println!("No DMA/Metro code found. All request headers:"); + log::debug!("No DMA/Metro code found. All request headers:"); for (name, value) in req.get_headers() { - println!(" {}: {:?}", name, value); + log::debug!(" {}: {:?}", name, value); } None } fn handle_main_page(settings: &Settings, mut req: T) -> Result { - println!( + log::info!( "Using ad_partner_url: {}, counter_store: {}", - settings.ad_server.ad_partner_url, settings.synthetic.counter_store, + settings.ad_server.ad_partner_url, + settings.synthetic.counter_store, ); - log_fastly::init_simple("mylogs", Info); - // Add DMA code check to main page as well let dma_code = get_dma_code(&mut req); - println!("Main page - DMA Code: {:?}", dma_code); + log::debug!("Main page - DMA Code: {:?}", dma_code); // Check GDPR consent before proceeding let consent = get_consent_from_request(&req).unwrap_or_default(); @@ -154,12 +205,12 @@ fn handle_main_page(settings: &Settings, mut req: T) -> Resul // 3. Fall back to fresh ID let synthetic_id = get_or_generate_synthetic_id(settings, &req); - println!( + log::debug!( "Existing Truted Server header: {:?}", req.get_header(HEADER_SYNTHETIC_TRUSTED_SERVER) ); - println!("Generated Fresh ID: {}", fresh_id); - println!("Using Trusted Server ID: {}", synthetic_id); + log::debug!("Generated Fresh ID: {}", fresh_id); + log::info!("Using Trusted Server ID: {}", synthetic_id); // Create response with the main page HTML let mut response = Response::from_status(StatusCode::OK) @@ -175,32 +226,24 @@ fn handle_main_page(settings: &Settings, mut req: T) -> Resul .with_header("x-compress-hint", "on"); // Copy geo headers from request to response - for header_name in [ - HEADER_X_GEO_CITY, - HEADER_X_GEO_COUNTRY, - HEADER_X_GEO_CONTINENT, - HEADER_X_GEO_COORDINATES, - HEADER_X_GEO_METRO_CODE, - HEADER_X_GEO_INFO_AVAILABLE, - ] { - if let Some(value) = req.get_header(header_name.clone()) { - response.set_header(header_name, value); - } - } + copy_geo_headers(&req, &mut response); // Only set cookies if we have consent if consent.functional { - response.set_header(header::SET_COOKIE, create_synthetic_cookie(&synthetic_id)); + response.set_header( + header::SET_COOKIE, + create_synthetic_cookie(&synthetic_id, settings), + ); } // Debug: Print all request headers - println!("All Request Headers:"); + log::debug!("All Request Headers:"); for (name, value) in req.get_headers() { log::info!("{}: {:?}", name, value); } // Debug: Print the response headers - println!("Response Headers:"); + log::debug!("Response Headers:"); for (name, value) in response.get_headers() { log::info!("{}: {:?}", name, value); } @@ -226,20 +269,18 @@ fn handle_ad_request( // Add DMA code extraction let dma_code = get_dma_code(&mut req); - println!("Client location - DMA Code: {:?}", dma_code); + log::info!("Client location - DMA Code: {:?}", dma_code); // Log headers for debugging - let client_ip = req - .get_client_ip_addr() - .map(|ip| ip.to_string()) - .unwrap_or_else(|| "Unknown".to_string()); let x_forwarded_for = req .get_header(HEADER_X_FORWARDED_FOR) - .map(|h| h.to_str().unwrap_or("Unknown")); + .and_then(|h| h.to_str().ok()); + + let client_ip = get_client_ip(x_forwarded_for); - println!("Client IP: {}", client_ip); - println!("X-Forwarded-For: {}", x_forwarded_for.unwrap_or("None")); - println!("Advertising consent: {}", advertising_consent); + log::info!("Client IP: {}", client_ip); + log::debug!("X-Forwarded-For: {}", x_forwarded_for.unwrap_or("None")); + log::info!("Advertising consent: {}", advertising_consent); // Generate synthetic ID only if we have consent let synthetic_id = if advertising_consent { @@ -252,36 +293,36 @@ fn handle_ad_request( // Only track visits if we have consent if advertising_consent { // Increment visit counter in KV store - println!("Opening KV store: {}", settings.synthetic.counter_store); + log::debug!("Opening KV store: {}", settings.synthetic.counter_store); if let Ok(Some(store)) = KVStore::open(settings.synthetic.counter_store.as_str()) { - println!("Fetching current count for synthetic ID: {}", synthetic_id); + log::debug!("Fetching current count for synthetic ID: {}", synthetic_id); let current_count: i32 = store .lookup(&synthetic_id) .map(|mut val| match String::from_utf8(val.take_body_bytes()) { Ok(s) => { - println!("Value from KV store: {}", s); + log::debug!("Value from KV store: {}", s); Some(s) } Err(e) => { - println!("Error converting bytes to string: {}", e); + log::error!("Error converting bytes to string: {}", e); None } }) .map(|opt_s| { - println!("Parsing string value: {:?}", opt_s); + log::debug!("Parsing string value: {:?}", opt_s); opt_s.and_then(|s| s.parse().ok()) }) .unwrap_or_else(|_| { - println!("No existing count found, starting at 0"); + log::debug!("No existing count found, starting at 0"); None }) .unwrap_or(0); let new_count = current_count + 1; - println!("Incrementing count from {} to {}", current_count, new_count); + log::debug!("Incrementing count from {} to {}", current_count, new_count); if let Err(e) = store.insert(&synthetic_id, new_count.to_string().as_bytes()) { - println!("Error updating KV store: {:?}", e); + log::error!("Error updating KV store: {:?}", e); } } } @@ -304,7 +345,7 @@ fn handle_ad_request( .replace("{{synthetic_id}}", "non-personalized") }; - println!("Sending request to backend: {}", ad_server_url); + log::info!("Sending request to backend: {}", ad_server_url); // Add header logging here let mut ad_req = FastlyRequest::get(ad_server_url); @@ -315,53 +356,51 @@ fn handle_ad_request( if advertising_consent { "true" } else { "false" }, ); - println!("Request headers to Equativ:"); + log::debug!("Request headers to Equativ:"); for (name, value) in ad_req.get_headers() { - println!(" {}: {:?}", name, value); + log::debug!(" {}: {:?}", name, value); } match ad_req.send(settings.ad_server.ad_partner_url.as_str()) { Ok(mut res) => { - println!( + log::info!( "Received response from backend with status: {}", res.get_status() ); // Extract Fastly PoP from the Compute environment - let fastly_pop = env::var("FASTLY_POP").unwrap_or_else(|_| "unknown".to_string()); + let fastly_pop = env::var("FASTLY_POP").unwrap_or_else(|_| "unknown".into()); let fastly_cache_generation = - env::var("FASTLY_CACHE_GENERATION").unwrap_or_else(|_| "unknown".to_string()); + env::var("FASTLY_CACHE_GENERATION").unwrap_or_else(|_| "unknown".into()); let fastly_customer_id = - env::var("FASTLY_CUSTOMER_ID").unwrap_or_else(|_| "unknown".to_string()); - let fastly_hostname = - env::var("FASTLY_HOSTNAME").unwrap_or_else(|_| "unknown".to_string()); - let fastly_region = env::var("FASTLY_REGION").unwrap_or_else(|_| "unknown".to_string()); + env::var("FASTLY_CUSTOMER_ID").unwrap_or_else(|_| "unknown".into()); + let fastly_hostname = env::var("FASTLY_HOSTNAME").unwrap_or_else(|_| "unknown".into()); + let fastly_region = env::var("FASTLY_REGION").unwrap_or_else(|_| "unknown".into()); let fastly_service_id = - env::var("FASTLY_SERVICE_ID").unwrap_or_else(|_| "unknown".to_string()); - // let fastly_service_version = env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| "unknown".to_string()); - let fastly_trace_id = - env::var("FASTLY_TRACE_ID").unwrap_or_else(|_| "unknown".to_string()); - - println!("Fastly Jason PoP: {}", fastly_pop); - println!("Fastly Compute Variables:"); - println!(" - FASTLY_CACHE_GENERATION: {}", fastly_cache_generation); - println!(" - FASTLY_CUSTOMER_ID: {}", fastly_customer_id); - println!(" - FASTLY_HOSTNAME: {}", fastly_hostname); - println!(" - FASTLY_POP: {}", fastly_pop); - println!(" - FASTLY_REGION: {}", fastly_region); - println!(" - FASTLY_SERVICE_ID: {}", fastly_service_id); - //println!(" - FASTLY_SERVICE_VERSION: {}", fastly_service_version); - println!(" - FASTLY_TRACE_ID: {}", fastly_trace_id); + env::var("FASTLY_SERVICE_ID").unwrap_or_else(|_| "unknown".into()); + // let fastly_service_version = env::var("FASTLY_SERVICE_VERSION").unwrap_or_else(|_| "unknown".into()); + let fastly_trace_id = env::var("FASTLY_TRACE_ID").unwrap_or_else(|_| "unknown".into()); + + log::debug!("Fastly Jason PoP: {}", fastly_pop); + log::debug!("Fastly Compute Variables:"); + log::debug!(" - FASTLY_CACHE_GENERATION: {}", fastly_cache_generation); + log::debug!(" - FASTLY_CUSTOMER_ID: {}", fastly_customer_id); + log::debug!(" - FASTLY_HOSTNAME: {}", fastly_hostname); + log::debug!(" - FASTLY_POP: {}", fastly_pop); + log::debug!(" - FASTLY_REGION: {}", fastly_region); + log::debug!(" - FASTLY_SERVICE_ID: {}", fastly_service_id); + //log::debug!(" - FASTLY_SERVICE_VERSION: {}", fastly_service_version); + log::debug!(" - FASTLY_TRACE_ID: {}", fastly_trace_id); // Log all response headers - println!("Response headers from Equativ:"); + log::debug!("Response headers from Equativ:"); for (name, value) in res.get_headers() { - println!(" {}: {:?}", name, value); + log::debug!(" {}: {:?}", name, value); } if res.get_status().is_success() { let body = res.take_body_str(); - println!("Backend response body: {}", body); + log::debug!("Backend response body: {}", body); // Parse the JSON response and extract opid if let Ok(ad_response) = serde_json::from_str::(&body) { @@ -378,36 +417,38 @@ fn handle_ad_request( .find(|¶m| param.starts_with("opid=")) .and_then(|param| param.split('=').nth(1)) { - println!("Found opid: {}", opid); + log::debug!("Found opid: {}", opid); // Store in opid KV store - println!( + log::debug!( "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"); + log::debug!("Successfully opened KV store"); match store.insert(&synthetic_id, opid.as_bytes()) { - Ok(_) => println!( + Ok(_) => log::info!( "Successfully stored opid {} for synthetic ID: {}", - opid, synthetic_id + opid, + synthetic_id ), Err(e) => { - println!("Error storing opid in KV store: {:?}", e) + log::error!("Error storing opid in KV store: {:?}", e) } } } Ok(None) => { - println!( + log::warn!( "KV store returned None: {}", settings.synthetic.opid_store ); } Err(e) => { - println!( + log::error!( "Error opening KV store '{}': {:?}", - settings.synthetic.opid_store, e + settings.synthetic.opid_store, + e ); } }; @@ -428,26 +469,15 @@ fn handle_ad_request( .with_body(body); // Copy geo headers from request to response - for header_name in &[ - HEADER_X_GEO_CITY, - HEADER_X_GEO_COUNTRY, - HEADER_X_GEO_CONTINENT, - HEADER_X_GEO_COORDINATES, - HEADER_X_GEO_METRO_CODE, - HEADER_X_GEO_INFO_AVAILABLE, - ] { - if let Some(value) = req.get_header(header_name.clone()) { - response.set_header(header_name, value); - } - } + copy_geo_headers(&req, &mut response); // Attach PoP info to the response //response.set_header("X-Debug-Fastly-PoP", &fastly_pop); - //println!("Added X-Debug-Fastly-PoP: {}", fastly_pop); + //log::debug!("Added X-Debug-Fastly-PoP: {}", fastly_pop); Ok(response) } else { - println!("Backend returned non-success status"); + log::warn!("Backend returned non-success status"); Ok(Response::from_status(StatusCode::NO_CONTENT) .with_header(header::CONTENT_TYPE, "application/json") .with_header("x-compress-hint", "on") @@ -455,7 +485,7 @@ fn handle_ad_request( } } Err(e) => { - println!("Error making backend request: {:?}", e); + log::error!("Error making backend request: {:?}", e); Ok(Response::from_status(StatusCode::NO_CONTENT) .with_header(header::CONTENT_TYPE, "application/json") .with_header("x-compress-hint", "on") @@ -469,7 +499,7 @@ async fn handle_prebid_test( settings: &Settings, mut req: T, ) -> Result { - println!("Starting prebid test request handling"); + log::info!("Starting prebid test request handling"); // Check consent status from headers let advertising_consent = req @@ -491,13 +521,13 @@ async fn handle_prebid_test( ) }; - println!( + log::debug!( "Existing Trusted Server header: {:?}", req.get_header(HEADER_SYNTHETIC_TRUSTED_SERVER) ); - println!("Generated Fresh ID: {}", fresh_id); - println!("Using Trusted Server ID: {}", synthetic_id); - println!("Advertising consent: {}", advertising_consent); + log::debug!("Generated Fresh ID: {}", fresh_id); + log::info!("Using Trusted Server ID: {}", synthetic_id); + log::info!("Advertising consent: {}", advertising_consent); // Set both IDs as headers req.set_header_str(HEADER_SYNTHETIC_FRESH, &fresh_id); @@ -507,21 +537,22 @@ async fn handle_prebid_test( if advertising_consent { "true" } else { "false" }, ); - println!( + log::info!( "Using Trusted Server ID: {}, Fresh ID: {}", - synthetic_id, fresh_id + synthetic_id, + fresh_id ); let prebid_req = match PrebidRequest::new(settings, &req) { Ok(req) => { - println!( + log::debug!( "Successfully created PrebidRequest with synthetic ID: {}", req.synthetic_id ); req } Err(e) => { - println!("Error creating PrebidRequest: {:?}", e); + log::error!("Error creating PrebidRequest: {:?}", e); return Ok(Response::from_status(StatusCode::INTERNAL_SERVER_ERROR) .with_header(header::CONTENT_TYPE, "application/json") .with_body_json(&json!({ @@ -531,20 +562,20 @@ async fn handle_prebid_test( } }; - println!("Attempting to send bid request to Prebid Server at prebid_backend"); + log::info!("Attempting to send bid request to Prebid Server at prebid_backend"); match prebid_req.send_bid_request(settings, &req).await { Ok(mut prebid_response) => { - println!("Received response from Prebid Server"); - println!("Response status: {}", prebid_response.get_status()); + log::info!("Received response from Prebid Server"); + log::info!("Response status: {}", prebid_response.get_status()); - println!("Response headers:"); + log::debug!("Response headers:"); for (name, value) in prebid_response.get_headers() { - println!(" {}: {:?}", name, value); + log::debug!(" {}: {:?}", name, value); } let body = prebid_response.take_body_str(); - println!("Response body: {}", body); + log::debug!("Response body: {}", body); Ok(Response::from_status(StatusCode::OK) .with_header(header::CONTENT_TYPE, "application/json") @@ -558,8 +589,8 @@ async fn handle_prebid_test( .with_body(body)) } Err(e) => { - println!("Error sending bid request: {:?}", e); - println!("Backend name used: prebid_backend"); + log::error!("Error sending bid request: {:?}", e); + log::error!("Backend name used: prebid_backend"); Ok(Response::from_status(StatusCode::INTERNAL_SERVER_ERROR) .with_header(header::CONTENT_TYPE, "application/json") .with_body_json(&json!({ diff --git a/trusted-server.toml b/trusted-server.toml index 7e75ae4..2285bcc 100644 --- a/trusted-server.toml +++ b/trusted-server.toml @@ -1,3 +1,7 @@ +[server] +domain = "auburndao.com" +cookie_domain = ".auburndao.com" + [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}}" From 52a6bb3111fa5c49a7ce1623baa7fd4f6e74c0a6 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 27 Jun 2025 09:53:24 -0700 Subject: [PATCH 6/6] Fixed logging --- crates/fastly/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fastly/src/main.rs b/crates/fastly/src/main.rs index 48b41ee..bfc4539 100644 --- a/crates/fastly/src/main.rs +++ b/crates/fastly/src/main.rs @@ -239,13 +239,13 @@ fn handle_main_page(settings: &Settings, mut req: T) -> Resul // Debug: Print all request headers log::debug!("All Request Headers:"); for (name, value) in req.get_headers() { - log::info!("{}: {:?}", name, value); + log::debug!("{}: {:?}", name, value); } // Debug: Print the response headers log::debug!("Response Headers:"); for (name, value) in response.get_headers() { - log::info!("{}: {:?}", name, value); + log::debug!("{}: {:?}", name, value); } // Prevent caching