javascript-solid-server 0.0.3 → 0.0.5
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 +1 -1
- package/src/auth/middleware.js +97 -0
- package/src/auth/token.js +112 -0
- package/src/handlers/container.js +24 -4
- package/src/handlers/resource.js +19 -10
- package/src/ldp/headers.js +31 -6
- package/src/server.js +20 -0
- package/src/wac/checker.js +257 -0
- package/src/wac/parser.js +284 -0
- package/test/auth.test.js +175 -0
- package/test/helpers.js +38 -4
- package/test/ldp.test.js +61 -20
- package/test/pod.test.js +16 -23
- package/test/wac.test.js +189 -0
- package/benchmark-report-2025-03-31T14-25-24.234Z.json +0 -44
package/test/helpers.js
CHANGED
|
@@ -11,6 +11,9 @@ const TEST_DATA_DIR = './data';
|
|
|
11
11
|
let server = null;
|
|
12
12
|
let baseUrl = null;
|
|
13
13
|
|
|
14
|
+
// Store tokens for pods by name
|
|
15
|
+
const podTokens = new Map();
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* Start a test server on a random available port
|
|
16
19
|
* @returns {Promise<{server: object, baseUrl: string}>}
|
|
@@ -39,6 +42,8 @@ export async function stopTestServer() {
|
|
|
39
42
|
}
|
|
40
43
|
// Clean up test data
|
|
41
44
|
await fs.emptyDir(TEST_DATA_DIR);
|
|
45
|
+
// Clear tokens
|
|
46
|
+
podTokens.clear();
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/**
|
|
@@ -51,7 +56,7 @@ export function getBaseUrl() {
|
|
|
51
56
|
/**
|
|
52
57
|
* Create a pod for testing
|
|
53
58
|
* @param {string} name - Pod name
|
|
54
|
-
* @returns {Promise<{webId: string, podUri: string}>}
|
|
59
|
+
* @returns {Promise<{webId: string, podUri: string, token: string}>}
|
|
55
60
|
*/
|
|
56
61
|
export async function createTestPod(name) {
|
|
57
62
|
const res = await fetch(`${baseUrl}/.pods`, {
|
|
@@ -64,18 +69,47 @@ export async function createTestPod(name) {
|
|
|
64
69
|
throw new Error(`Failed to create pod: ${res.status}`);
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
const result = await res.json();
|
|
73
|
+
|
|
74
|
+
// Store the token for this pod
|
|
75
|
+
if (result.token) {
|
|
76
|
+
podTokens.set(name, result.token);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get token for a pod
|
|
84
|
+
* @param {string} name - Pod name
|
|
85
|
+
* @returns {string|null}
|
|
86
|
+
*/
|
|
87
|
+
export function getPodToken(name) {
|
|
88
|
+
return podTokens.get(name) || null;
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
/**
|
|
71
92
|
* Make a request to the test server
|
|
72
93
|
* @param {string} path - URL path
|
|
73
|
-
* @param {object} options - fetch options
|
|
94
|
+
* @param {object} options - fetch options (can include `auth: 'podname'` for authenticated requests)
|
|
74
95
|
* @returns {Promise<Response>}
|
|
75
96
|
*/
|
|
76
97
|
export async function request(urlPath, options = {}) {
|
|
77
98
|
const url = urlPath.startsWith('http') ? urlPath : `${baseUrl}${urlPath}`;
|
|
78
|
-
|
|
99
|
+
|
|
100
|
+
// Handle authentication
|
|
101
|
+
const { auth, ...fetchOptions } = options;
|
|
102
|
+
if (auth) {
|
|
103
|
+
const token = podTokens.get(auth);
|
|
104
|
+
if (token) {
|
|
105
|
+
fetchOptions.headers = {
|
|
106
|
+
...fetchOptions.headers,
|
|
107
|
+
'Authorization': `Bearer ${token}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return fetch(url, fetchOptions);
|
|
79
113
|
}
|
|
80
114
|
|
|
81
115
|
/**
|
package/test/ldp.test.js
CHANGED
|
@@ -41,11 +41,12 @@ describe('LDP CRUD Operations', () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('should return resource content', async () => {
|
|
44
|
-
// Create resource first
|
|
44
|
+
// Create resource first (authenticated)
|
|
45
45
|
await request('/ldptest/public/test.json', {
|
|
46
46
|
method: 'PUT',
|
|
47
47
|
headers: { 'Content-Type': 'application/json' },
|
|
48
|
-
body: JSON.stringify({ hello: 'world' })
|
|
48
|
+
body: JSON.stringify({ hello: 'world' }),
|
|
49
|
+
auth: 'ldptest'
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
const res = await request('/ldptest/public/test.json');
|
|
@@ -58,7 +59,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
58
59
|
it('should return ETag header', async () => {
|
|
59
60
|
await request('/ldptest/public/etag-test.txt', {
|
|
60
61
|
method: 'PUT',
|
|
61
|
-
body: 'test content'
|
|
62
|
+
body: 'test content',
|
|
63
|
+
auth: 'ldptest'
|
|
62
64
|
});
|
|
63
65
|
|
|
64
66
|
const res = await request('/ldptest/public/etag-test.txt');
|
|
@@ -72,7 +74,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
72
74
|
it('should return headers without body', async () => {
|
|
73
75
|
await request('/ldptest/public/head-test.txt', {
|
|
74
76
|
method: 'PUT',
|
|
75
|
-
body: 'test content'
|
|
77
|
+
body: 'test content',
|
|
78
|
+
auth: 'ldptest'
|
|
76
79
|
});
|
|
77
80
|
|
|
78
81
|
const res = await request('/ldptest/public/head-test.txt', {
|
|
@@ -101,7 +104,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
101
104
|
const res = await request('/ldptest/public/new-resource.json', {
|
|
102
105
|
method: 'PUT',
|
|
103
106
|
headers: { 'Content-Type': 'application/json' },
|
|
104
|
-
body: JSON.stringify({ created: true })
|
|
107
|
+
body: JSON.stringify({ created: true }),
|
|
108
|
+
auth: 'ldptest'
|
|
105
109
|
});
|
|
106
110
|
|
|
107
111
|
assertStatus(res, 201);
|
|
@@ -112,13 +116,15 @@ describe('LDP CRUD Operations', () => {
|
|
|
112
116
|
// Create
|
|
113
117
|
await request('/ldptest/public/update-me.txt', {
|
|
114
118
|
method: 'PUT',
|
|
115
|
-
body: 'original'
|
|
119
|
+
body: 'original',
|
|
120
|
+
auth: 'ldptest'
|
|
116
121
|
});
|
|
117
122
|
|
|
118
123
|
// Update
|
|
119
124
|
const res = await request('/ldptest/public/update-me.txt', {
|
|
120
125
|
method: 'PUT',
|
|
121
|
-
body: 'updated'
|
|
126
|
+
body: 'updated',
|
|
127
|
+
auth: 'ldptest'
|
|
122
128
|
});
|
|
123
129
|
|
|
124
130
|
assertStatus(res, 204);
|
|
@@ -132,7 +138,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
132
138
|
it('should create parent containers', async () => {
|
|
133
139
|
const res = await request('/ldptest/public/nested/deep/file.txt', {
|
|
134
140
|
method: 'PUT',
|
|
135
|
-
body: 'nested content'
|
|
141
|
+
body: 'nested content',
|
|
142
|
+
auth: 'ldptest'
|
|
136
143
|
});
|
|
137
144
|
|
|
138
145
|
assertStatus(res, 201);
|
|
@@ -145,7 +152,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
145
152
|
it('should reject PUT to container path', async () => {
|
|
146
153
|
const res = await request('/ldptest/public/invalid/', {
|
|
147
154
|
method: 'PUT',
|
|
148
|
-
body: 'cannot put to container'
|
|
155
|
+
body: 'cannot put to container',
|
|
156
|
+
auth: 'ldptest'
|
|
149
157
|
});
|
|
150
158
|
|
|
151
159
|
assertStatus(res, 409);
|
|
@@ -160,7 +168,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
160
168
|
'Content-Type': 'application/json',
|
|
161
169
|
'Slug': 'posted-resource'
|
|
162
170
|
},
|
|
163
|
-
body: JSON.stringify({ posted: true })
|
|
171
|
+
body: JSON.stringify({ posted: true }),
|
|
172
|
+
auth: 'ldptest'
|
|
164
173
|
});
|
|
165
174
|
|
|
166
175
|
assertStatus(res, 201);
|
|
@@ -179,7 +188,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
179
188
|
'Content-Type': 'text/plain',
|
|
180
189
|
'Slug': 'my-custom-name.txt'
|
|
181
190
|
},
|
|
182
|
-
body: 'slug test'
|
|
191
|
+
body: 'slug test',
|
|
192
|
+
auth: 'ldptest'
|
|
183
193
|
});
|
|
184
194
|
|
|
185
195
|
const location = res.headers.get('Location');
|
|
@@ -192,7 +202,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
192
202
|
headers: {
|
|
193
203
|
'Slug': 'new-container',
|
|
194
204
|
'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"'
|
|
195
|
-
}
|
|
205
|
+
},
|
|
206
|
+
auth: 'ldptest'
|
|
196
207
|
});
|
|
197
208
|
|
|
198
209
|
assertStatus(res, 201);
|
|
@@ -208,12 +219,14 @@ describe('LDP CRUD Operations', () => {
|
|
|
208
219
|
it('should reject POST to non-container', async () => {
|
|
209
220
|
await request('/ldptest/public/file-not-container.txt', {
|
|
210
221
|
method: 'PUT',
|
|
211
|
-
body: 'just a file'
|
|
222
|
+
body: 'just a file',
|
|
223
|
+
auth: 'ldptest'
|
|
212
224
|
});
|
|
213
225
|
|
|
214
226
|
const res = await request('/ldptest/public/file-not-container.txt', {
|
|
215
227
|
method: 'POST',
|
|
216
|
-
body: 'trying to post'
|
|
228
|
+
body: 'trying to post',
|
|
229
|
+
auth: 'ldptest'
|
|
217
230
|
});
|
|
218
231
|
|
|
219
232
|
assertStatus(res, 405);
|
|
@@ -224,11 +237,13 @@ describe('LDP CRUD Operations', () => {
|
|
|
224
237
|
it('should delete resource', async () => {
|
|
225
238
|
await request('/ldptest/public/to-delete.txt', {
|
|
226
239
|
method: 'PUT',
|
|
227
|
-
body: 'delete me'
|
|
240
|
+
body: 'delete me',
|
|
241
|
+
auth: 'ldptest'
|
|
228
242
|
});
|
|
229
243
|
|
|
230
244
|
const res = await request('/ldptest/public/to-delete.txt', {
|
|
231
|
-
method: 'DELETE'
|
|
245
|
+
method: 'DELETE',
|
|
246
|
+
auth: 'ldptest'
|
|
232
247
|
});
|
|
233
248
|
|
|
234
249
|
assertStatus(res, 204);
|
|
@@ -240,7 +255,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
240
255
|
|
|
241
256
|
it('should return 404 for non-existent', async () => {
|
|
242
257
|
const res = await request('/ldptest/public/never-existed.txt', {
|
|
243
|
-
method: 'DELETE'
|
|
258
|
+
method: 'DELETE',
|
|
259
|
+
auth: 'ldptest'
|
|
244
260
|
});
|
|
245
261
|
|
|
246
262
|
assertStatus(res, 404);
|
|
@@ -253,11 +269,13 @@ describe('LDP CRUD Operations', () => {
|
|
|
253
269
|
headers: {
|
|
254
270
|
'Slug': 'container-to-delete',
|
|
255
271
|
'Link': '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"'
|
|
256
|
-
}
|
|
272
|
+
},
|
|
273
|
+
auth: 'ldptest'
|
|
257
274
|
});
|
|
258
275
|
|
|
259
276
|
const res = await request('/ldptest/public/container-to-delete/', {
|
|
260
|
-
method: 'DELETE'
|
|
277
|
+
method: 'DELETE',
|
|
278
|
+
auth: 'ldptest'
|
|
261
279
|
});
|
|
262
280
|
|
|
263
281
|
assertStatus(res, 204);
|
|
@@ -291,7 +309,8 @@ describe('LDP CRUD Operations', () => {
|
|
|
291
309
|
it('should return Link type header for resource', async () => {
|
|
292
310
|
await request('/ldptest/public/resource-link.txt', {
|
|
293
311
|
method: 'PUT',
|
|
294
|
-
body: 'test'
|
|
312
|
+
body: 'test',
|
|
313
|
+
auth: 'ldptest'
|
|
295
314
|
});
|
|
296
315
|
|
|
297
316
|
const res = await request('/ldptest/public/resource-link.txt');
|
|
@@ -318,5 +337,27 @@ describe('LDP CRUD Operations', () => {
|
|
|
318
337
|
|
|
319
338
|
assertHeader(res, 'Accept-Post');
|
|
320
339
|
});
|
|
340
|
+
|
|
341
|
+
it('should return acl Link header for resource', async () => {
|
|
342
|
+
await request('/ldptest/public/acl-test.txt', {
|
|
343
|
+
method: 'PUT',
|
|
344
|
+
body: 'test',
|
|
345
|
+
auth: 'ldptest'
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const res = await request('/ldptest/public/acl-test.txt');
|
|
349
|
+
const link = res.headers.get('Link');
|
|
350
|
+
|
|
351
|
+
assert.ok(link.includes('rel="acl"'), 'Should have acl link relation');
|
|
352
|
+
assert.ok(link.includes('acl-test.txt.acl'), 'ACL should be resource.acl');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should return acl Link header for container', async () => {
|
|
356
|
+
const res = await request('/ldptest/public/');
|
|
357
|
+
const link = res.headers.get('Link');
|
|
358
|
+
|
|
359
|
+
assert.ok(link.includes('rel="acl"'), 'Should have acl link relation');
|
|
360
|
+
assert.ok(link.includes('.acl'), 'Should link to .acl');
|
|
361
|
+
});
|
|
321
362
|
});
|
|
322
363
|
});
|
package/test/pod.test.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
startTestServer,
|
|
9
9
|
stopTestServer,
|
|
10
10
|
request,
|
|
11
|
+
createTestPod,
|
|
11
12
|
assertStatus,
|
|
12
13
|
assertHeader,
|
|
13
14
|
assertHeaderContains
|
|
@@ -80,46 +81,38 @@ describe('Pod Lifecycle', () => {
|
|
|
80
81
|
|
|
81
82
|
describe('Pod Structure', () => {
|
|
82
83
|
it('should create standard folders', async () => {
|
|
83
|
-
await
|
|
84
|
-
method: 'POST',
|
|
85
|
-
headers: { 'Content-Type': 'application/json' },
|
|
86
|
-
body: JSON.stringify({ name: 'carol' })
|
|
87
|
-
});
|
|
84
|
+
await createTestPod('carol');
|
|
88
85
|
|
|
89
|
-
// Check inbox exists
|
|
90
|
-
const inbox = await request('/carol/inbox/');
|
|
86
|
+
// Check inbox exists (needs auth - inbox only allows public append, not read)
|
|
87
|
+
const inbox = await request('/carol/inbox/', { auth: 'carol' });
|
|
91
88
|
assertStatus(inbox, 200);
|
|
92
89
|
|
|
93
|
-
// Check public exists
|
|
90
|
+
// Check public exists (public read via root ACL default)
|
|
94
91
|
const pub = await request('/carol/public/');
|
|
95
92
|
assertStatus(pub, 200);
|
|
96
93
|
|
|
97
|
-
// Check private exists
|
|
98
|
-
const priv = await request('/carol/private/');
|
|
94
|
+
// Check private exists (needs auth)
|
|
95
|
+
const priv = await request('/carol/private/', { auth: 'carol' });
|
|
99
96
|
assertStatus(priv, 200);
|
|
100
97
|
|
|
101
|
-
// Check settings exists
|
|
102
|
-
const settings = await request('/carol/settings/');
|
|
98
|
+
// Check settings exists (needs auth)
|
|
99
|
+
const settings = await request('/carol/settings/', { auth: 'carol' });
|
|
103
100
|
assertStatus(settings, 200);
|
|
104
101
|
});
|
|
105
102
|
|
|
106
103
|
it('should create settings files', async () => {
|
|
107
|
-
await
|
|
108
|
-
method: 'POST',
|
|
109
|
-
headers: { 'Content-Type': 'application/json' },
|
|
110
|
-
body: JSON.stringify({ name: 'dan' })
|
|
111
|
-
});
|
|
104
|
+
await createTestPod('dan');
|
|
112
105
|
|
|
113
|
-
// Check prefs
|
|
114
|
-
const prefs = await request('/dan/settings/prefs');
|
|
106
|
+
// Check prefs (needs auth - settings is private)
|
|
107
|
+
const prefs = await request('/dan/settings/prefs', { auth: 'dan' });
|
|
115
108
|
assertStatus(prefs, 200);
|
|
116
109
|
|
|
117
|
-
// Check public type index
|
|
118
|
-
const pubIndex = await request('/dan/settings/publicTypeIndex');
|
|
110
|
+
// Check public type index (needs auth)
|
|
111
|
+
const pubIndex = await request('/dan/settings/publicTypeIndex', { auth: 'dan' });
|
|
119
112
|
assertStatus(pubIndex, 200);
|
|
120
113
|
|
|
121
|
-
// Check private type index
|
|
122
|
-
const privIndex = await request('/dan/settings/privateTypeIndex');
|
|
114
|
+
// Check private type index (needs auth)
|
|
115
|
+
const privIndex = await request('/dan/settings/privateTypeIndex', { auth: 'dan' });
|
|
123
116
|
assertStatus(privIndex, 200);
|
|
124
117
|
});
|
|
125
118
|
});
|
package/test/wac.test.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAC (Web Access Control) tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, before, after } from 'node:test';
|
|
6
|
+
import assert from 'node:assert';
|
|
7
|
+
import {
|
|
8
|
+
startTestServer,
|
|
9
|
+
stopTestServer,
|
|
10
|
+
request,
|
|
11
|
+
createTestPod,
|
|
12
|
+
assertStatus,
|
|
13
|
+
assertHeader,
|
|
14
|
+
getBaseUrl
|
|
15
|
+
} from './helpers.js';
|
|
16
|
+
import { parseAcl, AccessMode, generateOwnerAcl, serializeAcl } from '../src/wac/parser.js';
|
|
17
|
+
import { checkAccess, getRequiredMode } from '../src/wac/checker.js';
|
|
18
|
+
|
|
19
|
+
describe('WAC Parser', () => {
|
|
20
|
+
describe('parseAcl', () => {
|
|
21
|
+
it('should parse a simple ACL', () => {
|
|
22
|
+
const acl = {
|
|
23
|
+
'@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
|
|
24
|
+
'@graph': [{
|
|
25
|
+
'@id': '#owner',
|
|
26
|
+
'@type': 'acl:Authorization',
|
|
27
|
+
'acl:agent': { '@id': 'https://alice.example/#me' },
|
|
28
|
+
'acl:accessTo': { '@id': 'https://alice.example/resource' },
|
|
29
|
+
'acl:mode': [{ '@id': 'acl:Read' }, { '@id': 'acl:Write' }]
|
|
30
|
+
}]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const auths = parseAcl(JSON.stringify(acl), 'https://alice.example/.acl');
|
|
34
|
+
|
|
35
|
+
assert.strictEqual(auths.length, 1);
|
|
36
|
+
assert.ok(auths[0].agents.includes('https://alice.example/#me'));
|
|
37
|
+
assert.ok(auths[0].modes.includes(AccessMode.READ));
|
|
38
|
+
assert.ok(auths[0].modes.includes(AccessMode.WRITE));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should parse public access', () => {
|
|
42
|
+
const acl = {
|
|
43
|
+
'@context': { 'acl': 'http://www.w3.org/ns/auth/acl#', 'foaf': 'http://xmlns.com/foaf/0.1/' },
|
|
44
|
+
'@graph': [{
|
|
45
|
+
'@id': '#public',
|
|
46
|
+
'@type': 'acl:Authorization',
|
|
47
|
+
'acl:agentClass': { '@id': 'foaf:Agent' },
|
|
48
|
+
'acl:accessTo': { '@id': 'https://alice.example/public/' },
|
|
49
|
+
'acl:mode': [{ '@id': 'acl:Read' }]
|
|
50
|
+
}]
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const auths = parseAcl(JSON.stringify(acl), 'https://alice.example/public/.acl');
|
|
54
|
+
|
|
55
|
+
assert.strictEqual(auths.length, 1);
|
|
56
|
+
assert.ok(auths[0].agentClasses.includes('foaf:Agent'));
|
|
57
|
+
assert.ok(auths[0].modes.includes(AccessMode.READ));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should parse default authorizations for containers', () => {
|
|
61
|
+
const acl = {
|
|
62
|
+
'@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
|
|
63
|
+
'@graph': [{
|
|
64
|
+
'@id': '#default',
|
|
65
|
+
'@type': 'acl:Authorization',
|
|
66
|
+
'acl:agent': { '@id': 'https://alice.example/#me' },
|
|
67
|
+
'acl:default': { '@id': 'https://alice.example/folder/' },
|
|
68
|
+
'acl:mode': [{ '@id': 'acl:Read' }]
|
|
69
|
+
}]
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const auths = parseAcl(JSON.stringify(acl), 'https://alice.example/folder/.acl');
|
|
73
|
+
|
|
74
|
+
assert.strictEqual(auths.length, 1);
|
|
75
|
+
assert.ok(auths[0].default.includes('https://alice.example/folder/'));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle invalid JSON gracefully', () => {
|
|
79
|
+
const auths = parseAcl('not valid json', 'https://example.com/.acl');
|
|
80
|
+
assert.strictEqual(auths.length, 0);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('generateOwnerAcl', () => {
|
|
85
|
+
it('should generate owner ACL with public read', () => {
|
|
86
|
+
const acl = generateOwnerAcl('https://alice.example/', 'https://alice.example/#me', true);
|
|
87
|
+
|
|
88
|
+
assert.ok(acl['@graph'].length >= 2);
|
|
89
|
+
|
|
90
|
+
// Find owner auth
|
|
91
|
+
const ownerAuth = acl['@graph'].find(a => a['@id'] === '#owner');
|
|
92
|
+
assert.ok(ownerAuth);
|
|
93
|
+
assert.strictEqual(ownerAuth['acl:agent']['@id'], 'https://alice.example/#me');
|
|
94
|
+
|
|
95
|
+
// Find public auth
|
|
96
|
+
const publicAuth = acl['@graph'].find(a => a['@id'] === '#public');
|
|
97
|
+
assert.ok(publicAuth);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('WAC Checker', () => {
|
|
103
|
+
describe('getRequiredMode', () => {
|
|
104
|
+
it('should return READ for GET', () => {
|
|
105
|
+
assert.strictEqual(getRequiredMode('GET'), AccessMode.READ);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return READ for HEAD', () => {
|
|
109
|
+
assert.strictEqual(getRequiredMode('HEAD'), AccessMode.READ);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should return APPEND for POST', () => {
|
|
113
|
+
assert.strictEqual(getRequiredMode('POST'), AccessMode.APPEND);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return WRITE for PUT', () => {
|
|
117
|
+
assert.strictEqual(getRequiredMode('PUT'), AccessMode.WRITE);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should return WRITE for DELETE', () => {
|
|
121
|
+
assert.strictEqual(getRequiredMode('DELETE'), AccessMode.WRITE);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('WAC Integration', () => {
|
|
127
|
+
let baseUrl;
|
|
128
|
+
|
|
129
|
+
before(async () => {
|
|
130
|
+
const result = await startTestServer();
|
|
131
|
+
baseUrl = result.baseUrl;
|
|
132
|
+
await createTestPod('wactest');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
after(async () => {
|
|
136
|
+
await stopTestServer();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('ACL Files', () => {
|
|
140
|
+
it('should create root .acl on pod creation', async () => {
|
|
141
|
+
const res = await request('/wactest/.acl');
|
|
142
|
+
|
|
143
|
+
assertStatus(res, 200);
|
|
144
|
+
const content = await res.json();
|
|
145
|
+
assert.ok(content['@graph'], 'Should be JSON-LD');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should create private folder .acl', async () => {
|
|
149
|
+
const res = await request('/wactest/private/.acl');
|
|
150
|
+
|
|
151
|
+
assertStatus(res, 200);
|
|
152
|
+
const content = await res.json();
|
|
153
|
+
assert.ok(content['@graph']);
|
|
154
|
+
|
|
155
|
+
// Should only have owner, no public
|
|
156
|
+
const hasPublic = content['@graph'].some(a =>
|
|
157
|
+
a['acl:agentClass'] && a['acl:agentClass']['@id'] === 'foaf:Agent'
|
|
158
|
+
);
|
|
159
|
+
assert.ok(!hasPublic, 'Private folder should not have public access');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should create inbox .acl with public append', async () => {
|
|
163
|
+
const res = await request('/wactest/inbox/.acl');
|
|
164
|
+
|
|
165
|
+
assertStatus(res, 200);
|
|
166
|
+
const content = await res.json();
|
|
167
|
+
|
|
168
|
+
// Should have public append
|
|
169
|
+
const publicAuth = content['@graph'].find(a =>
|
|
170
|
+
a['acl:agentClass'] && a['acl:agentClass']['@id'] === 'foaf:Agent'
|
|
171
|
+
);
|
|
172
|
+
assert.ok(publicAuth, 'Inbox should have public access');
|
|
173
|
+
|
|
174
|
+
const modes = publicAuth['acl:mode'].map(m => m['@id']);
|
|
175
|
+
assert.ok(modes.includes('acl:Append'), 'Public should have Append');
|
|
176
|
+
assert.ok(!modes.includes('acl:Read'), 'Public should not have Read');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('WAC-Allow Header', () => {
|
|
181
|
+
it('should return WAC-Allow header for public container', async () => {
|
|
182
|
+
const res = await request('/wactest/public/');
|
|
183
|
+
|
|
184
|
+
assertHeader(res, 'WAC-Allow');
|
|
185
|
+
const wacAllow = res.headers.get('WAC-Allow');
|
|
186
|
+
assert.ok(wacAllow.includes('public='), 'Should have public permissions');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"timestamp": "2025-03-31T14:25:24.234Z",
|
|
3
|
-
"server": "http://nostr.social:3000",
|
|
4
|
-
"testDuration": 30000,
|
|
5
|
-
"averageResponseTimes": {
|
|
6
|
-
"register": 49.28774029878249,
|
|
7
|
-
"login": 49.07647125548627,
|
|
8
|
-
"read": 145.50252931779679,
|
|
9
|
-
"write": 143.20483738812226,
|
|
10
|
-
"delete": 145.5004399698014
|
|
11
|
-
},
|
|
12
|
-
"throughputResults": [
|
|
13
|
-
{
|
|
14
|
-
"concurrentUsers": 1,
|
|
15
|
-
"operations": 300,
|
|
16
|
-
"duration": 629.3636230230331,
|
|
17
|
-
"throughput": 476.67197312581374
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"concurrentUsers": 5,
|
|
21
|
-
"operations": 1500,
|
|
22
|
-
"duration": 3072.7389999628067,
|
|
23
|
-
"throughput": 488.16381736885444
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"concurrentUsers": 10,
|
|
27
|
-
"operations": 3000,
|
|
28
|
-
"duration": 6042.516402959824,
|
|
29
|
-
"throughput": 496.4818959416479
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"concurrentUsers": 50,
|
|
33
|
-
"operations": 14000,
|
|
34
|
-
"duration": 30000,
|
|
35
|
-
"throughput": 466.6666666666667
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"concurrentUsers": 100,
|
|
39
|
-
"operations": 13900,
|
|
40
|
-
"duration": 30000,
|
|
41
|
-
"throughput": 463.3333333333333
|
|
42
|
-
}
|
|
43
|
-
]
|
|
44
|
-
}
|