diff --git a/README.md b/README.md index 29008223..a49e0b30 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,6 @@ APP_STATIC_URL | url prefix for static resources APP_STATIC_INCLUDE_CUSTOM_CSS | set to "true" to include custom.css (from APP_STATIC_URL) in static pages APP_UNTRUSTED_URL | URL at which to serve/expect plugin content. GRIST_ADAPT_DOMAIN | set to "true" to support multiple base domains (careful, host header should be trustworthy) -GRIST_ALLOWED_HOSTS | comma-separated list of permitted domains origin for requests (e.g. my.site,another.com) GRIST_APP_ROOT | directory containing Grist sandbox and assets (specifically the sandbox and static subdirectories). GRIST_BACKUP_DELAY_SECS | wait this long after a doc change before making a backup GRIST_BOOT_KEY | if set, offer diagnostics at /boot/GRIST_BOOT_KEY diff --git a/app/server/lib/Authorizer.ts b/app/server/lib/Authorizer.ts index c1d53361..761e76f2 100644 --- a/app/server/lib/Authorizer.ts +++ b/app/server/lib/Authorizer.ts @@ -18,7 +18,7 @@ import {makeId} from 'app/server/lib/idUtils'; import log from 'app/server/lib/log'; import {IPermitStore, Permit} from 'app/server/lib/Permit'; import {AccessTokenInfo} from 'app/server/lib/AccessTokens'; -import {allowHost, getOriginUrl, isEnvironmentAllowedHost, optStringParam} from 'app/server/lib/requestUtils'; +import {allowHost, getOriginUrl, optStringParam} from 'app/server/lib/requestUtils'; import * as cookie from 'cookie'; import {NextFunction, Request, RequestHandler, Response} from 'express'; import {IncomingMessage} from 'http'; @@ -271,7 +271,7 @@ export async function addRequestUser( // custom-domain owner could hijack such sessions. const allowedOrg = getAllowedOrgForSessionID(mreq.sessionID); if (allowedOrg) { - if (allowHost(req, allowedOrg.host) || isEnvironmentAllowedHost(allowedOrg.host)) { + if (allowHost(req, allowedOrg.host)) { customHostSession = ` custom-host-match ${allowedOrg.host}`; } else { // We need an exception for internal forwarding from home server to doc-workers. These use diff --git a/app/server/lib/requestUtils.ts b/app/server/lib/requestUtils.ts index 890b9702..e25cef58 100644 --- a/app/server/lib/requestUtils.ts +++ b/app/server/lib/requestUtils.ts @@ -8,7 +8,6 @@ import {RequestWithGrist} from 'app/server/lib/GristServer'; import log from 'app/server/lib/log'; import {Permit} from 'app/server/lib/Permit'; import {Request, Response} from 'express'; -import _ from 'lodash'; import {Writable} from 'stream'; // log api details outside of dev environment (when GRIST_HOSTED_VERSION is set) @@ -87,7 +86,7 @@ export function trustOrigin(req: Request, resp: Response): boolean { const origin = req.get('origin'); if (!origin) { return true; } // Not a CORS request. if (process.env.GRIST_HOST && req.hostname === process.env.GRIST_HOST) { return true; } - if (!allowHost(req, new URL(origin)) && !isEnvironmentAllowedHost(new URL(origin))) { return false; } + if (!allowHost(req, new URL(origin))) { return false; } // For a request to a custom domain, the full hostname must match. resp.header("Access-Control-Allow-Origin", origin); @@ -104,14 +103,14 @@ export function allowHost(req: Request, allowedHost: string|URL) { const allowedUrl = (typeof allowedHost === 'string') ? new URL(`${proto}://${allowedHost}`) : allowedHost; if (mreq.isCustomHost) { // For a request to a custom domain, the full hostname must match. - return actualUrl.hostname === allowedUrl.hostname; + return actualUrl.hostname === allowedUrl.hostname; } else { // For requests to a native subdomains, only the base domain needs to match. const allowedDomain = parseSubdomain(allowedUrl.hostname); const actualDomain = parseSubdomain(actualUrl.hostname); - return (!_.isEmpty(actualDomain) ? + return actualDomain.base ? actualDomain.base === allowedDomain.base : - allowedUrl.hostname === actualUrl.hostname); + actualUrl.hostname === allowedUrl.hostname; } } @@ -119,13 +118,6 @@ export function matchesBaseDomain(domain: string, baseDomain: string) { return domain === baseDomain || domain.endsWith("." + baseDomain); } -export function isEnvironmentAllowedHost(url: string|URL) { - const urlHost = (typeof url === 'string') ? url : url.hostname; - return (process.env.GRIST_ALLOWED_HOSTS || "").split(",").some(domain => - domain && matchesBaseDomain(urlHost, domain) - ); -} - export function isParameterOn(parameter: any): boolean { return gutil.isAffirmative(parameter); } diff --git a/test/server/lib/DocApi.ts b/test/server/lib/DocApi.ts index 22401c6f..2754fd6e 100644 --- a/test/server/lib/DocApi.ts +++ b/test/server/lib/DocApi.ts @@ -4865,23 +4865,6 @@ function testDocApi() { }); describe("Allowed Origin", () => { - it('should allow only example.com', async () => { - async function checkOrigin(origin: string, allowed: boolean) { - const resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/data`, - {...chimpy, headers: {...chimpy.headers, "Origin": origin}} - ); - assert.equal(resp.headers['access-control-allow-credentials'], allowed ? 'true' : undefined); - assert.equal(resp.status, allowed ? 200 : 403); - } - - await checkOrigin("https://www.toto.com", false); - await checkOrigin("https://badexample.com", false); - await checkOrigin("https://bad.com/example.com/toto", false); - await checkOrigin("https://example.com/path", true); - await checkOrigin("https://example.com:3000/path", true); - await checkOrigin("https://good.example.com/toto", true); - }); - it("should respond with correct CORS headers", async function () { const wid = await getWorkspaceId(userApi, 'Private'); const docId = await userApi.newDoc({name: 'CorsTestDoc'}, wid); diff --git a/test/server/lib/helpers/TestServer.ts b/test/server/lib/helpers/TestServer.ts index 0f8efb65..51a5d39f 100644 --- a/test/server/lib/helpers/TestServer.ts +++ b/test/server/lib/helpers/TestServer.ts @@ -49,7 +49,6 @@ export class TestServer { GRIST_PORT: '0', GRIST_DISABLE_S3: 'true', REDIS_URL: process.env.TEST_REDIS_URL, - GRIST_ALLOWED_HOSTS: `example.com,localhost`, GRIST_TRIGGER_WAIT_DELAY: '100', // this is calculated value, some tests expect 4 attempts and some will try 3 times GRIST_TRIGGER_MAX_ATTEMPTS: '4',