Skip to content

Commit 8b72ea4

Browse files
committed
Reverted admin auth middleware & minification changes (#23179)
ref #23006 ref https://linear.app/ghost/issue/ONC-919/ Following the minification changes to the various frontend public assets, it seems the auth frame is broken on Ghost Pro. It's unclear why this is - it still works perfectly fine locally, and I wasn't able to easily track down the cause given the urgency vs. benefit. Frankly, there's not much benefit here besides trying to establish a new model that a bit more purposeful. It seems likely it was an issue in the middleware and not the minification process.
1 parent 2d9443f commit 8b72ea4

File tree

9 files changed

+88
-185
lines changed

9 files changed

+88
-185
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ test/functional/*.png
122122
/ghost/core/core/frontend/public/ghost.min.css
123123
/ghost/core/core/frontend/public/comment-counts.min.js
124124
/ghost/core/core/frontend/public/member-attribution.min.js
125-
/ghost/core/core/frontend/public/admin-auth/admin-auth.min.js
126125
/ghost/core/core/frontend/public/ghost-stats.min.js
127126
# Caddyfile - for local development with ssl + caddy
128127
Caddyfile

ghost/core/core/bridge.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const logging = require('@tryghost/logging');
1616
const tpl = require('@tryghost/tpl');
1717
const themeEngine = require('./frontend/services/theme-engine');
1818
const appService = require('./frontend/services/apps');
19-
const {cardAssets} = require('./frontend/services/assets-minification');
19+
const {adminAuthAssets, cardAssets} = require('./frontend/services/assets-minification');
2020
const routerManager = require('./frontend/services/routing').routerManager;
2121
const settingsCache = require('./shared/settings-cache');
2222
const urlService = require('./server/services/url');
@@ -51,6 +51,10 @@ class Bridge {
5151
return themeEngine.getActive();
5252
}
5353

54+
ensureAdminAuthAssetsMiddleware() {
55+
return adminAuthAssets.serveMiddleware();
56+
}
57+
5458
async activateTheme(loadedTheme, checkedTheme) {
5559
let settings = {
5660
locale: settingsCache.get('locale')
@@ -65,6 +69,9 @@ class Bridge {
6569
const cardAssetConfig = this.getCardAssetConfig();
6670
debug('reload card assets config', cardAssetConfig);
6771
cardAssets.invalidate(cardAssetConfig);
72+
73+
// rebuild asset files
74+
adminAuthAssets.invalidate();
6875
} catch (err) {
6976
logging.error(new errors.InternalServerError({
7077
message: tpl(messages.activateFailed, {theme: loadedTheme.name}),
@@ -106,4 +113,4 @@ class Bridge {
106113

107114
const bridge = new Bridge();
108115

109-
module.exports = bridge;
116+
module.exports = bridge;

ghost/core/core/frontend/public/admin-auth/admin-auth.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// const debug = require('@tryghost/debug')('comments-counts-assets');
2+
const path = require('path');
3+
const fs = require('fs');
4+
const logging = require('@tryghost/logging');
5+
const config = require('../../../shared/config');
6+
const urlUtils = require('../../../shared/url-utils');
7+
8+
const Minifier = require('./Minifier');
9+
const AssetsMinificationBase = require('./AssetsMinificationBase');
10+
11+
module.exports = class AdminAuthAssets extends AssetsMinificationBase {
12+
constructor(options = {}) {
13+
super(options);
14+
15+
this.src = options.src || path.join(config.get('paths').assetSrc, 'admin-auth');
16+
/** @private */
17+
this.dest = options.dest || path.join(config.getContentPath('public'), 'admin-auth');
18+
19+
this.minifier = new Minifier({src: this.src, dest: this.dest});
20+
21+
try {
22+
fs.mkdirSync(this.dest, {recursive: true});
23+
fs.copyFileSync(path.join(this.src, 'index.html'), path.join(this.dest, 'index.html'));
24+
} catch (error) {
25+
if (error.code === 'EACCES') {
26+
logging.error('Ghost was not able to write admin-auth asset files due to permissions.');
27+
return;
28+
}
29+
30+
throw error;
31+
}
32+
}
33+
34+
/**
35+
* @override
36+
*/
37+
generateGlobs() {
38+
return {
39+
'admin-auth.min.js': '*.js'
40+
};
41+
}
42+
43+
/**
44+
* @private
45+
*/
46+
generateReplacements() {
47+
// Clean the URL, only keep schema, host and port (without trailing slashes or subdirectory)
48+
const url = new URL(urlUtils.getSiteUrl());
49+
const origin = url.origin;
50+
51+
return {
52+
// Properly encode the origin
53+
'\'{{SITE_ORIGIN}}\'': JSON.stringify(origin)
54+
};
55+
}
56+
57+
/**
58+
* Minify, move into the destination directory, and clear existing asset files.
59+
*
60+
* @override
61+
* @returns {Promise<void>}
62+
*/
63+
async load() {
64+
const globs = this.generateGlobs();
65+
const replacements = this.generateReplacements();
66+
await this.minify(globs, {replacements});
67+
}
68+
};
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const CardAssets = require('./CardAssets');
2+
const AdminAuthAssets = require('./AdminAuthAssets');
23
const cardAssets = new CardAssets();
4+
const adminAuthAssets = new AdminAuthAssets();
35

