Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/handlers/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,14 @@ export async function createPodStructure(name, webId, podUri, issuer, defaultQuo
const privateAcl = generatePrivateAcl(`${podUri}private/`, webId);
await storage.write(`${podPath}private/.acl`, serializeAcl(privateAcl));

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

// publicTypeIndex: public read, overrides the private default inherited from /settings/
const publicTypeIndexAcl = generateOwnerAcl(`${podUri}settings/publicTypeIndex.jsonld`, webId, false);
await storage.write(`${podPath}settings/publicTypeIndex.jsonld.acl`, serializeAcl(publicTypeIndexAcl));
Comment on lines +204 to +206
Comment on lines +204 to +206

// Inbox: owner full, public append
const inboxAcl = generateInboxAcl(`${podUri}inbox/`, webId);
await storage.write(`${podPath}inbox/.acl`, serializeAcl(inboxAcl));
Expand Down
4 changes: 4 additions & 0 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,10 @@ export function createServer(options = {}) {
const settingsAcl = generatePrivateAcl(`${podUri}settings/`, webId);
await storage.write('/settings/.acl', serializeAcl(settingsAcl));

// publicTypeIndex: public read, overrides the private default inherited from /settings/
const publicTypeIndexAcl = generateOwnerAcl(`${podUri}settings/publicTypeIndex.jsonld`, webId, false);
await storage.write('/settings/publicTypeIndex.jsonld.acl', serializeAcl(publicTypeIndexAcl));
Comment on lines +625 to +627
Comment on lines +625 to +627
Comment on lines +625 to +627

const inboxAcl = generateInboxAcl(`${podUri}inbox/`, webId);
await storage.write('/inbox/.acl', serializeAcl(inboxAcl));

Expand Down
5 changes: 4 additions & 1 deletion src/utils/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ export function getContentType(filePath) {
'.md': 'text/markdown',
'.m3u': 'audio/mpegurl',
'.m3u8': 'application/vnd.apple.mpegurl',
'.pls': 'audio/x-scpls'
'.pls': 'audio/x-scpls',
// Solid ACL/meta as extensions (e.g. publicTypeIndex.jsonld.acl)
'.acl': 'application/ld+json',
'.meta': 'application/ld+json'
Comment on lines +241 to +243
};

// Solid convention dotfiles (.acl, .meta) are RDF resources. path.extname
Expand Down
49 changes: 49 additions & 0 deletions test/idp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,55 @@ describe('Identity Provider - Single-user mode landing', () => {
});
});

// Root-level pod (singleUserName: '/') — verifies createRootPodStructure wires
// publicTypeIndex as public-read and privateTypeIndex as owner-only.
// Regression coverage for #297.
describe('Identity Provider - Root pod type index ACLs', () => {
let server;
let baseUrl;
const ROOT_POD_DATA_DIR = './test-data-idp-root-pod';

before(async () => {
await fs.remove(ROOT_POD_DATA_DIR);
await fs.ensureDir(ROOT_POD_DATA_DIR);

const port = await getAvailablePort();
baseUrl = `http://${TEST_HOST}:${port}`;

server = createServer({
logger: false,
root: ROOT_POD_DATA_DIR,
idp: true,
idpIssuer: baseUrl,
singleUser: true,
singleUserName: '/',
forceCloseConnections: true,
});

await server.listen({ port, host: TEST_HOST });
});

after(async () => {
await server.close();
await fs.remove(ROOT_POD_DATA_DIR);
});

it('publicTypeIndex is readable without auth', async () => {
const res = await fetch(`${baseUrl}/settings/publicTypeIndex.jsonld`);
assert.strictEqual(res.status, 200);
});

it('privateTypeIndex requires auth', async () => {
const res = await fetch(`${baseUrl}/settings/privateTypeIndex.jsonld`);
assert.strictEqual(res.status, 401);
});

it('prefs requires auth', async () => {
const res = await fetch(`${baseUrl}/settings/prefs.jsonld`);
assert.strictEqual(res.status, 401);
});
});

describe('Identity Provider - Accounts', () => {
let server;
let accountsUrl;
Expand Down
16 changes: 16 additions & 0 deletions test/pod.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,21 @@ describe('Pod Lifecycle', () => {
const privIndex = await request('/dan/settings/privateTypeIndex.jsonld', { auth: 'dan' });
assertStatus(privIndex, 200);
});

it('should make publicTypeIndex publicly readable but keep privateTypeIndex private', async () => {
await createTestPod('elsa');

// publicTypeIndex: no auth required (per Solid Type Indexes spec)
const pubIndex = await request('/elsa/settings/publicTypeIndex.jsonld');
assertStatus(pubIndex, 200);

// privateTypeIndex: auth required
const privIndex = await request('/elsa/settings/privateTypeIndex.jsonld');
assertStatus(privIndex, 401);

// prefs: auth required (private by inheritance from /settings/)
const prefs = await request('/elsa/settings/prefs.jsonld');
assertStatus(prefs, 401);
});
});
});
11 changes: 11 additions & 0 deletions test/url.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,15 @@ describe('getContentType', () => {
assert.strictEqual(getContentType('/alice/notes/my-acl-plan.md'), 'text/markdown');
});
});

describe('.acl / .meta as extensions (#297)', () => {
it('treats *.acl (extension) as application/ld+json', () => {
assert.strictEqual(getContentType('/settings/publicTypeIndex.jsonld.acl'), 'application/ld+json');
assert.strictEqual(getContentType('/alice/private/secret.json.acl'), 'application/ld+json');
});

it('treats *.meta (extension) as application/ld+json', () => {
assert.strictEqual(getContentType('/alice/resource.meta'), 'application/ld+json');
});
});
});