javascript-solid-server 0.0.92 → 0.0.94
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/README.md +8 -0
- package/bin/jss.js +3 -0
- package/package.json +1 -1
- package/src/auth/middleware.js +35 -8
- package/src/config.js +4 -2
- package/src/handlers/resource.js +9 -5
- package/src/idp/interactions.js +3 -3
- package/src/mashlib/index.js +17 -0
- package/src/server.js +5 -1
- package/test/auth.test.js +63 -0
package/README.md
CHANGED
|
@@ -130,6 +130,7 @@ jss --help # Show help
|
|
|
130
130
|
| `--base-domain <domain>` | Base domain for subdomains | - |
|
|
131
131
|
| `--mashlib` | Enable Mashlib (local mode) | false |
|
|
132
132
|
| `--mashlib-cdn` | Enable Mashlib (CDN mode) | false |
|
|
133
|
+
| `--mashlib-module <url>` | Enable ES module data browser from a URL | - |
|
|
133
134
|
| `--mashlib-version <ver>` | Mashlib CDN version | 2.0.0 |
|
|
134
135
|
| `--solidos-ui` | Enable modern SolidOS UI (requires --mashlib) | false |
|
|
135
136
|
| `--git` | Enable Git HTTP backend | false |
|
|
@@ -161,6 +162,7 @@ export JSS_CONNEG=true
|
|
|
161
162
|
export JSS_SUBDOMAINS=true
|
|
162
163
|
export JSS_BASE_DOMAIN=example.com
|
|
163
164
|
export JSS_MASHLIB=true
|
|
165
|
+
export JSS_MASHLIB_MODULE=https://example.com/mashlib.js
|
|
164
166
|
export JSS_NOSTR=true
|
|
165
167
|
export JSS_INVITE_ONLY=true
|
|
166
168
|
export JSS_WEBID_TLS=true
|
|
@@ -367,6 +369,12 @@ cd src/mashlib-local
|
|
|
367
369
|
npm install && npm run build
|
|
368
370
|
```
|
|
369
371
|
|
|
372
|
+
**ES Module Mode** (for custom or next-gen mashlib builds):
|
|
373
|
+
```bash
|
|
374
|
+
jss start --mashlib-module https://example.com/mashlib.js
|
|
375
|
+
```
|
|
376
|
+
Loads an ES module-based data browser from any URL. Uses `<script type="module">` and `<div id="mashlib">` (self-initializing). CSS is auto-derived by replacing `.js` with `.css`. Content negotiation is auto-enabled.
|
|
377
|
+
|
|
370
378
|
**How it works:**
|
|
371
379
|
1. Browser requests `/alice/public/data.ttl` with `Accept: text/html`
|
|
372
380
|
2. Server returns Mashlib HTML wrapper
|
package/bin/jss.js
CHANGED
|
@@ -55,6 +55,7 @@ program
|
|
|
55
55
|
.option('--base-domain <domain>', 'Base domain for subdomain pods (e.g., "example.com")')
|
|
56
56
|
.option('--mashlib', 'Enable Mashlib data browser (local mode, requires mashlib in node_modules)')
|
|
57
57
|
.option('--mashlib-cdn', 'Enable Mashlib data browser (CDN mode, no local files needed)')
|
|
58
|
+
.option('--mashlib-module <url>', 'Enable ES module data browser from a URL')
|
|
58
59
|
.option('--no-mashlib', 'Disable Mashlib data browser')
|
|
59
60
|
.option('--mashlib-version <version>', 'Mashlib version for CDN mode (default: 2.0.0)')
|
|
60
61
|
.option('--solidos-ui', 'Enable modern Nextcloud-style UI (requires --mashlib)')
|
|
@@ -123,6 +124,7 @@ program
|
|
|
123
124
|
mashlib: config.mashlib || config.mashlibCdn,
|
|
124
125
|
mashlibCdn: config.mashlibCdn,
|
|
125
126
|
mashlibVersion: config.mashlibVersion,
|
|
127
|
+
mashlibModule: config.mashlibModule,
|
|
126
128
|
solidosUi: config.solidosUi,
|
|
127
129
|
git: config.git,
|
|
128
130
|
nostr: config.nostr,
|
|
@@ -158,6 +160,7 @@ program
|
|
|
158
160
|
} else if (config.mashlib) {
|
|
159
161
|
console.log(` Mashlib: local (data browser enabled)`);
|
|
160
162
|
}
|
|
163
|
+
if (config.mashlibModule) console.log(` Mashlib module: ${config.mashlibModule}`);
|
|
161
164
|
if (config.solidosUi) console.log(' SolidOS UI: enabled (modern interface)');
|
|
162
165
|
if (config.git) console.log(' Git: enabled (clone/push support)');
|
|
163
166
|
if (config.nostr) console.log(` Nostr: enabled (${config.nostrPath})`);
|
package/package.json
CHANGED
package/src/auth/middleware.js
CHANGED
|
@@ -9,7 +9,32 @@ import { checkAccess, getRequiredMode } from '../wac/checker.js';
|
|
|
9
9
|
import { AccessMode } from '../wac/parser.js';
|
|
10
10
|
import * as storage from '../storage/filesystem.js';
|
|
11
11
|
import { getEffectiveUrlPath } from '../utils/url.js';
|
|
12
|
-
import { generateDatabrowserHtml, generateSolidosUiHtml } from '../mashlib/index.js';
|
|
12
|
+
import { generateDatabrowserHtml, generateModuleDatabrowserHtml, generateSolidosUiHtml } from '../mashlib/index.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build a resource URL for WAC checking, normalizing path-based pod access
|
|
16
|
+
* to subdomain form so URLs match ACL entries.
|
|
17
|
+
*
|
|
18
|
+
* In subdomain mode, ACLs reference subdomain URLs (e.g. https://alice.example.com/public/).
|
|
19
|
+
* Path-based access on the main domain (e.g. https://example.com/alice/public/) must be
|
|
20
|
+
* normalized to match.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} request - Fastify request
|
|
23
|
+
* @param {string} urlPath - URL path (e.g. /alice/public/file.ttl)
|
|
24
|
+
* @returns {string} Normalized resource URL
|
|
25
|
+
*/
|
|
26
|
+
function buildResourceUrl(request, urlPath) {
|
|
27
|
+
if (request.subdomainsEnabled && request.baseDomain &&
|
|
28
|
+
request.hostname === request.baseDomain && !request.podName) {
|
|
29
|
+
const pathMatch = urlPath.match(/^\/([^/]+)(\/.*)?$/);
|
|
30
|
+
if (pathMatch && !pathMatch[1].startsWith('.')) {
|
|
31
|
+
const podName = pathMatch[1];
|
|
32
|
+
const remainder = pathMatch[2] || '/';
|
|
33
|
+
return `${request.protocol}://${podName}.${request.baseDomain}${remainder}`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return `${request.protocol}://${request.hostname}${urlPath}`;
|
|
37
|
+
}
|
|
13
38
|
|
|
14
39
|
/**
|
|
15
40
|
* Check if request is authorized
|
|
@@ -55,8 +80,8 @@ export async function authorize(request, reply, options = {}) {
|
|
|
55
80
|
const resourceExists = stats !== null;
|
|
56
81
|
const isContainer = stats?.isDirectory || urlPath.endsWith('/');
|
|
57
82
|
|
|
58
|
-
// Build resource URL
|
|
59
|
-
const resourceUrl =
|
|
83
|
+
// Build resource URL, normalizing path-based pod access to subdomain form for WAC
|
|
84
|
+
const resourceUrl = buildResourceUrl(request, urlPath);
|
|
60
85
|
|
|
61
86
|
// Get required access mode - use override if provided, otherwise derive from method
|
|
62
87
|
const requiredMode = options.requiredMode || getRequiredMode(method);
|
|
@@ -70,9 +95,9 @@ export async function authorize(request, reply, options = {}) {
|
|
|
70
95
|
// Check write permission on parent container
|
|
71
96
|
const parentPath = getParentPath(storagePath);
|
|
72
97
|
checkPath = parentPath;
|
|
73
|
-
// For URL, also need to get parent
|
|
98
|
+
// For URL, also need to get parent (normalized for subdomain WAC matching)
|
|
74
99
|
const parentUrlPath = getParentPath(urlPath);
|
|
75
|
-
checkUrl =
|
|
100
|
+
checkUrl = buildResourceUrl(request, parentUrlPath);
|
|
76
101
|
checkIsContainer = true;
|
|
77
102
|
}
|
|
78
103
|
|
|
@@ -123,10 +148,12 @@ export function handleUnauthorized(request, reply, isAuthenticated, wacAllow, au
|
|
|
123
148
|
// If mashlib is enabled, serve mashlib instead of static error page
|
|
124
149
|
// Mashlib has built-in login functionality via panes.runDataBrowser()
|
|
125
150
|
if (request.mashlibEnabled) {
|
|
126
|
-
// Use SolidOS UI if enabled,
|
|
151
|
+
// Use SolidOS UI if enabled, ES module if configured, otherwise classic mashlib
|
|
127
152
|
const html = request.solidosUiEnabled
|
|
128
153
|
? generateSolidosUiHtml()
|
|
129
|
-
:
|
|
154
|
+
: request.mashlibModule
|
|
155
|
+
? generateModuleDatabrowserHtml(request.mashlibModule)
|
|
156
|
+
: generateDatabrowserHtml(request.url, request.mashlibCdn ? request.mashlibVersion : null);
|
|
130
157
|
return reply.code(statusCode).type('text/html').send(html);
|
|
131
158
|
}
|
|
132
159
|
return reply.code(statusCode).type('text/html').send(getErrorPage(statusCode, isAuthenticated, request));
|
|
@@ -379,7 +406,7 @@ async function authorizeAclAccess(request, urlPath, method, webId, authError) {
|
|
|
379
406
|
// /foo/bar.acl protects /foo/bar (resource)
|
|
380
407
|
const protectedPath = urlPath.replace(/\.acl$/, '');
|
|
381
408
|
const isProtectedContainer = protectedPath.endsWith('/');
|
|
382
|
-
const protectedUrl =
|
|
409
|
+
const protectedUrl = buildResourceUrl(request, protectedPath);
|
|
383
410
|
|
|
384
411
|
// Get storage path for the protected resource
|
|
385
412
|
const storagePath = getEffectiveUrlPath(request).replace(/\.acl$/, '');
|
package/src/config.js
CHANGED
|
@@ -41,6 +41,7 @@ export const defaults = {
|
|
|
41
41
|
mashlib: false,
|
|
42
42
|
mashlibCdn: false,
|
|
43
43
|
mashlibVersion: '2.0.0',
|
|
44
|
+
mashlibModule: false,
|
|
44
45
|
|
|
45
46
|
// SolidOS UI (modern Nextcloud-style interface)
|
|
46
47
|
solidosUi: false,
|
|
@@ -113,6 +114,7 @@ const envMap = {
|
|
|
113
114
|
JSS_MASHLIB: 'mashlib',
|
|
114
115
|
JSS_MASHLIB_CDN: 'mashlibCdn',
|
|
115
116
|
JSS_MASHLIB_VERSION: 'mashlibVersion',
|
|
117
|
+
JSS_MASHLIB_MODULE: 'mashlibModule',
|
|
116
118
|
JSS_SOLIDOS_UI: 'solidosUi',
|
|
117
119
|
JSS_GIT: 'git',
|
|
118
120
|
JSS_NOSTR: 'nostr',
|
|
@@ -238,7 +240,7 @@ export async function loadConfig(cliOptions = {}, configFile = null) {
|
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
// Mashlib requires content negotiation for Turtle support
|
|
241
|
-
if (config.mashlib || config.mashlibCdn) {
|
|
243
|
+
if (config.mashlib || config.mashlibCdn || config.mashlibModule) {
|
|
242
244
|
config.conneg = true;
|
|
243
245
|
}
|
|
244
246
|
|
|
@@ -293,7 +295,7 @@ export function printConfig(config) {
|
|
|
293
295
|
console.log(` Notifications: ${config.notifications}`);
|
|
294
296
|
console.log(` IdP: ${config.idp ? (config.idpIssuer || 'enabled') : 'disabled'}`);
|
|
295
297
|
console.log(` Subdomains: ${config.subdomains ? (config.baseDomain || 'enabled') : 'disabled'}`);
|
|
296
|
-
console.log(` Mashlib: ${config.mashlibCdn ? `CDN v${config.mashlibVersion}` : config.mashlib ? 'local' : 'disabled'}`);
|
|
298
|
+
console.log(` Mashlib: ${config.mashlibModule ? `module (${config.mashlibModule})` : config.mashlibCdn ? `CDN v${config.mashlibVersion}` : config.mashlib ? 'local' : 'disabled'}`);
|
|
297
299
|
console.log(` SolidOS UI: ${config.solidosUi ? 'enabled' : 'disabled'}`);
|
|
298
300
|
console.log('─'.repeat(40));
|
|
299
301
|
}
|
package/src/handlers/resource.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '../rdf/conneg.js';
|
|
16
16
|
import { emitChange } from '../notifications/events.js';
|
|
17
17
|
import { checkIfMatch, checkIfNoneMatchForGet, checkIfNoneMatchForWrite } from '../utils/conditional.js';
|
|
18
|
-
import { generateDatabrowserHtml, generateSolidosUiHtml, shouldServeMashlib } from '../mashlib/index.js';
|
|
18
|
+
import { generateDatabrowserHtml, generateModuleDatabrowserHtml, generateSolidosUiHtml, shouldServeMashlib } from '../mashlib/index.js';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Live reload script - injected into HTML when --live-reload is enabled
|
|
@@ -230,10 +230,12 @@ export async function handleGet(request, reply) {
|
|
|
230
230
|
|
|
231
231
|
// Check if we should serve Mashlib data browser for containers
|
|
232
232
|
if (shouldServeMashlib(request, request.mashlibEnabled, 'application/ld+json')) {
|
|
233
|
-
// Use SolidOS UI if enabled,
|
|
233
|
+
// Use SolidOS UI if enabled, ES module if configured, otherwise classic mashlib
|
|
234
234
|
const html = request.solidosUiEnabled
|
|
235
235
|
? generateSolidosUiHtml()
|
|
236
|
-
:
|
|
236
|
+
: request.mashlibModule
|
|
237
|
+
? generateModuleDatabrowserHtml(request.mashlibModule)
|
|
238
|
+
: generateDatabrowserHtml(resourceUrl, request.mashlibCdn ? request.mashlibVersion : null);
|
|
237
239
|
const headers = getAllHeaders({
|
|
238
240
|
isContainer: true,
|
|
239
241
|
etag: stats.etag,
|
|
@@ -307,10 +309,12 @@ export async function handleGet(request, reply) {
|
|
|
307
309
|
// Check if we should serve Mashlib data browser
|
|
308
310
|
// Only for RDF resources when Accept: text/html is requested
|
|
309
311
|
if (shouldServeMashlib(request, request.mashlibEnabled, storedContentType)) {
|
|
310
|
-
// Use SolidOS UI if enabled,
|
|
312
|
+
// Use SolidOS UI if enabled, ES module if configured, otherwise classic mashlib
|
|
311
313
|
const html = request.solidosUiEnabled
|
|
312
314
|
? generateSolidosUiHtml()
|
|
313
|
-
:
|
|
315
|
+
: request.mashlibModule
|
|
316
|
+
? generateModuleDatabrowserHtml(request.mashlibModule)
|
|
317
|
+
: generateDatabrowserHtml(resourceUrl, request.mashlibCdn ? request.mashlibVersion : null);
|
|
314
318
|
const headers = getAllHeaders({
|
|
315
319
|
isContainer: false,
|
|
316
320
|
etag: stats.etag,
|
package/src/idp/interactions.js
CHANGED
|
@@ -140,9 +140,9 @@ export async function handleLogin(request, reply, provider) {
|
|
|
140
140
|
// Show passkey registration prompt before completing login
|
|
141
141
|
// Store the pending login in the interaction
|
|
142
142
|
interaction.result = {
|
|
143
|
+
passkeyPromptPending: true,
|
|
143
144
|
login: { accountId: account.id, remember: true }
|
|
144
145
|
};
|
|
145
|
-
interaction.passkeyPromptPending = true;
|
|
146
146
|
await interaction.save(interaction.exp - Math.floor(Date.now() / 1000));
|
|
147
147
|
return reply.type('text/html').send(passkeyPromptPage(uid, account.id));
|
|
148
148
|
}
|
|
@@ -473,7 +473,7 @@ export async function handlePasskeyComplete(request, reply, provider) {
|
|
|
473
473
|
|
|
474
474
|
// If this is a post-login passkey registration flow, validate accountId matches
|
|
475
475
|
// the already-authenticated user to prevent account takeover
|
|
476
|
-
if (interaction.passkeyPromptPending && interaction.result?.login?.accountId) {
|
|
476
|
+
if (interaction.result?.passkeyPromptPending && interaction.result?.login?.accountId) {
|
|
477
477
|
if (interaction.result.login.accountId !== accountId) {
|
|
478
478
|
request.log.warn({ expected: interaction.result.login.accountId, provided: accountId }, 'AccountId mismatch in passkey complete');
|
|
479
479
|
return reply.code(403).type('text/html').send(errorPage('Access denied', 'Account mismatch.'));
|
|
@@ -520,7 +520,7 @@ export async function handlePasskeySkip(request, reply, provider) {
|
|
|
520
520
|
}
|
|
521
521
|
|
|
522
522
|
// Validate the interaction is in the passkey prompt state
|
|
523
|
-
if (!interaction.passkeyPromptPending) {
|
|
523
|
+
if (!interaction.result?.passkeyPromptPending) {
|
|
524
524
|
return reply.code(400).type('text/html').send(errorPage('Invalid state', 'Not in passkey prompt flow.'));
|
|
525
525
|
}
|
|
526
526
|
|
package/src/mashlib/index.js
CHANGED
|
@@ -40,6 +40,23 @@ export function generateDatabrowserHtml(resourceUrl, cdnVersion = null) {
|
|
|
40
40
|
})</script><script defer="defer" src="/mashlib.min.js"></script><link href="/mash.css" rel="stylesheet"></head><body id="PageBody"><header id="PageHeader"></header><div class="TabulatorOutline" id="DummyUUID" role="main"><table id="outline"></table><div id="GlobalDashboard"></div></div><footer id="PageFooter"></footer></body></html>`;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Generate ES module-based databrowser HTML
|
|
45
|
+
*
|
|
46
|
+
* @param {string} moduleUrl - URL to the ES module entry point
|
|
47
|
+
* @returns {string} HTML content
|
|
48
|
+
*/
|
|
49
|
+
export function generateModuleDatabrowserHtml(moduleUrl) {
|
|
50
|
+
const cssUrl = moduleUrl.replace(/\.js$/, '.css');
|
|
51
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"/>
|
|
52
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
53
|
+
<title>Solid Data Browser</title>
|
|
54
|
+
<link rel="stylesheet" href="${cssUrl}"></head>
|
|
55
|
+
<body><div id="mashlib"></div>
|
|
56
|
+
<script type="module" src="${moduleUrl}"></script>
|
|
57
|
+
</body></html>`;
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
/**
|
|
44
61
|
* Check if request wants HTML and mashlib should handle it
|
|
45
62
|
* @param {object} request - Fastify request
|
package/src/server.js
CHANGED
|
@@ -54,7 +54,9 @@ export function createServer(options = {}) {
|
|
|
54
54
|
const baseDomain = options.baseDomain || null;
|
|
55
55
|
// Mashlib data browser is OFF by default
|
|
56
56
|
// mashlibCdn: if true, load from CDN; if false, serve locally
|
|
57
|
-
|
|
57
|
+
// mashlibModule: URL to ES module entry point (alternative to classic mashlib)
|
|
58
|
+
const mashlibModule = options.mashlibModule ?? false;
|
|
59
|
+
const mashlibEnabled = options.mashlib || !!mashlibModule;
|
|
58
60
|
const mashlibCdn = options.mashlibCdn ?? false;
|
|
59
61
|
const mashlibVersion = options.mashlibVersion ?? '2.0.0';
|
|
60
62
|
// SolidOS UI (modern Nextcloud-style interface) - requires mashlib
|
|
@@ -147,6 +149,7 @@ export function createServer(options = {}) {
|
|
|
147
149
|
fastify.decorateRequest('mashlibEnabled', null);
|
|
148
150
|
fastify.decorateRequest('mashlibCdn', null);
|
|
149
151
|
fastify.decorateRequest('mashlibVersion', null);
|
|
152
|
+
fastify.decorateRequest('mashlibModule', null);
|
|
150
153
|
fastify.decorateRequest('solidosUiEnabled', null);
|
|
151
154
|
fastify.decorateRequest('defaultQuota', null);
|
|
152
155
|
fastify.decorateRequest('config', null);
|
|
@@ -160,6 +163,7 @@ export function createServer(options = {}) {
|
|
|
160
163
|
request.mashlibEnabled = mashlibEnabled;
|
|
161
164
|
request.mashlibCdn = mashlibCdn;
|
|
162
165
|
request.mashlibVersion = mashlibVersion;
|
|
166
|
+
request.mashlibModule = mashlibModule;
|
|
163
167
|
request.solidosUiEnabled = solidosUiEnabled;
|
|
164
168
|
request.defaultQuota = defaultQuota;
|
|
165
169
|
request.config = { public: options.public, readOnly: options.readOnly };
|
package/test/auth.test.js
CHANGED
|
@@ -149,6 +149,69 @@ describe('Authentication', () => {
|
|
|
149
149
|
const res = await request('/inboxread/inbox/');
|
|
150
150
|
assertStatus(res, 401);
|
|
151
151
|
});
|
|
152
|
+
|
|
153
|
+
it('should allow any authenticated user with acl:AuthenticatedAgent', async () => {
|
|
154
|
+
await createTestPod('authuser1');
|
|
155
|
+
await createTestPod('authuser2');
|
|
156
|
+
|
|
157
|
+
// Create a test resource with acl:AuthenticatedAgent ACL
|
|
158
|
+
const baseUrl = getBaseUrl();
|
|
159
|
+
|
|
160
|
+
// First, create a resource (this will create parent containers)
|
|
161
|
+
await request('/authuser1/authenticated-only/test.txt', {
|
|
162
|
+
method: 'PUT',
|
|
163
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
164
|
+
body: 'authenticated content',
|
|
165
|
+
auth: 'authuser1'
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Now create a custom ACL for the container with acl:AuthenticatedAgent
|
|
169
|
+
// Include owner with Control so they can manage the ACL
|
|
170
|
+
const acl = {
|
|
171
|
+
'@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
|
|
172
|
+
'@graph': [
|
|
173
|
+
{
|
|
174
|
+
'@id': '#owner',
|
|
175
|
+
'@type': 'acl:Authorization',
|
|
176
|
+
'acl:agent': { '@id': `${baseUrl}/authuser1/profile/card#me` },
|
|
177
|
+
'acl:accessTo': { '@id': `${baseUrl}/authuser1/authenticated-only/` },
|
|
178
|
+
'acl:default': { '@id': `${baseUrl}/authuser1/authenticated-only/` },
|
|
179
|
+
'acl:mode': [
|
|
180
|
+
{ '@id': 'acl:Read' },
|
|
181
|
+
{ '@id': 'acl:Write' },
|
|
182
|
+
{ '@id': 'acl:Control' }
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
'@id': '#authenticated',
|
|
187
|
+
'@type': 'acl:Authorization',
|
|
188
|
+
'acl:agentClass': { '@id': 'acl:AuthenticatedAgent' },
|
|
189
|
+
'acl:accessTo': { '@id': `${baseUrl}/authuser1/authenticated-only/` },
|
|
190
|
+
'acl:default': { '@id': `${baseUrl}/authuser1/authenticated-only/` },
|
|
191
|
+
'acl:mode': [{ '@id': 'acl:Read' }]
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
await request('/authuser1/authenticated-only/.acl', {
|
|
197
|
+
method: 'PUT',
|
|
198
|
+
headers: { 'Content-Type': 'application/json' },
|
|
199
|
+
body: JSON.stringify(acl),
|
|
200
|
+
auth: 'authuser1'
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Test 1: Anonymous access should be denied
|
|
204
|
+
const res1 = await request('/authuser1/authenticated-only/test.txt');
|
|
205
|
+
assertStatus(res1, 401);
|
|
206
|
+
|
|
207
|
+
// Test 2: Owner should have access
|
|
208
|
+
const res2 = await request('/authuser1/authenticated-only/test.txt', { auth: 'authuser1' });
|
|
209
|
+
assertStatus(res2, 200);
|
|
210
|
+
|
|
211
|
+
// Test 3: Different authenticated user should also have access (key test!)
|
|
212
|
+
const res3 = await request('/authuser1/authenticated-only/test.txt', { auth: 'authuser2' });
|
|
213
|
+
assertStatus(res3, 200);
|
|
214
|
+
});
|
|
152
215
|
});
|
|
153
216
|
|
|
154
217
|
describe('WAC-Allow Header', () => {
|