2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Implemented admin auth origin check (#15135)

refs https://github.com/TryGhost/Team/issues/1694

- Added replacements option to `@tryghost/minifier` + updated documentation and name of 'options' param which was a bit confusing. 
- At compile time, we'll replace `'{{SITE_ORIGIN}}'` with the actual and JS encoded origin string.
- Block requests to the auth frame with the wrong origin, but log a warning for now to make debugging easier.
- Limit who can read the response messages by origin
This commit is contained in:
Simon Backx 2022-08-03 15:59:08 +02:00 committed by GitHub
parent c2f2312ad2
commit e1bee3c647
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 11 deletions

View file

@ -4,6 +4,7 @@ const path = require('path');
const fs = require('fs').promises;
const logging = require('@tryghost/logging');
const config = require('../../../shared/config');
const urlUtils = require('../../../shared/url-utils');
class AdminAuthAssetsService {
constructor(options = {}) {
@ -24,13 +25,27 @@ class AdminAuthAssetsService {
};
}
/**
* @private
*/
generateReplacements() {
// Clean the URL, only keep schema, host and port (without trailing slashes or subdirectory)
const url = new URL(urlUtils.getSiteUrl());
const origin = url.origin;
return {
// Properly encode the origin
'\'{{SITE_ORIGIN}}\'': JSON.stringify(origin)
};
}
/**
* @private
* @returns {Promise<void>}
*/
async minify(globs) {
async minify(globs, options) {
try {
await this.minifier.minify(globs);
await this.minifier.minify(globs, options);
} catch (error) {
if (error.code === 'EACCES') {
logging.error('Ghost was not able to write admin-auth asset files due to permissions.');
@ -84,8 +99,9 @@ class AdminAuthAssetsService {
*/
async load() {
const globs = this.generateGlobs();
const replacements = this.generateReplacements();
await this.clearFiles();
await this.minify(globs);
await this.minify(globs, {replacements});
await this.copyStatic();
}
}

View file

@ -1,8 +1,12 @@
const adminUrl = window.location.href.replace('auth-frame/', '');
// At compile time, we'll replace the value with the actual origin.
const siteOrigin = '{{SITE_ORIGIN}}';
window.addEventListener('message', async function (event) {
if (event.origin !== '*') {
// return;
if (event.origin !== siteOrigin) {
console.warn('Ignored message to admin auth iframe because of mismatch in origin', 'expected', siteOrigin, 'got', event.origin, 'with data', event.data);
return;
}
let data = null;
try {
@ -16,7 +20,7 @@ window.addEventListener('message', async function (event) {
uid: data.uid,
error: error,
result: result
}), '*');
}), siteOrigin);
}
if (data.action === 'getUser') {

View file

@ -109,14 +109,34 @@ class Minifier {
}
}
async minify(options) {
debug('Begin', options);
const destinations = Object.keys(options);
/**
* Minify files
*
* @param {Object} globs An object in the form of
* ```js
* {
* 'destination1.js': 'glob/*.js',
* 'destination2.js': 'glob2/*.js'
* }
* ```
* @param {Object} [options]
* @param {Object} [options.replacements] Key value pairs that should get replaced in the content before minifying
* @returns {Promise<string[]>} List of minified files (keys of globs)
*/
async minify(globs, options) {
debug('Begin', globs);
const destinations = Object.keys(globs);
const minifiedFiles = [];
for (const dest of destinations) {
const src = options[dest];
const contents = await this.getSrcFileContents(src);
const src = globs[dest];
let contents = await this.getSrcFileContents(src);
if (options?.replacements) {
for (const key of Object.keys(options.replacements)) {
contents = contents.replace(key, options.replacements[key]);
}
}
let minifiedContents;
if (dest.endsWith('.css')) {

View file

@ -71,6 +71,21 @@ describe('Minifier', function () {
result.should.be.an.Array().with.lengthOf(2);
});
it('can replace the content', async function () {
let result = await minifier.minify({
'card.min.js': 'js/*.js'
}, {
replacements: {
'.kg-gallery-image': 'randomword'
}
});
result.should.be.an.Array().with.lengthOf(1);
const outputPath = minifier.getFullDest(result[0]);
const content = await fs.readFile(outputPath, {encoding: 'utf8'});
content.should.match(/randomword/);
});
});
describe('Bad inputs', function () {