javascript-solid-server 0.0.77 → 0.0.78

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 CHANGED
@@ -6,8 +6,9 @@ A minimal, fast, JSON-LD native Solid server.
6
6
 
7
7
  ## Features
8
8
 
9
- ### Implemented (v0.0.77)
9
+ ### Implemented (v0.0.78)
10
10
 
11
+ - **Schnorr SSO** - Passwordless login via BIP-340 Schnorr signatures using NIP-07 browser extensions (Podkey, nos2x, Alby)
11
12
  - **Passkey Authentication** - WebAuthn/FIDO2 passwordless login with Touch ID, Face ID, or security keys
12
13
  - **HTTP Range Requests** - Partial content delivery for large files and media streaming
13
14
  - **Single-User Mode** - Simplified setup for personal pod servers
@@ -693,6 +694,29 @@ jss start --idp
693
694
 
694
695
  Passkeys are stored per-account and work across devices via platform sync (iCloud Keychain, Google Password Manager, etc.).
695
696
 
697
+ ### Schnorr SSO (v0.0.78+)
698
+
699
+ Sign in with your Nostr key using NIP-07 browser extensions:
700
+
701
+ ```bash
702
+ jss start --idp
703
+ ```
704
+
705
+ **How it works:**
706
+ 1. User clicks "Sign in with Schnorr" on the login page
707
+ 2. NIP-07 extension (Podkey, nos2x, Alby) signs a NIP-98 auth event
708
+ 3. Server verifies BIP-340 Schnorr signature
709
+ 4. User authenticated via linked did:nostr identity
710
+
711
+ **Requirements:**
712
+ - Account must have a `did:nostr:<pubkey>` WebID linked
713
+ - User needs a NIP-07 compatible browser extension
714
+
715
+ **Benefits:**
716
+ - No passwords - cryptographic authentication
717
+ - Works with existing Nostr identity
718
+ - Single sign-on across Solid and Nostr ecosystems
719
+
696
720
  ### Solid-OIDC (External IdP)
697
721
 
698
722
  The server also accepts DPoP-bound access tokens from external Solid identity providers:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.77",
3
+ "version": "0.0.78",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/idp/index.js CHANGED
@@ -15,6 +15,8 @@ import {
15
15
  handleRegisterPost,
16
16
  handlePasskeyComplete,
17
17
  handlePasskeySkip,
18
+ handleSchnorrLogin,
19
+ handleSchnorrComplete,
18
20
  } from './interactions.js';
19
21
  import {
20
22
  handleCredentials,
@@ -355,6 +357,22 @@ export async function idpPlugin(fastify, options) {
355
357
  return handlePasskeySkip(request, reply, provider);
356
358
  });
357
359
 
360
+ // Schnorr (NIP-98) interaction handlers
361
+ fastify.post('/idp/interaction/:uid/schnorr-login', {
362
+ config: {
363
+ rateLimit: {
364
+ max: 10,
365
+ timeWindow: '1 minute'
366
+ }
367
+ }
368
+ }, async (request, reply) => {
369
+ return handleSchnorrLogin(request, reply, provider);
370
+ });
371
+
372
+ fastify.get('/idp/interaction/:uid/schnorr-complete', async (request, reply) => {
373
+ return handleSchnorrComplete(request, reply, provider);
374
+ });
375
+
358
376
  fastify.log.info(`IdP initialized with issuer: ${issuer}`);
359
377
  }
360
378
 
@@ -3,11 +3,12 @@
3
3
  * Handles the user-facing parts of the authentication flow
4
4
  */
5
5
 
6
- import { authenticate, findById, createAccount, updateLastLogin, setPasskeyPromptDismissed } from './accounts.js';
6
+ import { authenticate, findById, findByWebId, createAccount, updateLastLogin, setPasskeyPromptDismissed } from './accounts.js';
7
7
  import { loginPage, consentPage, errorPage, registerPage, passkeyPromptPage } from './views.js';
8
8
  import * as storage from '../storage/filesystem.js';
9
9
  import { createPodStructure } from '../handlers/container.js';
10
10
  import { validateInvite } from './invites.js';
11
+ import { verifyNostrAuth } from '../auth/nostr.js';
11
12
 
12
13
  // Security: Maximum body size for IdP form submissions (1MB)
13
14
  const MAX_BODY_SIZE = 1024 * 1024;
@@ -542,3 +543,120 @@ export async function handlePasskeySkip(request, reply, provider) {
542
543
  return reply.code(500).type('text/html').send(errorPage('Error', err.message));
543
544
  }
544
545
  }
