diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index bfc744d345..ae3e4c151b 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -42,6 +42,16 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, { transition.send('loadServerNotifications'); transition.send('checkForOutdatedDesktopApp'); + // trigger a background refresh of the access token to enable + // "infinite" sessions. We also trigger a logout if the refresh + // token is invalid to prevent attackers with only the access token + // from loading the admin + let session = this.get('session.session'); + let authenticator = session._lookupAuthenticator(session.authenticator); + if (authenticator && authenticator.onOnline) { + authenticator.onOnline(); + } + // return the feature loading promise so that we block until settings // are loaded in order for synchronous access everywhere return this.get('feature').fetch(); diff --git a/ghost/admin/tests/acceptance/authentication-test.js b/ghost/admin/tests/acceptance/authentication-test.js index 44bc430526..9885900e32 100644 --- a/ghost/admin/tests/acceptance/authentication-test.js +++ b/ghost/admin/tests/acceptance/authentication-test.js @@ -14,6 +14,7 @@ import {authenticateSession, invalidateSession} from 'ghost-admin/tests/helpers/ import {Response} from 'ember-cli-mirage'; import windowProxy from 'ghost-admin/utils/window-proxy'; import ghostPaths from 'ghost-admin/utils/ghost-paths'; +import OAuth2Authenticator from 'ghost-admin/authenticators/oauth2'; const Ghost = ghostPaths(); @@ -29,6 +30,41 @@ describe('Acceptance: Authentication', function () { destroyApp(application); }); + 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 app tokens on boot', function () { + /* eslint-disable camelcase */ + authenticateSession(application, { + access_token: 'testAccessToken', + refresh_token: 'refreshAccessToken' + }); + /* eslint-enable camelcase */ + + visit('/'); + + andThen(() => { + let requests = server.pretender.handledRequests; + let refreshRequest = requests.findBy('url', '/ghost/api/v0.1/authentication/token'); + + expect(refreshRequest).to.exist; + expect(refreshRequest.method, 'method').to.equal('POST'); + + let requestBody = $.deparam(refreshRequest.requestBody); + expect(requestBody.grant_type, 'grant_type').to.equal('password'); + expect(requestBody.username.access_token, 'access_token').to.equal('testAccessToken'); + expect(requestBody.username.refresh_token, 'refresh_token').to.equal('refreshAccessToken'); + + }); + }); + }); + describe('general page', function () { beforeEach(function () { originalReplaceLocation = windowProxy.replaceLocation;