Skip to content

Commit 1d16ab1

Browse files
Merge pull request JavaScriptSolidServer#298 from JavaScriptSolidServer/issue-297-publictypeindex-acl
fix: publicTypeIndex.jsonld should be publicly readable
2 parents 271fda3 + 97a4c31 commit 1d16ab1

6 files changed

Lines changed: 89 additions & 2 deletions

File tree

src/handlers/container.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,14 @@ export async function createPodStructure(name, webId, podUri, issuer, defaultQuo
197197
const privateAcl = generatePrivateAcl(`${podUri}private/`, webId);
198198
await storage.write(`${podPath}private/.acl`, serializeAcl(privateAcl));
199199

200-
// settings folder: owner only
200+
// settings folder: owner only (contains private preferences)
201201
const settingsAcl = generatePrivateAcl(`${podUri}settings/`, webId);
202202
await storage.write(`${podPath}settings/.acl`, serializeAcl(settingsAcl));
203203

204+
// publicTypeIndex: public read, overrides the private default inherited from /settings/
205+
const publicTypeIndexAcl = generateOwnerAcl(`${podUri}settings/publicTypeIndex.jsonld`, webId, false);
206+
await storage.write(`${podPath}settings/publicTypeIndex.jsonld.acl`, serializeAcl(publicTypeIndexAcl));
207+
204208
// Inbox: owner full, public append
205209
const inboxAcl = generateInboxAcl(`${podUri}inbox/`, webId);
206210
await storage.write(`${podPath}inbox/.acl`, serializeAcl(inboxAcl));

src/server.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,10 @@ export function createServer(options = {}) {
622622
const settingsAcl = generatePrivateAcl(`${podUri}settings/`, webId);
623623
await storage.write('/settings/.acl', serializeAcl(settingsAcl));
624624

625+
// publicTypeIndex: public read, overrides the private default inherited from /settings/
626+
const publicTypeIndexAcl = generateOwnerAcl(`${podUri}settings/publicTypeIndex.jsonld`, webId, false);
627+
await storage.write('/settings/publicTypeIndex.jsonld.acl', serializeAcl(publicTypeIndexAcl));
628+
625629
const inboxAcl = generateInboxAcl(`${podUri}inbox/`, webId);
626630
await storage.write('/inbox/.acl', serializeAcl(inboxAcl));
627631

src/utils/url.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,10 @@ export function getContentType(filePath) {
237237
'.md': 'text/markdown',
238238
'.m3u': 'audio/mpegurl',
239239
'.m3u8': 'application/vnd.apple.mpegurl',
240-
'.pls': 'audio/x-scpls'
240+
'.pls': 'audio/x-scpls',
241+
// Solid ACL/meta as extensions (e.g. publicTypeIndex.jsonld.acl)
242+
'.acl': 'application/ld+json',
243+
'.meta': 'application/ld+json'
241244
};
242245

243246
// Solid convention dotfiles (.acl, .meta) are RDF resources. path.extname

test/idp.test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,55 @@ describe('Identity Provider - Single-user mode landing', () => {
409409
});
410410
});
411411

412+
// Root-level pod (singleUserName: '/') — verifies createRootPodStructure wires
413+
// publicTypeIndex as public-read and privateTypeIndex as owner-only.
414+
// Regression coverage for #297.
415+
describe('Identity Provider - Root pod type index ACLs', () => {
416+
let server;
417+
let baseUrl;
418+
const ROOT_POD_DATA_DIR = './test-data-idp-root-pod';
419+
420+
before(async () => {
421+
await fs.remove(ROOT_POD_DATA_DIR);
422+
await fs.ensureDir(ROOT_POD_DATA_DIR);
423+
424+
const port = await getAvailablePort();
425+
baseUrl = `http://${TEST_HOST}:${port}`;
426+
427+
server = createServer({
428+
logger: false,
429+
root: ROOT_POD_DATA_DIR,
430+
idp: true,
431+
idpIssuer: baseUrl,
432+
singleUser: true,
433+
singleUserName: '/',
434+
forceCloseConnections: true,
435+
});
436+
437+
await server.listen({ port, host: TEST_HOST });
438+
});
439+
440+
after(async () => {
441+
await server.close();
442+
await fs.remove(ROOT_POD_DATA_DIR);
443+
});
444+
445+
it('publicTypeIndex is readable without auth', async () => {
446+
const res = await fetch(`${baseUrl}/settings/publicTypeIndex.jsonld`);
447+
assert.strictEqual(res.status, 200);
448+
});
449+
450+
it('privateTypeIndex requires auth', async () => {
451+
const res = await fetch(`${baseUrl}/settings/privateTypeIndex.jsonld`);
452+
assert.strictEqual(res.status, 401);
453+
});
454+
455+
it('prefs requires auth', async () => {
456+
const res = await fetch(`${baseUrl}/settings/prefs.jsonld`);
457+
assert.strictEqual(res.status, 401);
458+
});
459+
});
460+
412461
describe('Identity Provider - Accounts', () => {
413462
let server;
414463
let accountsUrl;

test/pod.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,21 @@ describe('Pod Lifecycle', () => {
115115
const privIndex = await request('/dan/settings/privateTypeIndex.jsonld', { auth: 'dan' });
116116
assertStatus(privIndex, 200);
117117
});
118+
119+
it('should make publicTypeIndex publicly readable but keep privateTypeIndex private', async () => {
120+
await createTestPod('elsa');
121+
122+
// publicTypeIndex: no auth required (per Solid Type Indexes spec)
123+
const pubIndex = await request('/elsa/settings/publicTypeIndex.jsonld');
124+
assertStatus(pubIndex, 200);
125+
126+
// privateTypeIndex: auth required
127+
const privIndex = await request('/elsa/settings/privateTypeIndex.jsonld');
128+
assertStatus(privIndex, 401);
129+
130+
// prefs: auth required (private by inheritance from /settings/)
131+
const prefs = await request('/elsa/settings/prefs.jsonld');
132+
assertStatus(prefs, 401);
133+
});
118134
});
119135
});

test/url.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,15 @@ describe('getContentType', () => {
106106
assert.strictEqual(getContentType('/alice/notes/my-acl-plan.md'), 'text/markdown');
107107
});
108108
});
109+
110+
describe('.acl / .meta as extensions (#297)', () => {
111+
it('treats *.acl (extension) as application/ld+json', () => {
112+
assert.strictEqual(getContentType('/settings/publicTypeIndex.jsonld.acl'), 'application/ld+json');
113+
assert.strictEqual(getContentType('/alice/private/secret.json.acl'), 'application/ld+json');
114+
});
115+
116+
it('treats *.meta (extension) as application/ld+json', () => {
117+
assert.strictEqual(getContentType('/alice/resource.meta'), 'application/ld+json');
118+
});
119+
});
109120
});

0 commit comments

Comments
 (0)