javascript-solid-server 0.0.123 → 0.0.125

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.
@@ -333,7 +333,9 @@
333
333
  "Bash(npm init:*)",
334
334
  "WebFetch(domain:webtorrent.io)",
335
335
  "Bash(TORRENT=/home/melvin/.claude/projects/-home-melvin-remote-github-com-JavaScriptSolidServer-JavaScriptSolidServer/a05da419-92b7-4056-93b8-e97b2035d4ae/tool-results/webfetch-1774004425803-fce7mx.bin npx -y parse-torrent $TORRENT)",
336
- "Bash(gh issue:*)"
336
+ "Bash(gh issue:*)",
337
+ "Bash(sed -i 's|Settings/|settings/|g' src/server.js src/handlers/container.js src/webid/profile.js)",
338
+ "Bash(sed -i 's|// Settings folder|// settings folder|' src/handlers/container.js)"
337
339
  ]
338
340
  }
339
341
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.123",
3
+ "version": "0.0.125",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -165,12 +165,12 @@ export async function createPodStructure(name, webId, podUri, issuer, defaultQuo
165
165
  const podPath = `/${name}/`;
166
166
 
167
167
  // Create pod directory structure
168
- // Uses 'Settings' (capital S) for mashlib compatibility
168
+ // Pod settings directory
169
169
  await storage.createContainer(podPath);
170
170
  await storage.createContainer(`${podPath}inbox/`);
171
171
  await storage.createContainer(`${podPath}public/`);
172
172
  await storage.createContainer(`${podPath}private/`);
173
- await storage.createContainer(`${podPath}Settings/`);
173
+ await storage.createContainer(`${podPath}settings/`);
174
174
  await storage.createContainer(`${podPath}profile/`);
175
175
 
176
176
  // Generate and write WebID profile at /profile/card (standard Solid location)
@@ -179,14 +179,14 @@ export async function createPodStructure(name, webId, podUri, issuer, defaultQuo
179
179
 
180
180
  // Generate and write preferences (mashlib-compatible paths)
181
181
  const prefs = generatePreferences({ webId, podUri });
182
- await storage.write(`${podPath}Settings/Preferences.ttl`, serialize(prefs));
182
+ await storage.write(`${podPath}settings/Preferences.ttl`, serialize(prefs));
183
183
 
184
184
  // Generate and write type indexes with .ttl extension for mashlib
185
- const publicTypeIndex = generateTypeIndex(`${podUri}Settings/publicTypeIndex.ttl`);
186
- await storage.write(`${podPath}Settings/publicTypeIndex.ttl`, serialize(publicTypeIndex));
185
+ const publicTypeIndex = generateTypeIndex(`${podUri}settings/publicTypeIndex.ttl`);
186
+ await storage.write(`${podPath}settings/publicTypeIndex.ttl`, serialize(publicTypeIndex));
187
187
 
188
- const privateTypeIndex = generateTypeIndex(`${podUri}Settings/privateTypeIndex.ttl`);
189
- await storage.write(`${podPath}Settings/privateTypeIndex.ttl`, serialize(privateTypeIndex));
188
+ const privateTypeIndex = generateTypeIndex(`${podUri}settings/privateTypeIndex.ttl`);
189
+ await storage.write(`${podPath}settings/privateTypeIndex.ttl`, serialize(privateTypeIndex));
190
190
 
191
191
  // Create default ACL files
192
192
  // Pod root: owner full control, public read
@@ -197,9 +197,9 @@ export async function createPodStructure(name, webId, podUri, issuer, defaultQuo
197
197
  const privateAcl = generatePrivateAcl(`${podUri}private/`, webId);
198
198
  await storage.write(`${podPath}private/.acl`, serializeAcl(privateAcl));
199
199
 
200
- // Settings folder: owner only
201
- const settingsAcl = generatePrivateAcl(`${podUri}Settings/`, webId);
202
- await storage.write(`${podPath}Settings/.acl`, serializeAcl(settingsAcl));
200
+ // settings folder: owner only
201
+ const settingsAcl = generatePrivateAcl(`${podUri}settings/`, webId);
202
+ await storage.write(`${podPath}settings/.acl`, serializeAcl(settingsAcl));
203
203
 
204
204
  // Inbox: owner full, public append
205
205
  const inboxAcl = generateInboxAcl(`${podUri}inbox/`, webId);
package/src/server.js CHANGED
@@ -571,7 +571,7 @@ export function createServer(options = {}) {
571
571
  await storage.createContainer('/inbox/');
572
572
  await storage.createContainer('/public/');
573
573
  await storage.createContainer('/private/');
574
- await storage.createContainer('/Settings/');
574
+ await storage.createContainer('/settings/');
575
575
  await storage.createContainer('/profile/');
576
576
 
577
577
  // Generate profile
@@ -580,13 +580,13 @@ export function createServer(options = {}) {
580
580
 
581
581
  // Preferences and type indexes
582
582
  const prefs = generatePreferences({ webId, podUri });
583
- await storage.write('/Settings/Preferences.ttl', serialize(prefs));
583
+ await storage.write('/settings/Preferences.ttl', serialize(prefs));
584
584
 
585
- const publicTypeIndex = generateTypeIndex(`${podUri}Settings/publicTypeIndex.ttl`);
586
- await storage.write('/Settings/publicTypeIndex.ttl', serialize(publicTypeIndex));
585
+ const publicTypeIndex = generateTypeIndex(`${podUri}settings/publicTypeIndex.ttl`);
586
+ await storage.write('/settings/publicTypeIndex.ttl', serialize(publicTypeIndex));
587
587
 
588
- const privateTypeIndex = generateTypeIndex(`${podUri}Settings/privateTypeIndex.ttl`);
589
- await storage.write('/Settings/privateTypeIndex.ttl', serialize(privateTypeIndex));
588
+ const privateTypeIndex = generateTypeIndex(`${podUri}settings/privateTypeIndex.ttl`);
589
+ await storage.write('/settings/privateTypeIndex.ttl', serialize(privateTypeIndex));
590
590
 
591
591
  // ACL files
592
592
  const rootAcl = generateOwnerAcl(podUri, webId, true);
@@ -595,8 +595,8 @@ export function createServer(options = {}) {
595
595
  const privateAcl = generatePrivateAcl(`${podUri}private/`, webId);
596
596
  await storage.write('/private/.acl', serializeAcl(privateAcl));
597
597
 
598
- const settingsAcl = generatePrivateAcl(`${podUri}Settings/`, webId);
599
- await storage.write('/Settings/.acl', serializeAcl(settingsAcl));
598
+ const settingsAcl = generatePrivateAcl(`${podUri}settings/`, webId);
599
+ await storage.write('/settings/.acl', serializeAcl(settingsAcl));
600
600
 
601
601
  const inboxAcl = generateInboxAcl(`${podUri}inbox/`, webId);
602
602
  await storage.write('/inbox/.acl', serializeAcl(inboxAcl));
@@ -163,15 +163,26 @@ async function checkAuthorizations(authorizations, targetUrl, agentWebId, requir
163
163
  c.type === 'PaymentCondition' || c.type === 'https://webacl.org/ns#PaymentCondition'
164
164
  );
165
165
  if (paymentCondition) {
166
- // Check if agent has sufficient balance
167
- const cost = parseInt(paymentCondition.amount, 10) || 0;
166
+ const parsed = parseInt(paymentCondition.amount, 10);
167
+ const cost = Number.isNaN(parsed) ? -1 : parsed;
168
168
  const currency = paymentCondition.currency || 'sat';
169
- if (agentWebId && cost > 0) {
169
+
170
+ // Skip invalid amounts
171
+ if (cost < 0) continue;
172
+
173
+ if (agentWebId) {
170
174
  try {
171
175
  const ledger = await readLedger();
176
+
177
+ // Zero-cost gate: verify they have a ledger entry (have deposited at some point)
178
+ if (cost === 0) {
179
+ const hasEntry = ledger.entries?.some(e => e.url === agentWebId);
180
+ if (hasEntry) return { allowed: true };
181
+ }
182
+
183
+ // Paid access: check balance and deduct
172
184
  const balance = getBalance(ledger, agentWebId);
173
- if (balance >= cost) {
174
- // Deduct and grant access
185
+ if (cost > 0 && balance >= cost) {
175
186
  debit(ledger, agentWebId, cost, currency === 'sats' ? 'sat' : currency);
176
187
  const { writeLedger } = await import('../webledger.js');
177
188
  await writeLedger(ledger);
@@ -45,9 +45,9 @@ export function generateProfileJsonLd({ webId, name, podUri, issuer }) {
45
45
  'inbox': `${pod}inbox/`,
46
46
  'storage': pod,
47
47
  'oidcIssuer': issuer,
48
- 'preferencesFile': `${pod}Settings/Preferences.ttl`,
49
- 'publicTypeIndex': `${pod}Settings/publicTypeIndex.ttl`,
50
- 'privateTypeIndex': `${pod}Settings/privateTypeIndex.ttl`
48
+ 'preferencesFile': `${pod}settings/Preferences.ttl`,
49
+ 'publicTypeIndex': `${pod}settings/publicTypeIndex.ttl`,
50
+ 'privateTypeIndex': `${pod}settings/privateTypeIndex.ttl`
51
51
  };
52
52
  }
53
53
 
@@ -133,7 +133,7 @@ function escapeHtml(str) {
133
133
 
134
134
  /**
135
135
  * Generate preferences file as JSON-LD
136
- * Uses mashlib-compatible paths (Settings/Preferences.ttl)
136
+ * Uses mashlib-compatible paths (settings/Preferences.ttl)
137
137
  * @param {object} options
138
138
  * @param {string} options.webId - Full WebID URI
139
139
  * @param {string} options.podUri - Pod root URI
@@ -149,9 +149,9 @@ export function generatePreferences({ webId, podUri }) {
149
149
  'publicTypeIndex': { '@id': 'solid:publicTypeIndex', '@type': '@id' },
150
150
  'privateTypeIndex': { '@id': 'solid:privateTypeIndex', '@type': '@id' }
151
151
  },
152
- '@id': `${pod}Settings/Preferences.ttl`,
153
- 'publicTypeIndex': `${pod}Settings/publicTypeIndex.ttl`,
154
- 'privateTypeIndex': `${pod}Settings/privateTypeIndex.ttl`
152
+ '@id': `${pod}settings/Preferences.ttl`,
153
+ 'publicTypeIndex': `${pod}settings/publicTypeIndex.ttl`,
154
+ 'privateTypeIndex': `${pod}settings/privateTypeIndex.ttl`
155
155
  };
156
156
  }
157
157
 
package/test/pod.test.js CHANGED
@@ -96,7 +96,7 @@ describe('Pod Lifecycle', () => {
96
96
  assertStatus(priv, 200);
97
97
 
98
98
  // Check Settings exists (needs auth)
99
- const settings = await request('/carol/Settings/', { auth: 'carol' });
99
+ const settings = await request('/carol/settings/', { auth: 'carol' });
100
100
  assertStatus(settings, 200);
101
101
  });
102
102
 
@@ -104,15 +104,15 @@ describe('Pod Lifecycle', () => {
104
104
  await createTestPod('dan');
105
105
 
106
106
  // Check Preferences.ttl (needs auth - Settings is private)
107
- const prefs = await request('/dan/Settings/Preferences.ttl', { auth: 'dan' });
107
+ const prefs = await request('/dan/settings/Preferences.ttl', { auth: 'dan' });
108
108
  assertStatus(prefs, 200);
109
109
 
110
110
  // Check public type index (needs auth)
111
- const pubIndex = await request('/dan/Settings/publicTypeIndex.ttl', { auth: 'dan' });
111
+ const pubIndex = await request('/dan/settings/publicTypeIndex.ttl', { auth: 'dan' });
112
112
  assertStatus(pubIndex, 200);
113
113
 
114
114
  // Check private type index (needs auth)
115
- const privIndex = await request('/dan/Settings/privateTypeIndex.ttl', { auth: 'dan' });
115
+ const privIndex = await request('/dan/settings/privateTypeIndex.ttl', { auth: 'dan' });
116
116
  assertStatus(privIndex, 200);
117
117
  });
118
118
  });
package/test/wac.test.js CHANGED
@@ -428,6 +428,30 @@ describe('WAC Conditions', () => {
428
428
  assert.strictEqual(auths[0].conditions[0].type, 'UnknownFutureCondition');
429
429
  });
430
430
 
431
+ it('should parse zero-cost PaymentCondition', async () => {
432
+ const acl = {
433
+ '@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
434
+ '@graph': [{
435
+ '@id': '#gate',
436
+ '@type': 'acl:Authorization',
437
+ 'acl:agentClass': { '@id': 'acl:AuthenticatedAgent' },
438
+ 'acl:accessTo': { '@id': 'https://alice.example/members/' },
439
+ 'acl:mode': [{ '@id': 'acl:Read' }],
440
+ 'acl:condition': {
441
+ '@type': 'PaymentCondition',
442
+ 'amount': '0',
443
+ 'currency': 'sats'
444
+ }
445
+ }]
446
+ };
447
+
448
+ const auths = await parseAcl(JSON.stringify(acl), 'https://alice.example/members/.acl');
449
+ const condition = auths[0].conditions[0];
450
+
451
+ assert.strictEqual(condition.type, 'PaymentCondition');
452
+ assert.strictEqual(condition.amount, '0');
453
+ });
454
+
431
455
  it('should parse PaymentCondition with all fields', async () => {
432
456
  const acl = {
433
457
  '@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },