javascript-solid-server 0.0.32 → 0.0.34

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -28,6 +28,11 @@ export async function authorize(request, reply) {
28
28
  // Get WebID from token (supports both simple and Solid-OIDC tokens)
29
29
  const { webId, error: authError } = await getWebIdFromRequestAsync(request);
30
30
 
31
+ // Log auth failures for debugging
32
+ if (authError) {
33
+ request.log.warn({ authError, method, urlPath, hasAuth: !!request.headers.authorization }, 'Auth error');
34
+ }
35
+
31
36
  // Get effective storage path (includes pod name in subdomain mode)
32
37
  const storagePath = getEffectiveUrlPath(request);
33
38
 
@@ -89,16 +89,19 @@ export async function verifySolidOidc(request) {
89
89
  } catch (err) {
90
90
  // Handle specific JWT errors
91
91
  if (err.code === 'ERR_JWT_EXPIRED') {
92
+ console.error('Solid-OIDC: Access token expired');
92
93
  return { webId: null, error: 'Access token expired' };
93
94
  }
94
95
  if (err.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
96
+ console.error('Solid-OIDC: Invalid token signature');
95
97
  return { webId: null, error: 'Invalid token signature' };
96
98
  }
97
99
  if (err.code === 'ERR_JWKS_NO_MATCHING_KEY') {
100
+ console.error('Solid-OIDC: No matching key found in JWKS');
98
101
  return { webId: null, error: 'No matching key found in JWKS' };
99
102
  }
100
103
 
101
- console.error('Solid-OIDC verification error:', err.message);
104
+ console.error('Solid-OIDC verification error:', err.code, err.message);
102
105
  return { webId: null, error: 'Token verification failed' };
103
106
  }
104
107
  }
@@ -177,8 +180,8 @@ async function verifyDpopProof(dpopProof, request, accessToken) {
177
180
  return { thumbprint, error: null };
178
181
 
179
182
  } catch (err) {
180
- console.error('DPoP verification error:', err.message);
181
- return { thumbprint: null, error: 'Invalid DPoP proof' };
183
+ console.error('DPoP verification error:', err.code, err.message);
184
+ return { thumbprint: null, error: 'Invalid DPoP proof: ' + err.message };
182
185
  }
183
186
  }
184
187
 