46
module.exports = {
5-
cardAssets
7+
cardAssets,
8+
adminAuthAssets
69
};

ghost/core/core/server/web/admin/app.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const shared = require('../shared');
99
const errorHandler = require('@tryghost/mw-error-handler');
1010
const sentry = require('../../../shared/sentry');
1111
const redirectAdminUrls = require('./middleware/redirect-admin-urls');
12-
const createServeAuthFrameFileMw = require('./middleware/serve-auth-frame-file');
12+
const bridge = require('../../../bridge');
1313

1414
/**
1515
*
@@ -39,7 +39,7 @@ module.exports = function setupAdminApp() {
3939
// request to the Admin API /users/me/ endpoint to check if the user is logged in.
4040
//
4141
// Used by comments-ui to add moderation options to front-end comments when logged in.
42-
adminApp.use('/auth-frame', function authFrameMw(req, res, next) {
42+
adminApp.use('/auth-frame', bridge.ensureAdminAuthAssetsMiddleware(), function authFrameMw(req, res, next) {
4343
// only render content when we have an Admin session cookie,
4444
// otherwise return a 204 to avoid JS and API requests being made unnecessarily
4545
try {
@@ -52,7 +52,9 @@ module.exports = function setupAdminApp() {
5252
} catch (err) {
5353
next(err);
5454
}
55-
}, createServeAuthFrameFileMw(config, urlUtils));
55+
}, serveStatic(
56+
path.join(config.getContentPath('public'), 'admin-auth')
57+
));
5658

5759
// Ember CLI's live-reload script
5860
if (config.get('env') === 'development') {
@@ -93,4 +95,4 @@ module.exports = function setupAdminApp() {
9395
debug('Admin setup end');
9496

9597
return adminApp;
96-
};
98+
};

ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 0 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
const sinon = require('sinon');
2-
const fs = require('node:fs/promises');
32
// Thing we are testing
43
const redirectAdminUrls = require('../../../../../core/server/web/admin/middleware/redirect-admin-urls');
5-
const createServeAuthFrameFileMw = require('../../../../../core/server/web/admin/middleware/serve-auth-frame-file');
6-
const path = require('node:path');
74

85
describe('Admin App', function () {
96
afterEach(function () {
@@ -61,144 +58,5 @@ describe('Admin App', function () {
6158
res.redirect.called.should.be.false();
6259
});
6360
});
64-
65-
describe('serveAuthFrameFile', function () {
66-
let config;
67-
let urlUtils;
68-
let readFile;
69-
70-
const siteUrl = 'https://foo.bar';
71-
const publicFilePath = 'foo/bar/public';
72-
73-
const indexContent = '<html><body><h1>Hello, world!</h1></body></html>';
74-
const fooJsContent = '(function() { console.log("Hello, world!"); })();';
75-
const fooJsContentWithSiteOrigin = '(function() { console.log("{{SITE_ORIGIN}}"); })();';
76-
77-
function createMiddleware() {
78-
return createServeAuthFrameFileMw(config, urlUtils);
79-
}
80-
81-
beforeEach(function () {
82-
config = {
83-
get: sinon.stub()
84-
};
85-
86-
config.get.withArgs('paths').returns({
87-
publicFilePath
88-
});
89-
90-
urlUtils = {
91-
getSiteUrl: sinon.stub().returns(siteUrl)
92-
};
93-
readFile = sinon.stub(fs, 'readFile');
94-
95-
const adminAuthFilePath = filename => path.join(publicFilePath, 'admin-auth', filename);
96-
97-
readFile.withArgs(adminAuthFilePath('index.html'))
98-
.resolves(Buffer.from(indexContent));
99-
readFile.withArgs(adminAuthFilePath('foo.js'))
100-
.resolves(Buffer.from(fooJsContent));
101-
readFile.withArgs(adminAuthFilePath('foo-2.js'))
102-
.resolves(Buffer.from(fooJsContentWithSiteOrigin));
103-
readFile.withArgs(adminAuthFilePath('bar.js'))
104-
.rejects(new Error('File not found'));
105-
});
106-
107-
afterEach(function () {
108-
readFile.restore();
109-
});
110-
111-
it('should serve index.html if the url is /', async function () {
112-
const middleware = createMiddleware();
113-
114-
const req = {
115-
url: '/'
116-
};
117-
const res = {
118-
end: sinon.stub()
119-
};
120-
const next = sinon.stub();
121-
122-
await middleware(req, res, next);
123-
124-
res.end.calledWith(indexContent).should.be.true();
125-
next.called.should.be.false();
126-
});
127-
128-
it('should serve the correct file corresponding to the url', async function () {
129-
const middleware = createMiddleware();
130-
131-
const req = {
132-
url: '/foo.js'
133-
};
134-
const res = {
135-
end: sinon.stub()
136-
};
137-
const next = sinon.stub();
138-
139-
await middleware(req, res, next);
140-
141-
res.end.calledWith(fooJsContent).should.be.true();
142-
next.called.should.be.false();
143-
});
144-
145-
it('should replace {{SITE_ORIGIN}} with the site url', async function () {
146-
const middleware = createMiddleware();
147-
148-
const req = {
149-
url: '/foo-2.js'
150-
};
151-
const res = {
152-
end: sinon.stub()
153-
};
154-
const next = sinon.stub();
155-
156-
await middleware(req, res, next);
157-
158-
res.end.calledOnce.should.be.true();
159-
160-
const args = res.end.getCall(0).args;
161-
args[0].toString().includes(siteUrl).should.be.true();
162-
args[0].toString().includes(`{{SITE_ORIGIN}}`).should.be.false();
163-
});
164-
165-
it('should not allow path traversal', async function () {
166-
const middleware = createMiddleware();
167-
168-
const req = {
169-
url: '/foo/../../foo.js'
170-
};
171-
const res = {
172-
end: sinon.stub()
173-
};
174-
const next = sinon.stub();
175-
176-
await middleware(req, res, next);
177-
178-
res.end.calledOnce.should.be.true();
179-
180-
// Because we use base name when resolving the file, foo.js should be served
181-
res.end.calledWith(fooJsContent).should.be.true();
182-
183-
next.calledOnce.should.be.false();
184-
});
185-
186-
it('should call next if the file is not found', async function () {
187-
const middleware = createMiddleware();
188-
189-
const req = {
190-
url: '/bar.js'
191-
};
192-
const res = {
193-
end: sinon.stub()
194-
};
195-
const next = sinon.stub();
196-
197-
await middleware(req, res, next);
198-
199-
res.end.calledOnce.should.be.false();
200-
next.calledOnce.should.be.true();
201-
});
202-
});
20361
});
20462
});

0 commit comments

Comments
 (0)