@aamp/protocol 1.1.2 → 1.1.3

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/src/express.ts CHANGED
@@ -1,104 +1,95 @@
1
- /**
2
- * Layer 3: Framework Adapters
3
- * Zero-friction integration for Express/Node.js.
4
- */
5
- import { AAMPPublisher } from './publisher';
6
- import { AccessPolicy, ContentOrigin, SignedAccessRequest } from './types';
7
- import { generateKeyPair, verifySignature } from './crypto';
8
- import { HEADERS } from './constants';
9
-
10
- export interface AAMPConfig {
11
- policy: Omit<AccessPolicy, 'version'>;
12
- meta: {
13
- origin: keyof typeof ContentOrigin;
14
- paymentPointer?: string;
15
- };
16
- }
17
-
18
- export class AAMP {
19
- private publisher: AAMPPublisher;
20
- private origin: ContentOrigin;
21
- private ready: Promise<void>;
22
-
23
- private constructor(config: AAMPConfig) {
24
- this.publisher = new AAMPPublisher({
25
- version: '1.1',
26
- ...config.policy
27
- } as AccessPolicy);
28
-
29
- this.origin = ContentOrigin[config.meta.origin];
30
-
31
- this.ready = generateKeyPair().then(keys => {
32
- return this.publisher.initialize(keys);
33
- });
34
- }
35
-
36
- static init(config: AAMPConfig): AAMP {
37
- return new AAMP(config);
38
- }
39
-
40
- /**
41
- * Express Middleware
42
- */
43
- middleware() {
44
- return async (req: any, res: any, next: any) => {
45
- await this.ready;
46
-
47
- // 1. Inject Provenance Headers (Passive Protection)
48
- const headers = await this.publisher.generateResponseHeaders(this.origin);
49
- Object.entries(headers).forEach(([k, v]) => {
50
- res.setHeader(k, v);
51
- });
52
-
53
- // 2. Active Verification (If Agent sends signed headers)
54
- const payloadHeader = req.headers[HEADERS.PAYLOAD];
55
- const sigHeader = req.headers[HEADERS.SIGNATURE];
56
- const keyHeader = req.headers[HEADERS.PUBLIC_KEY];
57
-
58
- if (payloadHeader && sigHeader && keyHeader) {
59
- try {
60
- const headerJson = atob(payloadHeader); // RAW STRING
61
- const requestHeader = JSON.parse(headerJson);
62
-
63
- const signedRequest: SignedAccessRequest = {
64
- header: requestHeader,
65
- signature: sigHeader,
66
- publicKey: keyHeader
67
- };
68
-
69
- const agentKey = await crypto.subtle.importKey(
70
- "spki",
71
- new Uint8Array(atob(keyHeader).split('').map(c => c.charCodeAt(0))),
72
- { name: "ECDSA", namedCurve: "P-256" },
73
- true,
74
- ["verify"]
75
- );
76
-
77
- // Pass raw headerJson to ensure signature matches exactly what was signed
78
- const result = await this.publisher.verifyRequest(signedRequest, agentKey, headerJson);
79
-
80
- if (!result.allowed) {
81
- res.status(403).json({ error: result.reason });
82
- return;
83
- }
84
-
85
- // Verified!
86
- req.aamp = { verified: true, ...requestHeader };
87
-
88
- } catch (e) {
89
- res.status(400).json({ error: "Invalid AAMP Signature" });
90
- return;
91
- }
92
- }
93
-
94
- next();
95
- };
96
- }
97
-
98
- discoveryHandler() {
99
- return (req: any, res: any) => {
100
- res.setHeader('Content-Type', 'application/json');
101
- res.send(JSON.stringify(this.publisher.getPolicy(), null, 2));
102
- };
103
- }
1
+ /**
2
+ * Layer 3: Framework Adapters
3
+ * Zero-friction integration for Express/Node.js.
4
+ */
5
+ import { AAMPPublisher } from './publisher';
6
+ import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types';
7
+ import { generateKeyPair } from './crypto';
8
+
9
+ export interface AAMPConfig {
10
+ policy: Omit<AccessPolicy, 'version'>;
11
+ meta: {
12
+ origin: keyof typeof ContentOrigin;
13
+ paymentPointer?: string;
14
+ };
15
+ strategy?: UnauthenticatedStrategy;
16
+ // Optional: Provide a Redis/Memcached adapter here for production
17
+ cache?: IdentityCache;
18
+ }
19
+
20
+ export class AAMP {
21
+ private publisher: AAMPPublisher;
22
+ private origin: ContentOrigin;
23
+ private ready: Promise<void>;
24
+
25
+ private constructor(config: AAMPConfig) {
26
+ this.publisher = new AAMPPublisher(
27
+ { version: '1.1', ...config.policy } as AccessPolicy,
28
+ config.strategy || 'PASSIVE',
29
+ config.cache
30
+ );
31
+
32
+ this.origin = ContentOrigin[config.meta.origin];
33
+
34
+ this.ready = generateKeyPair().then(keys => {
35
+ return this.publisher.initialize(keys);
36
+ });
37
+ }
38
+
39
+ static init(config: AAMPConfig): AAMP {
40
+ return new AAMP(config);
41
+ }
42
+
43
+ /**
44
+ * Express Middleware
45
+ */
46
+ middleware() {
47
+ return async (req: any, res: any, next: any) => {
48
+ await this.ready;
49
+
50
+ // Normalize headers to lowercase dictionary
51
+ const headers: Record<string, string> = {};
52
+ Object.keys(req.headers).forEach(key => {
53
+ headers[key.toLowerCase()] = req.headers[key] as string;
54
+ });
55
+
56
+ // Retrieve Raw Payload if available (optional but good for crypto)
57
+ // Note: Express body parsing might interfere, so we usually rely on the header content.
58
+ const rawPayload = headers['x-aamp-payload'];
59
+
60
+ // Evaluate Visitor
61
+ const result = await this.publisher.evaluateVisitor(headers, rawPayload);
62
+
63
+ // Enforce Decision
64
+ if (!result.allowed) {
65
+ res.status(result.status).json({
66
+ error: result.reason,
67
+ visitor_type: result.visitorType
68
+ });
69
+ return;
70
+ }
71
+
72
+ // Inject Provenance Headers (For the humans/agents that got through)
73
+ const respHeaders = await this.publisher.generateResponseHeaders(this.origin);
74
+ Object.entries(respHeaders).forEach(([k, v]) => {
75
+ res.setHeader(k, v);
76
+ });
77
+
78
+ // Attach metadata to request for downstream use
79
+ req.aamp = {
80
+ verified: result.visitorType === 'VERIFIED_AGENT',
81
+ type: result.visitorType,
82
+ ...result.metadata
83
+ };
84
+
85
+ next();
86
+ };
87
+ }
88
+
89
+ discoveryHandler() {
90
+ return (req: any, res: any) => {
91
+ res.setHeader('Content-Type', 'application/json');
92
+ res.send(JSON.stringify(this.publisher.getPolicy(), null, 2));
93
+ };
94
+ }
104
95
  }
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
- /**
2
- * AAMP SDK Public API
3
- *
4
- * This is the main entry point for the library.
5
- */
6
-
7
- export * from './types';
8
- export * from './constants';
9
- export * from './agent';
10
- export * from './publisher';
11
- export * from './crypto';
12
- export * from './express'; // Node.js / Express Adapter
1
+ /**
2
+ * AAMP SDK Public API
3
+ *
4
+ * This is the main entry point for the library.
5
+ */
6
+
7
+ export * from './types';
8
+ export * from './constants';
9
+ export * from './agent';
10
+ export * from './publisher';
11
+ export * from './crypto';
12
+ export * from './express'; // Node.js / Express Adapter
13
13
  export { AAMPNext } from './nextjs'; // Serverless / Next.js Adapter
package/src/nextjs.ts CHANGED
@@ -1,109 +1,92 @@
1
- /**
2
- * Layer 3: Framework Adapters
3
- * Serverless integration for Next.js (App Router & API Routes).
4
- */
5
- import { AAMPPublisher } from './publisher';
6
- import { AccessPolicy, ContentOrigin, SignedAccessRequest } from './types';
7
- import { generateKeyPair } from './crypto';
8
- import { HEADERS } from './constants';
9
-
10
- type NextRequest = any;
11
- type NextResponse = any;
12
-
13
- const createJsonResponse = (body: any, status = 200) => {
14
- return new Response(JSON.stringify(body), {
15
- status,
16
- headers: { 'Content-Type': 'application/json' }
17
- });
18
- };
19
-
20
- export interface AAMPConfig {
21
- policy: Omit<AccessPolicy, 'version'>;
22
- meta: {
23
- origin: keyof typeof ContentOrigin;
24
- paymentPointer?: string;
25
- };
26
- }
27
-
28
- export class AAMPNext {
29
- private publisher: AAMPPublisher;
30
- private origin: ContentOrigin;
31
- private ready: Promise<void>;
32
-
33
- private constructor(config: AAMPConfig) {
34
- this.publisher = new AAMPPublisher({
35
- version: '1.1',
36
- ...config.policy
37
- } as AccessPolicy);
38
- this.origin = ContentOrigin[config.meta.origin];
39
-
40
- this.ready = generateKeyPair().then(keys => this.publisher.initialize(keys));
41
- }
42
-
43
- static init(config: AAMPConfig): AAMPNext {
44
- return new AAMPNext(config);
45
- }
46
-
47
- /**
48
- * Serverless Route Wrapper
49
- */
50
- withProtection(handler: (req: NextRequest) => Promise<NextResponse>) {
51
- return async (req: NextRequest) => {
52
- await this.ready;
53
-
54
- // 1. Active Verification
55
- const payloadHeader = req.headers.get(HEADERS.PAYLOAD);
56
- const sigHeader = req.headers.get(HEADERS.SIGNATURE);
57
- const keyHeader = req.headers.get(HEADERS.PUBLIC_KEY);
58
-
59
- if (payloadHeader && sigHeader && keyHeader) {
60
- try {
61
- const headerJson = atob(payloadHeader); // RAW STRING
62
- const requestHeader = JSON.parse(headerJson);
63
-
64
- const signedRequest: SignedAccessRequest = {
65
- header: requestHeader,
66
- signature: sigHeader,
67
- publicKey: keyHeader
68
- };
69
-
70
- const agentKey = await crypto.subtle.importKey(
71
- "spki",
72
- new Uint8Array(atob(keyHeader).split('').map(c => c.charCodeAt(0))),
73
- { name: "ECDSA", namedCurve: "P-256" },
74
- true,
75
- ["verify"]
76
- );
77
-
78
- // Pass raw headerJson to ensure signature matches exactly what was signed
79
- const result = await this.publisher.verifyRequest(signedRequest, agentKey, headerJson);
80
-
81
- if (!result.allowed) {
82
- return createJsonResponse({ error: result.reason }, 403);
83
- }
84
- } catch (e) {
85
- return createJsonResponse({ error: "Invalid AAMP Signature" }, 400);
86
- }
87
- }
88
-
89
- // 2. Execute Handler
90
- const response = await handler(req);
91
-
92
- // 3. Inject Provenance Headers (Passive)
93
- const aampHeaders = await this.publisher.generateResponseHeaders(this.origin);
94
- if (response && response.headers) {
95
- Object.entries(aampHeaders).forEach(([k, v]) => {
96
- response.headers.set(k, v);
97
- });
98
- }
99
-
100
- return response;
101
- };
102
- }
103
-
104
- discoveryHandler() {
105
- return async () => {
106
- return createJsonResponse(this.publisher.getPolicy());
107
- };
108
- }
1
+ /**
2
+ * Layer 3: Framework Adapters
3
+ * Serverless integration for Next.js (App Router & API Routes).
4
+ */
5
+ import { AAMPPublisher } from './publisher';
6
+ import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types';
7
+ import { generateKeyPair } from './crypto';
8
+
9
+ type NextRequest = any;
10
+ type NextResponse = any;
11
+
12
+ const createJsonResponse = (body: any, status = 200) => {
13
+ return new Response(JSON.stringify(body), {
14
+ status,
15
+ headers: { 'Content-Type': 'application/json' }
16
+ });
17
+ };
18
+
19
+ export interface AAMPConfig {
20
+ policy: Omit<AccessPolicy, 'version'>;
21
+ meta: {
22
+ origin: keyof typeof ContentOrigin;
23
+ paymentPointer?: string;
24
+ };
25
+ strategy?: UnauthenticatedStrategy;
26
+ // Optional: Provide a KV/Redis adapter here for production
27
+ cache?: IdentityCache;
28
+ }
29
+
30
+ export class AAMPNext {
31
+ private publisher: AAMPPublisher;
32
+ private origin: ContentOrigin;
33
+ private ready: Promise<void>;
34
+
35
+ private constructor(config: AAMPConfig) {
36
+ this.publisher = new AAMPPublisher(
37
+ { version: '1.1', ...config.policy } as AccessPolicy,
38
+ config.strategy || 'PASSIVE',
39
+ config.cache
40
+ );
41
+ this.origin = ContentOrigin[config.meta.origin];
42
+ this.ready = generateKeyPair().then(keys => this.publisher.initialize(keys));
43
+ }
44
+
45
+ static init(config: AAMPConfig): AAMPNext {
46
+ return new AAMPNext(config);
47
+ }
48
+
49
+ /**
50
+ * Serverless Route Wrapper
51
+ */
52
+ withProtection(handler: (req: NextRequest) => Promise<NextResponse>) {
53
+ return async (req: NextRequest) => {
54
+ await this.ready;
55
+
56
+ // Extract Headers map
57
+ const headers: Record<string, string> = {};
58
+ req.headers.forEach((value: string, key: string) => {
59
+ headers[key.toLowerCase()] = value;
60
+ });
61
+
62
+ // Evaluate
63
+ const result = await this.publisher.evaluateVisitor(headers, headers['x-aamp-payload']);
64
+
65
+ if (!result.allowed) {
66
+ return createJsonResponse({
67
+ error: result.reason,
68
+ visitor_type: result.visitorType
69
+ }, result.status);
70
+ }
71
+
72
+ // Execute Handler
73
+ const response = await handler(req);
74
+
75
+ // Inject Provenance
76
+ const aampHeaders = await this.publisher.generateResponseHeaders(this.origin);
77
+ if (response && response.headers) {
78
+ Object.entries(aampHeaders).forEach(([k, v]) => {
79
+ response.headers.set(k, v);
80
+ });
81
+ }
82
+
83
+ return response;
84
+ };
85
+ }
86
+
87
+ discoveryHandler() {
88
+ return async () => {
89
+ return createJsonResponse(this.publisher.getPolicy());
90
+ };
91
+ }
109
92
  }