javascript-solid-server 0.0.80 → 0.0.82
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/.claude/settings.local.json +44 -1
- package/README.md +1 -1
- package/bin/jss.js +14 -0
- package/fonstr-data/index.html +120 -0
- package/package.json +1 -2
- package/src/ap/store.js +38 -63
- package/src/auth/middleware.js +6 -0
- package/src/config.js +8 -0
- package/src/handlers/container.js +10 -0
- package/src/handlers/resource.js +15 -0
- package/src/server.js +2 -0
|
@@ -277,7 +277,50 @@
|
|
|
277
277
|
"Bash(node index.js:*)",
|
|
278
278
|
"Bash(timeout 3 npx lws-server@0.0.3:*)",
|
|
279
279
|
"Bash(npx lws-server@0.0.3)",
|
|
280
|
-
"Bash(node /tmp/jssd/verify-spacing.js:*)"
|
|
280
|
+
"Bash(node /tmp/jssd/verify-spacing.js:*)",
|
|
281
|
+
"Bash(gh run list:*)",
|
|
282
|
+
"Bash(pm2 startup:*)",
|
|
283
|
+
"Bash(npm run build:*)",
|
|
284
|
+
"Bash(npx esbuild:*)",
|
|
285
|
+
"Bash(gzip:*)",
|
|
286
|
+
"Bash(sort:*)",
|
|
287
|
+
"Bash(echo __NEW_LINE_496cf3994d374e5c__ echo '1. jsonld \\(277 KB\\) - used by:' grep -r \"from ['''']jsonld\" src/ --include=*.ts --include=*.js 2)",
|
|
288
|
+
"Bash(/dev/null __NEW_LINE_496cf3994d374e5c__ echo echo '2. readable-stream \\(180 KB\\) - used by:' grep -r readable-stream node_modules/n3/package.json node_modules/jsonld/package.json)",
|
|
289
|
+
"Bash(head -3 __NEW_LINE_496cf3994d374e5c__ echo echo '3. n3 \\(156 KB\\) - used by:' grep -r \"from ['''']n3\" src/ --include=*.ts --include=*.js 2)",
|
|
290
|
+
"Bash(/dev/null __NEW_LINE_496cf3994d374e5c__ echo echo '4. @xmldom/xmldom \\(136 KB\\) - used by:' grep -r xmldom src/ --include=*.ts --include=*.js 2)",
|
|
291
|
+
"Bash(/dev/null __NEW_LINE_496cf3994d374e5c__ echo echo '5. @frogcat/ttl2jsonld \\(135 KB\\) - used by:' grep -r ttl2jsonld src/ --include=*.ts --include=*.js)",
|
|
292
|
+
"Bash(echo \"\" __NEW_LINE_b9f7c85f1ada8a30__ echo \"1. jsonld \\(277 KB\\):\" grep -rn \"jsonld\" src/ --include=\"*.ts\" --include=\"*.js\")",
|
|
293
|
+
"Bash(head -5 __NEW_LINE_b9f7c85f1ada8a30__ echo \"\" echo \"2. n3 \\(156 KB\\):\" grep -rn \"from ''''n3''''\" src/ --include=\"*.ts\" --include=\"*.js\")",
|
|
294
|
+
"Bash(head -5 __NEW_LINE_b9f7c85f1ada8a30__ echo \"\" echo \"3. @xmldom \\(136 KB\\):\" grep -rn \"xmldom\" src/ --include=\"*.ts\" --include=\"*.js\")",
|
|
295
|
+
"Bash(head -5 __NEW_LINE_b9f7c85f1ada8a30__ echo \"\" echo \"4. @frogcat/ttl2jsonld \\(135 KB\\):\" grep -rn \"ttl2jsonld\" src/ --include=\"*.ts\" --include=\"*.js\")",
|
|
296
|
+
"Bash(head -5 __NEW_LINE_b9f7c85f1ada8a30__ echo \"\" echo \"5. cross-fetch \\(20 KB\\):\" grep -rn \"cross-fetch\" src/ --include=\"*.ts\" --include=\"*.js\")",
|
|
297
|
+
"Bash(head -5 __NEW_LINE_b9f7c85f1ada8a30__ echo \"\" echo \"6. solid-namespace \\(3 KB\\):\" grep -rn \"solid-namespace\" src/ --include=\"*.ts\" --include=\"*.js\")",
|
|
298
|
+
"Bash(echo __NEW_LINE_fbfd1802fc51eb60__ echo '1. jsonld \\(277 KB\\):' grep -n jsonld src/*.ts src/*.js)",
|
|
299
|
+
"Bash(grep -E \"import|require\" __NEW_LINE_fbfd1802fc51eb60__ echo echo '2. n3 \\(156 KB\\):' grep -n ''n3'' src/*.ts src/*.js)",
|
|
300
|
+
"Bash(grep -E \"import|require\" __NEW_LINE_fbfd1802fc51eb60__ echo echo '3. @xmldom \\(136 KB\\):' grep -n xmldom src/*.ts src/*.js)",
|
|
301
|
+
"Bash(grep -E \"import|require\" __NEW_LINE_fbfd1802fc51eb60__ echo echo '4. @frogcat/ttl2jsonld \\(135 KB\\):' grep -n ttl2jsonld src/*.ts src/*.js)",
|
|
302
|
+
"Bash(grep -E \"import|require\" __NEW_LINE_fbfd1802fc51eb60__ echo echo '5. cross-fetch \\(20 KB\\):' grep -n cross-fetch src/*.ts src/*.js)",
|
|
303
|
+
"Bash(grep -E \"import|require\" __NEW_LINE_fbfd1802fc51eb60__ echo echo '6. solid-namespace \\(3 KB\\):' grep -n solid-namespace src/*.ts src/*.js)",
|
|
304
|
+
"Bash(npm run build:all:*)",
|
|
305
|
+
"Bash(git status:*)",
|
|
306
|
+
"Bash(npm run lint-fix:*)",
|
|
307
|
+
"Bash(npm uninstall:*)",
|
|
308
|
+
"Bash(npm search:*)",
|
|
309
|
+
"Bash(npm run lint:*)",
|
|
310
|
+
"Bash(npm run typecheck:*)",
|
|
311
|
+
"Bash(du:*)",
|
|
312
|
+
"Bash(for pkg in activitystreams-pane chat-pane contacts-pane folder-pane issue-pane meeting-pane profile-pane source-pane pane-registry)",
|
|
313
|
+
"Bash(do du -sh node_modules/$pkg)",
|
|
314
|
+
"Bash(for pkg in solid-ui solid-logic react react-dom lodash core-js @inrupt marked dompurify)",
|
|
315
|
+
"Bash(npm why:*)",
|
|
316
|
+
"Bash(for pkg in activitystreams-pane chat-pane contacts-pane folder-pane issue-pane meeting-pane profile-pane source-pane)",
|
|
317
|
+
"Bash(wc -l echo 'Brings: mime-types \\(we already removed this pattern\\)' echo echo '=== profile-pane unique deps ===')",
|
|
318
|
+
"Bash(npm run build-prod:*)",
|
|
319
|
+
"Bash(ln:*)",
|
|
320
|
+
"WebFetch(domain:remotestorage.io)",
|
|
321
|
+
"Bash(gh issue create --title \"Feature: --public flag to skip WAC and allow open access\" --body \"$\\(cat << ''ENDOFFILE''\n## Summary\n\nAdd a `--public` flag that disables WAC \\(Web Access Control\\) checks, allowing unauthenticated read/write access to all resources. This enables JSS to be used as a simple file server similar to `npx serve`, but with REST write capabilities.\n\n**Difficulty**: 15/100 \n**Estimated Effort**: 2-4 hours \n**Dependencies**: None\n\n---\n\n## Motivation\n\nCurrently, JSS requires either:\n1. ACL files to grant access, or\n2. Authentication via Solid-OIDC\n\nThis makes it impossible to use JSS as a simple \"just serve this folder\" tool like `npx serve`. The `--public` flag would enable:\n\n1. **Quick local development** - No auth setup needed\n2. **Simple file sharing** - LAN file server with write support\n3. **jsserve wrapper** - Foundation for `npx jsserve` \\(see future issue\\)\n4. **WebDAV alternative** - Simple REST-based file server\n5. **Testing/demos** - Quick Solid server without auth complexity\n\n---\n\n## Proposed Implementation\n\n### CLI Flag\n\n```bash\njss start --public # Open read/write, no auth\njss start --public --read-only # Open read, no writes \\(like npx serve\\)\n```\n\n### Environment Variable\n\n```bash\nJSS_PUBLIC=true jss start\n```\n\n### Config File\n\n```json\n{\n \"public\": true\n}\n```\n\n---\n\n## Implementation Details\n\n### 1. Add flag to CLI \\(`bin/jss.js`\\)\n\n```javascript\n.option\\(''--public'', ''Allow unauthenticated access to all resources \\(disables WAC\\)''\\)\n.option\\(''--read-only'', ''Disable PUT/DELETE methods \\(read-only mode\\)''\\)\n```\n\n### 2. Add to config \\(`src/config.js`\\)\n\n```javascript\nconst DEFAULTS = {\n // ... existing ...\n public: false,\n readOnly: false,\n};\n\n// Environment variable mapping\nif \\(process.env.JSS_PUBLIC\\) {\n config.public = process.env.JSS_PUBLIC === ''true'';\n}\nif \\(process.env.JSS_READ_ONLY\\) {\n config.readOnly = process.env.JSS_READ_ONLY === ''true'';\n}\n```\n\n### 3. Skip WAC when public \\(`src/auth/middleware.js`\\)\n\n```javascript\nexport async function authorize\\(request, reply\\) {\n // Public mode - skip all auth/WAC checks\n if \\(request.config?.public\\) {\n return; // Allow request to proceed\n }\n \n // ... existing WAC logic ...\n}\n```\n\n### 4. Block writes when read-only \\(`src/handlers/resource.js`, `src/handlers/container.js`\\)\n\n```javascript\n// At start of PUT/DELETE handlers\nif \\(request.config?.readOnly\\) {\n return reply.code\\(405\\).send\\({ \n error: ''Method Not Allowed'',\n message: ''Server is in read-only mode''\n }\\);\n}\n```\n\n---\n\n## Behavior Matrix\n\n| Flag Combination | GET | PUT/DELETE | Auth Required |\n|------------------|-----|------------|---------------|\n| \\(default\\) | ACL | ACL | Yes \\(if ACL requires\\) |\n| `--public` | ✅ Allow | ✅ Allow | No |\n| `--public --read-only` | ✅ Allow | ❌ Block | No |\n| `--read-only` \\(no public\\) | ACL | ❌ Block | Yes |\n\n---\n\n## Security Considerations\n\n### Warning on Startup\n\nWhen `--public` is enabled, show a clear warning:\n\n```\n⚠️ WARNING: Server running in PUBLIC mode\n All files are readable and writable without authentication.\n Do not use in production or expose to the internet.\n```\n\n### Binding to localhost by default?\n\nConsider: When `--public` is set, should the default host be `localhost` instead of `0.0.0.0`?\n\n```javascript\nif \\(config.public && !explicitHostSet\\) {\n config.host = ''localhost''; // Safer default for public mode\n}\n```\n\nUser can override with `--public --host 0.0.0.0` if they explicitly want network access.\n\n---\n\n## Examples\n\n### Local Development\n```bash\n# Quick Solid-compatible file server for development\njss start --public --port 3000 --root ./test-data\n```\n\n### Read-Only File Sharing\n```bash\n# Share files on LAN, no writes allowed\njss start --public --read-only --host 0.0.0.0 --root ~/shared\n```\n\n### Testing Solid Apps\n```bash\n# Test app without auth complexity\njss start --public --root ./fixtures\nnpm test\n```\n\n---\n\n## Files to Modify\n\n| File | Changes |\n|------|---------|\n| `bin/jss.js` | Add `--public` and `--read-only` options \\(~5 LOC\\) |\n| `src/config.js` | Add defaults and env var mapping \\(~10 LOC\\) |\n| `src/auth/middleware.js` | Skip WAC when public \\(~5 LOC\\) |\n| `src/handlers/resource.js` | Block writes when read-only \\(~5 LOC\\) |\n| `src/handlers/container.js` | Block writes when read-only \\(~5 LOC\\) |\n| **Total** | **~30 LOC** |\n\n---\n\n## Testing\n\n```javascript\ndescribe\\(''--public flag'', \\(\\) => {\n it\\(''should allow unauthenticated GET'', async \\(\\) => {\n const server = await createServer\\({ public: true, root: tmpDir }\\);\n const res = await request\\(server\\).get\\(''/file.txt''\\);\n expect\\(res.status\\).toBe\\(200\\);\n }\\);\n\n it\\(''should allow unauthenticated PUT'', async \\(\\) => {\n const server = await createServer\\({ public: true, root: tmpDir }\\);\n const res = await request\\(server\\)\n .put\\(''/new-file.txt''\\)\n .send\\(''content''\\);\n expect\\(res.status\\).toBe\\(201\\);\n }\\);\n\n it\\(''should block PUT when read-only'', async \\(\\) => {\n const server = await createServer\\({ public: true, readOnly: true, root: tmpDir }\\);\n const res = await request\\(server\\)\n .put\\(''/new-file.txt''\\)\n .send\\(''content''\\);\n expect\\(res.status\\).toBe\\(405\\);\n }\\);\n}\\);\n```\n\n---\n\n## Related Issues\n\n- Future: `jsserve` package \\(thin wrapper using this flag\\)\n- #100 - Production Readiness \\(this is a dev/convenience feature\\)\n\n---\n\n## Open Questions\n\n1. Should `--public` default to `localhost` binding for safety?\n2. Should there be a `--public-read` \\(read-only public\\) shorthand?\n3. Should `--public` disable IdP/login UI entirely, or just make it optional?\nENDOFFILE\n\\)\")",
|
|
322
|
+
"Bash(npx serve --help:*)",
|
|
323
|
+
"Bash(npm exec serve:*)"
|
|
281
324
|
]
|
|
282
325
|
}
|
|
283
326
|
}
|
package/README.md
CHANGED
|
@@ -1047,7 +1047,7 @@ Minimal dependencies for a fast, secure server:
|
|
|
1047
1047
|
- **oidc-provider** - OpenID Connect Identity Provider (only when IdP enabled)
|
|
1048
1048
|
- **bcryptjs** - Password hashing (only when IdP enabled)
|
|
1049
1049
|
- **microfed** - ActivityPub primitives (only when activitypub enabled)
|
|
1050
|
-
- **
|
|
1050
|
+
- **sql.js** - SQLite storage for federation data (WASM, cross-platform)
|
|
1051
1051
|
|
|
1052
1052
|
## License
|
|
1053
1053
|
|
package/bin/jss.js
CHANGED
|
@@ -76,6 +76,8 @@ program
|
|
|
76
76
|
.option('--single-user-name <name>', 'Username for single-user mode (default: me)')
|
|
77
77
|
.option('--webid-tls', 'Enable WebID-TLS client certificate authentication')
|
|
78
78
|
.option('--no-webid-tls', 'Disable WebID-TLS authentication')
|
|
79
|
+
.option('--public', 'Allow unauthenticated access (skip WAC, open read/write)')
|
|
80
|
+
.option('--read-only', 'Disable PUT/DELETE/PATCH methods (read-only mode)')
|
|
79
81
|
.option('-q, --quiet', 'Suppress log output')
|
|
80
82
|
.option('--print-config', 'Print configuration and exit')
|
|
81
83
|
.action(async (options) => {
|
|
@@ -131,6 +133,8 @@ program
|
|
|
131
133
|
webidTls: config.webidTls,
|
|
132
134
|
singleUser: config.singleUser,
|
|
133
135
|
singleUserName: config.singleUserName,
|
|
136
|
+
public: config.public,
|
|
137
|
+
readOnly: config.readOnly,
|
|
134
138
|
});
|
|
135
139
|
|
|
136
140
|
await server.listen({ port: config.port, host: config.host });
|
|
@@ -156,6 +160,16 @@ program
|
|
|
156
160
|
if (config.singleUser) console.log(` Single-user: ${config.singleUserName || 'me'} (registration disabled)`);
|
|
157
161
|
else if (config.inviteOnly) console.log(' Registration: invite-only');
|
|
158
162
|
if (config.webidTls) console.log(' WebID-TLS: enabled (client certificate auth)');
|
|
163
|
+
if (config.public) {
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log(' ⚠️ WARNING: PUBLIC MODE ENABLED');
|
|
166
|
+
console.log(' All files are accessible without authentication.');
|
|
167
|
+
if (!config.readOnly) {
|
|
168
|
+
console.log(' Anyone can read, write, and delete files.');
|
|
169
|
+
}
|
|
170
|
+
console.log(' Do not expose to the internet!');
|
|
171
|
+
}
|
|
172
|
+
if (config.readOnly) console.log(' Read-only: enabled (PUT/DELETE/PATCH disabled)');
|
|
159
173
|
console.log('\n Press Ctrl+C to stop\n');
|
|
160
174
|
}
|
|
161
175
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>fonstr - Your Nostr Relay</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
11
|
+
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
12
|
+
color: white;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
padding: 2rem;
|
|
18
|
+
}
|
|
19
|
+
.container {
|
|
20
|
+
text-align: center;
|
|
21
|
+
max-width: 600px;
|
|
22
|
+
}
|
|
23
|
+
h1 {
|
|
24
|
+
font-size: 3rem;
|
|
25
|
+
margin-bottom: 0.5rem;
|
|
26
|
+
}
|
|
27
|
+
.emoji {
|
|
28
|
+
font-size: 4rem;
|
|
29
|
+
margin-bottom: 1rem;
|
|
30
|
+
}
|
|
31
|
+
p {
|
|
32
|
+
font-size: 1.25rem;
|
|
33
|
+
opacity: 0.95;
|
|
34
|
+
margin-bottom: 2rem;
|
|
35
|
+
line-height: 1.6;
|
|
36
|
+
}
|
|
37
|
+
.relay-info {
|
|
38
|
+
background: rgba(255, 255, 255, 0.2);
|
|
39
|
+
backdrop-filter: blur(10px);
|
|
40
|
+
border-radius: 1rem;
|
|
41
|
+
padding: 2rem;
|
|
42
|
+
margin: 2rem 0;
|
|
43
|
+
}
|
|
44
|
+
code {
|
|
45
|
+
background: rgba(255, 255, 255, 0.3);
|
|
46
|
+
padding: 0.5rem 1rem;
|
|
47
|
+
border-radius: 0.5rem;
|
|
48
|
+
font-size: 1.1rem;
|
|
49
|
+
display: inline-block;
|
|
50
|
+
margin: 0.5rem 0;
|
|
51
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
52
|
+
}
|
|
53
|
+
.stats {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
56
|
+
gap: 1rem;
|
|
57
|
+
margin-top: 2rem;
|
|
58
|
+
}
|
|
59
|
+
.stat {
|
|
60
|
+
background: rgba(255, 255, 255, 0.15);
|
|
61
|
+
padding: 1rem;
|
|
62
|
+
border-radius: 0.5rem;
|
|
63
|
+
}
|
|
64
|
+
.stat-label {
|
|
65
|
+
font-size: 0.9rem;
|
|
66
|
+
opacity: 0.8;
|
|
67
|
+
}
|
|
68
|
+
.stat-value {
|
|
69
|
+
font-size: 1.5rem;
|
|
70
|
+
font-weight: 700;
|
|
71
|
+
margin-top: 0.25rem;
|
|
72
|
+
}
|
|
73
|
+
a {
|
|
74
|
+
color: white;
|
|
75
|
+
text-decoration: none;
|
|
76
|
+
border-bottom: 2px solid rgba(255, 255, 255, 0.5);
|
|
77
|
+
transition: border-color 0.2s;
|
|
78
|
+
}
|
|
79
|
+
a:hover {
|
|
80
|
+
border-color: white;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<div class="container">
|
|
86
|
+
<div class="emoji">⚡</div>
|
|
87
|
+
<h1>fonstr</h1>
|
|
88
|
+
<p>Your Nostr relay is running!</p>
|
|
89
|
+
|
|
90
|
+
<div class="relay-info">
|
|
91
|
+
<p style="font-size: 1rem; margin-bottom: 1rem; opacity: 0.9;">Connect to your relay:</p>
|
|
92
|
+
<code>ws://localhost:4444/relay</code>
|
|
93
|
+
|
|
94
|
+
<div class="stats">
|
|
95
|
+
<div class="stat">
|
|
96
|
+
<div class="stat-label">Status</div>
|
|
97
|
+
<div class="stat-value">✓ Online</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="stat">
|
|
100
|
+
<div class="stat-label">Protocol</div>
|
|
101
|
+
<div class="stat-value">NIP-01</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="stat">
|
|
104
|
+
<div class="stat-label">Port</div>
|
|
105
|
+
<div class="stat-value">4444</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<p style="font-size: 1rem;">
|
|
111
|
+
Add this relay to your favorite Nostr client and start using it!<br>
|
|
112
|
+
<a href="https://fonstr.com" target="_blank">Learn more about fonstr</a>
|
|
113
|
+
</p>
|
|
114
|
+
|
|
115
|
+
<p style="font-size: 0.9rem; opacity: 0.7; margin-top: 2rem;">
|
|
116
|
+
Replace this page by editing <code style="font-size: 0.8rem;">index.html</code> in your data directory
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "javascript-solid-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.82",
|
|
4
4
|
"description": "A minimal, fast Solid server",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"@fastify/websocket": "^8.3.1",
|
|
29
29
|
"@simplewebauthn/server": "^13.2.2",
|
|
30
30
|
"bcryptjs": "^3.0.3",
|
|
31
|
-
"better-sqlite3": "^12.5.0",
|
|
32
31
|
"commander": "^14.0.2",
|
|
33
32
|
"fastify": "^4.29.1",
|
|
34
33
|
"fs-extra": "^11.2.0",
|
package/src/ap/store.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* ActivityPub SQLite Storage
|
|
3
3
|
* Persistence layer for federation data
|
|
4
4
|
*
|
|
5
|
-
* Uses
|
|
6
|
-
*
|
|
5
|
+
* Uses sql.js (WASM) for cross-platform compatibility
|
|
6
|
+
* Works on Android/Termux, Windows, and all platforms
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
@@ -11,7 +11,6 @@ import { dirname } from 'path'
|
|
|
11
11
|
|
|
12
12
|
let db = null
|
|
13
13
|
let dbPath = null
|
|
14
|
-
let usingSqlJs = false
|
|
15
14
|
|
|
16
15
|
// SQL schema
|
|
17
16
|
const SCHEMA = `
|
|
@@ -59,7 +58,7 @@ const SCHEMA = `
|
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
60
|
* Initialize the database
|
|
62
|
-
*
|
|
61
|
+
* Uses sql.js (WASM) for cross-platform compatibility
|
|
63
62
|
* @param {string} path - Path to SQLite file
|
|
64
63
|
*/
|
|
65
64
|
export async function initStore(path = 'data/activitypub.db') {
|
|
@@ -71,43 +70,31 @@ export async function initStore(path = 'data/activitypub.db') {
|
|
|
71
70
|
|
|
72
71
|
dbPath = path
|
|
73
72
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const initSqlJs = (await import('sql.js')).default
|
|
86
|
-
const SQL = await initSqlJs()
|
|
87
|
-
|
|
88
|
-
// Load existing database if it exists
|
|
89
|
-
if (existsSync(path)) {
|
|
90
|
-
const buffer = readFileSync(path)
|
|
91
|
-
db = new SQL.Database(buffer)
|
|
92
|
-
} else {
|
|
93
|
-
db = new SQL.Database()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
db.run(SCHEMA)
|
|
97
|
-
usingSqlJs = true
|
|
98
|
-
|
|
99
|
-
// Save initial database
|
|
100
|
-
saveDatabase()
|
|
101
|
-
|
|
102
|
-
return db
|
|
73
|
+
// Use sql.js (WASM, works everywhere)
|
|
74
|
+
const initSqlJs = (await import('sql.js')).default
|
|
75
|
+
const SQL = await initSqlJs()
|
|
76
|
+
|
|
77
|
+
// Load existing database if it exists
|
|
78
|
+
if (existsSync(path)) {
|
|
79
|
+
const buffer = readFileSync(path)
|
|
80
|
+
db = new SQL.Database(buffer)
|
|
81
|
+
} else {
|
|
82
|
+
db = new SQL.Database()
|
|
103
83
|
}
|
|
84
|
+
|
|
85
|
+
db.run(SCHEMA)
|
|
86
|
+
|
|
87
|
+
// Save initial database
|
|
88
|
+
saveDatabase()
|
|
89
|
+
|
|
90
|
+
return db
|
|
104
91
|
}
|
|
105
92
|
|
|
106
93
|
/**
|
|
107
94
|
* Save sql.js database to disk
|
|
108
95
|
*/
|
|
109
96
|
function saveDatabase() {
|
|
110
|
-
if (
|
|
97
|
+
if (db && dbPath) {
|
|
111
98
|
const data = db.export()
|
|
112
99
|
const buffer = Buffer.from(data)
|
|
113
100
|
writeFileSync(dbPath, buffer)
|
|
@@ -124,45 +111,33 @@ export function getStore() {
|
|
|
124
111
|
return db
|
|
125
112
|
}
|
|
126
113
|
|
|
127
|
-
// Helper
|
|
114
|
+
// Helper functions for sql.js API
|
|
128
115
|
function runStmt(sql, params = []) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
saveDatabase()
|
|
132
|
-
} else {
|
|
133
|
-
db.prepare(sql).run(...params)
|
|
134
|
-
}
|
|
116
|
+
db.run(sql, params)
|
|
117
|
+
saveDatabase()
|
|
135
118
|
}
|
|
136
119
|
|
|
137
120
|
function getOne(sql, params = []) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const row = stmt.getAsObject()
|
|
143
|
-
stmt.free()
|
|
144
|
-
return row
|
|
145
|
-
}
|
|
121
|
+
const stmt = db.prepare(sql)
|
|
122
|
+
stmt.bind(params)
|
|
123
|
+
if (stmt.step()) {
|
|
124
|
+
const row = stmt.getAsObject()
|
|
146
125
|
stmt.free()
|
|
147
|
-
return
|
|
148
|
-
} else {
|
|
149
|
-
return db.prepare(sql).get(...params)
|
|
126
|
+
return row
|
|
150
127
|
}
|
|
128
|
+
stmt.free()
|
|
129
|
+
return null
|
|
151
130
|
}
|
|
152
131
|
|
|
153
132
|
function getAll(sql, params = []) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
results.push(stmt.getAsObject())
|
|
160
|
-
}
|
|
161
|
-
stmt.free()
|
|
162
|
-
return results
|
|
163
|
-
} else {
|
|
164
|
-
return db.prepare(sql).all(...params)
|
|
133
|
+
const results = []
|
|
134
|
+
const stmt = db.prepare(sql)
|
|
135
|
+
stmt.bind(params)
|
|
136
|
+
while (stmt.step()) {
|
|
137
|
+
results.push(stmt.getAsObject())
|
|
165
138
|
}
|
|
139
|
+
stmt.free()
|
|
140
|
+
return results
|
|
166
141
|
}
|
|
167
142
|
|
|
168
143
|
// Followers
|
package/src/auth/middleware.js
CHANGED
|
@@ -28,6 +28,12 @@ export async function authorize(request, reply, options = {}) {
|
|
|
28
28
|
return { authorized: true, webId: null, wacAllow: 'user="read write append control", public="read write append"', authError: null };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Public mode - skip all WAC checks, allow unauthenticated access
|
|
32
|
+
if (request.config?.public) {
|
|
33
|
+
const modes = request.config?.readOnly ? 'read' : 'read write append';
|
|
34
|
+
return { authorized: true, webId: null, wacAllow: `public="${modes}"`, authError: null };
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
// Get WebID from token (supports both simple and Solid-OIDC tokens)
|
|
32
38
|
const { webId, error: authError } = await getWebIdFromRequestAsync(request);
|
|
33
39
|
|
package/src/config.js
CHANGED
|
@@ -73,6 +73,12 @@ export const defaults = {
|
|
|
73
73
|
// Storage quota (bytes) - 50MB default
|
|
74
74
|
defaultQuota: 50 * 1024 * 1024,
|
|
75
75
|
|
|
76
|
+
// Public mode - skip WAC, allow unauthenticated access
|
|
77
|
+
public: false,
|
|
78
|
+
|
|
79
|
+
// Read-only mode - disable PUT/DELETE/PATCH
|
|
80
|
+
readOnly: false,
|
|
81
|
+
|
|
76
82
|
// Logging
|
|
77
83
|
logger: true,
|
|
78
84
|
quiet: false,
|
|
@@ -117,6 +123,8 @@ const envMap = {
|
|
|
117
123
|
JSS_SINGLE_USER_NAME: 'singleUserName',
|
|
118
124
|
JSS_WEBID_TLS: 'webidTls',
|
|
119
125
|
JSS_DEFAULT_QUOTA: 'defaultQuota',
|
|
126
|
+
JSS_PUBLIC: 'public',
|
|
127
|
+
JSS_READ_ONLY: 'readOnly',
|
|
120
128
|
};
|
|
121
129
|
|
|
122
130
|
/**
|
|
@@ -25,6 +25,11 @@ function getRequestPaths(request) {
|
|
|
25
25
|
* Handle POST request to container (create new resource)
|
|
26
26
|
*/
|
|
27
27
|
export async function handlePost(request, reply) {
|
|
28
|
+
// Read-only mode - block all writes
|
|
29
|
+
if (request.config?.readOnly) {
|
|
30
|
+
return reply.code(405).send({ error: 'Method Not Allowed', message: 'Server is in read-only mode' });
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
const { urlPath, storagePath } = getRequestPaths(request);
|
|
29
34
|
|
|
30
35
|
// Ensure target is a container
|
|
@@ -233,6 +238,11 @@ export async function createPodStructure(name, webId, podUri, issuer, defaultQuo
|
|
|
233
238
|
* /{name}/settings/privateTypeIndex
|
|
234
239
|
*/
|
|
235
240
|
export async function handleCreatePod(request, reply) {
|
|
241
|
+
// Read-only mode - block pod creation
|
|
242
|
+
if (request.config?.readOnly) {
|
|
243
|
+
return reply.code(405).send({ error: 'Method Not Allowed', message: 'Server is in read-only mode' });
|
|
244
|
+
}
|
|
245
|
+
|
|
236
246
|
const { name, email, password } = request.body || {};
|
|
237
247
|
const idpEnabled = request.idpEnabled;
|
|
238
248
|
|
package/src/handlers/resource.js
CHANGED
|
@@ -511,6 +511,11 @@ export async function handleHead(request, reply) {
|
|
|
511
511
|
* Handle PUT request
|
|
512
512
|
*/
|
|
513
513
|
export async function handlePut(request, reply) {
|
|
514
|
+
// Read-only mode - block all writes
|
|
515
|
+
if (request.config?.readOnly) {
|
|
516
|
+
return reply.code(405).send({ error: 'Method Not Allowed', message: 'Server is in read-only mode' });
|
|
517
|
+
}
|
|
518
|
+
|
|
514
519
|
const { urlPath, storagePath, resourceUrl } = getRequestPaths(request);
|
|
515
520
|
const connegEnabled = request.connegEnabled || false;
|
|
516
521
|
|
|
@@ -644,6 +649,11 @@ export async function handlePut(request, reply) {
|
|
|
644
649
|
* Handle DELETE request
|
|
645
650
|
*/
|
|
646
651
|
export async function handleDelete(request, reply) {
|
|
652
|
+
// Read-only mode - block all writes
|
|
653
|
+
if (request.config?.readOnly) {
|
|
654
|
+
return reply.code(405).send({ error: 'Method Not Allowed', message: 'Server is in read-only mode' });
|
|
655
|
+
}
|
|
656
|
+
|
|
647
657
|
const { storagePath, resourceUrl } = getRequestPaths(request);
|
|
648
658
|
|
|
649
659
|
// Check if resource exists and get current ETag
|
|
@@ -716,6 +726,11 @@ export async function handleOptions(request, reply) {
|
|
|
716
726
|
* Supports N3 Patch format (text/n3) and SPARQL Update for updating RDF resources
|
|
717
727
|
*/
|
|
718
728
|
export async function handlePatch(request, reply) {
|
|
729
|
+
// Read-only mode - block all writes
|
|
730
|
+
if (request.config?.readOnly) {
|
|
731
|
+
return reply.code(405).send({ error: 'Method Not Allowed', message: 'Server is in read-only mode' });
|
|
732
|
+
}
|
|
733
|
+
|
|
719
734
|
const { urlPath, storagePath, resourceUrl } = getRequestPaths(request);
|
|
720
735
|
|
|
721
736
|
// Don't allow PATCH to containers
|
package/src/server.js
CHANGED
|
@@ -135,6 +135,7 @@ export function createServer(options = {}) {
|
|
|
135
135
|
fastify.decorateRequest('mashlibVersion', null);
|
|
136
136
|
fastify.decorateRequest('solidosUiEnabled', null);
|
|
137
137
|
fastify.decorateRequest('defaultQuota', null);
|
|
138
|
+
fastify.decorateRequest('config', null);
|
|
138
139
|
fastify.addHook('onRequest', async (request) => {
|
|
139
140
|
request.connegEnabled = connegEnabled;
|
|
140
141
|
request.notificationsEnabled = notificationsEnabled;
|
|
@@ -146,6 +147,7 @@ export function createServer(options = {}) {
|
|
|
146
147
|
request.mashlibVersion = mashlibVersion;
|
|
147
148
|
request.solidosUiEnabled = solidosUiEnabled;
|
|
148
149
|
request.defaultQuota = defaultQuota;
|
|
150
|
+
request.config = { public: options.public, readOnly: options.readOnly };
|
|
149
151
|
|
|
150
152
|
// Extract pod name from subdomain if enabled
|
|
151
153
|
if (subdomainsEnabled && baseDomain) {
|