1
0
Fork 0
mirror of https://github.com/TryGhost/Ghost-Admin.git synced 2023-12-14 02:33:04 +01:00

Fix auth regressions after ESA 1.0 upgrade

refs #6039, closes #6047, closes #6048

- delete old/unused fixtures file
- add failing tests for #6047 & #6048
- redirect to sign-in if we get a 401 when making an API request
- fix incorrect `this.notifications` call in tag controller
- raise `authorizationFailed` action in application route's `sessionInvalidated` hook so that it can be handled by leaf routes (fixes re-auth modal display)
- close "saving failed" alert when successfully re-authenticated
- adds a "window-proxy" util so that we can override `window.*` operations in tests
- fix `gh-selectize` attempting to register event handlers when the component has already been destroyed
This commit is contained in:
Kevin Ansfield 2015-11-04 15:20:11 +00:00
parent 8859d686d3
commit 7db0ffbda2
16 changed files with 273 additions and 71 deletions

View file

@ -1,10 +1,15 @@
import DS from 'ember-data';
import ghostPaths from 'ghost/utils/ghost-paths';
import Ember from 'ember';
const {inject} = Ember;
export default DS.RESTAdapter.extend({
host: window.location.origin,
namespace: ghostPaths().apiRoot.slice(1),
session: inject.service('session'),
shouldBackgroundReloadRecord: function () {
return false;
},
@ -43,5 +48,15 @@ export default DS.RESTAdapter.extend({
return response.then(function () {
return null;
});
},
handleResponse: function (status) {
if (status === 401) {
if (this.get('session.isAuthenticated')) {
this.get('session').invalidate();
}
}
return this._super(...arguments);
}
});

View file

@ -17,16 +17,18 @@ export default EmberSelectizeComponent.extend({
if (!openOnFocus) {
Ember.run.next(this, function () {
var selectize = this._selectize;
selectize.on('dropdown_open', function () {
if (Ember.isBlank(selectize.$control_input.val())) {
selectize.close();
}
});
selectize.on('type', function (filter) {
if (Ember.isBlank(filter)) {
selectize.close();
}
});
if (selectize) {
selectize.on('dropdown_open', function () {
if (Ember.isBlank(selectize.$control_input.val())) {
selectize.close();
}
});
selectize.on('type', function (filter) {
if (Ember.isBlank(filter)) {
selectize.close();
}
});
}
});
}
}),

View file

@ -24,6 +24,7 @@ export default Ember.Controller.extend(ValidationEngine, {
this.get('session').authenticate(authStrategy, this.get('identification'), this.get('password')).then(function () {
self.send('closeModal');
self.set('password', '');
self.get('notifications').closeAlerts('post.save');
}).catch(function () {
// if authentication fails a rejected promise will be returned.
// it needs to be caught so it doesn't generate an exception in the console,

View file

@ -9,6 +9,7 @@ export default Ember.Controller.extend({
isMobile: alias('tagsController.isMobile'),
tagsController: inject.controller('settings.tags'),
notifications: inject.service(),
saveTagProperty: function (propKey, newValue) {
const tag = this.get('tag'),
@ -27,10 +28,10 @@ export default Ember.Controller.extend({
tag.save().then((savedTag) => {
// replace 'new' route with 'tag' route
this.replaceWith('settings.tags.tag', savedTag);
this.replaceRoute('settings.tags.tag', savedTag);
}).catch((error) => {
if (error) {
this.notifications.showAPIError(error, {key: 'tag.save'});
this.get('notifications').showAPIError(error, {key: 'tag.save'});
}
});
},

View file

@ -52,6 +52,23 @@ export default function () {
this.get('/notifications/', 'notifications');
/* Posts ---------------------------------------------------------------- */
this.post('/posts/', function (db, request) {
const [attrs] = JSON.parse(request.requestBody).posts;
let post;
if (isBlank(attrs.slug) && !isBlank(attrs.title)) {
attrs.slug = attrs.title.dasherize();
}
post = db.posts.insert(attrs);
return {
posts: [post]
};
});
/* Settings ------------------------------------------------------------- */
this.get('/settings/', function (db, request) {
@ -84,6 +101,16 @@ export default function () {
};
});
/* Slugs ---------------------------------------------------------------- */
this.get('/slugs/post/:slug/', function (db, request) {
return {
slugs: [
{slug: request.params.slug.dasherize}
]
};
});
/* Tags ----------------------------------------------------------------- */
this.post('/tags/', function (db, request) {
@ -119,7 +146,7 @@ export default function () {
this.put('/tags/:id/', function (db, request) {
const id = request.params.id,
[attrs] = JSON.parse(request.requestBody).contacts,
[attrs] = JSON.parse(request.requestBody).tags,
record = db.tags.update(id, attrs);
return {
@ -137,6 +164,8 @@ export default function () {
users: [db.users.find(1)]
};
});
this.get('/users/', 'users');
}
/*

View file

@ -0,0 +1,6 @@
/* jscs:disable */
import Mirage from 'ember-cli-mirage';
export default Mirage.Factory.extend({
// TODO: fill in with actual factory data
});

View file

@ -1,9 +1,11 @@
/* global key */
import Ember from 'ember';
import AuthConfiguration from 'ember-simple-auth/configuration';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
import ShortcutsRoute from 'ghost/mixins/shortcuts-route';
import ctrlOrCmd from 'ghost/utils/ctrl-or-cmd';
import windowProxy from 'ghost/utils/window-proxy';
const shortcuts = {};
@ -42,6 +44,10 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
});
},
sessionInvalidated: function () {
this.send('authorizationFailed');
},
actions: {
openMobileMenu: function () {
this.controller.set('showMobileMenu', true);
@ -75,6 +81,10 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
});
},
authorizationFailed: function () {
windowProxy.replaceLocation(AuthConfiguration.baseURL);
},
openModal: function (modalName, model, type) {
this.get('dropdown').closeDropdowns();
key.setScope('modal');

View file

@ -36,7 +36,7 @@ export default AuthenticatedRoute.extend(base, {
return post;
}
return self.replaceWith('posts.index');
return self.replaceRoute('posts.index');
});
},
@ -45,7 +45,7 @@ export default AuthenticatedRoute.extend(base, {
return self.get('session.user').then(function (user) {
if (user.get('isAuthor') && !post.isAuthoredByUser(user)) {
return self.replaceWith('posts.index');
return self.replaceRoute('posts.index');
}
});
},

View file

@ -32,7 +32,7 @@ export default AuthenticatedRoute.extend(ShortcutsRoute, {
return post;
}
return self.replaceWith('posts.index');
return self.replaceRoute('posts.index');
});
},
@ -41,7 +41,7 @@ export default AuthenticatedRoute.extend(ShortcutsRoute, {
return self.get('session.user').then(function (user) {
if (user.get('isAuthor') && !post.isAuthoredByUser(user)) {
return self.replaceWith('posts.index');
return self.replaceRoute('posts.index');
}
});
},

View file

@ -1,8 +1,7 @@
{{#gh-editor editorScrollInfo=editorScrollInfo as |ghEditor|}}
<header class="view-header">
{{#gh-view-title classNames="gh-editor-title" openMobileMenu="openMobileMenu"}}
{{gh-trim-focus-input type="text" id="entry-title"placeholder="Your Post Title" value=model.titleScratch
tabindex="1" focus=shouldFocusTitle}}
{{gh-trim-focus-input type="text" id="entry-title" placeholder="Your Post Title" value=model.titleScratch tabindex="1" focus=shouldFocusTitle}}
{{/gh-view-title}}
<section class="view-actions">
<button type="button" class="post-settings" title="Post Settings" {{action "openSettingsMenu"}}>

View file

@ -0,0 +1,9 @@
export default {
changeLocation: function (url) {
window.location = url;
},
replaceLocation: function (url) {
window.location.replace(url);
}
};

View file

@ -0,0 +1,132 @@
/* jshint expr:true */
import {
describe,
it,
beforeEach,
afterEach
} from 'mocha';
import { expect } from 'chai';
import Ember from 'ember';
import startApp from '../helpers/start-app';
import { authenticateSession, currentSession, invalidateSession } from 'ghost/tests/helpers/ember-simple-auth';
import Mirage from 'ember-cli-mirage';
import windowProxy from 'ghost/utils/window-proxy';
const {run} = Ember;
describe('Acceptance: Authentication', function () {
let application,
originalReplaceLocation;
beforeEach(function () {
application = startApp();
});
afterEach(function () {
run(application, 'destroy');
});
describe('general page', function () {
beforeEach(function () {
originalReplaceLocation = windowProxy.replaceLocation;
windowProxy.replaceLocation = function (url) {
visit(url);
};
server.loadFixtures();
const role = server.create('role', {name: 'Administrator'}),
user = server.create('user', {roles: [role], slug: 'test-user'});
});
afterEach(function () {
windowProxy.replaceLocation = originalReplaceLocation;
});
it('invalidates session on 401 API response', function () {
const role = server.create('role', {name: 'Administrator'}),
user = server.create('user', {roles: [role]});
// return a 401 when attempting to retrieve tags
server.get('/users/', (db, request) => {
return new Mirage.Response(401, {}, {
errors: [
{message: 'Access denied.', errorType: 'UnauthorizedError'}
]
});
});
authenticateSession(application);
visit('/team');
andThen(() => {
expect(currentURL(), 'url after 401').to.equal('/signin');
});
});
});
describe('editor', function () {
let origDebounce = Ember.run.debounce;
let origThrottle = Ember.run.throttle;
// we don't want the autosave interfering in this test
beforeEach(function () {
Ember.run.debounce = function () { };
Ember.run.throttle = function () { };
});
it('displays re-auth modal attempting to save with invalid session', function () {
const role = server.create('role', {name: 'Administrator'}),
user = server.create('user', {roles: [role]});
// simulate an invalid session when saving the edited post
server.put('/posts/:id/', (db, request) => {
let post = db.posts.find(request.params.id),
[attrs] = JSON.parse(request.requestBody).posts;
if (attrs.markdown === 'Edited post body') {
return new Mirage.Response(401, {}, {
errors: [
{message: 'Access denied.', errorType: 'UnauthorizedError'}
]
});
} else {
return {
posts: [post]
};
}
});
server.loadFixtures();
authenticateSession(application);
visit('/editor');
// create the post
fillIn('#entry-title', 'Test Post');
fillIn('textarea.markdown-editor', 'Test post body');
click('.js-publish-button');
andThen(() => {
// we shouldn't have a modal at this point
expect(find('.modal-container #login').length, 'modal exists').to.equal(0);
// we also shouldn't have any alerts
expect(find('.gh-alert').length, 'no of alerts').to.equal(0);
});
// update the post
fillIn('textarea.markdown-editor', 'Edited post body');
click('.js-publish-button');
andThen(() => {
// we should see a re-auth modal
expect(find('.modal-container #login').length, 'modal exists').to.equal(1);
});
});
// don't clobber debounce/throttle for future tests
afterEach(function () {
Ember.run.debounce = origDebounce;
Ember.run.throttle = origThrottle;
});
});
});

View file

@ -79,7 +79,7 @@ describe('Acceptance: Settings - Tags', function () {
// TODO: this should always be run for acceptance tests
server.loadFixtures();
authenticateSession(application);
return authenticateSession(application);
});
it('it renders, can be navigated, can edit, create & delete tags', function () {
@ -138,7 +138,9 @@ describe('Acceptance: Settings - Tags', function () {
keydown(38);
keyup(38);
});
});
andThen(() => {
// it navigates to previous tag
expect(currentURL(), 'url after keyboard up arrow').to.equal(`/settings/tags/${tag1.slug}`);
@ -153,7 +155,9 @@ describe('Acceptance: Settings - Tags', function () {
keydown(40);
keyup(40);
});
});
andThen(() => {
// it navigates to previous tag
expect(currentURL(), 'url after keyboard down arrow').to.equal(`/settings/tags/${tag2.slug}`);

View file

@ -1,51 +0,0 @@
export default [{
created_at: '2015-09-11T09:44:30.805Z',
created_by: 1,
id: 5,
key: 'title',
type: 'blog',
updated_at: '2015-10-04T16:26:05.195Z',
updated_by: 1,
uuid: '39e16daf-43fa-4bf0-87d4-44948ba8bf4c',
value: 'Test Blog'
}, {
created_at: '2015-09-11T09:44:30.806Z',
created_by: 1,
id: 6,
key: 'description',
type: 'blog',
updated_at: '2015-10-04T16:26:05.198Z',
updated_by: 1,
uuid: 'e6c8b636-6925-4c4a-a5d9-1dc0870fb8ea',
value: 'Thoughts, stories and ideas.'
}, {
created_at: '2015-09-11T09:44:30.809Z',
created_by: 1,
id: 10,
key: 'postsPerPage',
type: 'blog',
updated_at: '2015-10-04T16:26:05.211Z',
updated_by: 1,
uuid: '775e6ca1-bcc3-4347-a53d-15d5d76c04a4',
value: '5'
}, {
created_at: '2015-09-11T09:44:30.809Z',
created_by: 1,
id: 13,
key: 'ghost_head',
type: 'blog',
updated_at: '2015-09-23T13:32:49.858Z',
updated_by: 1,
uuid: 'df7f3151-bc08-4a77-be9d-dd315b630d51',
value: ''
}, {
created_at: '2015-09-11T09:44:30.809Z',
created_by: 1,
id: 14,
key: 'ghost_foot',
type: 'blog',
updated_at: '2015-09-23T13:32:49.858Z',
updated_by: 1,
uuid: '0649d45e-828b-4dd0-8381-3dff6d1d5ddb',
value: ''
}];

View file

@ -0,0 +1,42 @@
/* jshint expr:true */
import { expect } from 'chai';
import {
describe,
it
} from 'mocha';
import Ember from 'ember';
import ValidationEngineMixin from 'ghost/mixins/validation-engine';
describe('ValidationEngineMixin', function () {
// Replace this with your real tests.
// it('works', function () {
// var ValidationEngineObject = Ember.Object.extend(ValidationEngineMixin);
// var subject = ValidationEngineObject.create();
// expect(subject).to.be.ok;
// });
describe('#validate', function () {
it('loads the correct validator');
it('rejects if the validator doesn\'t exist');
it('resolves with valid object');
it('rejects with invalid object');
it('clears all existing errors');
describe('with a specified property', function () {
it('resolves with valid property');
it('rejects with invalid property');
it('adds property to hasValidated array');
it('clears existing error on specified property');
});
it('handles a passed in model');
it('uses this.model if available');
});
describe('#save', function () {
it('calls validate');
it('rejects with validation errors');
it('calls object\'s #save if validation passes');
it('skips validation if it\'s a deletion');
});
});

View file

@ -22,6 +22,9 @@ const {run} = Ember,
// TODO: These tests have way too much duplication, consider creating test
// helpers for validations
// TODO: Move testing of validation-engine behaviour into validation-engine-test
// and replace these tests with specific validator tests
describe('Unit: Validator: tag-settings', function () {
it('validates all fields by default', function () {
let tag = Tag.create({}),