546
+
547
+ /**
548
+ * Handle POST /idp/interaction/:uid/schnorr-login
549
+ * Authenticates user via Schnorr signature (NIP-98)
550
+ */
551
+ export async function handleSchnorrLogin(request, reply, provider) {
552
+ const { uid } = request.params;
553
+
554
+ try {
555
+ const interaction = await provider.Interaction.find(uid);
556
+ if (!interaction) {
557
+ return reply.code(404).type('application/json').send({
558
+ success: false,
559
+ error: 'Session expired. Please try again.'
560
+ });
561
+ }
562
+
563
+ // Verify the Schnorr signature
564
+ const authResult = await verifyNostrAuth(request);
565
+
566
+ if (authResult.error) {
567
+ request.log.warn({ error: authResult.error }, 'Schnorr auth failed');
568
+ return reply.code(401).type('application/json').send({
569
+ success: false,
570
+ error: authResult.error
571
+ });
572
+ }
573
+
574
+ // authResult.webId is either a resolved WebID or did:nostr:pubkey
575
+ const identity = authResult.webId;
576
+ request.log.info({ identity, uid }, 'Schnorr auth verified');
577
+
578
+ // Try to find an existing account linked to this identity
579
+ let account = await findByWebId(identity);
580
+
581
+ if (!account) {
582
+ // No account linked to this did:nostr
583
+ // For now, return error - user needs to link their did:nostr to an account
584
+ // Future: could auto-create account or prompt for linking
585
+ return reply.code(403).type('application/json').send({
586
+ success: false,
587
+ error: 'No account linked to this identity. Please register or link your Schnorr key to an existing account.'
588
+ });
589
+ }
590
+
591
+ // Update last login
592
+ await updateLastLogin(account.id);
593
+
594
+ // Complete the OIDC interaction
595
+ const result = {
596
+ login: {
597
+ accountId: account.id,
598
+ remember: true,
599
+ },
600
+ };
601
+
602
+ // Save the login result
603
+ interaction.result = result;
604
+ await interaction.save(interaction.exp - Math.floor(Date.now() / 1000));
605
+
606
+ request.log.info({ accountId: account.id, identity, uid }, 'Schnorr login successful');
607
+
608
+ // Return success with redirect URL
609
+ // The client will follow this redirect
610
+ const redirectUrl = `/idp/interaction/${uid}/schnorr-complete?accountId=${encodeURIComponent(account.id)}`;
611
+
612
+ return reply.type('application/json').send({
613
+ success: true,
614
+ redirectUrl
615
+ });
616
+ } catch (err) {
617
+ request.log.error(err, 'Schnorr login error');
618
+ return reply.code(500).type('application/json').send({
619
+ success: false,
620
+ error: err.message
621
+ });
622
+ }
623
+ }
624
+
625
+ /**
626
+ * Handle GET /idp/interaction/:uid/schnorr-complete
627
+ * Completes OIDC interaction after Schnorr login
628
+ */
629
+ export async function handleSchnorrComplete(request, reply, provider) {
630
+ const { uid } = request.params;
631
+ const { accountId } = request.query;
632
+
633
+ if (!accountId) {
634
+ return reply.code(400).type('text/html').send(errorPage('Missing account', 'Account ID is required.'));
635
+ }
636
+
637
+ try {
638
+ const interaction = await provider.Interaction.find(uid);
639
+ if (!interaction) {
640
+ return reply.code(404).type('text/html').send(errorPage('Session expired', 'Please try logging in again.'));
641
+ }
642
+
643
+ // Validate accountId matches the interaction result
644
+ if (interaction.result?.login?.accountId !== accountId) {
645
+ request.log.warn({ expected: interaction.result?.login?.accountId, provided: accountId }, 'AccountId mismatch in schnorr complete');
646
+ return reply.code(403).type('text/html').send(errorPage('Access denied', 'Account mismatch.'));
647
+ }
648
+
649
+ const account = await findById(accountId);
650
+ if (!account) {
651
+ return reply.code(404).type('text/html').send(errorPage('Account not found', 'The account could not be found.'));
652
+ }
653
+
654
+ request.log.info({ accountId: account.id, uid }, 'Schnorr login completed');
655
+
656
+ reply.hijack();
657
+ return provider.interactionFinished(request.raw, reply.raw, interaction.result, { mergeWithLastSubmission: false });
658
+ } catch (err) {
659
+ request.log.error(err, 'Schnorr complete error');
660
+ return reply.code(500).type('text/html').send(errorPage('Error', err.message));
661
+ }
662
+ }
package/src/idp/views.js CHANGED
@@ -121,6 +121,23 @@ const styles = `
121
121
  width: 20px;
122
122
  height: 20px;
123
123
  }
124
+ .btn-schnorr {
125
+ background: #7b1fa2;
126
+ color: white;
127
+ width: 100%;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ gap: 8px;
132
+ margin-top: 12px;
133
+ }
134
+ .btn-schnorr:hover {
135
+ background: #6a1b9a;
136
+ }
137
+ .btn-schnorr svg {
138
+ width: 20px;
139
+ height: 20px;
140
+ }
124
141
  .divider {
125
142
  display: flex;
126
143
  align-items: center;
@@ -187,6 +204,12 @@ const passkeyIcon = `
187
204
  </svg>
