mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 09:03:12 +03:00
Use Admin API v2 with session auth (#1046)
refs #9865 - removed all `oauth2` and token-based ESA auth - added new `cookie` authenticator which handles session creation - updated the session store to extend from the `ephemeral` in-memory store and to restore by fetching the currently logged in user and using the success/failure state to indicate authentication state - ESA automatically calls this `.restore()` method on app boot - the `session` service caches the current-user query so there's no unnecessary requests being made for the "logged in" state - removed the now-unnecessary token refresh and logout routines from the `application` route - removed the now-unnecessary token refresh routines from the `ajax` service - removed `access_token` query param from iframe file downloaders - changed Ember Data adapters and `ghost-paths` to use the `/ghost/api/v2/admin/` namespace
This commit is contained in:
parent
656a20272a
commit
3e5a62309f
@ -14,14 +14,9 @@ export default RESTAdapter.extend(DataAdapterMixin, AjaxServiceSupport, {
|
||||
return false;
|
||||
},
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
authorize(xhr) {
|
||||
if (this.get('session.isAuthenticated')) {
|
||||
let {access_token} = this.get('session.data.authenticated');
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${access_token}`);
|
||||
}
|
||||
authorize(/*xhr*/) {
|
||||
// noop - we're using server-side session cookies
|
||||
},
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
query(store, type, query) {
|
||||
let id;
|
||||
|
41
ghost/admin/app/authenticators/cookie.js
Normal file
41
ghost/admin/app/authenticators/cookie.js
Normal file
@ -0,0 +1,41 @@
|
||||
import Authenticator from 'ember-simple-auth/authenticators/base';
|
||||
import RSVP from 'rsvp';
|
||||
import {computed} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default Authenticator.extend({
|
||||
ajax: service(),
|
||||
ghostPaths: service(),
|
||||
|
||||
sessionEndpoint: computed('ghostPaths.apiRoot', function () {
|
||||
return `${this.ghostPaths.apiRoot}/session`;
|
||||
}),
|
||||
|
||||
restore: function () {
|
||||
return RSVP.resolve();
|
||||
},
|
||||
|
||||
authenticate(identification, password) {
|
||||
const data = {username: identification, password};
|
||||
const options = {
|
||||
data,
|
||||
contentType: 'application/json;charset=utf-8',
|
||||
// ember-ajax will try and parse the response as JSON if not explicitly set
|
||||
dataType: 'text'
|
||||
};
|
||||
|
||||
return this.ajax.post(this.sessionEndpoint, options);
|
||||
},
|
||||
|
||||
invalidate() {
|
||||
// if we're invalidating because of a 401 we can end up in an infinite
|
||||
// loop if we then try to perform a DELETE /session/ request
|
||||
// TODO: find a more elegant way to handle this
|
||||
if (this.ajax.skipSessionDeletion) {
|
||||
this.ajax.skipSessionDeletion = false;
|
||||
return RSVP.resolve();
|
||||
}
|
||||
|
||||
return this.ajax.del(this.sessionEndpoint);
|
||||
}
|
||||
});
|
@ -1,91 +0,0 @@
|
||||
import Authenticator from 'ember-simple-auth/authenticators/oauth2-password-grant';
|
||||
import RSVP from 'rsvp';
|
||||
import {assign} from '@ember/polyfills';
|
||||
import {computed} from '@ember/object';
|
||||
import {isEmpty} from '@ember/utils';
|
||||
import {run} from '@ember/runloop';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {makeArray as wrap} from '@ember/array';
|
||||
|
||||
export default Authenticator.extend({
|
||||
ajax: service(),
|
||||
session: service(),
|
||||
config: service(),
|
||||
ghostPaths: service(),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
let handler = run.bind(this, () => {
|
||||
this.onOnline();
|
||||
});
|
||||
window.addEventListener('online', handler);
|
||||
},
|
||||
|
||||
serverTokenEndpoint: computed('ghostPaths.apiRoot', function () {
|
||||
return `${this.get('ghostPaths.apiRoot')}/authentication/token`;
|
||||
}),
|
||||
|
||||
// disable general token revocation because the requests will always 401
|
||||
// (revocation is triggered by invalid access token so it's already invalid)
|
||||
// we have a separate logout procedure that sends revocation requests
|
||||
serverTokenRevocationEndpoint: null,
|
||||
|
||||
makeRequest(url, data) {
|
||||
/* eslint-disable camelcase */
|
||||
data.client_id = this.get('config.clientId');
|
||||
data.client_secret = this.get('config.clientSecret');
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
let options = {
|
||||
data,
|
||||
dataType: 'json',
|
||||
contentType: 'application/x-www-form-urlencoded'
|
||||
};
|
||||
|
||||
return this.get('ajax').post(url, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked when "navigator.online" event is trigerred.
|
||||
* This is a helper function to handle intermittent internet connectivity. Token is refreshed
|
||||
* when browser status becomes "online".
|
||||
*/
|
||||
onOnline() {
|
||||
if (this.get('session.isAuthenticated')) {
|
||||
let autoRefresh = this.get('refreshAccessTokens');
|
||||
if (autoRefresh) {
|
||||
let expiresIn = this.get('session.data.authenticated.expires_in');
|
||||
let token = this.get('session.data.authenticated.refresh_token');
|
||||
return this._refreshAccessToken(expiresIn, token);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
authenticate(identification, password, scope = [], headers = {}) {
|
||||
return new RSVP.Promise((resolve, reject) => {
|
||||
let data = {grant_type: 'password', username: identification, password};
|
||||
let serverTokenEndpoint = this.get('serverTokenEndpoint');
|
||||
let scopesString = wrap(scope).join(' ');
|
||||
if (!isEmpty(scopesString)) {
|
||||
data.scope = scopesString;
|
||||
}
|
||||
this.makeRequest(serverTokenEndpoint, data, headers).then((response) => {
|
||||
run(() => {
|
||||
/* eslint-disable camelcase */
|
||||
let expiresAt = this._absolutizeExpirationTime(response.expires_in);
|
||||
this._scheduleAccessTokenRefresh(response.expires_in, expiresAt, response.refresh_token);
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
if (!isEmpty(expiresAt)) {
|
||||
response = assign(response, {expires_at: expiresAt});
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
});
|
||||
}, (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@ -28,7 +28,7 @@ export default ModalComponent.extend(ValidationEngine, {
|
||||
|
||||
_authenticate() {
|
||||
let session = this.get('session');
|
||||
let authStrategy = 'authenticator:oauth2';
|
||||
let authStrategy = 'authenticator:cookie';
|
||||
let identification = this.get('identification');
|
||||
let password = this.get('password');
|
||||
|
||||
|
@ -56,7 +56,7 @@ export default Controller.extend(ValidationEngine, {
|
||||
}
|
||||
});
|
||||
this.get('notifications').showAlert(resp.passwordreset[0].message, {type: 'warn', delayed: true, key: 'password.reset'});
|
||||
this.get('session').authenticate('authenticator:oauth2', this.get('email'), credentials.newPassword);
|
||||
this.get('session').authenticate('authenticator:cookie', this.get('email'), credentials.newPassword);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.get('notifications').showAPIError(error, {key: 'password.reset'});
|
||||
|
@ -171,9 +171,7 @@ export default Controller.extend({
|
||||
},
|
||||
|
||||
downloadTheme(theme) {
|
||||
let themeURL = `${this.get('ghostPaths.apiRoot')}/themes/${theme.name}`;
|
||||
let accessToken = this.get('session.data.authenticated.access_token');
|
||||
let downloadURL = `${themeURL}/download/?access_token=${accessToken}`;
|
||||
let downloadURL = `${this.get('ghostPaths.apiRoot')}/themes/${theme.name}`;
|
||||
let iframe = $('#iframeDownload');
|
||||
|
||||
if (iframe.length === 0) {
|
||||
|
@ -129,9 +129,7 @@ export default Controller.extend({
|
||||
},
|
||||
|
||||
downloadFile(endpoint) {
|
||||
let url = this.get('ghostPaths.url').api(endpoint);
|
||||
let accessToken = this.get('session.data.authenticated.access_token');
|
||||
let downloadURL = `${url}?access_token=${accessToken}`;
|
||||
let downloadURL = this.get('ghostPaths.url').api(endpoint);
|
||||
let iframe = $('#iframeDownload');
|
||||
|
||||
if (iframe.length === 0) {
|
||||
|
@ -140,7 +140,7 @@ export default Controller.extend(ValidationEngine, {
|
||||
// Don't call the success handler, otherwise we will be redirected to admin
|
||||
this.set('session.skipAuthSuccessHandler', true);
|
||||
|
||||
return this.get('session').authenticate('authenticator:oauth2', this.get('email'), this.get('password')).then(() => {
|
||||
return this.get('session').authenticate('authenticator:cookie', this.get('email'), this.get('password')).then(() => {
|
||||
this.set('blogCreated', true);
|
||||
return this._afterAuthentication(result);
|
||||
}).catch((error) => {
|
||||
|
@ -79,7 +79,7 @@ export default Controller.extend(ValidationEngine, {
|
||||
|
||||
validateAndAuthenticate: task(function* () {
|
||||
let signin = this.get('signin');
|
||||
let authStrategy = 'authenticator:oauth2';
|
||||
let authStrategy = 'authenticator:cookie';
|
||||
|
||||
this.set('flowErrors', '');
|
||||
// Manually trigger events for input fields, ensuring legacy compatibility with
|
||||
|
@ -133,7 +133,7 @@ export default Controller.extend({
|
||||
let password = this.get('signupDetails.password');
|
||||
|
||||
return this.get('session')
|
||||
.authenticate('authenticator:oauth2', email, password);
|
||||
.authenticate('authenticator:cookie', email, password);
|
||||
},
|
||||
|
||||
_sendImage: task(function* () {
|
||||
|
@ -4,7 +4,6 @@ import RSVP from 'rsvp';
|
||||
import Route from '@ember/routing/route';
|
||||
import ShortcutsRoute from 'ghost-admin/mixins/shortcuts-route';
|
||||
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
import moment from 'moment';
|
||||
import windowProxy from 'ghost-admin/utils/window-proxy';
|
||||
import {htmlSafe} from '@ember/string';
|
||||
import {
|
||||
@ -56,23 +55,6 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
||||
transition.send('loadServerNotifications');
|
||||
transition.send('checkForOutdatedDesktopApp');
|
||||
|
||||
// trigger a background token refresh to enable "infinite" sessions
|
||||
// NOTE: we only do this if the last refresh was > 1 day ago to avoid
|
||||
// potential issues with multiple tabs and concurrent admin loads/refreshes.
|
||||
// see https://github.com/TryGhost/Ghost/issues/8616
|
||||
let session = this.get('session.session');
|
||||
let expiresIn = session.get('authenticated.expires_in') * 1000;
|
||||
let expiresAt = session.get('authenticated.expires_at');
|
||||
let lastRefresh = moment(expiresAt - expiresIn);
|
||||
let oneDayAgo = moment().subtract(1, 'day');
|
||||
|
||||
if (lastRefresh.isBefore(oneDayAgo)) {
|
||||
let authenticator = session._lookupAuthenticator(session.authenticator);
|
||||
if (authenticator && authenticator.onOnline) {
|
||||
authenticator.onOnline();
|
||||
}
|
||||
}
|
||||
|
||||
let featurePromise = this.get('feature').fetch();
|
||||
let settingsPromise = this.get('settings').fetch();
|
||||
let privateConfigPromise = this.get('config').fetchPrivate();
|
||||
@ -109,35 +91,6 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
|
||||
this.send('loadServerNotifications', true);
|
||||
},
|
||||
|
||||
// this is only called by the `signout` route at present.
|
||||
// it's separate to the normal ESA session invalidadition because it will
|
||||
// actually send the token revocation requests whereas we have to avoid
|
||||
// those most of the time because they will fail if we have invalid tokens
|
||||
logout() {
|
||||
let session = this.get('session');
|
||||
// revoke keys on the server
|
||||
if (session.get('isAuthenticated')) {
|
||||
let auth = session.get('data.authenticated');
|
||||
let revokeEndpoint = `${this.get('ghostPaths.apiRoot')}/authentication/revoke`;
|
||||
let authenticator = session.get('session')._lookupAuthenticator(session.get('session.authenticator'));
|
||||
let requests = [];
|
||||
['refresh_token', 'access_token'].forEach((tokenType) => {
|
||||
let data = {
|
||||
token_type_hint: tokenType,
|
||||
token: auth[tokenType]
|
||||
};
|
||||
authenticator.makeRequest(revokeEndpoint, data);
|
||||
});
|
||||
RSVP.all(requests).finally(() => {
|
||||
// remove local keys and refresh
|
||||
session.invalidate();
|
||||
});
|
||||
} else {
|
||||
// remove local keys and refresh
|
||||
session.invalidate();
|
||||
}
|
||||
},
|
||||
|
||||
authorizationFailed() {
|
||||
windowProxy.replaceLocation(AuthConfiguration.baseURL);
|
||||
},
|
||||
|
@ -1,11 +1,7 @@
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import Ember from 'ember';
|
||||
import styleBody from 'ghost-admin/mixins/style-body';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
// ember-cli-shims doesn't export canInvoke
|
||||
const {canInvoke} = Ember;
|
||||
|
||||
export default AuthenticatedRoute.extend(styleBody, {
|
||||
notifications: service(),
|
||||
|
||||
@ -13,12 +9,8 @@ export default AuthenticatedRoute.extend(styleBody, {
|
||||
|
||||
classNames: ['ghost-signout'],
|
||||
|
||||
afterModel(model, transition) {
|
||||
this.get('notifications').clearAll();
|
||||
if (canInvoke(transition, 'send')) {
|
||||
transition.send('logout');
|
||||
} else {
|
||||
this.send('logout');
|
||||
}
|
||||
afterModel(/*model, transition*/) {
|
||||
this.notifications.clearAll();
|
||||
this.session.invalidate();
|
||||
}
|
||||
});
|
||||
|
@ -9,7 +9,6 @@ import {inject as service} from '@ember/service';
|
||||
|
||||
const JSON_CONTENT_TYPE = 'application/json';
|
||||
const GHOST_REQUEST = /\/ghost\/api\//;
|
||||
const TOKEN_REQUEST = /authentication\/(?:token|ghost|revoke)/;
|
||||
|
||||
function isJSONContentType(header) {
|
||||
if (!header || isNone(header)) {
|
||||
@ -119,57 +118,33 @@ export function isThemeValidationError(errorOrStatus, payload) {
|
||||
let ajaxService = AjaxService.extend({
|
||||
session: service(),
|
||||
|
||||
// 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
|
||||
// DELETE would fail and get stuck in an infinite loop
|
||||
// TODO: find a more elegant way to handle this
|
||||
skipSessionDeletion: false,
|
||||
|
||||
headers: computed('session.isAuthenticated', function () {
|
||||
let session = this.get('session');
|
||||
let headers = {};
|
||||
|
||||
headers['X-Ghost-Version'] = config.APP.version;
|
||||
headers['App-Pragma'] = 'no-cache';
|
||||
|
||||
if (session.get('isAuthenticated')) {
|
||||
/* eslint-disable camelcase */
|
||||
let {access_token} = session.get('data.authenticated');
|
||||
headers.Authorization = `Bearer ${access_token}`;
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
|
||||
return headers;
|
||||
}).volatile(),
|
||||
|
||||
// ember-ajax recognises `application/vnd.api+json` as a JSON-API request
|
||||
// and formats appropriately, we want to handle `application/json` the same
|
||||
_makeRequest(hash) {
|
||||
let isAuthenticated = this.get('session.isAuthenticated');
|
||||
let isGhostRequest = GHOST_REQUEST.test(hash.url);
|
||||
let isTokenRequest = isGhostRequest && TOKEN_REQUEST.test(hash.url);
|
||||
let tokenExpiry = this.get('session.authenticated.expires_at');
|
||||
let isTokenExpired = tokenExpiry < (new Date()).getTime();
|
||||
|
||||
if (isJSONContentType(hash.contentType) && hash.type !== 'GET') {
|
||||
if (typeof hash.data === 'object') {
|
||||
hash.data = JSON.stringify(hash.data);
|
||||
}
|
||||
}
|
||||
|
||||
// we can get into a situation where the app is left open without a
|
||||
// network connection and the token subsequently expires, this will
|
||||
// result in the next network request returning a 401 and killing the
|
||||
// session. This is an attempt to detect that and restore the session
|
||||
// using the stored refresh token before continuing with the request
|
||||
//
|
||||
// TODO:
|
||||
// - this might be quite blunt, if we have a lot of requests at once
|
||||
// we probably want to queue the requests until the restore completes
|
||||
// BUG:
|
||||
// - the original caller gets a rejected promise with `undefined` instead
|
||||
// of the AjaxError object when session restore fails. This isn't a
|
||||
// huge deal because the session will be invalidated and app reloaded
|
||||
// but it would be nice to be consistent
|
||||
if (isAuthenticated && isGhostRequest && !isTokenRequest && isTokenExpired) {
|
||||
return this.get('session').restore().then(() => this._makeRequest(hash));
|
||||
}
|
||||
hash.withCredentials = true;
|
||||
|
||||
return this._super(...arguments);
|
||||
return this._super(hash);
|
||||
},
|
||||
|
||||
handleResponse(status, headers, payload, request) {
|
||||
@ -192,7 +167,8 @@ let ajaxService = AjaxService.extend({
|
||||
let isUnauthorized = this.isUnauthorizedError(status, headers, payload);
|
||||
|
||||
if (isAuthenticated && isGhostRequest && isUnauthorized) {
|
||||
this.get('session').invalidate();
|
||||
this.skipSessionDeletion = true;
|
||||
this.session.invalidate();
|
||||
}
|
||||
|
||||
return this._super(...arguments);
|
||||
|
@ -1,10 +1,28 @@
|
||||
import AdaptiveStore from 'ember-simple-auth/session-stores/adaptive';
|
||||
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
||||
import EphemeralStore from 'ember-simple-auth/session-stores/ephemeral';
|
||||
import RSVP from 'rsvp';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
const paths = ghostPaths();
|
||||
const keyName = `ghost${(paths.subdir.indexOf('/') === 0 ? `-${paths.subdir.substr(1)}` : '') }:session`;
|
||||
// Ghost already uses a cookie to store it's session so we don't need to keep
|
||||
// track of any other peristent login state separately in Ember Simple Auth
|
||||
export default EphemeralStore.extend({
|
||||
session: service(),
|
||||
|
||||
export default AdaptiveStore.extend({
|
||||
localStorageKey: keyName,
|
||||
cookieName: keyName
|
||||
// when loading the app we want ESA to try fetching the currently logged
|
||||
// in user. This will succeed/fail depending on whether we have a valid
|
||||
// session cookie or not so we can use that as an indication of the session
|
||||
// being authenticated
|
||||
restore() {
|
||||
return this.session.user.then(() => {
|
||||
// provide the necessary data for internal-session to mark the
|
||||
// session as authenticated
|
||||
let data = {authenticated: {authenticator: 'authenticator:cookie'}};
|
||||
this.persist(data);
|
||||
return data;
|
||||
}).catch(() => {
|
||||
// ensure the session.user doesn't return the same rejected promise
|
||||
// after a succussful login
|
||||
this.session.notifyPropertyChange('user');
|
||||
return RSVP.reject();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -18,7 +18,7 @@ export default function () {
|
||||
let subdir = path.substr(0, path.search('/ghost/'));
|
||||
let adminRoot = `${subdir}/ghost/`;
|
||||
let assetRoot = `${subdir}/ghost/assets/`;
|
||||
let apiRoot = `${subdir}/ghost/api/v0.1`;
|
||||
let apiRoot = `${subdir}/ghost/api/v2/admin`;
|
||||
|
||||
function assetUrl(src) {
|
||||
return subdir + src;
|
||||
|
@ -35,7 +35,7 @@ export default function () {
|
||||
export function testConfig() {
|
||||
this.passthrough('/write-coverage'); // For code coverage
|
||||
// this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server
|
||||
this.namespace = '/ghost/api/v0.1'; // make this `api`, for example, if your API is namespaced
|
||||
this.namespace = '/ghost/api/v2/admin'; // make this `api`, for example, if your API is namespaced
|
||||
// this.timing = 400; // delay for each request, automatically set to 0 during testing
|
||||
// this.logging = true;
|
||||
|
||||
|
@ -3,14 +3,9 @@ import {Response} from 'ember-cli-mirage';
|
||||
import {isBlank} from '@ember/utils';
|
||||
|
||||
export default function mockAuthentication(server) {
|
||||
server.post('/authentication/token', function () {
|
||||
server.post('/session', function () {
|
||||
// Password sign-in
|
||||
return {
|
||||
access_token: 'MirageAccessToken',
|
||||
expires_in: 172800,
|
||||
refresh_token: 'MirageRefreshToken',
|
||||
token_type: 'Bearer'
|
||||
};
|
||||
return new Response(201);
|
||||
});
|
||||
|
||||
server.post('/authentication/passwordreset', function (schema, request) {
|
||||
|
@ -41,7 +41,6 @@
|
||||
"coveralls": "3.0.2",
|
||||
"csscomb": "4.2.0",
|
||||
"current-device": "0.7.8",
|
||||
"deparam": "1.0.5",
|
||||
"element-resize-detector": "^1.1.14",
|
||||
"ember-ajax": "3.1.1",
|
||||
"ember-assign-helper": "0.1.2",
|
||||
|
@ -1,5 +1,3 @@
|
||||
import OAuth2Authenticator from 'ghost-admin/authenticators/oauth2';
|
||||
import deparam from 'npm:deparam';
|
||||
import destroyApp from '../helpers/destroy-app';
|
||||
import startApp from '../helpers/start-app';
|
||||
import windowProxy from 'ghost-admin/utils/window-proxy';
|
||||
@ -25,88 +23,16 @@ describe('Acceptance: Authentication', function () {
|
||||
beforeEach(function () {
|
||||
// ensure the /users/me route doesn't error
|
||||
server.create('user');
|
||||
|
||||
server.get('authentication/setup', function () {
|
||||
return {setup: [{status: false}]};
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to setup when setup isn\'t complete', async function () {
|
||||
await visit('settings/labs');
|
||||
|
||||
expect(currentURL()).to.equal('/setup/one');
|
||||
});
|
||||
});
|
||||
|
||||
describe('token handling', function () {
|
||||
beforeEach(function () {
|
||||
// replace the default test authenticator with our own authenticator
|
||||
application.register('authenticator:test', OAuth2Authenticator);
|
||||
|
||||
let role = server.create('role', {name: 'Administrator'});
|
||||
server.create('user', {roles: [role], slug: 'test-user'});
|
||||
});
|
||||
|
||||
it('refreshes tokens on boot if last refreshed > 24hrs ago', async function () {
|
||||
/* eslint-disable camelcase */
|
||||
// the tokens here don't matter, we're using the actual oauth
|
||||
// authenticator so we get the tokens back from the mirage endpoint
|
||||
await authenticateSession(application, {
|
||||
access_token: 'access_token',
|
||||
refresh_token: 'refresh_token'
|
||||
});
|
||||
|
||||
// authenticating the session above will trigger a token refresh
|
||||
// request so we need to clear it to ensure we aren't testing the
|
||||
// test behaviour instead of application behaviour
|
||||
server.pretender.handledRequests = [];
|
||||
|
||||
// fake a longer session so it appears that we last refreshed > 24hrs ago
|
||||
let {__container__: container} = application;
|
||||
let {session} = container.lookup('service:session');
|
||||
let newSession = session.get('content');
|
||||
newSession.authenticated.expires_in = 172800 * 2;
|
||||
session.get('store').persist(newSession);
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
await visit('/');
|
||||
|
||||
let requests = server.pretender.handledRequests;
|
||||
let refreshRequest = requests.findBy('url', '/ghost/api/v0.1/authentication/token');
|
||||
|
||||
expect(refreshRequest, 'token refresh request').to.exist;
|
||||
expect(refreshRequest.method, 'method').to.equal('POST');
|
||||
|
||||
let requestBody = deparam(refreshRequest.requestBody);
|
||||
expect(requestBody.grant_type, 'grant_type').to.equal('refresh_token');
|
||||
expect(requestBody.refresh_token, 'refresh_token').to.equal('MirageRefreshToken');
|
||||
});
|
||||
|
||||
it('doesn\'t refresh tokens on boot if last refreshed < 24hrs ago', async function () {
|
||||
/* eslint-disable camelcase */
|
||||
// the tokens here don't matter, we're using the actual oauth
|
||||
// authenticator so we get the tokens back from the mirage endpoint
|
||||
await authenticateSession(application, {
|
||||
access_token: 'access_token',
|
||||
refresh_token: 'refresh_token'
|
||||
});
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
// authenticating the session above will trigger a token refresh
|
||||
// request so we need to clear it to ensure we aren't testing the
|
||||
// test behaviour instead of application behaviour
|
||||
server.pretender.handledRequests = [];
|
||||
|
||||
// we've only just refreshed tokens above so we should always be < 24hrs
|
||||
await visit('/');
|
||||
|
||||
let requests = server.pretender.handledRequests;
|
||||
let refreshRequest = requests.findBy('url', '/ghost/api/v0.1/authentication/token');
|
||||
|
||||
expect(refreshRequest, 'refresh request').to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('general page', function () {
|
||||
let newLocation;
|
||||
|
||||
|
@ -100,7 +100,7 @@ describe('Acceptance: Error Handling', function () {
|
||||
|
||||
describe('logged out', function () {
|
||||
it('displays alert', async function () {
|
||||
server.post('/authentication/token', versionMismatchResponse);
|
||||
server.post('/session', versionMismatchResponse);
|
||||
|
||||
await visit('/signin');
|
||||
await fillIn('[name="identification"]', 'test@example.com');
|
||||
|
@ -200,7 +200,7 @@ describe('Acceptance: Setup', function () {
|
||||
|
||||
it('handles invalid origin error on step 2', async function () {
|
||||
// mimick the API response for an invalid origin
|
||||
server.post('/authentication/token', function () {
|
||||
server.post('/session', function () {
|
||||
return new Response(401, {}, {
|
||||
errors: [
|
||||
{
|
||||
|
@ -1,4 +1,3 @@
|
||||
import deparam from 'npm:deparam';
|
||||
import destroyApp from '../helpers/destroy-app';
|
||||
import startApp from '../helpers/start-app';
|
||||
import {Response} from 'ember-cli-mirage';
|
||||
@ -37,26 +36,16 @@ describe('Acceptance: Signin', function () {
|
||||
let role = server.create('role', {name: 'Administrator'});
|
||||
server.create('user', {roles: [role], slug: 'test-user'});
|
||||
|
||||
server.post('/authentication/token', function (schema, {requestBody}) {
|
||||
/* eslint-disable camelcase */
|
||||
server.post('/session', function (schema, {requestBody}) {
|
||||
let {
|
||||
grant_type: grantType,
|
||||
username,
|
||||
password,
|
||||
client_id: clientId
|
||||
} = deparam(requestBody);
|
||||
password
|
||||
} = JSON.parse(requestBody);
|
||||
|
||||
expect(grantType, 'grant type').to.equal('password');
|
||||
expect(username, 'username').to.equal('test@example.com');
|
||||
expect(clientId, 'client id').to.equal('ghost-admin');
|
||||
expect(username).to.equal('test@example.com');
|
||||
|
||||
if (password === 'thisissupersafe') {
|
||||
return {
|
||||
access_token: 'MirageAccessToken',
|
||||
expires_in: 3600,
|
||||
refresh_token: 'MirageRefreshToken',
|
||||
token_type: 'Bearer'
|
||||
};
|
||||
return new Response(201);
|
||||
} else {
|
||||
return new Response(401, {}, {
|
||||
errors: [{
|
||||
@ -65,7 +54,6 @@ describe('Acceptance: Signin', function () {
|
||||
}]
|
||||
});
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -20,7 +20,7 @@ describe('Integration: Adapter: tag', function () {
|
||||
});
|
||||
|
||||
it('loads tags from regular endpoint when all are fetched', function (done) {
|
||||
server.get('/ghost/api/v0.1/tags/', function () {
|
||||
server.get('/ghost/api/v2/admin/tags/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({tags: [
|
||||
{
|
||||
id: 1,
|
||||
@ -42,7 +42,7 @@ describe('Integration: Adapter: tag', function () {
|
||||
});
|
||||
|
||||
it('loads tag from slug endpoint when single tag is queried and slug is passed in', function (done) {
|
||||
server.get('/ghost/api/v0.1/tags/slug/tag-1/', function () {
|
||||
server.get('/ghost/api/v2/admin/tags/slug/tag-1/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({tags: [
|
||||
{
|
||||
id: 1,
|
||||
|
@ -20,7 +20,7 @@ describe('Integration: Adapter: user', function () {
|
||||
});
|
||||
|
||||
it('loads users from regular endpoint when all are fetched', function (done) {
|
||||
server.get('/ghost/api/v0.1/users/', function () {
|
||||
server.get('/ghost/api/v2/admin/users/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({users: [
|
||||
{
|
||||
id: 1,
|
||||
@ -42,7 +42,7 @@ describe('Integration: Adapter: user', function () {
|
||||
});
|
||||
|
||||
it('loads user from slug endpoint when single user is queried and slug is passed in', function (done) {
|
||||
server.get('/ghost/api/v0.1/users/slug/user-1/', function () {
|
||||
server.get('/ghost/api/v2/admin/users/slug/user-1/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({users: [
|
||||
{
|
||||
id: 1,
|
||||
@ -60,7 +60,7 @@ describe('Integration: Adapter: user', function () {
|
||||
});
|
||||
|
||||
it('handles "include" parameter when querying single user via slug', function (done) {
|
||||
server.get('/ghost/api/v0.1/users/slug/user-1/', (request) => {
|
||||
server.get('/ghost/api/v2/admin/users/slug/user-1/', (request) => {
|
||||
let params = request.queryParams;
|
||||
expect(params.include, 'include query').to.equal('roles,count.posts');
|
||||
|
||||
|
@ -18,13 +18,13 @@ const notificationsStub = Service.extend({
|
||||
});
|
||||
|
||||
const stubSuccessfulUpload = function (server, delay = 0) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, '"/content/images/test.png"'];
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const stubFailedUpload = function (server, code, error, delay = 0) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [code, {'Content-Type': 'application/json'}, JSON.stringify({
|
||||
errors: [{
|
||||
errorType: error,
|
||||
@ -43,7 +43,7 @@ describe('Integration: Component: gh-file-uploader', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
server = new Pretender();
|
||||
this.set('uploadUrl', '/ghost/api/v0.1/uploads/');
|
||||
this.set('uploadUrl', '/ghost/api/v2/admin/uploads/');
|
||||
|
||||
this.register('service:notifications', notificationsStub);
|
||||
this.inject.service('notifications', {as: 'notifications'});
|
||||
@ -90,7 +90,7 @@ describe('Integration: Component: gh-file-uploader', function () {
|
||||
|
||||
wait().then(() => {
|
||||
expect(server.handledRequests.length).to.equal(1);
|
||||
expect(server.handledRequests[0].url).to.equal('/ghost/api/v0.1/uploads/');
|
||||
expect(server.handledRequests[0].url).to.equal('/ghost/api/v2/admin/uploads/');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -214,7 +214,7 @@ describe('Integration: Component: gh-file-uploader', function () {
|
||||
});
|
||||
|
||||
it('handles file too large error directly from the web server', function (done) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [413, {}, ''];
|
||||
});
|
||||
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
|
||||
@ -240,7 +240,7 @@ describe('Integration: Component: gh-file-uploader', function () {
|
||||
});
|
||||
|
||||
it('handles unknown failure', function (done) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [500, {'Content-Type': 'application/json'}, ''];
|
||||
});
|
||||
this.render(hbs`{{gh-file-uploader url=uploadUrl}}`);
|
||||
|
@ -29,13 +29,13 @@ const sessionStub = Service.extend({
|
||||
});
|
||||
|
||||
const stubSuccessfulUpload = function (server, delay = 0) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, '"/content/images/test.png"'];
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const stubFailedUpload = function (server, code, error, delay = 0) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [code, {'Content-Type': 'application/json'}, JSON.stringify({
|
||||
errors: [{
|
||||
errorType: error,
|
||||
@ -89,27 +89,12 @@ describe('Integration: Component: gh-image-uploader', function () {
|
||||
|
||||
wait().then(() => {
|
||||
expect(server.handledRequests.length).to.equal(1);
|
||||
expect(server.handledRequests[0].url).to.equal('/ghost/api/v0.1/uploads/');
|
||||
expect(server.handledRequests[0].url).to.equal('/ghost/api/v2/admin/uploads/');
|
||||
expect(server.handledRequests[0].requestHeaders.Authorization).to.be.undefined;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('adds authentication headers to request', function (done) {
|
||||
stubSuccessfulUpload(server);
|
||||
|
||||
this.get('sessionService').set('isAuthenticated', true);
|
||||
|
||||
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
|
||||
fileUpload(this.$('input[type="file"]'), ['test'], {name: 'test.png'});
|
||||
|
||||
wait().then(() => {
|
||||
let [request] = server.handledRequests;
|
||||
expect(request.requestHeaders.Authorization).to.equal('Bearer AccessMe123');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fires update action on successful upload', function (done) {
|
||||
let update = sinon.spy();
|
||||
this.set('update', update);
|
||||
@ -229,7 +214,7 @@ describe('Integration: Component: gh-image-uploader', function () {
|
||||
});
|
||||
|
||||
it('handles file too large error directly from the web server', function (done) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [413, {}, ''];
|
||||
});
|
||||
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
|
||||
@ -255,7 +240,7 @@ describe('Integration: Component: gh-image-uploader', function () {
|
||||
});
|
||||
|
||||
it('handles unknown failure', function (done) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [500, {'Content-Type': 'application/json'}, ''];
|
||||
});
|
||||
this.render(hbs`{{gh-image-uploader image=image update=(action update)}}`);
|
||||
|
@ -10,13 +10,13 @@ import {run} from '@ember/runloop';
|
||||
import {setupComponentTest} from 'ember-mocha';
|
||||
|
||||
const stubSuccessfulUpload = function (server, delay = 0) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, '"/content/images/test.png"'];
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const stubFailedUpload = function (server, code, error, delay = 0) {
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
return [code, {'Content-Type': 'application/json'}, JSON.stringify({
|
||||
errors: [{
|
||||
errorType: error,
|
||||
@ -54,7 +54,7 @@ describe('Integration: Component: gh-uploader', function () {
|
||||
|
||||
let [lastRequest] = server.handledRequests;
|
||||
expect(server.handledRequests.length).to.equal(1);
|
||||
expect(lastRequest.url).to.equal('/ghost/api/v0.1/uploads/');
|
||||
expect(lastRequest.url).to.equal('/ghost/api/v2/admin/uploads/');
|
||||
// requestBody is a FormData object
|
||||
// this will fail in anything other than Chrome and Firefox
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility
|
||||
@ -139,7 +139,7 @@ describe('Integration: Component: gh-uploader', function () {
|
||||
|
||||
it('onComplete returns results in same order as selected', async function () {
|
||||
// first request has a delay to simulate larger file
|
||||
server.post('/ghost/api/v0.1/uploads/', function () {
|
||||
server.post('/ghost/api/v2/admin/uploads/', function () {
|
||||
// second request has no delay to simulate small file
|
||||
stubSuccessfulUpload(server, 0);
|
||||
|
||||
@ -268,7 +268,7 @@ describe('Integration: Component: gh-uploader', function () {
|
||||
});
|
||||
|
||||
it('uploads to supplied `uploadUrl`', async function () {
|
||||
server.post('/ghost/api/v0.1/images/', function () {
|
||||
server.post('/ghost/api/v2/admin/images/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, '"/content/images/test.png"'];
|
||||
});
|
||||
|
||||
@ -277,7 +277,7 @@ describe('Integration: Component: gh-uploader', function () {
|
||||
await wait();
|
||||
|
||||
let [lastRequest] = server.handledRequests;
|
||||
expect(lastRequest.url).to.equal('/ghost/api/v0.1/images/');
|
||||
expect(lastRequest.url).to.equal('/ghost/api/v2/admin/images/');
|
||||
});
|
||||
|
||||
it('passes supplied paramName in request', async function () {
|
||||
|
@ -1,6 +1,4 @@
|
||||
import Pretender from 'pretender';
|
||||
import RSVP from 'rsvp';
|
||||
import Service from '@ember/service';
|
||||
import config from 'ghost-admin/config/environment';
|
||||
import {describe, it} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
@ -9,6 +7,7 @@ import {
|
||||
isUnauthorizedError
|
||||
} from 'ember-ajax/errors';
|
||||
import {
|
||||
isMaintenanceError,
|
||||
isRequestEntityTooLargeError,
|
||||
isUnsupportedMediaTypeError,
|
||||
isVersionMismatchError
|
||||
@ -175,96 +174,16 @@ describe('Integration: Service: ajax', function () {
|
||||
});
|
||||
});
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
describe('session handling', function () {
|
||||
let sessionStub = Service.extend({
|
||||
isAuthenticated: true,
|
||||
restoreCalled: false,
|
||||
authenticated: null,
|
||||
it('handles error checking for MaintenanceError on 503 errors', function (done) {
|
||||
stubAjaxEndpoint(server, {}, 503);
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
let authenticated = {
|
||||
expires_at: (new Date()).getTime() - 10000,
|
||||
access_token: 'AccessMe123',
|
||||
refresh_token: 'RefreshMe123'
|
||||
};
|
||||
this.authenticated = authenticated;
|
||||
this.data = {authenticated};
|
||||
},
|
||||
|
||||
restore() {
|
||||
this.restoreCalled = true;
|
||||
this.authenticated.expires_at = (new Date()).getTime() + 10000;
|
||||
return RSVP.resolve();
|
||||
},
|
||||
|
||||
authorize() {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
server.get('/ghost/api/v0.1/test/', function () {
|
||||
return [
|
||||
200,
|
||||
{'Content-Type': 'application/json'},
|
||||
JSON.stringify({
|
||||
success: true
|
||||
})
|
||||
];
|
||||
});
|
||||
|
||||
server.post('/ghost/api/v0.1/authentication/token', function () {
|
||||
return [
|
||||
401,
|
||||
{'Content-Type': 'application/json'},
|
||||
JSON.stringify({})
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
it('can restore an expired session', function (done) {
|
||||
let ajax = this.subject();
|
||||
ajax.set('session', sessionStub.create());
|
||||
|
||||
ajax.request('/ghost/api/v0.1/test/');
|
||||
|
||||
ajax.request('/ghost/api/v0.1/test/').then((result) => {
|
||||
expect(ajax.get('session.restoreCalled'), 'restoreCalled').to.be.true;
|
||||
expect(result.success, 'result.success').to.be.true;
|
||||
done();
|
||||
}).catch(() => {
|
||||
expect(true, 'request failed').to.be.false;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('errors correctly when session restoration fails', function (done) {
|
||||
let ajax = this.subject();
|
||||
let invalidateCalled = false;
|
||||
|
||||
ajax.set('session', sessionStub.create());
|
||||
ajax.set('session.restore', function () {
|
||||
this.set('restoreCalled', true);
|
||||
return ajax.post('/ghost/api/v0.1/authentication/token');
|
||||
});
|
||||
ajax.set('session.invalidate', function () {
|
||||
invalidateCalled = true;
|
||||
});
|
||||
|
||||
stubAjaxEndpoint(server, {}, 401);
|
||||
|
||||
ajax.request('/ghost/api/v0.1/test/').then(() => {
|
||||
expect(true, 'request was successful').to.be.false;
|
||||
done();
|
||||
}).catch(() => {
|
||||
// TODO: fix the error return when a session restore fails
|
||||
// expect(isUnauthorizedError(error)).to.be.true;
|
||||
expect(ajax.get('session.restoreCalled'), 'restoreCalled').to.be.true;
|
||||
expect(invalidateCalled, 'invalidateCalled').to.be.true;
|
||||
ajax.request('/test/').then(() => {
|
||||
expect(false).to.be.true;
|
||||
}).catch((error) => {
|
||||
expect(isMaintenanceError(error)).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import {expect} from 'chai';
|
||||
import {setupTest} from 'ember-mocha';
|
||||
|
||||
function stubAvailableTimezonesEndpoint(server) {
|
||||
server.get('/ghost/api/v0.1/configuration/timezones', function () {
|
||||
server.get('/ghost/api/v2/admin/configuration/timezones', function () {
|
||||
return [
|
||||
200,
|
||||
{'Content-Type': 'application/json'},
|
||||
@ -58,7 +58,7 @@ describe('Integration: Service: config', function () {
|
||||
|
||||
it('normalizes blogUrl to non-trailing-slash', function (done) {
|
||||
let stubBlogUrl = function stubBlogUrl(blogUrl) {
|
||||
server.get('/ghost/api/v0.1/configuration/', function () {
|
||||
server.get('/ghost/api/v2/admin/configuration/', function () {
|
||||
return [
|
||||
200,
|
||||
{'Content-Type': 'application/json'},
|
||||
|
@ -16,11 +16,11 @@ function stubSettings(server, labs, validSave = true) {
|
||||
}
|
||||
];
|
||||
|
||||
server.get('/ghost/api/v0.1/settings/', function () {
|
||||
server.get('/ghost/api/v2/admin/settings/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({settings})];
|
||||
});
|
||||
|
||||
server.put('/ghost/api/v0.1/settings/', function (request) {
|
||||
server.put('/ghost/api/v2/admin/settings/', function (request) {
|
||||
let statusCode = (validSave) ? 200 : 400;
|
||||
let response = (validSave) ? request.requestBody : JSON.stringify({
|
||||
errors: [{
|
||||
@ -46,11 +46,11 @@ function stubUser(server, accessibility, validSave = true) {
|
||||
}]
|
||||
}];
|
||||
|
||||
server.get('/ghost/api/v0.1/users/me/', function () {
|
||||
server.get('/ghost/api/v2/admin/users/me/', function () {
|
||||
return [200, {'Content-Type': 'application/json'}, JSON.stringify({users})];
|
||||
});
|
||||
|
||||
server.put('/ghost/api/v0.1/users/1/', function (request) {
|
||||
server.put('/ghost/api/v2/admin/users/1/', function (request) {
|
||||
let statusCode = (validSave) ? 200 : 400;
|
||||
let response = (validSave) ? request.requestBody : JSON.stringify({
|
||||
errors: [{
|
||||
|
@ -5,7 +5,7 @@ import {expect} from 'chai';
|
||||
import {setupTest} from 'ember-mocha';
|
||||
|
||||
function stubSlugEndpoint(server, type, slug) {
|
||||
server.get('/ghost/api/v0.1/slugs/:type/:slug/', function (request) {
|
||||
server.get('/ghost/api/v2/admin/slugs/:type/:slug/', function (request) {
|
||||
expect(request.params.type).to.equal(type);
|
||||
expect(request.params.slug).to.equal(slug);
|
||||
|
||||
|
@ -23,7 +23,7 @@ describe('Integration: Service: store', function () {
|
||||
let {version} = config.APP;
|
||||
let store = this.subject();
|
||||
|
||||
server.get('/ghost/api/v0.1/posts/1/', function () {
|
||||
server.get('/ghost/api/v2/admin/posts/1/', function () {
|
||||
return [
|
||||
404,
|
||||
{'Content-Type': 'application/json'},
|
||||
|
71
ghost/admin/tests/unit/authenticators/cookie-test.js
Normal file
71
ghost/admin/tests/unit/authenticators/cookie-test.js
Normal file
@ -0,0 +1,71 @@
|
||||
import RSVP from 'rsvp';
|
||||
import Service from '@ember/service';
|
||||
import sinon from 'sinon';
|
||||
import {beforeEach, describe, it} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
import {setupTest} from 'ember-mocha';
|
||||
|
||||
const mockAjax = Service.extend({
|
||||
skipSessionDeletion: false,
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.post = sinon.stub().resolves();
|
||||
this.del = sinon.stub().resolves();
|
||||
}
|
||||
});
|
||||
|
||||
const mockGhostPaths = Service.extend({
|
||||
apiRoot: '/ghost/api/v2/admin'
|
||||
});
|
||||
|
||||
describe('Unit: Authenticator: cookie', () => {
|
||||
setupTest('authenticator:cookie', {});
|
||||
|
||||
beforeEach(function () {
|
||||
this.register('service:ajax', mockAjax);
|
||||
this.inject.service('ajax', {as: 'ajax'});
|
||||
|
||||
this.register('service:ghost-paths', mockGhostPaths);
|
||||
this.inject.service('ghost-paths', {as: 'ghostPaths'});
|
||||
});
|
||||
|
||||
describe('#restore', function () {
|
||||
it('returns a resolving promise', function () {
|
||||
return this.subject().restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#authenticate', function () {
|
||||
it('posts the username and password to the sessionEndpoint and returns the promise', function () {
|
||||
let authenticator = this.subject();
|
||||
let post = authenticator.ajax.post;
|
||||
|
||||
return authenticator.authenticate('AzureDiamond', 'hunter2').then(() => {
|
||||
expect(post.args[0][0]).to.equal('/ghost/api/v2/admin/session');
|
||||
expect(post.args[0][1]).to.deep.include({
|
||||
data: {
|
||||
username: 'AzureDiamond',
|
||||
password: 'hunter2'
|
||||
}
|
||||
});
|
||||
expect(post.args[0][1]).to.deep.include({
|
||||
dataType: 'text'
|
||||
});
|
||||
expect(post.args[0][1]).to.deep.include({
|
||||
contentType: 'application/json;charset=utf-8'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#invalidate', function () {
|
||||
it('makes a delete request to the sessionEndpoint', function () {
|
||||
let authenticator = this.subject();
|
||||
let del = authenticator.ajax.del;
|
||||
|
||||
return authenticator.invalidate().then(() => {
|
||||
expect(del.args[0][0]).to.equal('/ghost/api/v2/admin/session');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -34,7 +34,7 @@ describe('Unit: Model: invite', function () {
|
||||
let model = this.subject();
|
||||
let role;
|
||||
|
||||
server.post('/ghost/api/v0.1/invites/', function () {
|
||||
server.post('/ghost/api/v2/admin/invites/', function () {
|
||||
return [200, {}, '{}'];
|
||||
});
|
||||
|
||||
|
@ -3483,11 +3483,6 @@ delegates@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||
|
||||
deparam@1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/deparam/-/deparam-1.0.5.tgz#74011bbabd26b40f860c3e3cc2cd61bed4eb43f2"
|
||||
integrity sha1-dAEbur0mtA+GDD48ws1hvtTrQ/I=
|
||||
|
||||
depd@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
|
||||
|
Loading…
Reference in New Issue
Block a user