@paywalls-net/filter 1.2.0 → 1.2.2
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/README.md +12 -5
- package/package.json +1 -1
- package/src/index.js +71 -16
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# paywalls-net/filter
|
|
2
2
|
|
|
3
|
-
SDK for
|
|
3
|
+
SDK for paywalls.net authorization and real-time licensing services. For use with CDN or edge environments.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -10,19 +10,26 @@ npm install @paywalls-net/filter
|
|
|
10
10
|
|
|
11
11
|
## Environment Variables
|
|
12
12
|
- `PAYWALLS_PUBLISHER_ID`: The unique identifier for the publisher using paywalls.net services.
|
|
13
|
-
- `PAYWALLS_CLOUD_API_HOST`: The host for the paywalls.net API. eg `https://cloud-api.paywalls.net`.
|
|
14
13
|
- `PAYWALLS_CLOUD_API_KEY`: The API key for accessing paywalls.net services. NOTE: This key should be treated like a password and kept secret and stored in a secure secrets vault or environment variable.
|
|
15
14
|
|
|
16
15
|
## Usage
|
|
16
|
+
The following is an example of using the SDK with Cloudflare Workers:
|
|
17
|
+
|
|
17
18
|
```javascript
|
|
18
19
|
import { init } from '@paywalls-net/filter';
|
|
19
20
|
|
|
21
|
+
// Initialize the paywalls.net handler for Cloudflare
|
|
20
22
|
const handleRequest = await init('cloudflare');
|
|
21
23
|
|
|
22
24
|
export default {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
async fetch(request, env, ctx) {
|
|
26
|
+
let pw_response = await handleRequest(request, env, ctx);
|
|
27
|
+
if (pw_response) {
|
|
28
|
+
// If the handler returns a response, return it
|
|
29
|
+
return pw_response;
|
|
30
|
+
}
|
|
31
|
+
return fetch(request); // Proceed to origin/CDN
|
|
32
|
+
}
|
|
26
33
|
};
|
|
27
34
|
```
|
|
28
35
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -5,15 +5,26 @@
|
|
|
5
5
|
|
|
6
6
|
import { classifyUserAgent, loadAgentPatterns } from './user-agent-classification.js';
|
|
7
7
|
|
|
8
|
+
const PAYWALLS_CLOUD_API_HOST = "https://cloud-api.paywalls.net";
|
|
9
|
+
|
|
8
10
|
async function logAccess(cfg, request, access) {
|
|
9
11
|
// Separate html from the status in the access object.
|
|
10
12
|
const { response, ...status } = access;
|
|
11
13
|
|
|
12
14
|
// Get all headers as a plain object (name-value pairs)
|
|
13
15
|
let headers = {};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
if (typeof request.headers.entries === "function") {
|
|
17
|
+
// Standard Headers object (e.g., in Cloudflare Workers)
|
|
18
|
+
for (const [key, value] of request.headers.entries()) {
|
|
19
|
+
headers[key] = value;
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
// CloudFront headers object
|
|
23
|
+
for (const key in request.headers) {
|
|
24
|
+
headers[key] = request.headers[key][0]?.value || "";
|
|
25
|
+
}
|
|
16
26
|
}
|
|
27
|
+
|
|
17
28
|
const url = new URL(request.url);
|
|
18
29
|
let body = {
|
|
19
30
|
account_id: cfg.paywallsPublisherId,
|
|
@@ -124,10 +135,14 @@ function isCloudflareKnownBot(request) {
|
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
function isTestBot(request) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
try {
|
|
139
|
+
// check if the URL has a query parameter to always test as a bot
|
|
140
|
+
const url = new URL(request.url || request.uri);
|
|
141
|
+
const uaParam = url.searchParams.get("user-agent");
|
|
142
|
+
return uaParam && uaParam.includes("bot");
|
|
143
|
+
} catch (err) {
|
|
144
|
+
throw new Error(`test bot failed: ${request.url} | ${request.uri} | ${err.message}`);
|
|
145
|
+
}
|
|
131
146
|
}
|
|
132
147
|
async function isPaywallsKnownBot(cfg, request) {
|
|
133
148
|
const userAgent = request.headers.get("User-Agent");
|
|
@@ -140,6 +155,11 @@ async function isRecognizedBot(cfg, request) {
|
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Create response in format used by most CDNs
|
|
160
|
+
* @param {*} authz
|
|
161
|
+
* @returns
|
|
162
|
+
*/
|
|
143
163
|
function setHeaders(authz) {
|
|
144
164
|
let headers = {
|
|
145
165
|
"Content-Type": "text/html",
|
|
@@ -157,6 +177,11 @@ function setHeaders(authz) {
|
|
|
157
177
|
});
|
|
158
178
|
}
|
|
159
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Create CloudFront format response
|
|
182
|
+
* @param {*} authz
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
160
185
|
function setCloudFrontHeaders(authz) {
|
|
161
186
|
const headers = {};
|
|
162
187
|
|
|
@@ -200,7 +225,7 @@ async function cloudflare(config = null) {
|
|
|
200
225
|
|
|
201
226
|
return async function handle(request, env, ctx) {
|
|
202
227
|
const paywallsConfig = {
|
|
203
|
-
paywallsAPIHost: env.PAYWALLS_CLOUD_API_HOST,
|
|
228
|
+
paywallsAPIHost: env.PAYWALLS_CLOUD_API_HOST || PAYWALLS_CLOUD_API_HOST,
|
|
204
229
|
paywallsAPIKey: env.PAYWALLS_CLOUD_API_KEY,
|
|
205
230
|
paywallsPublisherId: env.PAYWALLS_PUBLISHER_ID
|
|
206
231
|
};
|
|
@@ -214,7 +239,7 @@ async function cloudflare(config = null) {
|
|
|
214
239
|
if (authz.access === 'deny') {
|
|
215
240
|
return setHeaders(authz);
|
|
216
241
|
} else {
|
|
217
|
-
console.log("Bot-like request allowed. Proceeding to origin/CDN.");
|
|
242
|
+
// console.log("Bot-like request allowed. Proceeding to origin/CDN.");
|
|
218
243
|
}
|
|
219
244
|
}
|
|
220
245
|
};
|
|
@@ -223,7 +248,7 @@ async function cloudflare(config = null) {
|
|
|
223
248
|
|
|
224
249
|
async function fastly(config) {
|
|
225
250
|
const paywallsConfig = {
|
|
226
|
-
paywallsAPIHost: config.get('PAYWALLS_CLOUD_API_HOST'),
|
|
251
|
+
paywallsAPIHost: config.get('PAYWALLS_CLOUD_API_HOST') || PAYWALLS_CLOUD_API_HOST,
|
|
227
252
|
paywallsAPIKey: config.get('PAYWALLS_API_KEY'),
|
|
228
253
|
paywallsPublisherId: config.get('PAYWALLS_PUBLISHER_ID')
|
|
229
254
|
};
|
|
@@ -241,34 +266,64 @@ async function fastly(config) {
|
|
|
241
266
|
}
|
|
242
267
|
};
|
|
243
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Adapt to CloudFront format
|
|
271
|
+
* Lambda@Edge events see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-request
|
|
272
|
+
* CloudFront events see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html#functions-event-structure-example
|
|
273
|
+
* @param {*} request
|
|
274
|
+
* @returns
|
|
275
|
+
*/
|
|
276
|
+
function requestShim(request) {
|
|
277
|
+
if (!request.headers.get) {
|
|
278
|
+
// add get() to headers object to adapt to CloudFront
|
|
279
|
+
request.headers.get = (name) => {
|
|
280
|
+
const header = request.headers[name.toLowerCase()];
|
|
281
|
+
return header ? header[0].value : null;
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// combine the CloudFront host, request.uri and request.querystring into request.url
|
|
286
|
+
if (!request.url && request.uri) {
|
|
287
|
+
let host = request.headers.get('host');
|
|
288
|
+
request.url = `http://${host}${request.uri}`;
|
|
289
|
+
if (request.querystring) {
|
|
290
|
+
request.url += `?${request.querystring}`;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return request;
|
|
295
|
+
}
|
|
244
296
|
|
|
245
297
|
async function cloudfront(config) {
|
|
246
298
|
const paywallsConfig = {
|
|
247
|
-
paywallsAPIHost: config.
|
|
248
|
-
paywallsAPIKey: config.
|
|
249
|
-
paywallsPublisherId: config.
|
|
299
|
+
paywallsAPIHost: config.PAYWALLS_CLOUD_API_HOST || PAYWALLS_CLOUD_API_HOST,
|
|
300
|
+
paywallsAPIKey: config.PAYWALLS_API_KEY,
|
|
301
|
+
paywallsPublisherId: config.PAYWALLS_PUBLISHER_ID
|
|
250
302
|
};
|
|
251
303
|
await loadAgentPatterns(paywallsConfig);
|
|
252
304
|
|
|
253
|
-
return async function handle(
|
|
305
|
+
return async function handle(event, ctx) {
|
|
306
|
+
let request = event.Records[0].cf.request;
|
|
307
|
+
request = requestShim(request);
|
|
254
308
|
if (await isRecognizedBot(paywallsConfig, request)) {
|
|
255
309
|
const authz = await checkAgentStatus(paywallsConfig, request);
|
|
256
310
|
|
|
257
|
-
|
|
311
|
+
// log the result asynchronously
|
|
312
|
+
ctx.callbackWaitsForEmptyEventLoop = false;
|
|
313
|
+
logAccess(paywallsConfig, request, authz);
|
|
258
314
|
|
|
259
315
|
if (authz.access === 'deny') {
|
|
260
316
|
return setCloudFrontHeaders(authz);
|
|
261
317
|
}
|
|
262
318
|
}
|
|
263
|
-
|
|
264
319
|
};
|
|
265
320
|
}
|
|
266
321
|
|
|
267
322
|
|
|
268
|
-
|
|
269
323
|
/**
|
|
270
324
|
* Initializes the appropriate handler based on the CDN.
|
|
271
325
|
* @param {string} cdn - The name of the CDN (e.g., 'cloudflare', 'fastly', 'cloudfront').
|
|
326
|
+
* @param {Object} [config={}] - Optional configuration object for the CDN handler.
|
|
272
327
|
* @returns {Function} - The handler function for the specified CDN.
|
|
273
328
|
*/
|
|
274
329
|
export async function init(cdn, config = {}) {
|