javascript-solid-server 0.0.17 → 0.0.19
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 +11 -1
- package/bin/jss.js +3 -0
- package/package.json +1 -1
- package/src/handlers/resource.js +17 -1
- package/src/idp/interactions.js +13 -12
- package/src/ldp/headers.js +30 -0
- package/src/utils/url.js +18 -2
- package/test-data-idp-accounts/.idp/accounts/95d4d470-1e8c-4b17-bcff-a62b8a54813b.json +9 -0
- package/test-data-idp-accounts/.idp/accounts/_email_index.json +1 -1
- package/test-data-idp-accounts/.idp/accounts/_webid_index.json +1 -1
- package/test-data-idp-accounts/.idp/keys/jwks.json +8 -8
- package/test-data-idp-accounts/.idp/accounts/ea61c611-2dda-41b8-8787-c6a22c5f33cc.json +0 -9
|
@@ -55,7 +55,17 @@
|
|
|
55
55
|
"WebFetch(domain:communitysolidserver.github.io)",
|
|
56
56
|
"WebFetch(domain:solidos.github.io)",
|
|
57
57
|
"WebFetch(domain:solidcommunity.net)",
|
|
58
|
-
"WebFetch(domain:www.npmjs.com)"
|
|
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:*)"
|
|
59
69
|
]
|
|
60
70
|
}
|
|
61
71
|
}
|
package/bin/jss.js
CHANGED
|
@@ -59,6 +59,9 @@ program
|
|
|
59
59
|
try {
|
|
60
60
|
const config = await loadConfig(options, options.config);
|
|
61
61
|
|
|
62
|
+
// Set DATA_ROOT env var so all modules use the same data directory
|
|
63
|
+
process.env.DATA_ROOT = path.resolve(config.root);
|
|
64
|
+
|
|
62
65
|
if (options.printConfig) {
|
|
63
66
|
printConfig(config);
|
|
64
67
|
process.exit(0);
|
package/package.json
CHANGED
package/src/handlers/resource.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as storage from '../storage/filesystem.js';
|
|
2
|
-
import { getAllHeaders } from '../ldp/headers.js';
|
|
2
|
+
import { getAllHeaders, getNotFoundHeaders } from '../ldp/headers.js';
|
|
3
3
|
import { generateContainerJsonLd, serializeJsonLd } from '../ldp/container.js';
|
|
4
4
|
import { isContainer, getContentType, isRdfContentType, getEffectiveUrlPath } from '../utils/url.js';
|
|
5
5
|
import { parseN3Patch, applyN3Patch, validatePatch } from '../patch/n3-patch.js';
|
|
@@ -37,6 +37,10 @@ export async function handleGet(request, reply) {
|
|
|
37
37
|
const stats = await storage.stat(storagePath);
|
|
38
38
|
|
|
39
39
|
if (!stats) {
|
|
40
|
+
const origin = request.headers.origin;
|
|
41
|
+
const connegEnabled = request.connegEnabled || false;
|
|
42
|
+
const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
|
|
43
|
+
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
|
|
40
44
|
return reply.code(404).send({ error: 'Not Found' });
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -219,6 +223,10 @@ export async function handleHead(request, reply) {
|
|
|
219
223
|
const stats = await storage.stat(storagePath);
|
|
220
224
|
|
|
221
225
|
if (!stats) {
|
|
226
|
+
const origin = request.headers.origin;
|
|
227
|
+
const connegEnabled = request.connegEnabled || false;
|
|
228
|
+
const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
|
|
229
|
+
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
|
|
222
230
|
return reply.code(404).send();
|
|
223
231
|
}
|
|
224
232
|
|
|
@@ -366,6 +374,10 @@ export async function handleDelete(request, reply) {
|
|
|
366
374
|
// Check if resource exists and get current ETag
|
|
367
375
|
const stats = await storage.stat(storagePath);
|
|
368
376
|
if (!stats) {
|
|
377
|
+
const origin = request.headers.origin;
|
|
378
|
+
const connegEnabled = request.connegEnabled || false;
|
|
379
|
+
const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
|
|
380
|
+
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
|
|
369
381
|
return reply.code(404).send({ error: 'Not Found' });
|
|
370
382
|
}
|
|
371
383
|
|
|
@@ -442,6 +454,10 @@ export async function handlePatch(request, reply) {
|
|
|
442
454
|
// Check if resource exists
|
|
443
455
|
const stats = await storage.stat(storagePath);
|
|
444
456
|
if (!stats) {
|
|
457
|
+
const origin = request.headers.origin;
|
|
458
|
+
const connegEnabled = request.connegEnabled || false;
|
|
459
|
+
const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
|
|
460
|
+
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
|
|
445
461
|
return reply.code(404).send({ error: 'Not Found' });
|
|
446
462
|
}
|
|
447
463
|
|
package/src/idp/interactions.js
CHANGED
|
@@ -118,20 +118,22 @@ export async function handleLogin(request, reply, provider) {
|
|
|
118
118
|
|
|
119
119
|
request.log.info({ accountId: account.id, uid }, 'Login successful');
|
|
120
120
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
// We use interactionResult to get the redirect URL, then save it and return JSON
|
|
127
|
-
|
|
128
|
-
// Save the login result to the interaction for programmatic clients
|
|
129
|
-
// This allows the auth endpoint to continue the flow when resumed
|
|
121
|
+
// Detect if this is a browser (wants HTML/redirect) or programmatic client (wants JSON)
|
|
122
|
+
const acceptHeader = request.headers.accept || '';
|
|
123
|
+
const wantsBrowserRedirect = acceptHeader.includes('text/html') && !acceptHeader.includes('application/json');
|
|
124
|
+
|
|
125
|
+
// Save the login result to the interaction
|
|
130
126
|
interaction.result = result;
|
|
131
127
|
await interaction.save(interaction.exp - Math.floor(Date.now() / 1000));
|
|
132
128
|
|
|
133
|
-
// For
|
|
134
|
-
|
|
129
|
+
// For browsers (mashlib, etc): do a proper HTTP redirect
|
|
130
|
+
if (wantsBrowserRedirect) {
|
|
131
|
+
reply.hijack();
|
|
132
|
+
return provider.interactionFinished(request.raw, reply.raw, result, { mergeWithLastSubmission: false });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// For CTH and programmatic clients: return JSON with location
|
|
136
|
+
// CTH expects a 200 response with "location" in body (CSS v3+ style)
|
|
135
137
|
try {
|
|
136
138
|
reply.hijack();
|
|
137
139
|
|
|
@@ -188,7 +190,6 @@ export async function handleLogin(request, reply, provider) {
|
|
|
188
190
|
request.log.warn({ err: err.message, errName: err.name, uid }, 'interactionFinished failed, using fallback');
|
|
189
191
|
|
|
190
192
|
// Fallback: return the redirect URL for manual following
|
|
191
|
-
// The interaction result is already saved above
|
|
192
193
|
const redirectTo = `/idp/auth/${uid}`;
|
|
193
194
|
return reply
|
|
194
195
|
.code(200)
|
package/src/ldp/headers.js
CHANGED
|
@@ -108,3 +108,33 @@ export function getAllHeaders({ isContainer = false, etag = null, contentType =
|
|
|
108
108
|
...getCorsHeaders(origin)
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get headers for 404 responses (non-existent resources)
|
|
114
|
+
* These headers tell clients what methods are supported for creating the resource
|
|
115
|
+
* @param {object} options
|
|
116
|
+
* @returns {object}
|
|
117
|
+
*/
|
|
118
|
+
export function getNotFoundHeaders({ resourceUrl = null, origin = null, connegEnabled = false }) {
|
|
119
|
+
// Determine if this would be a container based on URL ending with /
|
|
120
|
+
const isContainer = resourceUrl?.endsWith('/') || false;
|
|
121
|
+
const aclUrl = resourceUrl ? getAclUrl(resourceUrl, isContainer) : null;
|
|
122
|
+
|
|
123
|
+
// Get Accept-* headers
|
|
124
|
+
const acceptHeaders = getAcceptHeaders(connegEnabled, isContainer);
|
|
125
|
+
|
|
126
|
+
const headers = {
|
|
127
|
+
...getCorsHeaders(origin),
|
|
128
|
+
'Link': aclUrl ? `<${aclUrl}>; rel="acl"` : '',
|
|
129
|
+
'Accept-Patch': 'text/n3, application/sparql-update',
|
|
130
|
+
'Accept-Put': acceptHeaders['Accept-Put'] || 'application/ld+json, */*',
|
|
131
|
+
'Allow': 'GET, HEAD, PUT, PATCH, OPTIONS' + (isContainer ? ', POST' : ''),
|
|
132
|
+
'Vary': connegEnabled ? 'Accept, Authorization, Origin' : 'Authorization, Origin'
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (isContainer && acceptHeaders['Accept-Post']) {
|
|
136
|
+
headers['Accept-Post'] = acceptHeaders['Accept-Post'];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return headers;
|
|
140
|
+
}
|
package/src/utils/url.js
CHANGED
|
@@ -120,7 +120,13 @@ export function getContentType(filePath) {
|
|
|
120
120
|
'.jpeg': 'image/jpeg',
|
|
121
121
|
'.gif': 'image/gif',
|
|
122
122
|
'.svg': 'image/svg+xml',
|
|
123
|
-
'.pdf': 'application/pdf'
|
|
123
|
+
'.pdf': 'application/pdf',
|
|
124
|
+
'.ttl': 'text/turtle',
|
|
125
|
+
'.n3': 'text/n3',
|
|
126
|
+
'.nt': 'application/n-triples',
|
|
127
|
+
'.rdf': 'application/rdf+xml',
|
|
128
|
+
'.nq': 'application/n-quads',
|
|
129
|
+
'.trig': 'application/trig'
|
|
124
130
|
};
|
|
125
131
|
return types[ext] || 'application/octet-stream';
|
|
126
132
|
}
|
|
@@ -131,5 +137,15 @@ export function getContentType(filePath) {
|
|
|
131
137
|
* @returns {boolean}
|
|
132
138
|
*/
|
|
133
139
|
export function isRdfContentType(contentType) {
|
|
134
|
-
|
|
140
|
+
const rdfTypes = [
|
|
141
|
+
'application/ld+json',
|
|
142
|
+
'application/json',
|
|
143
|
+
'text/turtle',
|
|
144
|
+
'text/n3',
|
|
145
|
+
'application/n-triples',
|
|
146
|
+
'application/rdf+xml',
|
|
147
|
+
'application/n-quads',
|
|
148
|
+
'application/trig'
|
|
149
|
+
];
|
|
150
|
+
return rdfTypes.includes(contentType);
|
|
135
151
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "95d4d470-1e8c-4b17-bcff-a62b8a54813b",
|
|
3
|
+
"email": "credtest@example.com",
|
|
4
|
+
"passwordHash": "$2b$10$dfsBq19GkIsL/qaer5LlxeSD8cMFJS4U8o7Wt06f/u29Cdhc8eJ3i",
|
|
5
|
+
"webId": "http://localhost:3101/credtest/#me",
|
|
6
|
+
"podName": "credtest",
|
|
7
|
+
"createdAt": "2025-12-27T13:57:26.941Z",
|
|
8
|
+
"lastLogin": "2025-12-27T13:57:27.182Z"
|
|
9
|
+
}
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
"keys": [
|
|
4
4
|
{
|
|
5
5
|
"kty": "EC",
|
|
6
|
-
"x": "
|
|
7
|
-
"y": "
|
|
6
|
+
"x": "_0WHR5nff4EnQiPYLZEuPKNmV7qWxizbdY6NeepX4gc",
|
|
7
|
+
"y": "JowkolFdgQSs4GZ7nGN5ljMII8QAxHDYv2Qp4x5eHgo",
|
|
8
8
|
"crv": "P-256",
|
|
9
|
-
"d": "
|
|
10
|
-
"kid": "
|
|
9
|
+
"d": "zvFCMQvrEJwSTkfloHws3EFQRA0o5abR22so5LcCTao",
|
|
10
|
+
"kid": "ed8976e4-f907-4fb4-82ff-846a62807dd7",
|
|
11
11
|
"use": "sig",
|
|
12
12
|
"alg": "ES256",
|
|
13
|
-
"iat":
|
|
13
|
+
"iat": 1766843846
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
"cookieKeys": [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"oQ-IdseWVLpOiwPmpYeY1ShFHI3lLHRiW6NsYeNBpP0",
|
|
19
|
+
"xrKoXpxC4O7wOZ65jNr4Kw7DnFYdkyK4pw2HOJE4D10"
|
|
20
20
|
],
|
|
21
|
-
"createdAt": "2025-12-27T13:
|
|
21
|
+
"createdAt": "2025-12-27T13:57:26.882Z"
|
|
22
22
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "ea61c611-2dda-41b8-8787-c6a22c5f33cc",
|
|
3
|
-
"email": "credtest@example.com",
|
|
4
|
-
"passwordHash": "$2b$10$EVWVKsbQ4A6DdswLEK/0ZOclDrBHBo/9GbWeP1s5uvxy4jWjTnY0m",
|
|
5
|
-
"webId": "http://localhost:3101/credtest/#me",
|
|
6
|
-
"podName": "credtest",
|
|
7
|
-
"createdAt": "2025-12-27T13:12:43.961Z",
|
|
8
|
-
"lastLogin": "2025-12-27T13:12:44.207Z"
|
|
9
|
-
}
|