188
205
  `;
189
206
 
207
+ const schnorrIcon = `
208
+ <svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
209
+ <path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
210
+ </svg>
211
+ `;
212
+
190
213
  const scopeDescriptions = {
191
214
  openid: 'Access your identity',
192
215
  webid: 'Access your WebID',
@@ -213,7 +236,7 @@ function escapeJs(text) {
213
236
  /**
214
237
  * Login page HTML
215
238
  */
216
- export function loginPage(uid, clientId, error = null, passkeyEnabled = true) {
239
+ export function loginPage(uid, clientId, error = null, passkeyEnabled = true, schnorrEnabled = true) {
217
240
  const appName = clientId || 'An application';
218
241
  const safeUid = escapeJs(uid);
219
242
 
@@ -222,7 +245,18 @@ export function loginPage(uid, clientId, error = null, passkeyEnabled = true) {
222
245
  ${passkeyIcon}
223
246
  Sign in with Passkey
224
247
  </button>
248
+ ` : '';
225
249
 
250
+ const schnorrSection = schnorrEnabled ? `
251
+ <button type="button" class="btn btn-schnorr" onclick="loginWithSchnorr()" id="schnorrBtn">
252
+ ${schnorrIcon}
253
+ Sign in with Schnorr
254
+ </button>
255
+ ` : '';
256
+
257
+ const ssoSection = (passkeyEnabled || schnorrEnabled) ? `
258
+ ${passkeySection}
259
+ ${schnorrSection}
226
260
  <div class="divider"><span>or</span></div>
227
261
  ` : '';
228
262
 
