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
9 changes: 7 additions & 2 deletions src/auth/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ import { generateDatabrowserHtml, generateModuleDatabrowserHtml } from '../mashl
* @param {string} urlPath - URL path (e.g. /alice/public/file.ttl)
* @returns {string} Normalized resource URL
*/
function buildResourceUrl(request, urlPath) {
export function buildResourceUrl(request, urlPath) {
// Use request.headers.host (includes port) instead of request.hostname (strips port)
const host = request.headers.host || request.hostname;
if (request.subdomainsEnabled && request.baseDomain &&
request.hostname === request.baseDomain && !request.podName) {
const pathMatch = urlPath.match(/^\/([^/]+)(\/.*)?$/);
if (pathMatch && !pathMatch[1].startsWith('.')) {
// Treat a path segment as a pod name only if it looks like one:
// - not a dotfile (.well-known, .acl, .meta, ...)
// - no dot (pod names are DNS labels; file names have extensions)
// This avoids rewriting /mashlib.js to https://mashlib.js.basedomain/
// which would fail WAC against the base domain's ACL. (#307)
if (pathMatch && !pathMatch[1].startsWith('.') && !pathMatch[1].includes('.')) {
const podName = pathMatch[1];
const remainder = pathMatch[2] || '/';
return `${request.protocol}://${podName}.${request.baseDomain}${remainder}`;
Expand Down
109 changes: 109 additions & 0 deletions test/subdomain-base-files.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Regression tests for #307 — buildResourceUrl rewriting the base-domain
* root file paths into non-existent pod subdomains.
*
* Unit tests against buildResourceUrl directly, since Node's fetch() overrides
* the Host header with the TCP target, which makes end-to-end tests of
* subdomain routing impossible without a real reverse proxy.
*/

import { describe, it } from 'node:test';
import assert from 'node:assert';
import { buildResourceUrl } from '../src/auth/middleware.js';

function makeRequest({ hostname, baseDomain, subdomainsEnabled = true, podName = null, protocol = 'https' }) {
return {
protocol,
hostname,
headers: { host: hostname },
subdomainsEnabled,
baseDomain,
podName
};
}

describe('buildResourceUrl — base-domain files (#307)', () => {
const baseDomain = 'example.com';

it('base-domain root (/) — no rewrite', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(buildResourceUrl(req, '/'), 'https://example.com/');
});

it('base-domain /welcome.js — no rewrite (filename has extension)', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(buildResourceUrl(req, '/welcome.js'), 'https://example.com/welcome.js');
});

it('base-domain /mashlib.js — no rewrite', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(buildResourceUrl(req, '/mashlib.js'), 'https://example.com/mashlib.js');
});

it('base-domain /terms.html — no rewrite', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(buildResourceUrl(req, '/terms.html'), 'https://example.com/terms.html');
});

it('base-domain /.well-known/foo — no rewrite (leading dot)', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(
buildResourceUrl(req, '/.well-known/foo'),
'https://example.com/.well-known/foo'
);
});
});

describe('buildResourceUrl — pod routing still works', () => {
const baseDomain = 'example.com';

it('base-domain /alice/ — rewrites to alice.example.com (no dot → pod name)', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(buildResourceUrl(req, '/alice/'), 'https://alice.example.com/');
});

it('base-domain /alice — rewrites (bare pod-root without trailing slash)', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(buildResourceUrl(req, '/alice'), 'https://alice.example.com/');
});

it('base-domain /alice/profile/card.jsonld — rewrites to alice.example.com/profile/card.jsonld', () => {
const req = makeRequest({ hostname: baseDomain, baseDomain });
assert.strictEqual(
buildResourceUrl(req, '/alice/profile/card.jsonld'),
'https://alice.example.com/profile/card.jsonld'
);
});

it('already-on-subdomain request — no rewrite (hostname !== baseDomain)', () => {
const req = makeRequest({
hostname: 'alice.example.com',
baseDomain,
podName: 'alice'
});
assert.strictEqual(
buildResourceUrl(req, '/profile/card.jsonld'),
'https://alice.example.com/profile/card.jsonld'
);
});
});

describe('buildResourceUrl — subdomain mode disabled', () => {
it('no rewrite when subdomainsEnabled is false', () => {
const req = makeRequest({
hostname: 'example.com',
baseDomain: 'example.com',
subdomainsEnabled: false
});
assert.strictEqual(buildResourceUrl(req, '/alice/'), 'https://example.com/alice/');
});

it('no rewrite when baseDomain is not set', () => {
const req = makeRequest({
hostname: 'example.com',
baseDomain: null,
subdomainsEnabled: true
});
assert.strictEqual(buildResourceUrl(req, '/alice/'), 'https://example.com/alice/');
});
});