mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-27 22:49:56 -05: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:
parent
c2f2312ad2
commit
e1bee3c647
4 changed files with 66 additions and 11 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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')) {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
Loading…
Add table
Reference in a new issue