javascript-solid-server 0.0.120 → 0.0.122
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/package.json +1 -1
- package/scripts/demo-402.sh +56 -0
- package/src/wac/checker.js +21 -2
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Demo: HTTP 402 Payment-Gated Article
|
|
3
|
+
# Requires: JSS running on localhost:3000 with --pay --pay-cost 10
|
|
4
|
+
|
|
5
|
+
BASE="http://localhost:3000"
|
|
6
|
+
|
|
7
|
+
echo "=== Setting up payment-gated article demo ==="
|
|
8
|
+
|
|
9
|
+
# 1. Create the premium article
|
|
10
|
+
echo "Creating premium article..."
|
|
11
|
+
curl -s -X PUT "$BASE/premium/article.jsonld" \
|
|
12
|
+
-H "Content-Type: application/ld+json" \
|
|
13
|
+
-d '{
|
|
14
|
+
"@context": "https://schema.org",
|
|
15
|
+
"@type": "Article",
|
|
16
|
+
"headline": "The Future of Web Payments",
|
|
17
|
+
"author": "Melvin Carvalho",
|
|
18
|
+
"datePublished": "2026-03-26",
|
|
19
|
+
"articleBody": "This premium article explains how HTTP 402 enables native web payments. The decentralised web finally has a business model. No Stripe. No PayPal. No app store taking 30%. Just HTTP status codes and Lightning invoices."
|
|
20
|
+
}'
|
|
21
|
+
|
|
22
|
+
# 2. Create the ACL with PaymentCondition
|
|
23
|
+
echo "Creating payment-gated ACL..."
|
|
24
|
+
curl -s -X PUT "$BASE/premium/article.jsonld.acl" \
|
|
25
|
+
-H "Content-Type: application/ld+json" \
|
|
26
|
+
-d '{
|
|
27
|
+
"@context": {
|
|
28
|
+
"acl": "http://www.w3.org/ns/auth/acl#",
|
|
29
|
+
"foaf": "http://xmlns.com/foaf/0.1/"
|
|
30
|
+
},
|
|
31
|
+
"@graph": [{
|
|
32
|
+
"@id": "#paid",
|
|
33
|
+
"@type": "acl:Authorization",
|
|
34
|
+
"acl:agentClass": { "@id": "acl:AuthenticatedAgent" },
|
|
35
|
+
"acl:accessTo": { "@id": "'$BASE'/premium/article.jsonld" },
|
|
36
|
+
"acl:mode": [{ "@id": "acl:Read" }],
|
|
37
|
+
"acl:condition": {
|
|
38
|
+
"@type": "PaymentCondition",
|
|
39
|
+
"amount": "10",
|
|
40
|
+
"currency": "sats",
|
|
41
|
+
"chain": "tbtc4"
|
|
42
|
+
}
|
|
43
|
+
}]
|
|
44
|
+
}'
|
|
45
|
+
|
|
46
|
+
echo ""
|
|
47
|
+
echo "=== Demo ready ==="
|
|
48
|
+
echo ""
|
|
49
|
+
echo "1. Try accessing the article (should get 402):"
|
|
50
|
+
echo " curl $BASE/premium/article.jsonld"
|
|
51
|
+
echo ""
|
|
52
|
+
echo "2. Deposit testnet4 sats:"
|
|
53
|
+
echo " curl -X POST -H 'Authorization: Nostr <nip98-token>' $BASE/pay/.deposit -d 'txo:tbtc4:txid:vout'"
|
|
54
|
+
echo ""
|
|
55
|
+
echo "3. Try again (should get 200 + article):"
|
|
56
|
+
echo " curl -H 'Authorization: Nostr <nip98-token>' $BASE/premium/article.jsonld"
|
package/src/wac/checker.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import * as storage from '../storage/filesystem.js';
|
|
7
7
|
import { parseAcl, AccessMode, AgentClass } from './parser.js';
|
|
8
8
|
import { getAclUrl } from '../ldp/headers.js';
|
|
9
|
+
import { readLedger, getBalance, debit } from '../webledger.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Check if agent has required access mode for resource
|
|
@@ -37,7 +38,7 @@ export async function checkAccess({
|
|
|
37
38
|
|
|
38
39
|
// Check authorizations
|
|
39
40
|
// Note: For default ACLs, we check if the ACL's default rules apply to the actual resource URL
|
|
40
|
-
const result = checkAuthorizations(
|
|
41
|
+
const result = await checkAuthorizations(
|
|
41
42
|
authorizations,
|
|
42
43
|
resourceUrl, // Use actual resource URL, not the ACL container URL
|
|
43
44
|
agentWebId,
|
|
@@ -128,7 +129,7 @@ function getParentPath(path) {
|
|
|
128
129
|
// Supported condition types
|
|
129
130
|
const SUPPORTED_CONDITIONS = ['PaymentCondition', 'https://webacl.org/ns#PaymentCondition'];
|
|
130
131
|
|
|
131
|
-
function checkAuthorizations(authorizations, targetUrl, agentWebId, requiredMode, isDefault) {
|
|
132
|
+
async function checkAuthorizations(authorizations, targetUrl, agentWebId, requiredMode, isDefault) {
|
|
132
133
|
for (const auth of authorizations) {
|
|
133
134
|
// For default ACLs, check if auth has default rules and matches target
|
|
134
135
|
// For direct ACLs, check if accessTo matches target
|
|
@@ -162,6 +163,24 @@ function checkAuthorizations(authorizations, targetUrl, agentWebId, requiredMode
|
|
|
162
163
|
c.type === 'PaymentCondition' || c.type === 'https://webacl.org/ns#PaymentCondition'
|
|
163
164
|
);
|
|
164
165
|
if (paymentCondition) {
|
|
166
|
+
// Check if agent has sufficient balance
|
|
167
|
+
const cost = parseInt(paymentCondition.amount, 10) || 0;
|
|
168
|
+
const currency = paymentCondition.currency || 'sat';
|
|
169
|
+
if (agentWebId && cost > 0) {
|
|
170
|
+
try {
|
|
171
|
+
const ledger = await readLedger();
|
|
172
|
+
const balance = getBalance(ledger, agentWebId);
|
|
173
|
+
if (balance >= cost) {
|
|
174
|
+
// Deduct and grant access
|
|
175
|
+
debit(ledger, agentWebId, cost, currency === 'sats' ? 'sat' : currency);
|
|
176
|
+
const { writeLedger } = await import('../webledger.js');
|
|
177
|
+
await writeLedger(ledger);
|
|
178
|
+
return { allowed: true, paid: cost };
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
// Ledger read failed — fall through to payment required
|
|
182
|
+
}
|
|
183
|
+
}
|
|
165
184
|
return { allowed: false, paymentRequired: paymentCondition };
|
|
166
185
|
}
|
|
167
186
|
}
|