@@ -125,35 +125,35 @@ export async function handlePost(request, reply) {
125
125
  * Create pod directory structure (reusable for registration)
126
126
  * @param {string} name - Pod name (username)
127
127
  * @param {string} webId - User's WebID URI
128
- * @param {string} baseUrl - Base URL (without trailing slash)
128
+ * @param {string} podUri - Pod root URI (e.g., https://alice.example.com/ or https://example.com/alice/)
129
+ * @param {string} issuer - OIDC issuer URI
129
130
  */
130
- export async function createPodStructure(name, webId, baseUrl) {
131
+ export async function createPodStructure(name, webId, podUri, issuer) {
131
132
  const podPath = `/${name}/`;
132
- const podUri = `${baseUrl}/${name}/`;
133
- const issuer = baseUrl + '/';
134
133
 
135
134
  // Create pod directory structure
135
+ // Uses 'Settings' (capital S) for mashlib compatibility
136
136
  await storage.createContainer(podPath);
137
137
  await storage.createContainer(`${podPath}inbox/`);
138
138
  await storage.createContainer(`${podPath}public/`);
139
139
  await storage.createContainer(`${podPath}private/`);
140
- await storage.createContainer(`${podPath}settings/`);
140
+ await storage.createContainer(`${podPath}Settings/`);
141
141
  await storage.createContainer(`${podPath}profile/`);
142
142
 
143
143
  // Generate and write WebID profile at /profile/card (standard Solid location)
144
144
  const profileHtml = generateProfile({ webId, name, podUri, issuer });
145
145
  await storage.write(`${podPath}profile/card`, profileHtml);
146
146
 
147
- // Generate and write preferences
147
+ // Generate and write preferences (mashlib-compatible paths)
148
148
  const prefs = generatePreferences({ webId, podUri });
149
- await storage.write(`${podPath}settings/prefs`, serialize(prefs));
149
+ await storage.write(`${podPath}Settings/Preferences.ttl`, serialize(prefs));
150
150
 
151
- // Generate and write type indexes
152
- const publicTypeIndex = generateTypeIndex(`${podUri}settings/publicTypeIndex`);
153
- await storage.write(`${podPath}settings/publicTypeIndex`, serialize(publicTypeIndex));
151
+ // Generate and write type indexes with .ttl extension for mashlib
152
+ const publicTypeIndex = generateTypeIndex(`${podUri}Settings/publicTypeIndex.ttl`);
153
+ await storage.write(`${podPath}Settings/publicTypeIndex.ttl`, serialize(publicTypeIndex));
154
154
 
155
- const privateTypeIndex = generateTypeIndex(`${podUri}settings/privateTypeIndex`);
156
- await storage.write(`${podPath}settings/privateTypeIndex`, serialize(privateTypeIndex));
155
+ const privateTypeIndex = generateTypeIndex(`${podUri}Settings/privateTypeIndex.ttl`);
156
+ await storage.write(`${podPath}Settings/privateTypeIndex.ttl`, serialize(privateTypeIndex));
157
157
 
158
158
  // Create default ACL files
159
159
  // Pod root: owner full control, public read
@@ -165,8 +165,8 @@ export async function createPodStructure(name, webId, baseUrl) {
165
165
  await storage.write(`${podPath}private/.acl`, serializeAcl(privateAcl));
166
166
 
167
167
  // Settings folder: owner only
168
- const settingsAcl = generatePrivateAcl(`${podUri}settings/`, webId);
169
- await storage.write(`${podPath}settings/.acl`, serializeAcl(settingsAcl));
168
+ const settingsAcl = generatePrivateAcl(`${podUri}Settings/`, webId);
169
+ await storage.write(`${podPath}Settings/.acl`, serializeAcl(settingsAcl));
170
170
 
171
171
  // Inbox: owner full, public append
172
172
  const inboxAcl = generateInboxAcl(`${podUri}inbox/`, webId);
@@ -253,7 +253,7 @@ export async function handleCreatePod(request, reply) {
253
253
 
254
254
  try {
255
255
  // Use shared pod creation function
256
- await createPodStructure(name, webId, baseUri);
256
+ await createPodStructure(name, webId, podUri, issuer);
257
257
  } catch (err) {
258
258
  console.error('Pod creation error:', err);
259
259
  // Cleanup on failure
@@ -146,6 +146,42 @@ export async function handleGet(request, reply) {
146
146
  return reply.type('text/html').send(html);
147
147
  }
148
148
 
149
+ // Check if Turtle/N3 format is requested via content negotiation
150
+ const acceptHeader = request.headers.accept || '';
151
+ const wantsTurtle = connegEnabled && (
152
+ acceptHeader.includes('text/turtle') ||
153
+ acceptHeader.includes('text/n3') ||
154
+ acceptHeader.includes('application/n-triples')
155
+ );
156
+
157
+ if (wantsTurtle) {
158
+ // Convert container JSON-LD to Turtle
159
+ try {
160
+ const { content: turtleContent } = await fromJsonLd(
161
+ jsonLd,
162
+ 'text/turtle',
163
+ resourceUrl,
164
+ true
165
+ );
166
+
167
+ const headers = getAllHeaders({
168
+ isContainer: true,
169
+ etag: stats.etag,
170
+ contentType: 'text/turtle',
171
+ origin,
172
+ resourceUrl,
173
+ connegEnabled
174
+ });
175
+ headers['Vary'] = 'Accept';
176
+
177
+ Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
178
+ return reply.send(turtleContent);
179
+ } catch (err) {
180
+ // Fall through to JSON-LD if conversion fails
181
+ console.error('Failed to convert container to Turtle:', err.message);
182
+ }
183
+ }
184
+
149
185
  const headers = getAllHeaders({
150
186
  isContainer: true,
151
187
  etag: stats.etag,
@@ -196,7 +232,9 @@ export async function handleGet(request, reply) {
196
232
  if (connegEnabled) {
197
233
  const contentStr = content.toString();
198
234
  const acceptHeader = request.headers.accept || '';
199
- const wantsTurtle = acceptHeader.includes('text/turtle') ||
235
+ // Serve Turtle if: URL ends with .ttl OR Accept header requests it
236
+ const wantsTurtle = urlPath.endsWith('.ttl') ||
237
+ acceptHeader.includes('text/turtle') ||
200
238
  acceptHeader.includes('text/n3') ||
201
239
  acceptHeader.includes('application/n-triples');
202
240
 
@@ -233,7 +271,8 @@ export async function handleGet(request, reply) {
233
271
  // Plain JSON-LD file
234
272
  try {
235
273
  const jsonLd = JSON.parse(contentStr);
236
- const targetType = selectContentType(acceptHeader, connegEnabled);
274
+ // Use Turtle if URL ends with .ttl, otherwise use Accept header preference
275
+ const targetType = wantsTurtle ? 'text/turtle' : selectContentType(acceptHeader, connegEnabled);
237
276
  const { content: outputContent, contentType: outputType } = await fromJsonLd(
238
277
  jsonLd,
239
278
  targetType,
@@ -355,9 +355,20 @@ export async function handleRegisterPost(request, reply, issuer) {
355
355
 
356
356
  try {
357
357
  // Build URLs - WebID follows standard Solid convention: /profile/card#me
358
+ const subdomainsEnabled = request.subdomainsEnabled;
359
+ const baseDomain = request.baseDomain;
358
360
  const baseUrl = issuer.endsWith('/') ? issuer.slice(0, -1) : issuer;
359
- const podUri = `${baseUrl}/${username}/`;
360
- const webId = `${podUri}profile/card#me`;
361
+
362
+ let podUri, webId;
363
+ if (subdomainsEnabled && baseDomain) {
364
+ // Subdomain mode: alice.example.com/profile/card#me
365
+ podUri = `${request.protocol}://${username}.${baseDomain}/`;
366
+ webId = `${podUri}profile/card#me`;
367
+ } else {
368
+ // Path mode: example.com/alice/profile/card#me
369
+ podUri = `${baseUrl}/${username}/`;
370
+ webId = `${podUri}profile/card#me`;
371
+ }
361
372
 
362
373
  // Check if pod already exists
363
374
  const podPath = `${username}/`;
@@ -367,7 +378,7 @@ export async function handleRegisterPost(request, reply, issuer) {
367
378
  }
368
379
 
369
380
  // Create pod structure
370
- await createPodStructure(username, webId, baseUrl);
381
+ await createPodStructure(username, webId, podUri, issuer);
371
382
 
372
383
  // Create account
373
384
  await createAccount({
@@ -90,7 +90,7 @@ export function getCorsHeaders(origin) {
90
90
  return {
91
91
  'Access-Control-Allow-Origin': origin || '*',
92
92
  'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS',
93
- 'Access-Control-Allow-Headers': 'Accept, Authorization, Content-Type, If-Match, If-None-Match, Link, Slug, Origin',
93
+ 'Access-Control-Allow-Headers': 'Accept, Authorization, Content-Type, DPoP, If-Match, If-None-Match, Link, Slug, Origin',
94
94
  'Access-Control-Expose-Headers': 'Accept-Patch, Accept-Post, Allow, Content-Type, ETag, Link, Location, Updates-Via, WAC-Allow',
95
95
  'Access-Control-Allow-Credentials': 'true',
96
96
  'Access-Control-Max-Age': '86400'
@@ -34,6 +34,8 @@ export function generateProfileJsonLd({ webId, name, podUri, issuer }) {
34
34
  'storage': { '@id': 'pim:storage', '@type': '@id' },
35
35
  'oidcIssuer': { '@id': 'solid:oidcIssuer', '@type': '@id' },
36
36
  'preferencesFile': { '@id': 'pim:preferencesFile', '@type': '@id' },
37
+ 'publicTypeIndex': { '@id': 'solid:publicTypeIndex', '@type': '@id' },
38
+ 'privateTypeIndex': { '@id': 'solid:privateTypeIndex', '@type': '@id' },
37
39
  'mainEntityOfPage': { '@id': 'schema:mainEntityOfPage', '@type': '@id' }
38
40
  },
39
41
  '@id': webId,
@@ -43,7 +45,9 @@ export function generateProfileJsonLd({ webId, name, podUri, issuer }) {
43
45
  'inbox': `${pod}inbox/`,
44
46
  'storage': pod,
45
47
  'oidcIssuer': issuer,
46
- 'preferencesFile': `${pod}settings/prefs`
48
+ 'preferencesFile': `${pod}Settings/Preferences.ttl`,
49
+ 'publicTypeIndex': `${pod}Settings/publicTypeIndex.ttl`,
50
+ 'privateTypeIndex': `${pod}Settings/privateTypeIndex.ttl`
47
51
  };
48
52
  }
49
53
 
@@ -129,6 +133,7 @@ function escapeHtml(str) {
129
133
 
130
134
  /**
131
135
  * Generate preferences file as JSON-LD
136
+ * Uses mashlib-compatible paths (Settings/Preferences.ttl)
132
137
  * @param {object} options
133
138
  * @param {string} options.webId - Full WebID URI
134
139
  * @param {string} options.podUri - Pod root URI
@@ -144,9 +149,9 @@ export function generatePreferences({ webId, podUri }) {
144
149
  'publicTypeIndex': { '@id': 'solid:publicTypeIndex', '@type': '@id' },
145
150
  'privateTypeIndex': { '@id': 'solid:privateTypeIndex', '@type': '@id' }
146
151
  },
147
- '@id': `${pod}settings/prefs`,
148
- 'publicTypeIndex': `${pod}settings/publicTypeIndex`,
149
- 'privateTypeIndex': `${pod}settings/privateTypeIndex`
152
+ '@id': `${pod}Settings/Preferences.ttl`,
153
+ 'publicTypeIndex': `${pod}Settings/publicTypeIndex.ttl`,
154
+ 'privateTypeIndex': `${pod}Settings/privateTypeIndex.ttl`
150
155
  };
151
156
  }
152
157