@aamp/protocol 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/crypto.js CHANGED
@@ -23,7 +23,10 @@ async function verifySignature(publicKey, data, signatureHex) {
23
23
  const encoder = new TextEncoder();
24
24
  const encodedData = encoder.encode(data);
25
25
  const signatureBytes = hexToBuf(signatureHex);
26
- return await crypto.subtle.verify({ name: "ECDSA", hash: { name: "SHA-256" } }, publicKey, signatureBytes, encodedData);
26
+ console.log(" šŸ” [AAMP Crypto] Verifying ECDSA P-256 Signature...");
27
+ const isValid = await crypto.subtle.verify({ name: "ECDSA", hash: { name: "SHA-256" } }, publicKey, signatureBytes, encodedData);
28
+ console.log(` ${isValid ? "āœ…" : "āŒ"} [AAMP Crypto] Signature Result: ${isValid ? "VALID" : "INVALID"}`);
29
+ return isValid;
27
30
  }
28
31
  async function exportPublicKey(key) {
29
32
  const exported = await crypto.subtle.exportKey("spki", key);
package/dist/publisher.js CHANGED
@@ -55,6 +55,7 @@ class AAMPPublisher {
55
55
  // 1. Check for AAMP Headers
56
56
  const hasAamp = reqHeaders[constants_1.HEADERS.PAYLOAD] && reqHeaders[constants_1.HEADERS.SIGNATURE] && reqHeaders[constants_1.HEADERS.PUBLIC_KEY];
57
57
  if (hasAamp) {
58
+ console.log("\nšŸ”Ž [AAMP Middleware] Detected Agent Headers. Starting Verification...");
58
59
  // It claims to be an Agent. Verify it.
59
60
  return await this.handleAgent(reqHeaders, rawPayload);
60
61
  }
@@ -104,32 +105,23 @@ class AAMPPublisher {
104
105
  const userAgent = headers['user-agent'] || '';
105
106
  // A. The "Obvious Bot" Blocklist (Fast Fail)
106
107
  const botSignatures = ['python-requests', 'curl', 'wget', 'scrapy', 'bot', 'crawler', 'spider'];
107
- // Exception: Googlebot (if you want SEO). We'll treat Googlebot as a bot,
108
- // real implementations might white-list it via IP verification (not possible in just JS headers).
109
108
  if (botSignatures.some(sig => userAgent.toLowerCase().includes(sig))) {
110
109
  return false;
111
110
  }
112
111
  // B. Trusted Infrastructure Signals (The Real World Solution)
113
- // If Cloudflare or Vercel says "This is a real user", we trust them.
114
- // Cloudflare: 'cf-visitor' exists. 'cf-ipcountry' exists.
115
112
  if (headers['cf-visitor'] || headers['cf-ray'])
116
113
  return true;
117
- // Vercel: 'x-vercel-id'
118
114
  if (headers['x-vercel-id'])
119
115
  return true;
120
- // AWS CloudFront: 'cloudfront-viewer-address'
121
116
  if (headers['cloudfront-viewer-address'])
122
117
  return true;
123
118
  // C. The "Browser Fingerprint" (Fallback for direct connections)
124
- // Real browsers almost always send these headers
125
119
  const hasAcceptLanguage = !!headers['accept-language'];
126
120
  const hasSecFetchDest = !!headers['sec-fetch-dest'];
127
121
  const hasUpgradeInsecure = !!headers['upgrade-insecure-requests'];
128
- // If it has typical browser headers, we allow it.
129
122
  if (hasAcceptLanguage && (hasSecFetchDest || hasUpgradeInsecure)) {
130
123
  return true;
131
124
  }
132
- // If it has no browser headers and no trusted proxy headers -> It's likely a script.
133
125
  return false;
134
126
  }
135
127
  /**
@@ -151,6 +143,7 @@ class AAMPPublisher {
151
143
  // Verify Core Logic
152
144
  const result = await this.verifyRequestLogic(signedRequest, agentKey, headerJson);
153
145
  if (!result.allowed) {
146
+ console.log(`ā›” [AAMP Deny] Reason: ${result.reason}`);
154
147
  return {
155
148
  allowed: false,
156
149
  status: 403,
@@ -158,6 +151,7 @@ class AAMPPublisher {
158
151
  visitorType: 'VERIFIED_AGENT'
159
152
  };
160
153
  }
154
+ console.log(`āœ… [AAMP Allow] Verified Agent: ${requestHeader.agent_id}`);
161
155
  return {
162
156
  allowed: true,
163
157
  status: 200,
@@ -167,6 +161,7 @@ class AAMPPublisher {
167
161
  };
168
162
  }
169
163
  catch (e) {
164
+ console.error(e);
170
165
  return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
171
166
  }
172
167
  }
@@ -185,9 +180,11 @@ class AAMPPublisher {
185
180
  let identityVerified = false;
186
181
  const claimedDomain = request.header.agent_id;
187
182
  const pubKeyString = await (0, crypto_1.exportPublicKey)(requestPublicKey);
183
+ console.log(` šŸ†” [AAMP Identity] Verifying DNS Binding for: ${claimedDomain}`);
188
184
  // Check Cache First
189
185
  const cachedKey = await this.cache.get(claimedDomain);
190
186
  if (cachedKey === pubKeyString) {
187
+ console.log(" ⚔ [AAMP Cache] Identity found in cache.");
191
188
  identityVerified = true;
192
189
  }
193
190
  else if (this.isDomain(claimedDomain)) {
@@ -221,17 +218,33 @@ class AAMPPublisher {
221
218
  // Allow HTTP for localhost testing
222
219
  const protocol = (domain.includes('localhost') || domain.match(/:\d+$/)) ? 'http' : 'https';
223
220
  const url = `${protocol}://${domain}${constants_1.WELL_KNOWN_AGENT_PATH}`;
221
+ console.log(` šŸŒ [AAMP DNS] Fetching Manifest: ${url} ...`);
224
222
  // In production, we need a short timeout to prevent hanging
225
223
  const controller = new AbortController();
226
224
  const timeoutId = setTimeout(() => controller.abort(), 1500); // 1.5s max for DNS check
227
225
  const response = await fetch(url, { signal: controller.signal });
228
226
  clearTimeout(timeoutId);
229
- if (!response.ok)
227
+ if (!response.ok) {
228
+ console.log(` āŒ [AAMP DNS] Fetch Failed: ${response.status}`);
230
229
  return false;
230
+ }
231
231
  const manifest = await response.json();
232
- return manifest.agent_id === domain && manifest.public_key === requestKeySpki;
232
+ console.log(` šŸ“„ [AAMP DNS] Manifest received. Agent ID: ${manifest.agent_id}`);
233
+ // CHECK 1: Does the manifest actually belong to the domain?
234
+ if (manifest.agent_id !== domain) {
235
+ console.log(` āŒ [AAMP DNS] Mismatch: Manifest ID ${manifest.agent_id} != Claimed ${domain}`);
236
+ return false;
237
+ }
238
+ // CHECK 2: Does the key match?
239
+ if (manifest.public_key !== requestKeySpki) {
240
+ console.log(` āŒ [AAMP DNS] Key Mismatch: DNS Key != Request Key`);
241
+ return false;
242
+ }
243
+ console.log(` āœ… [AAMP DNS] Identity Confirmed.`);
244
+ return true;
233
245
  }
234
- catch {
246
+ catch (e) {
247
+ console.log(` āŒ [AAMP DNS] Error: ${e.message}`);
235
248
  return false;
236
249
  }
237
250
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aamp/protocol",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "TypeScript reference implementation of AAMP v1.1",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/crypto.ts CHANGED
@@ -28,12 +28,17 @@ export async function verifySignature(publicKey: CryptoKey, data: string, signat
28
28
  const encodedData = encoder.encode(data);
29
29
  const signatureBytes = hexToBuf(signatureHex);
30
30
 
31
- return await crypto.subtle.verify(
31
+ console.log(" šŸ” [AAMP Crypto] Verifying ECDSA P-256 Signature...");
32
+
33
+ const isValid = await crypto.subtle.verify(
32
34
  { name: "ECDSA", hash: { name: "SHA-256" } },
33
35
  publicKey,
34
36
  signatureBytes as any,
35
37
  encodedData as any
36
38
  );
39
+
40
+ console.log(` ${isValid ? "āœ…" : "āŒ"} [AAMP Crypto] Signature Result: ${isValid ? "VALID" : "INVALID"}`);
41
+ return isValid;
37
42
  }
38
43
 
39
44
  export async function exportPublicKey(key: CryptoKey): Promise<string> {
package/src/publisher.ts CHANGED
@@ -76,6 +76,7 @@ export class AAMPPublisher {
76
76
  const hasAamp = reqHeaders[HEADERS.PAYLOAD] && reqHeaders[HEADERS.SIGNATURE] && reqHeaders[HEADERS.PUBLIC_KEY];
77
77
 
78
78
  if (hasAamp) {
79
+ console.log("\nšŸ”Ž [AAMP Middleware] Detected Agent Headers. Starting Verification...");
79
80
  // It claims to be an Agent. Verify it.
80
81
  return await this.handleAgent(reqHeaders, rawPayload);
81
82
  }
@@ -130,35 +131,24 @@ export class AAMPPublisher {
130
131
 
131
132
  // A. The "Obvious Bot" Blocklist (Fast Fail)
132
133
  const botSignatures = ['python-requests', 'curl', 'wget', 'scrapy', 'bot', 'crawler', 'spider'];
133
- // Exception: Googlebot (if you want SEO). We'll treat Googlebot as a bot,
134
- // real implementations might white-list it via IP verification (not possible in just JS headers).
135
134
  if (botSignatures.some(sig => userAgent.toLowerCase().includes(sig))) {
136
135
  return false;
137
136
  }
138
137
 
139
138
  // B. Trusted Infrastructure Signals (The Real World Solution)
140
- // If Cloudflare or Vercel says "This is a real user", we trust them.
141
- // Cloudflare: 'cf-visitor' exists. 'cf-ipcountry' exists.
142
139
  if (headers['cf-visitor'] || headers['cf-ray']) return true;
143
-
144
- // Vercel: 'x-vercel-id'
145
140
  if (headers['x-vercel-id']) return true;
146
-
147
- // AWS CloudFront: 'cloudfront-viewer-address'
148
141
  if (headers['cloudfront-viewer-address']) return true;
149
142
 
150
143
  // C. The "Browser Fingerprint" (Fallback for direct connections)
151
- // Real browsers almost always send these headers
152
144
  const hasAcceptLanguage = !!headers['accept-language'];
153
145
  const hasSecFetchDest = !!headers['sec-fetch-dest'];
154
146
  const hasUpgradeInsecure = !!headers['upgrade-insecure-requests'];
155
147
 
156
- // If it has typical browser headers, we allow it.
157
148
  if (hasAcceptLanguage && (hasSecFetchDest || hasUpgradeInsecure)) {
158
149
  return true;
159
150
  }
160
151
 
161
- // If it has no browser headers and no trusted proxy headers -> It's likely a script.
162
152
  return false;
163
153
  }
164
154
 
@@ -192,6 +182,7 @@ export class AAMPPublisher {
192
182
  const result = await this.verifyRequestLogic(signedRequest, agentKey, headerJson);
193
183
 
194
184
  if (!result.allowed) {
185
+ console.log(`ā›” [AAMP Deny] Reason: ${result.reason}`);
195
186
  return {
196
187
  allowed: false,
197
188
  status: 403,
@@ -200,6 +191,7 @@ export class AAMPPublisher {
200
191
  };
201
192
  }
202
193
 
194
+ console.log(`āœ… [AAMP Allow] Verified Agent: ${requestHeader.agent_id}`);
203
195
  return {
204
196
  allowed: true,
205
197
  status: 200,
@@ -209,6 +201,7 @@ export class AAMPPublisher {
209
201
  };
210
202
 
211
203
  } catch (e) {
204
+ console.error(e);
212
205
  return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
213
206
  }
214
207
  }
@@ -235,10 +228,13 @@ export class AAMPPublisher {
235
228
  const claimedDomain = request.header.agent_id;
236
229
  const pubKeyString = await exportPublicKey(requestPublicKey);
237
230
 
231
+ console.log(` šŸ†” [AAMP Identity] Verifying DNS Binding for: ${claimedDomain}`);
232
+
238
233
  // Check Cache First
239
234
  const cachedKey = await this.cache.get(claimedDomain);
240
235
 
241
236
  if (cachedKey === pubKeyString) {
237
+ console.log(" ⚔ [AAMP Cache] Identity found in cache.");
242
238
  identityVerified = true;
243
239
  } else if (this.isDomain(claimedDomain)) {
244
240
  // Cache Miss: Perform DNS Fetch
@@ -277,6 +273,8 @@ export class AAMPPublisher {
277
273
  const protocol = (domain.includes('localhost') || domain.match(/:\d+$/)) ? 'http' : 'https';
278
274
  const url = `${protocol}://${domain}${WELL_KNOWN_AGENT_PATH}`;
279
275
 
276
+ console.log(` šŸŒ [AAMP DNS] Fetching Manifest: ${url} ...`);
277
+
280
278
  // In production, we need a short timeout to prevent hanging
281
279
  const controller = new AbortController();
282
280
  const timeoutId = setTimeout(() => controller.abort(), 1500); // 1.5s max for DNS check
@@ -284,10 +282,32 @@ export class AAMPPublisher {
284
282
  const response = await fetch(url, { signal: controller.signal });
285
283
  clearTimeout(timeoutId);
286
284
 
287
- if (!response.ok) return false;
285
+ if (!response.ok) {
286
+ console.log(` āŒ [AAMP DNS] Fetch Failed: ${response.status}`);
287
+ return false;
288
+ }
289
+
288
290
  const manifest = await response.json() as AgentIdentityManifest;
289
- return manifest.agent_id === domain && manifest.public_key === requestKeySpki;
290
- } catch { return false; }
291
+ console.log(` šŸ“„ [AAMP DNS] Manifest received. Agent ID: ${manifest.agent_id}`);
292
+
293
+ // CHECK 1: Does the manifest actually belong to the domain?
294
+ if (manifest.agent_id !== domain) {
295
+ console.log(` āŒ [AAMP DNS] Mismatch: Manifest ID ${manifest.agent_id} != Claimed ${domain}`);
296
+ return false;
297
+ }
298
+
299
+ // CHECK 2: Does the key match?
300
+ if (manifest.public_key !== requestKeySpki) {
301
+ console.log(` āŒ [AAMP DNS] Key Mismatch: DNS Key != Request Key`);
302
+ return false;
303
+ }
304
+
305
+ console.log(` āœ… [AAMP DNS] Identity Confirmed.`);
306
+ return true;
307
+ } catch (e: any) {
308
+ console.log(` āŒ [AAMP DNS] Error: ${e.message}`);
309
+ return false;
310
+ }
291
311
  }
292
312
 
293
313
  private isDomain(s: string): boolean {