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 ab6bf08..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]] @@ -715,9 +727,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", @@ -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" @@ -1533,21 +1551,26 @@ dependencies = [ "handlebars", "hex", "hmac", + "http", "log", "log-fastly", "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", "log", "log-fastly", "serde", @@ -1602,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" @@ -1614,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" @@ -1804,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/Cargo.toml b/Cargo.toml index 937838c..8e3663a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,4 @@ members = [ ] [profile.release] -debug = 1 \ No newline at end of file +debug = 1 diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index aa5c7fe..a0d32c8 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,10 +17,13 @@ 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"] } 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 de5edba..359377c 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -1,3 +1,18 @@ -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_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"); +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 3ec61fa..44a6c09 100644 --- a/crates/common/src/cookies.rs +++ b/crates/common/src/cookies.rs @@ -1,6 +1,8 @@ use cookie::{Cookie, CookieJar}; -use fastly::http::header; -use fastly::Request; +use http::header; + +use crate::http_wrapper::RequestWrapper; +use crate::settings::Settings; const COOKIE_MAX_AGE: i32 = 365 * 24 * 60 * 60; // 1 year @@ -17,7 +19,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(""); @@ -31,10 +33,10 @@ pub fn handle_request_cookies(req: &Request) -> 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, ) } @@ -42,6 +44,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 +84,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 +98,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 +110,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 +121,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); @@ -113,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 bb92a1e..ac192a6 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()) { @@ -56,14 +64,18 @@ pub fn get_consent_from_request(req: &Request) -> Option { None } -pub fn create_consent_cookie(consent: &GdprConsent) -> 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, req: Request) -> Result { +pub fn handle_consent_request( + settings: &Settings, + mut req: T, +) -> Result { match *req.get_method() { Method::GET => { // Return current consent status @@ -79,7 +91,10 @@ pub fn handle_consent_request(_settings: &Settings, req: Request) -> Result { @@ -89,11 +104,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 +128,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/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/http_wrapper.rs b/crates/common/src/http_wrapper.rs new file mode 100644 index 0000000..80994b3 --- /dev/null +++ b/crates/common/src/http_wrapper.rs @@ -0,0 +1,80 @@ +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; + + #[allow(clippy::wrong_self_convention)] + 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)] +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(&mut 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/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 7dff572..9c24083 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,9 +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 1d707ea..88a834b 100644 --- a/crates/common/src/prebid.rs +++ b/crates/common/src/prebid.rs @@ -1,8 +1,12 @@ -use fastly::http::{header, Method}; use fastly::{Error, Request, Response}; +use http::header; +use http::method::Method; 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; @@ -21,27 +25,27 @@ 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 - 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)); - // 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()) .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 @@ -64,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 @@ -89,21 +93,21 @@ 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()); // 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()); - 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!({ @@ -169,12 +173,13 @@ 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!( + 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 a22610c..f7e2319 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) @@ -45,7 +45,7 @@ pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String { 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"); @@ -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,19 +96,30 @@ 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); } - req + HttpRequestWrapper::new(builder) } 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(), @@ -129,18 +140,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 +159,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 +170,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/Cargo.toml b/crates/fastly/Cargo.toml index 5e478e2..7df67f0 100644 --- a/crates/fastly/Cargo.toml +++ b/crates/fastly/Cargo.toml @@ -2,10 +2,13 @@ name = "trusted-server-fastly" version = "0.1.0" edition = "2021" +publish = false [dependencies] +chrono = "0.4" 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 new file mode 100644 index 0000000..77d1e01 --- /dev/null +++ b/crates/fastly/src/http_wrapper.rs @@ -0,0 +1,54 @@ +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<'a> { + request: &'a mut FastlyRequest, +} + +impl<'a> FastlyRequestWrapper<'a> { + pub fn new(request: &'a mut FastlyRequest) -> Self { + FastlyRequestWrapper { request } + } +} + +impl<'a> RequestWrapper for FastlyRequestWrapper<'a> { + #[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 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 bd7dc84..bfc4539 100644 --- a/crates/fastly/src/main.rs +++ b/crates/fastly/src/main.rs @@ -1,37 +1,70 @@ +use std::env; + +use http::header; +use http::status::StatusCode; +use http::Method; + use fastly::geo::geo_lookup; -use fastly::http::{header, Method, StatusCode}; use fastly::KVStore; -use fastly::{Error, Request, Response}; -use log::LevelFilter::Info; +use fastly::{Error, Request as FastlyRequest, Response}; use serde_json::json; -use std::env; -use trusted_server_common::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_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::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; use trusted_server_common::why::WHY_TEMPLATE; #[fastly::main] -fn main(req: Request) -> Result { - let settings = Settings::new().unwrap(); - println!("Settings {settings:?}"); +fn main(mut fastly_req: FastlyRequest) -> Result { + // 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()) ); - 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), @@ -50,74 +83,106 @@ fn main(req: Request) -> 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 get_dma_code(req: &mut Request) -> Option { +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("X-Geo-City", city); - println!(" City: {}", city); + req.set_header_str(HEADER_X_GEO_CITY, city); + log::debug!(" City: {}", city); let country = geo.country_code(); - req.set_header("X-Geo-Country", country); - println!(" Country: {}", country); + req.set_header_str(HEADER_X_GEO_COUNTRY, country); + log::debug!(" Country: {}", country); - req.set_header("X-Geo-Continent", format!("{:?}", geo.continent())); - println!(" Continent: {:?}", geo.continent()); + req.set_header_str(HEADER_X_GEO_CONTINENT, &format!("{:?}", geo.continent())); + log::debug!(" 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()); + log::debug!(" 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()); - println!("Found DMA/Metro code: {}", metro_code); + req.set_header_str(HEADER_X_GEO_METRO_CODE, &metro_code.to_string()); + log::info!("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"); + 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: Request) -> Result { - println!( +fn handle_main_page(settings: &Settings, mut req: T) -> Result { + 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(); @@ -140,19 +205,19 @@ fn handle_main_page(settings: &Settings, mut req: Request) -> Result 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); @@ -209,20 +269,18 @@ fn handle_ad_request(settings: &Settings, mut req: Request) -> Result Result { - 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); } } } @@ -287,10 +345,10 @@ fn handle_ad_request(settings: &Settings, mut req: Request) -> Result Result { - 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) { @@ -361,36 +417,38 @@ fn handle_ad_request(settings: &Settings, mut req: Request) -> Result { - 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 ); } }; @@ -411,26 +469,15 @@ fn handle_ad_request(settings: &Settings, mut req: Request) -> Result Result { - 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") @@ -448,12 +495,15 @@ fn handle_ad_request(settings: &Settings, mut req: Request) -> Result Result { - println!("Starting prebid test request handling"); +async fn handle_prebid_test( + settings: &Settings, + mut req: T, +) -> Result { + log::info!("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); @@ -471,37 +521,38 @@ async fn handle_prebid_test(settings: &Settings, mut req: Request) -> Result { - 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!({ @@ -511,20 +562,20 @@ async fn handle_prebid_test(settings: &Settings, mut req: Request) -> Result { - 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") @@ -538,8 +589,8 @@ async fn handle_prebid_test(settings: &Settings, mut req: Request) -> Result { - 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}}"