fix: change site url breaks `meteor_runtime_config` (#35976)

pull/35996/head^2
Guilherme Gazzo 8 months ago committed by GitHub
parent abe8320936
commit 3b0ef43684
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/curly-teachers-sort.md
  2. 15
      apps/meteor/app/cors/server/cors.ts
  3. 32
      apps/meteor/server/configuration/configureBoilerplate.ts
  4. 72
      apps/meteor/tests/end-to-end/api/cors.ts

@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---
Fixes a bug where changing site url breaks `meteor_runtime_config` path until we reset the server

@ -1,4 +1,3 @@
import { createHash } from 'crypto';
import type http from 'http';
import type { UrlWithParsedQuery } from 'url';
import url from 'url';
@ -9,6 +8,7 @@ import { Meteor } from 'meteor/meteor';
import type { StaticFiles } from 'meteor/webapp';
import { WebApp, WebAppInternals } from 'meteor/webapp';
import { getWebAppHash } from '../../../server/configuration/configureBoilerplate';
import { settings } from '../../settings/server';
// Taken from 'connect' types
@ -128,18 +128,9 @@ WebAppInternals.staticFilesMiddleware = function (
// a cache of the file for the wrong hash and start a client loop due to the mismatch
// of the hashes of ui versions which would be checked against a websocket response
if (path === '/meteor_runtime_config.js') {
const program = WebApp.clientPrograms[arch] as (typeof WebApp.clientPrograms)[string] & {
meteorRuntimeConfigHash?: string;
meteorRuntimeConfig: string;
};
if (!program?.meteorRuntimeConfigHash) {
program.meteorRuntimeConfigHash = createHash('sha1')
.update(JSON.stringify(encodeURIComponent(program.meteorRuntimeConfig)))
.digest('hex');
}
const hash = getWebAppHash(arch);
if (program.meteorRuntimeConfigHash !== url.query.hash) {
if (!hash || hash !== url.query.hash) {
res.writeHead(404);
return res.end();
}

@ -1,13 +1,39 @@
import { createHash } from 'crypto';
import { Meteor } from 'meteor/meteor';
import { WebAppInternals } from 'meteor/webapp';
import { WebApp, WebAppInternals } from 'meteor/webapp';
import type { ICachedSettings } from '../../app/settings/server/CachedSettings';
const webAppHashes: Record<string, string> = {};
export function getWebAppHash(arch: string): string | undefined {
if (!webAppHashes[arch]) {
const program = WebApp.clientPrograms[arch] as (typeof WebApp.clientPrograms)[string] & {
meteorRuntimeConfig: string;
};
webAppHashes[arch] = createHash('sha1')
.update(JSON.stringify(encodeURIComponent(program.meteorRuntimeConfig)))
.digest('hex');
}
return webAppHashes[arch];
}
const { generateBoilerplate } = WebAppInternals;
WebAppInternals.generateBoilerplate = function (...args: Parameters<typeof generateBoilerplate>) {
for (const arch of Object.keys(WebApp.clientPrograms)) {
delete webAppHashes[arch];
}
return generateBoilerplate.apply(this, args);
};
export function configureBoilerplate(settings: ICachedSettings): void {
settings.watch<string>(
'Site_Url',
// Needed as WebAppInternals.generateBoilerplate needs to be called in a fiber
Meteor.bindEnvironment((value) => {
Meteor.bindEnvironment(async (value) => {
if (value == null || value.trim() === '') {
return;
}
@ -28,7 +54,7 @@ export function configureBoilerplate(settings: ICachedSettings): void {
process.env.MOBILE_ROOT_URL = host;
process.env.MOBILE_DDP_URL = host;
if (typeof WebAppInternals !== 'undefined' && WebAppInternals.generateBoilerplate) {
return WebAppInternals.generateBoilerplate();
await WebAppInternals.generateBoilerplate();
}
}),
);

@ -0,0 +1,72 @@
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { getCredentials, request } from '../../data/api-data';
import { updateSetting } from '../../data/permissions.helper';
const getHash = () =>
request
.get('/')
.expect(200)
.expect('Content-Type', 'text/html; charset=utf-8')
.then((res) => {
const hashMatch = res.text.match(/meteor_runtime_config\.js\?hash=([^"']+)/);
expect(hashMatch).to.not.be.null;
const hash = hashMatch![1];
return hash;
});
describe('[CORS]', () => {
before((done) => getCredentials(done));
after(async () => {
await updateSetting('Site_Url', 'http://localhost:3000');
});
describe('[/meteor_runtime_config.js]', () => {
it('should return 404 when hash is missing', (done) => {
void request.get('/meteor_runtime_config.js').expect(404).end(done);
});
it('should return 404 when hash is invalid', (done) => {
void request.get('/meteor_runtime_config.js').query({ hash: 'invalid_hash' }).expect(404).end(done);
});
it('should return runtime config when hash matches', async () => {
// First get the root page to extract the hash
const hash = await getHash();
// Now request with the extracted hash
await request
.get('/meteor_runtime_config.js')
.query({ hash })
.expect(200)
.expect('Content-Type', 'application/javascript; charset=UTF-8')
.expect((res) => {
expect(res.text).to.include('__meteor_runtime_config__');
expect(res.text).to.include('http://localhost:3000');
});
});
it('should return 404 when hash does not match', (done) => {
// First get the root page to extract the hash
void request.get('/meteor_runtime_config.js').query({ hash: 'invalidHash' }).expect(404).end(done);
});
it('should update hash when ROOT_URL changes', async () => {
const originalHash = getHash();
await updateSetting('Site_Url', 'http://new-url:3000');
const newHash = await getHash();
expect(newHash).to.not.equal(originalHash);
// it should return if the new hash is valid
await request
.get('/meteor_runtime_config.js')
.query({ hash: newHash })
.expect(200)
.expect('Content-Type', 'application/javascript; charset=UTF-8')
.expect((res) => {
expect(res.text).to.include('__meteor_runtime_config__');
expect(res.text).to.include('http://new-url:3000');
});
});
});
});
Loading…
Cancel
Save