@@ -315,6 +349,73 @@ export function loginPage(uid, clientId, error = null, passkeyEnabled = true) {
315
349
  </script>
316
350
  ` : '';
317
351
 
352
+ const schnorrScript = schnorrEnabled ? `
353
+ <script>
354
+ async function loginWithSchnorr() {
355
+ const btn = document.getElementById('schnorrBtn');
356
+
357
+ // Check for NIP-07 extension (window.nostr)
358
+ if (typeof window.nostr === 'undefined') {
359
+ alert('No Schnorr signer found. Please install a NIP-07 compatible extension like Podkey, nos2x, or Alby.');
360
+ return;
361
+ }
362
+
363
+ btn.disabled = true;
364
+ btn.textContent = 'Signing...';
365
+
366
+ try {
367
+ // Get the current URL for the auth event
368
+ const authUrl = window.location.origin + '/idp/interaction/${safeUid}/schnorr-login';
369
+
370
+ // Create NIP-98 event (kind 27235)
371
+ const event = {
372
+ kind: 27235,
373
+ created_at: Math.floor(Date.now() / 1000),
374
+ tags: [
375
+ ['u', authUrl],
376
+ ['method', 'POST']
377
+ ],
378
+ content: ''
379
+ };
380
+
381
+ // Sign with NIP-07 extension
382
+ const signedEvent = await window.nostr.signEvent(event);
383
+
384
+ // Send to server
385
+ const response = await fetch(authUrl, {
386
+ method: 'POST',
387
+ headers: {
388
+ 'Authorization': 'Nostr ' + btoa(JSON.stringify(signedEvent))
389
+ }
390
+ });
391
+
392
+ const result = await response.json();
393
+
394
+ if (result.success && result.redirectUrl) {
395
+ window.location.href = result.redirectUrl;
396
+ } else if (result.error) {
397
+ alert('Schnorr login failed: ' + result.error);
398
+ btn.disabled = false;
399
+ btn.textContent = 'Sign in with Schnorr';
400
+ } else {
401
+ alert('Schnorr login failed: Unknown error');
402
+ btn.disabled = false;
403
+ btn.textContent = 'Sign in with Schnorr';
404
+ }
405
+ } catch (err) {
406
+ console.error('Schnorr login error:', err);
407
+ if (err.message && err.message.includes('User rejected')) {
408
+ // User cancelled signing - do nothing
409
+ } else {
410
+ alert('Schnorr login failed: ' + err.message);
411
+ }
412
+ btn.disabled = false;
413
+ btn.textContent = 'Sign in with Schnorr';
414
+ }
415
+ }
416
+ </script>
417
+ ` : '';
418
+
318
419
  return `
319
420
  <!DOCTYPE html>
320
421
  <html lang="en">
@@ -337,7 +438,7 @@ export function loginPage(uid, clientId, error = null, passkeyEnabled = true) {
337
438
 
338
439
  ${error ? `<div class="error">${escapeHtml(error)}</div>` : ''}
339
440
 
340
- ${passkeySection}
441
+ ${ssoSection}
341
442
 
342
443
  <form method="POST" action="/idp/interaction/${uid}/login">
343
444
  <label for="username">Username</label>
@@ -358,6 +459,7 @@ export function loginPage(uid, clientId, error = null, passkeyEnabled = true) {
358
459
  </p>
359
460
  </div>
360
461
  ${passkeyScript}
462
+ ${schnorrScript}
361
463
  </body>
362
464
  </html>
363
465
  `;
@@ -1,266 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "WebSearch",
5
- "WebFetch(domain:github.com)",
6
- "WebFetch(domain:raw.githubusercontent.com)",
7
- "Bash(cat:*)",
8
- "WebFetch(domain:www.inrupt.com)",
9
- "Bash(npm install:*)",
10
- "Bash(timeout 3 node:*)",
11
- "Bash(PORT=3030 timeout 3 node:*)",
12
- "Bash(git commit:*)",
13
- "Bash(pkill:*)",
14
- "Bash(curl:*)",
15
- "Bash(npm test:*)",
16
- "Bash(git add:*)",
17
- "WebFetch(domain:solid.github.io)",
18
- "Bash(node:*)",
19
- "WebFetch(domain:solidservers.org)",
20
- "WebFetch(domain:solid-contrib.github.io)",
21
- "Bash(git clone:*)",
22
- "Bash(chmod:*)",
23
- "Bash(JSS_PORT=4000 JSS_CONNEG=true node bin/jss.js:*)",
24
- "Bash(find:*)",
25
- "Bash(timeout 5 node:*)",
26
- "Bash(npm view:*)",
27
- "Bash(npm ls:*)",
28
- "Bash(timeout 10 node:*)",
29
- "Bash(npm run test:cth:*)",
30
- "Bash(__NEW_LINE__ curl -s -X POST http://localhost:4000/.pods )",
31
- "Bash(lsof:*)",
32
- "Bash(xargs kill -9)",
33
- "Bash(docker:*)",
34
- "Bash(solidproject/conformance-test-harness )",
35
- "Bash(timeout 30 node:*)",
36
- "Bash(timeout 20 node:*)",
37
- "Bash(timeout 25 node:*)",
38
- "Bash(JSS_PORT=4000 JSS_CONNEG=true timeout 5 node:*)",
39
- "Bash(pgrep:*)",
40
- "Bash(python3:*)",
41
- "Bash(ls:*)",
42
- "Bash(timeout 15 node:*)",
43
- "Bash(echo 'No .idp folder' echo find /home/melvin/remote/github.com/JavaScriptSolidServer/JavaScriptSolidServer/data/.idp/ -name *.json)",
44
- "Bash(echo '=== Interactions ===' ls -la /home/melvin/remote/github.com/JavaScriptSolidServer/JavaScriptSolidServer/data/.idp/interaction/ echo echo '=== Latest interaction ===' cat /home/melvin/remote/github.com/JavaScriptSolidServer/JavaScriptSolidServer/data/.idp/interaction/*.json)",
45
- "Bash(1 echo \"\" echo \"=== Server errors ===\" grep -E \"(error|Error)\" /tmp/jss.log)",
46
- "Bash(echo 'Server not ready' curl -s -X POST http://localhost:4000/.pods -H 'Content-Type: application/json' -d {\"\"name\"\":\"\"alice\"\",\"\"email\"\":\"\"alice@example.com\"\",\"\"password\"\":\"\"alicepassword123\"\"})",
47
- "Bash(head -1 curl -s -X POST http://localhost:4000/.pods -H \"Content-Type: application/json\" -d '{\"\"\"\"name\"\"\"\":\"\"\"\"bob\"\"\"\",\"\"\"\"email\"\"\"\":\"\"\"\"bob@example.com\"\"\"\",\"\"\"\"password\"\"\"\":\"\"\"\"bobpassword123\"\"\"\"}')",
48
- "Bash(xargs:*)",
49
- "Bash(fuser:*)",
50
- "Bash(kill:*)",
51
- "Bash(ACCESS_TOKEN=\"eyJhbGciOiJFUzI1NiIsImtpZCI6IjQwY2U0YzIzLWY2OWQtNDU4NS05ODg2LTE4MDQzZWIyZjU2ZCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAvIiwic3ViIjoiYjhlZjY5YWUtODc0ZS00MDg5LThiMDktOGQwY2QyM2VlZWY3IiwiYXVkIjoic29saWQiLCJ3ZWJpZCI6Imh0dHA6Ly9sb2NhbGhvc3Q0MDAwL2FsaWNlLyNtZSIsImlhdCI6MTc2NjgyOTQ5MiwiZXhwIjoxNzY2ODMzMDkyLCJqdGkiOiIwMWY4ODVlZS05ZjY2LTQ3M2MtYmZkNC05MWM4ZGU3NGJhZjYiLCJjbGllbnRfaWQiOiJjcmVkZW50aWFsc19jbGllbnQiLCJzY29wZSI6Im9wZW5pZCB3ZWJpZCJ9.DYTlSRkORyDN28XtXk-zbR7xNLViD97KkPqUKb6chV860BaIgwa1suif4TxHQDnK_ejvbvmZ46_n5WwwRnf_Zw\" curl -sI -X PUT http://localhost:4000/alice/cth-test/ -H \"Content-Type: text/turtle\" -H \"Authorization: Bearer $ACCESS_TOKEN\")",
52
- "Bash(timeout 60 docker run:*)",
53
- "Bash(rm:*)",
54
- "Bash(mkdir:*)",
55
- "WebFetch(domain:communitysolidserver.github.io)",
56
- "WebFetch(domain:solidos.github.io)",
57
- "WebFetch(domain:solidcommunity.net)",
58
- "WebFetch(domain:www.npmjs.com)",
59
- "Bash(pm2 list:*)",
60
- "Bash(pm2 logs:*)",
61
- "Bash(pm2 restart:*)",
62
- "Bash(timeout 60 npm test:*)",
63
- "Bash(pm2 stop:*)",
64
- "Bash(pm2 delete:*)",
65
- "Bash(pm2 start:*)",
66
- "Bash(DATA_ROOT=/home/melvin/jss/data pm2 start:*)",
67
- "Bash(pm2 save:*)",
68
- "Bash(gh issue create:*)",
69
- "Bash(gh issue view:*)",
70
- "Bash(gh issue edit:*)",
71
- "WebFetch(domain:nostrcg.github.io)",
72
- "WebFetch(domain:melvincarvalho.github.io)",
73
- "WebFetch(domain:dev.to)",
74
- "WebFetch(domain:solidproject.org)",
75
- "WebFetch(domain:www.w3.org)",
76
- "Bash(wc:*)",
77
- "Bash(TOKEN=\"eyJraW5kIjoyNzIzNSwidGFncyI6W1sidSIsImh0dHA6Ly9sb2NhbGhvc3Q6NDAwMC9kZW1vL25vc3RyLXpvbmUvIl0sWyJtZXRob2QiLCJHRVQiXV0sImNyZWF0ZWRfYXQiOjE3NjY5MzQ1NjksImNvbnRlbnQiOiIiLCJwdWJrZXkiOiI4OTg5OWNmOWEyNGE5ZTdlMTNmODU3MGRkMGI1MmJiOTQyMjllNDI2OGM1MGQ1OWZhNjdhMzQ0MGQ0NmFhZTdkIiwiaWQiOiJiNTUyMDUyOTVmYmQwYzhjZDYwMzk1NTgwOWYxZGM5Y2MwMjdlY2U4N2NjYmNlNzcwNWY2MjdmNmQ0ODk1MGJkIiwic2lnIjoiOWYzN2Y0NzIyZDlkNmFmZGQ5OTNkYTM0MDg2MWQ2YzQ4MmY1NzQ1MmFmZTIwZmY2YmI5OTAxNGIwOTU3NjUwMWZiNTgyZjEzNzNlZmVhNjI4ZDI5ZjlhMzhmZTgyODU0ODlmMzAzYzlmYmJjYWE0OTQxZjUyZGZlMWYxNzVkOWMifQ==\")",
78
- "WebFetch(domain:solid-lite.org)",
79
- "Bash(git push:*)",
80
- "WebFetch(domain:linkedwebstorage.com)",
81
- "WebFetch(domain:w3c.github.io)",
82
- "WebFetch(domain:socialdocs.org)",
83
- "WebFetch(domain:nosdav.com)",
84
- "WebFetch(domain:sandy-mount.com)",
85
- "WebFetch(domain:ditto.pub)",
86
- "WebFetch(domain:blocktrails.org)",
87
- "WebFetch(domain:microfed.org)",
88
- "WebFetch(domain:soliddocs.org)",
89
- "WebFetch(domain:agenticalliance.com)",
90
- "WebFetch(domain:activitypub.rocks)",
91
- "WebFetch(domain:nostrgit.org)",
92
- "Bash(convert:*)",
93
- "WebFetch(domain:instantdomainsearch.com)",
94
- "Bash(for domain in jss.dev jss.sh jss.io jss.app solidserver.dev solid-server.dev)",
95
- "Bash(do echo -n '$domain: ')",
96
- "Bash(whois $domain)",
97
- "Bash(done)",
98
- "Bash(for domain in jss.dev jss.sh jss.io jss.app solidserver.dev)",
99
- "Bash(host:*)",
100
- "WebFetch(domain:nostr-components.github.io)",
101
- "Bash(ssh melvincarvalho.com \"pm2 list && echo ''---HAPROXY---'' && cat /etc/haproxy/haproxy.cfg 2>/dev/null | grep -A5 ''melvin\\|backend\\|frontend\\|acl host''\")",
102
- "Bash(ssh:*)",
103
- "Bash(time curl -s --connect-timeout 10 https://melvin.solid.live/credit/count.ttl)",
104
- "Bash(time curl -s --connect-timeout 10 https://melvin.solid.live/)",
105
- "Bash(time curl:*)",
106
- "Bash(time curl -s 'https://melvin.solid.live/credit/count.ttl')",
107
- "Bash(grep:*)",
108
- "Bash(scp:*)",
109
- "Bash(for i in 1 2 3)",
110
- "Bash(do echo \"Attempt $i:\")",
111
- "Bash(for i in 1 2 3 4 5)",
112
- "Bash(do curl -so /dev/null -w \"%{http_code} \" https://melvincarvalho.com/js/handlemutation.js)",
113
- "Bash(for i in 1 2 3 4 5 6 7 8 9 10)",
114
- "Bash(if [ ! -d \"jose\" ])",
115
- "Bash(then git clone --depth 1 --branch v0.7.0 https://github.com/solid/jose.git)",
116
- "Bash(fi)",
117
- "Bash(timeout 45 node:*)",
118
- "Bash(gh issue list:*)",
119
- "Bash(DATA_ROOT=/tmp/jss-git-test JSS_PORT=4444 timeout 3 node:*)",
120
- "Bash(pm2 show:*)",
121
- "Bash(git config:*)",
122
- "Bash(npm version:*)",
123
- "Bash(git init:*)",
124
- "Bash(gh repo create:*)",
125
- "Bash(./bin/git-credential-nostr generate:*)",
126
- "Bash(./bin/git-credential-nostr get:*)",
127
- "Bash(git-credential-nostr:*)",
128
- "Bash(git branch:*)",
129
- "Bash(GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push:*)",
130
- "Bash(GIT_CURL_VERBOSE=1 git push:*)",
131
- "Bash(git reset:*)",
132
- "Bash(echo:*)",
133
- "Bash(unset DATA_ROOT)",
134
- "Bash(timeout 30 npm test:*)",
135
- "Bash(/home/melvin/remote/github.com/JavaScriptSolidServer/git-credential-nostr/bin/git-credential-nostr acl:*)",
136
- "Bash(npm cache clean:*)",
137
- "Bash(git checkout:*)",
138
- "Bash(gh gist edit:*)",
139
- "Bash(gh gist view:*)",
140
- "Bash(./bin/git-credential-nostr acl:*)",
141
- "Bash(DATA_ROOT=/tmp/jss-git-test/data node:*)",
142
- "Bash(git remote set-url:*)",
143
- "Bash(for:*)",
144
- "Bash(^/**\" | head -1 | sed ''''s/.*\\* //'''')\")",
145
- "Bash(if [ ! -d \"node-solid-server\" ])",
146
- "Bash(then git clone --depth 1 https://github.com/nodeSolidServer/node-solid-server.git)",
147
- "Bash(node test-local-nss2.js:*)",
148
- "Bash(npm test)",
149
- "Bash(repos.json)",
150
- "Bash(*.log)",
151
- "Bash(node --check:*)",
152
- "Bash(gh repo view:*)",
153
- "Bash(noskey --help:*)",
154
- "Bash(npx noskey --help:*)",
155
- "Bash(noskey:*)",
156
- "Bash(node -e:*)",
157
- "Bash(node src/publish.js:*)",
158
- "Bash(git remote add:*)",
159
- "Bash(git fetch:*)",
160
- "Bash(git rev-parse:*)",
161
- "Bash(f502f06c1d7553f4b7159e8d57a1e14819dc3053b59399e080882cc8e6bb62ad )",
162
- "Bash(798715377357003683b979b41c5d99c0312e6e788d789f0d5df710465483aa3e )",
163
- "Bash(f810e7491da3390109ddc13a74a1fff985ba3a4735024f2b714c12d213f5ea11 )",
164
- "Bash(1 )",
165
- "Bash(911912000 )",
166
- "Bash(4ccef8c68cf18f8f156a0bb017dfd6e0cc7ebf1672fa2d769e02e2efc700328b 1000000 )",
167
- "Bash(798715377357003683b979b41c5d99c0312e6e788d789f0d5df710465483aa3e 910911000 )",
168
- "Bash(~/.gitmark/faucet.txt)",
169
- "Bash(blocktrails --version:*)",
170
- "Bash(blocktrails --help:*)",
171
- "Bash(blocktrails show:*)",
172
- "Bash(git restore:*)",
173
- "Bash(npm show:*)",
174
- "WebFetch(domain:gitlab.com)",
175
- "Bash(gh repo edit:*)",
176
- "WebFetch(domain:blocktrails.github.io)",
177
- "Bash(jq:*)",
178
- "Bash(SOLID_SYNC=true timeout 45 node:*)",
179
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm status)",
180
- "Bash(SOLID_SYNC=true ANCHOR=true timeout 8 node:*)",
181
- "Bash(SOLID_SYNC=true ANCHOR=true node:*)",
182
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm diff src/watcher.js)",
183
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm add src/watcher.js)",
184
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm commit -m \"$\\(cat <<''EOF''\nAdd transfer API and HTTP 402 middleware\n\n- Add POST /transfer endpoint for user-to-user token transfers\n- Add verify402Payment middleware for token-gated APIs\n- Add GET /api/quote demo endpoint \\(costs 1 GSAT\\)\n- Add GET /balance/:did and GET /state endpoints\n- Fix anchor function to use encodeBech32m for address derivation\n- Remove OP_RETURN from anchor tx \\(state hash stored in state.json\\)\nEOF\n\\)\")",
185
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm push)",
186
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm add demo.html src/watcher.js debug.html paywall.html transfer.html)",
187
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm commit -m \"$\\(cat <<''EOF''\nAdd NIP-98 paywall, transfer, withdraw, and debug pages\n\n- Implement NIP-98 \\(kind 27235\\) for HTTP 402 authentication\n- Add paywall.html demo page showing NIP-98 flow\n- Add transfer.html for user-to-user GSAT transfers\n- Add debug.html with anchors, state, verify, withdraw, and users tabs\n- Add POST /withdraw endpoint for sats → Bitcoin address\n- Add navigation to demo.html linking all pages\nEOF\n\\)\")",
188
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm add test-amm.mjs package.json)",
189
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm commit -m \"$\\(cat <<''EOF''\nAdd AMM tests for math, signatures, and NIP-98\n\n- AMM math tests \\(calculateGsatOut, calculateSatsOut, slippage, k invariant\\)\n- Signature verification tests \\(sell, transfer, withdraw requests\\)\n- NIP-98 event creation, verification, and encoding tests\n- Update package.json with test script\nEOF\n\\)\")",
190
- "Bash(SOLID_SYNC=true node src/watcher.js:*)",
191
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm add demo.html src/watcher.js)",
192
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm commit -m \"$\\(cat <<''EOF''\nAdd smart polling with manual deposit check\n\n- Change poll interval from 30s to 10 minutes\n- Add POST /check endpoint for manual deposit scan\n- Add 10-second rate limit between manual checks\n- Add \"Check Deposits\" button to demo.html\nEOF\n\\)\")",
193
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm add:*)",
194
- "Bash(git -C /home/melvin/remote/github.com/blocktrails/gitmark-amm commit -m \"Use blocktrails npm package instead of local path\")",
195
- "Bash(for addr in tb1pdypd4k38q4x0qz5x7hqavjhfpgt2n4tm0egggx587aafqn3wsnds8gm3yf tb1pqxmrkvuyea9v7vv323tmptjfle5tj9y6cpe5g8wqvlz6d5xmfhlqctx7py tb1p0fv2683x2j5htf9n7fkpmxsy4h7yuxmetelq2c6vp8u2zw9rhp2s5kha7v)",
196
- "Bash(do echo -n \"$addr: \" curl -s \"https://mempool.space/testnet4/api/address/$addr\")",
197
- "WebFetch(domain:webledgers.org)",
198
- "Bash(npm pack:*)",
199
- "Bash(npm info:*)",
200
- "Bash(tar:*)",
201
- "Bash(TEST_API=1 API_URL=https://api.solid.social node:*)",
202
- "Bash(webledgers show:*)",
203
- "Bash(webledgers set-balance:*)",
204
- "Bash(ssh melvincarvalho.com \"pm2 list | grep jss\")",
205
- "Bash(ssh melvincarvalho.com \"cd /home/ubuntu/jss && git pull && pm2 restart jss\")",
206
- "WebFetch(domain:registry.npmjs.org)",
207
- "WebFetch(domain:solid-chat.com)",
208
- "WebFetch(domain:developer.chrome.com)",
209
- "WebFetch(domain:css-tricks.com)",
210
- "Bash(node bin/jss.js:*)",
211
- "WebFetch(domain:nostr.social)",
212
- "Bash(xargs curl -s)",
213
- "Bash(ssh phone:*)",
214
- "Bash(dig:*)",
215
- "WebFetch(domain:fonstr.com)",
216
- "Bash(node -e \"import\\(''nostr-tools''\\).then\\(m => console.log\\(Object.keys\\(m\\).join\\(''\\\\n''\\)\\)\\)\":*)",
217
- "Bash(gh repo list:*)",
218
- "Bash(gh search:*)",
219
- "Bash(__NEW_LINE__ echo \"\")",
220
- "WebFetch(domain:webfinger.net)",
221
- "Bash(npm update:*)",
222
- "Bash(timeout 8 node:*)",
223
- "Bash(gh pr view:*)",
224
- "Bash(gh pr diff:*)",
225
- "Bash(gh pr review:*)",
226
- "Bash(gh api:*)",
227
- "Bash(gh pr comment:*)",
228
- "Bash(git pull:*)",
229
- "Bash(gh pr:*)",
230
- "Bash(node --test:*)",
231
- "Bash(TOKEN_SECRET=test node --test:*)",
232
- "Bash(gh search issues:*)",
233
- "Bash(timeout 60 bash:*)",
234
- "Bash(timeout 90 bash -c:*)",
235
- "Bash(git commit -m \"$\\(cat <<''EOF''\nsecurity: add ACL check on WebSocket subscription requests\n\nCheck WAC read permission before allowing subscription to prevent\ninformation leakage via notifications. Unauthorized subscriptions\nnow receive ''err <url> forbidden'' response.\n\nSecurity improvements:\n- Check ACL read access before allowing subscription\n- Validate URLs are on this server \\(prevents SSRF-like probing\\)\n- Add subscription limit and URL length validation\n\nFixes #62\nEOF\n\\)\")",
236
- "Bash(gh repo fork:*)",
237
- "Bash(timeout 180 npm test:*)",
238
- "Bash(git show:*)",
239
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server log --oneline -30 -- \"test/integration/acl-tls-test.mjs\" \"test-esm/integration/acl-tls-test.js\")",
240
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server log --oneline --follow -30 -- \"test/integration/acl-tls-test.mjs\")",
241
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server show 778095ad --stat)",
242
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server show 778095ad)",
243
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server show b183c7a0)",
244
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server log --oneline --before=\"2019-10-29\" --after=\"2019-10-01\" -20)",
245
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server show 1a92a912 --stat)",
246
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server log --oneline --all --grep=jaxoncreed)",
247
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server log --oneline --author=\"jaxoncreed\" -30)",
248
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server log --oneline --author=\"[Dd]mitri\" -20)",
249
- "Bash(git -C /home/melvin/remote/github.com/nodeSolidServer/node-solid-server log --oneline --all --grep=\"oidc\" -20)",
250
- "Bash(npm install)",
251
- "Bash(timeout 60 npx mocha:*)",
252
- "Bash(timeout 120 npx mocha:*)",
253
- "Bash(timeout 30 npx mocha:*)",
254
- "Bash(openssl x509:*)",
255
- "Bash(gh pr checks:*)",
256
- "Bash(gh run view:*)",
257
- "Bash(gh pr edit:*)",
258
- "WebFetch(domain:patch-diff.githubusercontent.com)",
259
- "Bash(git rebase:*)",
260
- "Bash(timeout 10 npm start)",
261
- "Bash(node bin/jss.js start:*)",
262
- "Bash(ssh solid.social \"cd /var/www/jss && git pull && pm2 restart jss\")",
263
- "Bash(ssh solid.social:*)"
264
- ]
265
- }
266
- }