diff --git a/.cargo/config.toml b/.cargo/config.toml index 9b60148..e0e3f5b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,4 +5,4 @@ test_details = ["test", "--target", "aarch64-apple-darwin"] target = "wasm32-wasip1" [target.'cfg(all(target_arch = "wasm32"))'] -runner = "viceroy run -C fastly.toml -- " +runner = "viceroy run -C ../../fastly.toml -- " diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1772033 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,68 @@ + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- for new features. + +### Changed +- for changes in existing functionality. + +### Deprecated for soon-to-be removed features. +- Removed for now removed features. + +### Fixed +- for any bug fixes. + +### Security +- in case of vulnerabilities. + +## [1.0.5] - 2025-05-19 + +### Changed + +- Refactor into crates to allow to separate Fastly implementation +- Remove references to POTSI +- Rename `potsi.toml` to `trusted-server.toml` + +### Added + +- Implemented GDPR consent for creating and passing synth headers + + +## [1.0.4] - 2025-04-29 + +### Added + +- Implemented GDPR consent for creating and passing synth headers + +## [1.0.3] - 2025-04-23 + +### Changes + +- Upgraded to Fastly CLI v11.2.0 + +## [1.0.2] - 2025-03-28 + +### Added +- Documented project gogernance in [ProjectGovernance.md] +- Document FAQ for POC [FAQ_POC.md] + +## [1.0.1] - 2025-03-27 + +### Changed + +- Allow to templatize synthetic cookies + +## [1.0.0] - 2025-03-26 + +### Added + +- Initial implementation of Trusted Server + diff --git a/Cargo.lock b/Cargo.lock index ff887b9..4ee937e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,9 +138,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.17" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "shlex", ] @@ -153,9 +153,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -726,9 +726,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.62" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1125,27 +1125,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "potsi" -version = "0.1.0" -dependencies = [ - "chrono", - "config", - "cookie", - "fastly 0.11.2", - "futures", - "handlebars", - "hex", - "hmac", - "log", - "log-fastly", - "serde", - "serde_json", - "sha2 0.10.8", - "tokio", - "url", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -1542,6 +1521,40 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" +[[package]] +name = "trusted-server-common" +version = "0.1.0" +dependencies = [ + "chrono", + "config", + "cookie", + "fastly 0.11.2", + "futures", + "handlebars", + "hex", + "hmac", + "log", + "log-fastly", + "serde", + "serde_json", + "sha2 0.10.8", + "tokio", + "url", +] + +[[package]] +name = "trusted-server-fastly" +version = "0.1.0" +dependencies = [ + "fastly 0.11.2", + "futures", + "log", + "log-fastly", + "serde", + "serde_json", + "trusted-server-common", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1661,11 +1674,37 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -1674,6 +1713,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 9875704..e0632e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,6 @@ -[package] -name = "potsi" -version = "0.1.0" -authors = [] -edition = "2021" -# Remove this line if you want to be able to publish this crate as open source on crates.io. -# Otherwise, `publish = false` prevents an accidental `cargo publish` from revealing private source. -publish = false -license = "Apache-2.0" - -[profile.release] -debug = 1 - -[dependencies] -fastly = "0.11.2" -hmac = "0.12.1" -sha2 = "0.10.6" -hex = "0.4.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.91" -cookie = "0.18.1" -log = "0.4.20" -log-fastly = "0.10.0" -futures = "0.3" -tokio = { version = "1.0", features = ["sync", "macros", "io-util", "rt", "time"] } -url = "2.4.1" -config = "0.15.11" -handlebars = "6.3.2" -chrono = "0.4" +[workspace] +resolver = "2" +members = [ + "crates/common", + "crates/fastly", +] diff --git a/README.md b/README.md index 214d8f5..13cfe9b 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ git clone git@github.com:IABTechLab/trusted-server.git :information_source: Note that you’ll have to edit the following files for your setup: - fastly.toml (service ID, author, description) -- potsi.toml (KV store ID names) +- trusted-server.toml (KV store ID names) ### Build diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml new file mode 100644 index 0000000..9dd7b7e --- /dev/null +++ b/crates/common/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "trusted-server-common" +version = "0.1.0" +authors = [] +edition = "2021" +# Remove this line if you want to be able to publish this crate as open source on crates.io. +# Otherwise, `publish = false` prevents an accidental `cargo publish` from revealing private source. +publish = false +license = "Apache-2.0" + +[profile.release] +debug = 1 + +[dependencies] +chrono = "0.4" +config = "0.15.11" +cookie = "0.18.1" +fastly = "0.11.2" +futures = "0.3" +handlebars = "6.3.2" +hex = "0.4.3" +hmac = "0.12.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.0", features = ["sync", "macros", "io-util", "rt", "time"] } +url = "2.4.1" diff --git a/src/constants.rs b/crates/common/src/constants.rs similarity index 61% rename from src/constants.rs rename to crates/common/src/constants.rs index 106d454..de5edba 100644 --- a/src/constants.rs +++ b/crates/common/src/constants.rs @@ -1,3 +1,3 @@ pub const SYNTHETIC_HEADER_FRESH: &str = "X-Synthetic-Fresh"; -pub const SYNTHETIC_HEADER_POTSI: &str = "X-Synthetic-Potsi"; +pub const SYNTHETIC_HEADER_TRUSTED_SERVER: &str = "X-Synthetic-Trusted-Server"; pub const SYNTHETIC_HEADER_PUB_USER_ID: &str = "X-Pub-User-ID"; diff --git a/src/cookies.rs b/crates/common/src/cookies.rs similarity index 100% rename from src/cookies.rs rename to crates/common/src/cookies.rs diff --git a/src/gdpr.rs b/crates/common/src/gdpr.rs similarity index 100% rename from src/gdpr.rs rename to crates/common/src/gdpr.rs diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs new file mode 100644 index 0000000..7dff572 --- /dev/null +++ b/crates/common/src/lib.rs @@ -0,0 +1,10 @@ +pub mod constants; +pub mod cookies; +pub mod gdpr; +pub mod models; +pub mod prebid; +pub mod privacy; +pub mod settings; +pub mod synthetic; +pub mod templates; +pub mod why; diff --git a/src/models.rs b/crates/common/src/models.rs similarity index 100% rename from src/models.rs rename to crates/common/src/models.rs diff --git a/src/prebid.rs b/crates/common/src/prebid.rs similarity index 90% rename from src/prebid.rs rename to crates/common/src/prebid.rs index ca5d8d0..1d707ea 100644 --- a/src/prebid.rs +++ b/crates/common/src/prebid.rs @@ -2,7 +2,7 @@ use fastly::http::{header, Method}; use fastly::{Error, Request, Response}; use serde_json::json; -use crate::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_POTSI}; +use crate::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_TRUSTED_SERVER}; use crate::settings::Settings; use crate::synthetic::generate_synthetic_id; @@ -29,9 +29,9 @@ impl PrebidRequest { /// # Returns /// * `Result` - New PrebidRequest or error pub fn new(settings: &Settings, req: &Request) -> Result { - // Get the POTSI ID from header (which we just set in handle_prebid_test) + // Get the Trusted Server ID from header (which we just set in handle_prebid_test) let synthetic_id = req - .get_header(SYNTHETIC_HEADER_POTSI) + .get_header(SYNTHETIC_HEADER_TRUSTED_SERVER) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| generate_synthetic_id(settings, req)); @@ -97,17 +97,17 @@ impl PrebidRequest { let mut req = Request::new(Method::POST, settings.prebid.server_url.to_owned()); // Get and store the POTSI ID value from the incoming request - let potsi_id = incoming_req - .get_header(SYNTHETIC_HEADER_POTSI) + let id: String = incoming_req + .get_header(SYNTHETIC_HEADER_TRUSTED_SERVER) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) .unwrap_or_else(|| self.synthetic_id.clone()); - println!("Found POTSI ID from incoming request: {}", potsi_id); + println!("Found Truted Server ID from incoming request: {}", id); // Construct the OpenRTB2 bid request let prebid_body = json!({ - "id": potsi_id, + "id": id, "imp": [{ "id": "imp1", "banner": { @@ -150,10 +150,10 @@ impl PrebidRequest { { "source": &self.domain, "uids": [{ - "id": &potsi_id, + "id": &id, "atype": 1, "ext": { - "type": "potsi" + "type": "potsi" // TODO: remove reference to potsi } }] } @@ -170,11 +170,11 @@ impl PrebidRequest { 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_POTSI, &potsi_id); + req.set_header(SYNTHETIC_HEADER_TRUSTED_SERVER, &id); println!( - "Sending prebid request with Fresh ID: {} and POTSI ID: {}", - self.synthetic_id, potsi_id + "Sending prebid request with Fresh ID: {} and Trusted Server ID: {}", + self.synthetic_id, id ); req.set_body_json(&prebid_body)?; diff --git a/src/privacy.rs b/crates/common/src/privacy.rs similarity index 100% rename from src/privacy.rs rename to crates/common/src/privacy.rs diff --git a/src/settings.rs b/crates/common/src/settings.rs similarity index 86% rename from src/settings.rs rename to crates/common/src/settings.rs index efd2af6..a3b70c3 100644 --- a/src/settings.rs +++ b/crates/common/src/settings.rs @@ -26,15 +26,15 @@ pub struct Synthetic { #[derive(Debug, Deserialize)] #[allow(unused)] -pub(crate) struct Settings { +pub struct Settings { pub ad_server: AdServer, pub prebid: Prebid, pub synthetic: Synthetic, } impl Settings { - pub(crate) fn new() -> Result { - let toml_bytes = include_bytes!("../potsi.toml"); + pub fn new() -> Result { + let toml_bytes = include_bytes!("../../../trusted-server.toml"); let toml_str = str::from_utf8(toml_bytes).unwrap(); let s = Config::builder() diff --git a/src/synthetic.rs b/crates/common/src/synthetic.rs similarity index 84% rename from src/synthetic.rs rename to crates/common/src/synthetic.rs index 64371ab..a22610c 100644 --- a/src/synthetic.rs +++ b/crates/common/src/synthetic.rs @@ -5,7 +5,7 @@ use hmac::{Hmac, Mac}; use serde_json::json; use sha2::Sha256; -use crate::constants::{SYNTHETIC_HEADER_POTSI, SYNTHETIC_HEADER_PUB_USER_ID}; +use crate::constants::{SYNTHETIC_HEADER_PUB_USER_ID, SYNTHETIC_HEADER_TRUSTED_SERVER}; use crate::cookies::handle_request_cookies; use crate::settings::Settings; @@ -59,24 +59,24 @@ 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 { - // First try to get existing POTSI ID from header - if let Some(potsi) = req - .get_header(SYNTHETIC_HEADER_POTSI) + // First try to get existing Trusted Server ID from header + if let Some(synthetic_id) = req + .get_header(SYNTHETIC_HEADER_TRUSTED_SERVER) .and_then(|h| h.to_str().ok()) .map(|s| s.to_string()) { - log::info!("Using existing POTSI ID from header: {}", potsi); - return potsi; + log::info!("Using existing Synthetic ID from header: {}", synthetic_id); + return synthetic_id; } let req_cookie_jar: Option = handle_request_cookies(req); match req_cookie_jar { Some(jar) => { - let potsi_cookie = jar.get("synthetic_id"); - if let Some(cookie) = potsi_cookie { - let potsi = cookie.value().to_string(); - log::info!("Using existing POTSI ID from cookie: {}", potsi); - return potsi; + let ts_cookie = jar.get("synthetic_id"); + if let Some(cookie) = ts_cookie { + let id = cookie.value().to_string(); + log::info!("Using existing Trusted Server ID from cookie: {}", id); + return id; } } None => { @@ -84,9 +84,12 @@ pub fn get_or_generate_synthetic_id(settings: &Settings, req: &Request) -> Strin } } - // If no existing POTSI ID found, generate a fresh one + // If no existing Synthetic ID found, generate a fresh one let fresh_id = generate_synthetic_id(settings, req); - log::info!("No existing POTSI ID found, using fresh ID: {}", fresh_id); + log::info!( + "No existing Synthetic ID found, using fresh ID: {}", + fresh_id + ); fresh_id } @@ -144,10 +147,13 @@ mod tests { #[test] fn test_get_or_generate_synthetic_id_with_header() { let settings = create_settings(); - let req = create_test_request(vec![(SYNTHETIC_HEADER_POTSI, "existing_potsi_id")]); + let req = create_test_request(vec![( + SYNTHETIC_HEADER_TRUSTED_SERVER, + "existing_synthetic_id", + )]); let synthetic_id = get_or_generate_synthetic_id(&settings, &req); - assert_eq!(synthetic_id, "existing_potsi_id"); + assert_eq!(synthetic_id, "existing_synthetic_id"); } #[test] diff --git a/src/templates.rs b/crates/common/src/templates.rs similarity index 100% rename from src/templates.rs rename to crates/common/src/templates.rs diff --git a/src/why.rs b/crates/common/src/why.rs similarity index 100% rename from src/why.rs rename to crates/common/src/why.rs diff --git a/crates/fastly/Cargo.toml b/crates/fastly/Cargo.toml new file mode 100644 index 0000000..5e478e2 --- /dev/null +++ b/crates/fastly/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "trusted-server-fastly" +version = "0.1.0" +edition = "2021" + +[dependencies] +fastly = "0.11.2" +futures = "0.3" +log = "0.4.20" +log-fastly = "0.10.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.91" +trusted-server-common = { path = "../common" } diff --git a/src/main.rs b/crates/fastly/src/main.rs similarity index 90% rename from src/main.rs rename to crates/fastly/src/main.rs index 2ca5499..bd7dc84 100644 --- a/src/main.rs +++ b/crates/fastly/src/main.rs @@ -6,25 +6,18 @@ use log::LevelFilter::Info; use serde_json::json; use std::env; -mod constants; -mod cookies; -use constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_POTSI}; -mod models; -use models::AdResponse; -mod prebid; -use prebid::PrebidRequest; -mod settings; -use settings::Settings; -mod synthetic; -use synthetic::generate_synthetic_id; -mod templates; -use templates::HTML_TEMPLATE; -mod gdpr; -use gdpr::get_consent_from_request; -mod privacy; -use privacy::PRIVACY_TEMPLATE; -mod why; -use why::WHY_TEMPLATE; +use trusted_server_common::constants::{SYNTHETIC_HEADER_FRESH, SYNTHETIC_HEADER_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, +}; +use trusted_server_common::models::AdResponse; +use trusted_server_common::prebid::PrebidRequest; +use trusted_server_common::privacy::PRIVACY_TEMPLATE; +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 { @@ -41,10 +34,10 @@ fn main(req: Request) -> Result { (&Method::GET, "/") => handle_main_page(&settings, req), (&Method::GET, "/ad-creative") => handle_ad_request(&settings, req), (&Method::GET, "/prebid-test") => handle_prebid_test(&settings, req).await, - (&Method::GET, "/gdpr/consent") => gdpr::handle_consent_request(&settings, req), - (&Method::POST, "/gdpr/consent") => gdpr::handle_consent_request(&settings, req), - (&Method::GET, "/gdpr/data") => gdpr::handle_data_subject_request(&settings, req), - (&Method::DELETE, "/gdpr/data") => gdpr::handle_data_subject_request(&settings, req), + (&Method::GET, "/gdpr/consent") => handle_consent_request(&settings, req), + (&Method::POST, "/gdpr/consent") => handle_consent_request(&settings, req), + (&Method::GET, "/gdpr/data") => handle_data_subject_request(&settings, req), + (&Method::DELETE, "/gdpr/data") => handle_data_subject_request(&settings, req), (&Method::GET, "/privacy-policy") => Ok(Response::from_status(StatusCode::OK) .with_body(PRIVACY_TEMPLATE) .with_header(header::CONTENT_TYPE, "text/html") @@ -139,27 +132,27 @@ fn handle_main_page(settings: &Settings, mut req: Request) -> Result Result Result Result { diff --git a/fastly.toml b/fastly.toml index d5afeae..4339d0f 100644 --- a/fastly.toml +++ b/fastly.toml @@ -6,14 +6,15 @@ cloned_from = "https://github.com/fastly/compute-starter-kit-rust-default" description = "aslk" language = "rust" manifest_version = 3 -name = "potsi" +name = "trusted-server-fastly" service_id = "q9waEnTgJfqQV1a0cYOuo3" [scripts] build = """ - cargo build --bin potsi --release --target wasm32-wasip1 --color always + cargo build --bin trusted-server-fastly --release --target wasm32-wasip1 --color always """ + [local_server] [local_server.backends] [local_server.backends.equativ_ad_api_2] # FIX ME: must match config diff --git a/potsi.toml b/trusted-server.toml similarity index 95% rename from potsi.toml rename to trusted-server.toml index ae26266..7e75ae4 100644 --- a/potsi.toml +++ b/trusted-server.toml @@ -8,7 +8,7 @@ server_url = "http://68.183.113.79:8000/openrtb2/auction" [synthetic] counter_store = "jevans_synth_id_counter" opid_store = "jevans_synth_id_opid" -secret_key = "potsi" +secret_key = "trusted-server" # Possible values # - "client_ip" # - "user_agent"