Skip to content
Merged
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
35 changes: 30 additions & 5 deletions src/auth/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,31 @@ import * as storage from '../storage/filesystem.js';
import { getEffectiveUrlPath } from '../utils/url.js';
import { generateDatabrowserHtml, generateSolidosUiHtml } from '../mashlib/index.js';

/**
* Build a resource URL for WAC checking, normalizing path-based pod access
* to subdomain form so URLs match ACL entries.
*
* In subdomain mode, ACLs reference subdomain URLs (e.g. https://alice.example.com/public/).
* Path-based access on the main domain (e.g. https://example.com/alice/public/) must be
* normalized to match.
*
* @param {object} request - Fastify request
* @param {string} urlPath - URL path (e.g. /alice/public/file.ttl)
* @returns {string} Normalized resource URL
*/
function buildResourceUrl(request, urlPath) {
if (request.subdomainsEnabled && request.baseDomain &&
request.hostname === request.baseDomain && !request.podName) {
const pathMatch = urlPath.match(/^\/([^/]+)(\/.*)?$/);
if (pathMatch && !pathMatch[1].startsWith('.')) {
const podName = pathMatch[1];
const remainder = pathMatch[2] || '/';
return `${request.protocol}://${podName}.${request.baseDomain}${remainder}`;
}
}
return `${request.protocol}://${request.hostname}${urlPath}`;
}

/**
* Check if request is authorized
* @param {object} request - Fastify request
Expand Down Expand Up @@ -55,8 +80,8 @@ export async function authorize(request, reply, options = {}) {
const resourceExists = stats !== null;
const isContainer = stats?.isDirectory || urlPath.endsWith('/');

// Build resource URL (uses actual request hostname which may be subdomain)
const resourceUrl = `${request.protocol}://${request.hostname}${urlPath}`;
// Build resource URL, normalizing path-based pod access to subdomain form for WAC
const resourceUrl = buildResourceUrl(request, urlPath);
Comment on lines +83 to +84

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change alters authorization behavior in subdomain mode for path-based pod access, but there doesn't appear to be test coverage for subdomains: true + baseDomain scenarios. Please add an integration test that starts the server with subdomains: true and baseDomain: "127.0.0.1", creates a pod, and verifies that GET /alice/public/ returns 200 (and e.g. /alice/private/ returns 401 without auth) to prevent regressions.

Copilot uses AI. Check for mistakes.

// Get required access mode - use override if provided, otherwise derive from method
const requiredMode = options.requiredMode || getRequiredMode(method);
Expand All @@ -70,9 +95,9 @@ export async function authorize(request, reply, options = {}) {
// Check write permission on parent container
const parentPath = getParentPath(storagePath);
checkPath = parentPath;
// For URL, also need to get parent
// For URL, also need to get parent (normalized for subdomain WAC matching)
const parentUrlPath = getParentPath(urlPath);
checkUrl = `${request.protocol}://${request.hostname}${parentUrlPath}`;
checkUrl = buildResourceUrl(request, parentUrlPath);
Comment on lines 83 to +100

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change adds URL-normalization behavior that affects authorization decisions in subdomain mode (resource URL, parent container URL for writes, and ACL protected resource URL), but there are no automated tests covering the path-based access case that previously returned 403. Since the repo already has WAC integration tests, please add a test that starts the server with { subdomains: true, baseDomain: "example.com" } and verifies that Host: example.com + path-based pod access authorizes the same as Host: <pod>.example.com (including the parent-container check for PUT/POST/PATCH and .acl access).

Copilot uses AI. Check for mistakes.
checkIsContainer = true;
}

Expand Down Expand Up @@ -379,7 +404,7 @@ async function authorizeAclAccess(request, urlPath, method, webId, authError) {
// /foo/bar.acl protects /foo/bar (resource)
const protectedPath = urlPath.replace(/\.acl$/, '');
const isProtectedContainer = protectedPath.endsWith('/');
const protectedUrl = `${request.protocol}://${request.hostname}${protectedPath}`;
const protectedUrl = buildResourceUrl(request, protectedPath);

// Get storage path for the protected resource
const storagePath = getEffectiveUrlPath(request).replace(/\.acl$/, '');
Expand Down