Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Upgrade to rust 1.87.0
- Upgrade to fastly-cli 11.3.0
- Changed to use constants for headers

## [1.0.6] - 2025-05-29

Expand Down
5 changes: 3 additions & 2 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,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"] }
Expand Down
24 changes: 21 additions & 3 deletions crates/common/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
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_X_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");
pub const HEADER_X_COMPRESS_HINT: HeaderName = HeaderName::from_static("x-compress-hint");
pub const HEADER_X_DEBUG_FASTLY_POP: HeaderName = HeaderName::from_static("x-debug-fastly-pop");
14 changes: 8 additions & 6 deletions crates/common/src/gdpr.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::cookies;
use crate::settings::Settings;
use fastly::http::{header, Method, StatusCode};
use fastly::{Error, Request, Response};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::constants::HEADER_X_SUBJECT_ID;
use crate::cookies;
use crate::settings::Settings;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GdprConsent {
pub analytics: bool,
Expand Down Expand Up @@ -93,7 +95,7 @@ pub fn handle_data_subject_request(_settings: &Settings, req: Request) -> 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<String, UserData> = HashMap::new();

Expand All @@ -110,7 +112,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"))
Expand Down Expand Up @@ -319,7 +321,7 @@ mod tests {
fn test_handle_data_subject_request_get_with_id() {
let settings = create_test_settings();
let mut req = Request::get("https://example.com/gdpr/data");
req.set_header("X-Subject-ID", "test-subject-123");
req.set_header(HEADER_X_SUBJECT_ID, "test-subject-123");

let response = handle_data_subject_request(&settings, req).unwrap();
assert_eq!(response.get_status(), StatusCode::OK);
Expand Down Expand Up @@ -348,7 +350,7 @@ mod tests {
fn test_handle_data_subject_request_delete_with_id() {
let settings = create_test_settings();
let mut req = Request::delete("https://example.com/gdpr/data");
req.set_header("X-Subject-ID", "test-subject-123");
req.set_header(HEADER_X_SUBJECT_ID, "test-subject-123");

let response = handle_data_subject_request(&settings, req).unwrap();
assert_eq!(response.get_status(), StatusCode::OK);
Expand Down
24 changes: 13 additions & 11 deletions crates/common/src/prebid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ 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::settings::Settings;
use crate::synthetic::generate_synthetic_id;

Expand Down Expand Up @@ -31,7 +33,7 @@ impl PrebidRequest {
pub fn new(settings: &Settings, req: &Request) -> Result<Self, Error> {
// 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));
Expand All @@ -41,7 +43,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
Expand Down Expand Up @@ -98,7 +100,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());
Expand Down Expand Up @@ -167,10 +169,10 @@ impl PrebidRequest {
});

req.set_header(header::CONTENT_TYPE, "application/json");
req.set_header("X-Forwarded-For", &self.client_ip);
req.set_header(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: {}",
Expand Down Expand Up @@ -211,10 +213,10 @@ mod tests {
fn test_prebid_request_new_with_full_headers() {
let settings = create_test_settings();
let mut req = Request::get("https://example.com/test");
req.set_header(SYNTHETIC_HEADER_TRUSTED_SERVER, "existing-synthetic-id");
req.set_header(HEADER_SYNTHETIC_TRUSTED_SERVER, "existing-synthetic-id");
req.set_header(header::REFERER, "https://test-domain.com/page");
req.set_header(header::ORIGIN, "https://test-domain.com");
req.set_header("X-Forwarded-For", "192.168.1.1, 10.0.0.1");
req.set_header(HEADER_X_FORWARDED_FOR, "192.168.1.1, 10.0.0.1");

let prebid_req = PrebidRequest::new(&settings, &req).unwrap();

Expand Down Expand Up @@ -280,7 +282,7 @@ mod tests {
fn test_prebid_request_x_forwarded_for_parsing() {
let settings = create_test_settings();
let mut req = Request::get("https://example.com/test");
req.set_header("X-Forwarded-For", "192.168.1.1, 10.0.0.1, 172.16.0.1");
req.set_header(HEADER_X_FORWARDED_FOR, "192.168.1.1, 10.0.0.1, 172.16.0.1");

let prebid_req = PrebidRequest::new(&settings, &req).unwrap();

Expand Down Expand Up @@ -330,7 +332,7 @@ mod tests {

// Test with empty X-Forwarded-For
let mut req = Request::get("https://example.com/test");
req.set_header("X-Forwarded-For", "");
req.set_header(HEADER_X_FORWARDED_FOR, "");
let prebid_req = PrebidRequest::new(&settings, &req).unwrap();
assert!(!prebid_req.client_ip.is_empty() || prebid_req.client_ip.is_empty());

Expand Down
28 changes: 13 additions & 15 deletions crates/common/src/synthetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use hmac::{Hmac, Mac};
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::settings::Settings;

Expand All @@ -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)
Expand Down Expand Up @@ -61,7 +61,7 @@ pub fn generate_synthetic_id(settings: &Settings, req: &Request) -> String {
pub fn get_or_generate_synthetic_id(settings: &Settings, req: &Request) -> String {
// First try to get existing 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())
{
Expand Down Expand Up @@ -96,9 +96,10 @@ pub fn get_or_generate_synthetic_id(settings: &Settings, req: &Request) -> Strin
#[cfg(test)]
mod tests {
use super::*;
use fastly::http::HeaderValue;
use crate::constants::HEADER_X_PUB_USER_ID;
use fastly::http::{HeaderName, HeaderValue};

fn create_test_request(headers: Vec<(&str, &str)>) -> Request {
fn create_test_request(headers: Vec<(HeaderName, &str)>) -> Request {
let mut req = Request::new("GET", "http://example.com");
for (key, value) in headers {
req.set_header(key, HeaderValue::from_str(value).unwrap());
Expand Down Expand Up @@ -129,11 +130,11 @@ 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_X_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);
Expand All @@ -148,7 +149,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",
)]);

Expand All @@ -159,10 +160,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");
Expand Down
Loading