@paywalls-net/filter 1.1.1 → 1.2.1
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 -6
- package/package.json +1 -1
- package/src/index.js +61 -9
- package/src/user-agent-classification.js +12 -5
package/README.md
CHANGED
|
@@ -9,20 +9,26 @@ npm install @paywalls-net/filter
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Environment Variables
|
|
12
|
-
- `PAYWALLS_PUBLISHER_ID`: The unique identifier for the publisher using
|
|
13
|
-
- `PAYWALLS_CLOUD_API_HOST`: The host for the
|
|
14
|
-
- `PAYWALLS_CLOUD_API_KEY`: The API key for accessing
|
|
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
|
+
- `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
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
```javascript
|
|
18
18
|
import { init } from '@paywalls-net/filter';
|
|
19
19
|
|
|
20
|
+
// Initialize the paywalls.net handler for Cloudflare
|
|
20
21
|
const handleRequest = await init('cloudflare');
|
|
21
22
|
|
|
22
23
|
export default {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
async fetch(request, env, ctx) {
|
|
25
|
+
let pw_response = await handleRequest(request, env, ctx);
|
|
26
|
+
if (pw_response) {
|
|
27
|
+
// If the handler returns a response, return it
|
|
28
|
+
return pw_response;
|
|
29
|
+
}
|
|
30
|
+
return fetch(request); // Proceed to origin/CDN
|
|
31
|
+
}
|
|
26
32
|
};
|
|
27
33
|
```
|
|
28
34
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -140,7 +140,7 @@ async function isRecognizedBot(cfg, request) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
|
|
143
|
-
function
|
|
143
|
+
function setHeaders(authz) {
|
|
144
144
|
let headers = {
|
|
145
145
|
"Content-Type": "text/html",
|
|
146
146
|
};
|
|
@@ -157,6 +157,38 @@ function sendResponse(authz) {
|
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
function setCloudFrontHeaders(authz) {
|
|
161
|
+
const headers = {};
|
|
162
|
+
|
|
163
|
+
if (authz.response?.headers) {
|
|
164
|
+
for (const [key, value] of Object.entries(authz.response.headers)) {
|
|
165
|
+
headers[key.toLowerCase()] = [
|
|
166
|
+
{
|
|
167
|
+
key: key,
|
|
168
|
+
value: value
|
|
169
|
+
}
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Add default Content-Type header if not already set
|
|
175
|
+
if (!headers["content-type"]) {
|
|
176
|
+
headers["content-type"] = [
|
|
177
|
+
{
|
|
178
|
+
key: "Content-Type",
|
|
179
|
+
value: "text/html"
|
|
180
|
+
}
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
status: authz.response?.code || 402,
|
|
186
|
+
statusDescription: authz.response?.code === 402 ? "Payment Required" : "Error",
|
|
187
|
+
headers: headers,
|
|
188
|
+
body: authz.response?.html || "Payment required."
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
160
192
|
/**
|
|
161
193
|
* Detect AI Bot and authorize it using paywalls.net.
|
|
162
194
|
* @param {Request} request
|
|
@@ -174,19 +206,17 @@ async function cloudflare(config = null) {
|
|
|
174
206
|
};
|
|
175
207
|
await loadAgentPatterns(paywallsConfig);
|
|
176
208
|
|
|
177
|
-
if (isRecognizedBot(paywallsConfig, request)) {
|
|
209
|
+
if (await isRecognizedBot(paywallsConfig, request)) {
|
|
178
210
|
const authz = await checkAgentStatus(paywallsConfig, request);
|
|
179
211
|
|
|
180
212
|
ctx.waitUntil(logAccess(paywallsConfig, request, authz));
|
|
181
213
|
|
|
182
214
|
if (authz.access === 'deny') {
|
|
183
|
-
return
|
|
215
|
+
return setHeaders(authz);
|
|
184
216
|
} else {
|
|
185
|
-
console.log("Bot-like request allowed. Proceeding to origin/CDN.");
|
|
217
|
+
// console.log("Bot-like request allowed. Proceeding to origin/CDN.");
|
|
186
218
|
}
|
|
187
219
|
}
|
|
188
|
-
|
|
189
|
-
return fetch(request); // Proceed to origin/CDN
|
|
190
220
|
};
|
|
191
221
|
}
|
|
192
222
|
|
|
@@ -200,17 +230,37 @@ async function fastly(config) {
|
|
|
200
230
|
await loadAgentPatterns(paywallsConfig);
|
|
201
231
|
|
|
202
232
|
return async function handle(request) {
|
|
203
|
-
if (isRecognizedBot(paywallsConfig,request)) {
|
|
233
|
+
if (await isRecognizedBot(paywallsConfig, request)) {
|
|
234
|
+
const authz = await checkAgentStatus(paywallsConfig, request);
|
|
235
|
+
|
|
236
|
+
await logAccess(paywallsConfig, request, authz);
|
|
237
|
+
|
|
238
|
+
if (authz.access === 'deny') {
|
|
239
|
+
return setHeaders(authz);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function cloudfront(config) {
|
|
246
|
+
const paywallsConfig = {
|
|
247
|
+
paywallsAPIHost: config.get('PAYWALLS_CLOUD_API_HOST'),
|
|
248
|
+
paywallsAPIKey: config.get('PAYWALLS_API_KEY'),
|
|
249
|
+
paywallsPublisherId: config.get('PAYWALLS_PUBLISHER_ID')
|
|
250
|
+
};
|
|
251
|
+
await loadAgentPatterns(paywallsConfig);
|
|
252
|
+
|
|
253
|
+
return async function handle(request) {
|
|
254
|
+
if (await isRecognizedBot(paywallsConfig, request)) {
|
|
204
255
|
const authz = await checkAgentStatus(paywallsConfig, request);
|
|
205
256
|
|
|
206
257
|
await logAccess(paywallsConfig, request, authz);
|
|
207
258
|
|
|
208
259
|
if (authz.access === 'deny') {
|
|
209
|
-
return
|
|
260
|
+
return setCloudFrontHeaders(authz);
|
|
210
261
|
}
|
|
211
262
|
}
|
|
212
263
|
|
|
213
|
-
return fetch(request, { backend: 'origin' });
|
|
214
264
|
};
|
|
215
265
|
}
|
|
216
266
|
|
|
@@ -228,6 +278,8 @@ export async function init(cdn, config = {}) {
|
|
|
228
278
|
return await cloudflare(config);
|
|
229
279
|
case 'fastly':
|
|
230
280
|
return await fastly(config);
|
|
281
|
+
case 'cloudfront':
|
|
282
|
+
return await cloudfront(config);
|
|
231
283
|
default:
|
|
232
284
|
throw new Error(`Unsupported CDN: ${cdn}`);
|
|
233
285
|
}
|
|
@@ -27,15 +27,22 @@ export async function loadAgentPatterns(cfg) {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
if (!response.ok) {
|
|
30
|
-
throw new Error(`Failed to fetch
|
|
30
|
+
throw new Error(`Failed to fetch agent patterns: ${response.status} ${response.statusText}`);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
const serializedPatterns = await response.json();
|
|
34
|
+
|
|
35
|
+
// Deserialize RegExp strings back into RegExp objects
|
|
36
|
+
cachedUserAgentPatterns = serializedPatterns.map((pattern) => ({
|
|
37
|
+
...pattern,
|
|
38
|
+
patterns: pattern.patterns.map((regexString) => new RegExp(regexString.slice(1, -1))) // Remove leading and trailing slashes
|
|
39
|
+
}));
|
|
40
|
+
|
|
34
41
|
cacheTimestamp = now;
|
|
35
42
|
return cachedUserAgentPatterns;
|
|
36
43
|
} catch (error) {
|
|
37
|
-
console.error('
|
|
38
|
-
throw
|
|
44
|
+
console.error('Error loading agent patterns:', error);
|
|
45
|
+
throw error;
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
48
|
|
|
@@ -56,7 +63,7 @@ export async function classifyUserAgent(cfg, userAgent) {
|
|
|
56
63
|
for (const config of userAgentPatterns) {
|
|
57
64
|
if (!config.patterns) continue;
|
|
58
65
|
for (const pattern of config.patterns) {
|
|
59
|
-
if (new RegExp(pattern).test(userAgent)) {
|
|
66
|
+
if (new RegExp(pattern).test(userAgent)) {
|
|
60
67
|
return {
|
|
61
68
|
operator: config.operator,
|
|
62
69
|
agent: config.agent || browser,
|