Skip to content

Commit 360896e

Browse files
authored
Proposal to standardize integrations (#113)
1 parent ffbdd85 commit 360896e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1914
-513
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Implemented basic authentication for configurable endpoint paths (#73)
13+
- Added integrations guide with example `testlight` integration
1314

1415
## [1.2.0] - 2025-10-14
1516

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ default-members = [
1616
debug = 1
1717

1818
[workspace.dependencies]
19+
async-trait = "0.1"
1920
base64 = "0.22"
2021
brotli = "8.0"
2122
bytes = "1.10"
@@ -34,13 +35,14 @@ handlebars = "6.3.2"
3435
hex = "0.4.3"
3536
hmac = "0.12.1"
3637
http = "1.3.1"
38+
jose-jwk = "0.1.2"
3739
log = "0.4.28"
3840
log-fastly = "0.11.9"
3941
lol_html = "2.7.0"
42+
once_cell = "1.19"
4043
pin-project-lite = "0.2"
41-
regex = "1.12.2"
42-
jose-jwk = "0.1.2"
4344
rand = "0.8"
45+
regex = "1.12.2"
4446
serde = { version = "1.0", features = ["derive"] }
4547
serde_json = "1.0.145"
4648
sha2 = "0.10.9"

FAQ_POC.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ NOT all the capabilities. Tech Lab will build and support core services to enabl
2525
**Does the Trusteed Server preclude using third party tags?**
2626
No, it does not. You should be able to begin migrating certain modules and parts of your content and experience as you go, without a forklift upgrade. We plan to support server side tagging capabilities to enable third party support.
2727

28-
**Why are you only using two vendors in the POC?**
28+
**Why are you only using two partners in the POC?**
2929
Fastly and Equativ volunteered time and resources to us and they fit the technical needs and requirements for Trusted Server. For the sake of getting to market ASAP, we chose to double down on these two partners. We do not play favorites or have any financial incentive with these two companies and will begin implementing on other partners in the near future. Any ad exchange supporting prebid server requests should already find support. We will prioritize modules for other edge cloud providers based on industry priorities
3030

3131
**How will this project be managed?**
@@ -39,4 +39,3 @@ Yes. As long as your managed service provider can separate the edge from the CMS
3939

4040
**How will this comply with Privacy regulations?**
4141
The trusted server will have modules to support Consent Management Providers (CMP) and send the GPP or TCF string as required in the ad request.
42-

README.md

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
1-
# Trusted Server
1+
# Trusted Server
22

3-
:information_source: Trusted Server is an open-source, cloud based orchestration framework and runtime for publishers. It moves code execution and operations that traditionally occurs in browsers (via 3rd party JS) to secure, zero-cold-start [WASM](https://webassembly.org) binaries running in [WASI](https://github.com/WebAssembly/WASI) supported environments. It importantly gives publishers benefits such as: dramatically increasing control over how and who they share their data with (while maintaining user-privacy compliance), increasing revenue from inventory inside cookie restricted or non-JS environments, ability to serve all assets under 1st party context, and provides secure cryptographic functions to ensure trust across the programmatic ad ecosystem.
3+
:information_source: Trusted Server is an open-source, cloud based orchestration framework and runtime for publishers. It moves code execution and operations that traditionally occurs in browsers (via 3rd party JS) to secure, zero-cold-start [WASM](https://webassembly.org) binaries running in [WASI](https://github.com/WebAssembly/WASI) supported environments. It importantly gives publishers benefits such as: dramatically increasing control over how and who they share their data with (while maintaining user-privacy compliance), increasing revenue from inventory inside cookie restricted or non-JS environments, ability to serve all assets under 1st party context, and provides secure cryptographic functions to ensure trust across the programmatic ad ecosystem.
44

55
Trusted Server is the new execution layer for the open-web, returning control of 1st party data, security, and overall user-experience back to publishers.
66

77
At this time, Trusted Server is designed to work with Fastly Compute. Follow these steps to configure Fastly Compute and deploy it.
88

99
## Getting Started: Edge-Cloud Support on Fastly
10+
1011
- Create account at Fastly if you don’t have one - manage.fastly.com
11-
- Log in to the Fastly control panel.
12-
- Go to Account > API tokens > Personal tokens.
13-
- Click Create token
14-
- Name the Token
15-
- Choose User Token
16-
- Choose Global API Access
17-
- Choose what makes sense for your Org in terms of Service Access
18-
- Copy key to a secure location because you will not be able to see it again
19-
20-
- Create new Compute Service
21-
- Click Compute and Create Service
22-
- Click “Create Empty Service” (below main options)
23-
- Add your domain of the website you’ll be testing or using and click update
24-
- Click on “Origins” section and add your ad-server / ssp partner information as hostnames (note after you save this information you can select port numbers and TLS on/off)
25-
- IMPORTANT: when you enter the FQDN or IP ADDR information and click Add you need to enter a “Name” in the first field that will be referenced in your code so something like “my_ad_partner_1”
26-
-
12+
- Log in to the Fastly control panel.
13+
- Go to Account > API tokens > Personal tokens.
14+
- Click Create token
15+
- Name the Token
16+
- Choose User Token
17+
- Choose Global API Access
18+
- Choose what makes sense for your Org in terms of Service Access
19+
- Copy key to a secure location because you will not be able to see it again
20+
21+
- Create new Compute Service
22+
- Click Compute and Create Service
23+
- Click “Create Empty Service” (below main options)
24+
- Add your domain of the website you’ll be testing or using and click update
25+
- Click on “Origins” section and add your ad-server / SSP integration information as hostnames (note after you save this information you can select port numbers and TLS on/off)
26+
- IMPORTANT: when you enter the FQDN or IP ADDR information and click Add you need to enter a “Name” in the first field that will be referenced in your code so something like “my_ad_integration_1”
27+
-
2728

2829
:warning: With a dev account, Fastly gives you a test domain by default, but you’re also able to create a CNAME to your own domain when you’re ready, along with 2 free TLS certs (non-wildcard). Note that Fastly Compute ONLY accepts client traffic via TLS, though origins and backends can be non-TLS.
2930

@@ -38,28 +39,33 @@ At this time, Trusted Server is designed to work with Fastly Compute. Follow the
3839
```sh
3940
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
4041
```
42+
4143
### Fastly CLI
4244

43-
#### Install Fastly CLI
45+
#### Install Fastly CLI
46+
4447
```sh
4548
brew install fastly/tap/fastly
4649
```
4750

48-
#### Verify Installation and Version
51+
#### Verify Installation and Version
52+
4953
```sh
5054
fastly version
5155
```
5256

5357
:warning: fastly cli version should be at least v12.1.0
5458

5559
#### Create profile and follow interactive prompt for pasting your API Token created earlier:
56-
```sh
60+
61+
```sh
5762
fastly profile create
5863
```
5964

6065
### Rust
6166

6267
#### Install Rust with asdf (our preference)
68+
6369
```sh
6470
brew install asdf
6571
asdf plugin add rust
@@ -70,6 +76,7 @@ asdf reshim
7076
### NodeJS
7177

7278
#### Install NodeJS with asdf
79+
7380
```sh
7481
brew install asdf
7582
asdf plugin add nodejs
@@ -79,34 +86,39 @@ asdf reshim
7986

8087
#### Fix path for Bash
8188

82-
Edit ~/.bash_profile to add path for asdf shims:
89+
Edit ~/.bash_profile to add path for asdf shims:
90+
8391
```sh
8492
export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH"
8593
```
8694

8795
#### Fix path for ZSH
8896

89-
Edit ~/.zshrc to add path for asdf shims:
97+
Edit ~/.zshrc to add path for asdf shims:
98+
9099
```sh
91100
export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH"
92101
```
93102

94103
#### Other shells
95-
See https://asdf-vm.com/guide/getting-started.html#_2-configure-asdf
96104

105+
See https://asdf-vm.com/guide/getting-started.html#_2-configure-asdf
97106

98-
### Clone Trusted Server and Configure Build
107+
### Clone Trusted Server and Configure Build
99108

100109
#### Clone Project (assumes you have 'git' installed on your system)
110+
101111
```sh
102112
git clone [email protected]:IABTechLab/trusted-server.git
103113
```
104114

105115
### Configure
116+
106117
#### Edit configuration files
118+
107119
:information_source: Note that you'll have to edit the following files for your setup:
108120

109-
- fastly.toml (service ID, author, description, Config/Secret Store IDs for request signing)
121+
- fastly.toml (service ID, author, description, Config/Secret Store IDs for request signing)
110122
- trusted-server.toml (KV store ID names - optional, request signing configuration)
111123

112124
### Build
@@ -124,11 +136,13 @@ fastly compute publish
124136
## Devleopment
125137

126138
#### Install viceroy for running tests
139+
127140
```sh
128141
cargo install viceroy
129142
```
130143

131144
#### Run Fastly server locally
145+
132146
- Review configuration for [local_server](fastly.toml#L16)
133147
- Review env variables overrides in [.env.dev](.env.dev)
134148

@@ -141,13 +155,15 @@ fastly -i compute serve
141155
```
142156

143157
#### Tests
158+
144159
```sh
145160
cargo test
146161
```
147162

148163
:warning: if test fails `viceroy` will not display line number of the failed test. Rerun it with `cargo test_details`.
149164

150165
#### Additional Rust Commands
166+
151167
- `cargo fmt`: Ensure uniform code formatting
152168
- `cargo clippy`: Ensure idiomatic code
153169
- `cargo check`: Ensure compilation succeeds on Linux, MacOS, Windows and WebAssembly
@@ -166,6 +182,7 @@ Request signing requires Fastly Config Store and Secret Store for key management
166182
- Secret Store: `signing_keys` - stores private signing keys
167183

168184
2. **Configure in trusted-server.toml**:
185+
169186
```toml
170187
[request_signing]
171188
enabled = true # Set to true to enable request signing
@@ -187,7 +204,6 @@ Once configured, the following endpoints are available:
187204
- **`POST /admin/keys/rotate`**: Generates and activates a new signing key
188205
- Optional body: `{"kid": "custom-key-id"}` (auto-generates date-based ID if omitted)
189206
- Response includes new key ID, previous key ID, and active keys list
190-
191207
- **`POST /admin/keys/deactivate`**: Deactivates or deletes a key
192208
- Request body: `{"kid": "key-to-deactivate", "delete": false}`
193209
- Set `delete: true` to permanently remove the key (also deactivates it)
@@ -196,9 +212,9 @@ Once configured, the following endpoints are available:
196212

197213
## First-Party Endpoints
198214

199-
- `/first-party/ad` (GET): returns HTML for a single slot (`slot`, `w`, `h` query params). The server inspects returned creative HTML and rewrites:
200-
- All absolute images and iframes to `/first-party/proxy?tsurl=<base-url>&<original-query-params>&tstoken=<sig>` (1×1 pixels are detected server‑side heuristically for logging). The `tstoken` is derived from encrypting the full target URL and hashing it.
201-
- `/third-party/ad` (POST): accepts tsjs ad units and proxies to Prebid Server.
215+
- `/first-party/ad` (GET): returns HTML for a single slot (`slot`, `w`, `h` query params). The server inspects returned creative HTML and rewrites:
216+
- All absolute images and iframes to `/first-party/proxy?tsurl=<base-url>&<original-query-params>&tstoken=<sig>` (1×1 pixels are detected server‑side heuristically for logging). The `tstoken` is derived from encrypting the full target URL and hashing it.
217+
- `/third-party/ad` (POST): accepts tsjs ad units and proxies to Prebid Server.
202218
- `/first-party/proxy` (GET): unified proxy for resources referenced by creatives.
203219
- Query params:
204220
- `tsurl`: Target URL without query (base URL) — required
@@ -223,7 +239,12 @@ Once configured, the following endpoints are available:
223239

224240
- Publisher origin proxy (`handle_publisher_request`): retrieves/generates the synthetic ID, stamps the response with `X-Synthetic-*` headers, and sets the `synthetic_id` cookie (Secure, SameSite=Lax) when absent so subsequent creative and click proxies can propagate the identifier.
225241

226-
Notes
227-
- Rewriting uses `lol_html`. Only absolute and protocol‑relative URLs are rewritten; relative URLs are left unchanged.
228-
- For the proxy endpoint, the base URL is carried in `tsurl`, the original query parameters are preserved individually, and `tstoken` authenticates the reconstructed full URL.
229-
- Synthetic identifiers are generated by `crates/common/src/synthetic.rs` and are surfaced in three places: publisher responses (headers + cookie), creative proxy target URLs (`synthetic_id` query param), and click redirect URLs. This ensures downstream partners can correlate impressions and clicks without direct third-party cookies.
242+
Notes
243+
244+
- Rewriting uses `lol_html`. Only absolute and protocol‑relative URLs are rewritten; relative URLs are left unchanged.
245+
- For the proxy endpoint, the base URL is carried in `tsurl`, the original query parameters are preserved individually, and `tstoken` authenticates the reconstructed full URL.
246+
- Synthetic identifiers are generated by `crates/common/src/synthetic.rs` and are surfaced in three places: publisher responses (headers + cookie), creative proxy target URLs (`synthetic_id` query param), and click redirect URLs. This ensures downstream integrations can correlate impressions and clicks without direct third-party cookies.
247+
248+
## Integration Modules
249+
250+
- See [`docs/integration_guide.md`](docs/integration_guide.md) for the full integration module guide, covering configuration, proxy routing, HTML shim hooks, and the `testlight` example implementation.

crates/common/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ brotli = { workspace = true }
1414
bytes = { workspace = true }
1515
chacha20poly1305 = { workspace = true }
1616
chrono = { workspace = true }
17+
async-trait = { workspace = true }
1718
config = { workspace = true }
1819
cookie = { workspace = true }
1920
derive_more = { workspace = true }
@@ -42,6 +43,7 @@ urlencoding = { workspace = true }
4243
uuid = { workspace = true }
4344
validator = { workspace = true }
4445
ed25519-dalek = { workspace = true }
46+
once_cell = { workspace = true }
4547

4648
[build-dependencies]
4749
config = { workspace = true }

crates/common/src/creative.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,12 @@ pub fn rewrite_creative_html(markup: &str, settings: &Settings) -> String {
300300
let mut rewriter = HtmlRewriter::new(
301301
HtmlSettings {
302302
element_content_handlers: vec![
303-
// Inject tsjs-creative at the top of body once
303+
// Inject unified tsjs bundle at the top of body once
304304
element!("body", {
305305
let injected = injected_ts_creative.clone();
306306
move |el| {
307307
if !injected.get() {
308-
let script_tag = tsjs::creative_script_tag();
308+
let script_tag = tsjs::unified_script_tag();
309309
el.prepend(&script_tag, ContentType::Html);
310310
injected.set(true);
311311
}
@@ -490,20 +490,20 @@ mod tests {
490490
let html = r#"<html><body><p>hello</p></body></html>"#;
491491
let out = rewrite_creative_html(html, &settings);
492492
assert!(
493-
out.contains("/static/tsjs=tsjs-creative.min.js"),
494-
"expected tsjs-creative injection: {}",
493+
out.contains("/static/tsjs=tsjs-unified.min.js"),
494+
"expected unified tsjs injection: {}",
495495
out
496496
);
497497
// Inject only once
498-
assert_eq!(out.matches("/static/tsjs=tsjs-creative.min.js").count(), 1);
498+
assert_eq!(out.matches("/static/tsjs=tsjs-unified.min.js").count(), 1);
499499
}
500500

501501
#[test]
502-
fn injects_tsjs_creative_once_with_multiple_bodies() {
502+
fn injects_tsjs_unified_once_with_multiple_bodies() {
503503
let settings = crate::test_support::tests::create_test_settings();
504504
let html = r#"<html><body>one</body><body>two</body></html>"#;
505505
let out = rewrite_creative_html(html, &settings);
506-
assert_eq!(out.matches("/static/tsjs=tsjs-creative.min.js").count(), 1);
506+
assert_eq!(out.matches("/static/tsjs=tsjs-unified.min.js").count(), 1);
507507
}
508508

509509
#[test]

crates/common/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ pub enum TrustedServerError {
5050
#[display("Prebid error: {message}")]
5151
Prebid { message: String },
5252

53+
/// Integration module error.
54+
#[display("Integration error ({integration}): {message}")]
55+
Integration {
56+
integration: String,
57+
message: String,
58+
},
59+
5360
/// Proxy error.
5461
#[display("Proxy error: {message}")]
5562
Proxy { message: String },
@@ -91,6 +98,7 @@ impl IntoHttpResponse for TrustedServerError {
9198
Self::InvalidUtf8 { .. } => StatusCode::BAD_REQUEST,
9299
Self::KvStore { .. } => StatusCode::SERVICE_UNAVAILABLE,
93100
Self::Prebid { .. } => StatusCode::BAD_GATEWAY,
101+
Self::Integration { .. } => StatusCode::BAD_GATEWAY,
94102
Self::Proxy { .. } => StatusCode::BAD_GATEWAY,
95103
Self::SyntheticId { .. } => StatusCode::INTERNAL_SERVER_ERROR,
96104
Self::Template { .. } => StatusCode::INTERNAL_SERVER_ERROR,

0 commit comments

Comments
 (0)