0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

Fixed "Authorization Failed" error screens when not logged in

no issue
- `/config/` can only be requested when authenticated
- updated `/config/` mock to look for an Authentication header and return a 403 if it's missing
- updated `ajax` service to add an `Authentication` header when authenticated in testing env (cookies are not present when testing)
- updated `config` service to add `fetchUnauthenticated()` and `fetchAuthenticated()` methods in addition to `.fetch()`
- updated `application` route to only fetch authenticated config when authenticated
- updated `signin` controller to correctly fetch config after sign-in
This commit is contained in:
Kevin Ansfield 2019-02-26 10:38:00 +07:00
parent ef857d25ba
commit 738823d8f8
5 changed files with 59 additions and 18 deletions

View file

@ -45,7 +45,7 @@ export default Controller.extend(ValidationEngine, {
let promises = []; let promises = [];
promises.pushObject(this.get('settings').fetch()); promises.pushObject(this.get('settings').fetch());
promises.pushObject(this.get('config').fetchPrivate()); promises.pushObject(this.get('config').fetchAuthenticated());
// fetch settings and private config for synchronous access // fetch settings and private config for synchronous access
yield RSVP.all(promises); yield RSVP.all(promises);

View file

@ -43,7 +43,7 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
routeAfterAuthentication: 'posts', routeAfterAuthentication: 'posts',
beforeModel() { beforeModel() {
return this.get('config').fetch(); return this.get('config').fetchUnauthenticated();
}, },
afterModel(model, transition) { afterModel(model, transition) {
@ -53,6 +53,7 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
this.set('appLoadTransition', transition); this.set('appLoadTransition', transition);
transition.send('loadServerNotifications'); transition.send('loadServerNotifications');
let configPromise = this.get('config').fetchAuthenticated();
let featurePromise = this.get('feature').fetch(); let featurePromise = this.get('feature').fetch();
let settingsPromise = this.get('settings').fetch(); let settingsPromise = this.get('settings').fetch();
let tourPromise = this.get('tour').fetchViewed(); let tourPromise = this.get('tour').fetchViewed();
@ -60,6 +61,7 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
// return the feature/settings load promises so that we block until // return the feature/settings load promises so that we block until
// they are loaded to enable synchronous access everywhere // they are loaded to enable synchronous access everywhere
return RSVP.all([ return RSVP.all([
configPromise,
featurePromise, featurePromise,
settingsPromise, settingsPromise,
tourPromise tourPromise

View file

@ -118,6 +118,8 @@ export function isThemeValidationError(errorOrStatus, payload) {
let ajaxService = AjaxService.extend({ let ajaxService = AjaxService.extend({
session: service(), session: service(),
isTesting: undefined,
// flag to tell our ESA authenticator not to try an invalidate DELETE request // flag to tell our ESA authenticator not to try an invalidate DELETE request
// because it's been triggered by this service's 401 handling which means the // because it's been triggered by this service's 401 handling which means the
// DELETE would fail and get stuck in an infinite loop // DELETE would fail and get stuck in an infinite loop
@ -130,9 +132,20 @@ let ajaxService = AjaxService.extend({
headers['X-Ghost-Version'] = config.APP.version; headers['X-Ghost-Version'] = config.APP.version;
headers['App-Pragma'] = 'no-cache'; headers['App-Pragma'] = 'no-cache';
if (this.session.isAuthenticated && this.isTesting) {
headers.Authorization = 'Test';
}
return headers; return headers;
}).volatile(), }).volatile(),
init() {
this._super(...arguments);
if (this.isTesting === undefined) {
this.isTesting = config.environment === 'test';
}
},
// ember-ajax recognises `application/vnd.api+json` as a JSON-API request // ember-ajax recognises `application/vnd.api+json` as a JSON-API request
// and formats appropriately, we want to handle `application/json` the same // and formats appropriately, we want to handle `application/json` the same
_makeRequest(hash) { _makeRequest(hash) {

View file

@ -9,6 +9,7 @@ const {_ProxyMixin} = Ember;
export default Service.extend(_ProxyMixin, { export default Service.extend(_ProxyMixin, {
ajax: service(), ajax: service(),
ghostPaths: service(), ghostPaths: service(),
session: service(),
content: null, content: null,
@ -18,23 +19,37 @@ export default Service.extend(_ProxyMixin, {
}, },
fetch() { fetch() {
let promises = [];
promises.push(this.fetchUnauthenticated());
if (this.session.isAuthenticated) {
promises.push(this.fetchAuthenticated());
}
return RSVP.all(promises);
},
fetchUnauthenticated() {
let siteUrl = this.ghostPaths.url.api('site'); let siteUrl = this.ghostPaths.url.api('site');
return this.ajax.request(siteUrl).then(({site}) => {
// normalize url to non-trailing-slash
site.blogUrl = site.url.replace(/\/$/, '');
site.blogTitle = site.title;
delete site.url;
delete site.title;
Object.assign(this.content, site);
}).then(() => {
this.notifyPropertyChange('content');
});
},
fetchAuthenticated() {
let configUrl = this.ghostPaths.url.api('config'); let configUrl = this.ghostPaths.url.api('config');
return this.ajax.request(configUrl).then(({config}) => {
return RSVP.all([ Object.assign(this.content, config);
this.ajax.request(siteUrl).then(({site}) => { }).then(() => {
// normalize url to non-trailing-slash
site.blogUrl = site.url.replace(/\/$/, '');
site.blogTitle = site.title;
delete site.url;
delete site.title;
Object.assign(this.content, site);
}),
this.ajax.request(configUrl).then(({config}) => {
Object.assign(this.content, config);
})
]).then(() => {
this.notifyPropertyChange('content'); this.notifyPropertyChange('content');
}); });
}, },

View file

@ -1,7 +1,18 @@
import {Response} from 'ember-cli-mirage';
import {isEmpty} from '@ember/utils'; import {isEmpty} from '@ember/utils';
export default function mockConfig(server) { export default function mockConfig(server) {
server.get('/config/', function ({db}) { server.get('/config/', function ({db}, request) {
if (!request.requestHeaders.Authorization) {
return new Response(403, {}, {
errors: [{
type: 'NoPermissionError',
message: 'Authorization failed',
context: 'Unable to determine the authenticated user or integration. Check that cookies are being passed through if using session authentication.'
}]
});
}
if (isEmpty(db.configs)) { if (isEmpty(db.configs)) {
server.loadFixtures('configs'); server.loadFixtures('configs');
} }