Skip to content

Commit

Permalink
✨ new route to finish Ghost setup when a one time access token is ava…
Browse files Browse the repository at this point in the history
…ilable in Ghost-Admin (#7765)

no issue

- added a new route
  • Loading branch information
kirrg001 authored and kevinansfield committed Dec 5, 2016
1 parent c289283 commit af48083
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 33 deletions.
104 changes: 82 additions & 22 deletions core/server/api/authentication.js
@@ -1,16 +1,16 @@
var _ = require('lodash'),
validator = require('validator'),
pipeline = require('../utils/pipeline'),
dataProvider = require('../models'),
settings = require('./settings'),
mail = require('./../mail'),
apiMail = require('./mail'),
globalUtils = require('../utils'),
utils = require('./utils'),
errors = require('../errors'),
events = require('../events'),
config = require('../config'),
i18n = require('../i18n'),
var _ = require('lodash'),
validator = require('validator'),
pipeline = require('../utils/pipeline'),
dataProvider = require('../models'),
settings = require('./settings'),
mail = require('./../mail'),
apiMail = require('./mail'),
globalUtils = require('../utils'),
utils = require('./utils'),
errors = require('../errors'),
events = require('../events'),
config = require('../config'),
i18n = require('../i18n'),
authentication;

/**
Expand Down Expand Up @@ -379,15 +379,17 @@ authentication = {
}

function formatResponse(isSetup) {
return {setup: [
{
status: isSetup,
// Pre-populate from config if, and only if the values exist in config.
title: config.title || undefined,
name: config.user_name || undefined,
email: config.user_email || undefined
}
]};
return {
setup: [
{
status: isSetup,
// Pre-populate from config if, and only if the values exist in config.
title: config.title || undefined,
name: config.user_name || undefined,
email: config.user_email || undefined
}
]
};
}

tasks = [
Expand Down Expand Up @@ -553,6 +555,64 @@ authentication = {
];

return pipeline(tasks, tokenDetails, localOptions);
},

/**
* search for token and check expiry
*
* finally delete old temporary token
*
* send welcome mail
* --> @TODO: in case user looses the redirect, no welcome mail :(
* --> send email on first login!
*/
onSetupStep3: function (data) {
var oldAccessToken, oldAccessTokenData, options = {context: {internal: true}};

return dataProvider.Accesstoken
.findOne({
token: data.token
})
.then(function (_oldAccessToken) {
if (!_oldAccessToken) {
throw new errors.NoPermissionError(i18n.t('errors.api.authentication.notTheBlogOwner'));
}

oldAccessToken = _oldAccessToken;
oldAccessTokenData = oldAccessToken.toJSON();

if (oldAccessTokenData.expires < Date.now()) {
throw new errors.NoPermissionError(i18n.t('errors.middleware.oauth.tokenExpired'));
}

var newAccessToken = globalUtils.uid(256),
refreshToken = globalUtils.uid(256),
newAccessExpiry = Date.now() + globalUtils.ONE_HOUR_MS,
refreshExpires = Date.now() + globalUtils.ONE_WEEK_MS;

return dataProvider.Accesstoken.add({
token: newAccessToken,
user_id: oldAccessTokenData.user_id,
client_id: oldAccessTokenData.client_id,
expires: newAccessExpiry
}, options)
.then(function then() {
return dataProvider.Refreshtoken.add({
token: refreshToken,
user_id: oldAccessTokenData.user_id,
client_id: oldAccessTokenData.client_id,
expires: refreshExpires
}, options);
}).then(function then() {
return oldAccessToken.destroy();
}).then(function () {
return {
access_token: newAccessToken,
refresh_token: refreshToken,
expires_in: newAccessExpiry
};
});
});
}
};

Expand Down
4 changes: 4 additions & 0 deletions core/server/routes/api.js
Expand Up @@ -144,6 +144,10 @@ apiRoutes = function apiRoutes(middleware) {
middleware.spamPrevention.forgotten,
api.http(api.authentication.generateResetToken)
);

// ## Endpoint to exchange a one time access-token with a pair of AT/RT
router.post('/authentication/setup/three', middleware.api.authenticateClient, api.http(api.authentication.onSetupStep3));

router.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
router.post('/authentication/invitation', api.http(api.authentication.acceptInvitation));
router.get('/authentication/invitation', api.http(api.authentication.isInvitation));
Expand Down
3 changes: 2 additions & 1 deletion core/server/translations/en.json
Expand Up @@ -79,7 +79,8 @@
"oauth": {
"invalidClient": "Invalid client.",
"invalidRefreshToken": "Invalid refresh token.",
"refreshTokenExpired": "Refresh token expired."
"refreshTokenExpired": "Refresh token expired.",
"tokenExpired": "Token expired."
},
"privateblogging": {
"wrongPassword": "Wrong password"
Expand Down
107 changes: 97 additions & 10 deletions core/test/functional/routes/api/authentication_spec.js
@@ -1,12 +1,13 @@
var supertest = require('supertest'),
should = require('should'),
testUtils = require('../../../utils'),
user = testUtils.DataGenerator.forModel.users[0],
ghost = require('../../../../../core'),
config = require('../../../../../core/server/config'),
var supertest = require('supertest'),
should = require('should'),
testUtils = require('../../../utils'),
user = testUtils.DataGenerator.forModel.users[0],
ghost = require('../../../../../core'),
models = require('../../../../../core/server/models'),
config = require('../../../../../core/server/config'),
request;

describe('Authentication API', function () {
describe.only('Authentication API', function () {
var accesstoken = '';

before(function (done) {
Expand Down Expand Up @@ -38,7 +39,7 @@ describe('Authentication API', function () {
client_id: 'ghost-admin',
client_secret: 'not_available'
}).expect('Content-Type', /json/)
// TODO: make it possible to override oauth2orize's header so that this is consistent
// TODO: make it possible to override oauth2orize's header so that this is consistent
.expect('Cache-Control', 'no-store')
.expect(200)
.end(function (err, res) {
Expand Down Expand Up @@ -111,7 +112,7 @@ describe('Authentication API', function () {
client_id: 'ghost-admin',
client_secret: 'not_available'
}).expect('Content-Type', /json/)
// TODO: make it possible to override oauth2orize's header so that this is consistent
// TODO: make it possible to override oauth2orize's header so that this is consistent
.expect('Cache-Control', 'no-store')
.expect(200)
.end(function (err, res) {
Expand All @@ -127,7 +128,7 @@ describe('Authentication API', function () {
client_id: 'ghost-admin',
client_secret: 'not_available'
}).expect('Content-Type', /json/)
// TODO: make it possible to override oauth2orize's header so that this is consistent
// TODO: make it possible to override oauth2orize's header so that this is consistent
.expect('Cache-Control', 'no-store')
.expect(200)
.end(function (err, res) {
Expand Down Expand Up @@ -163,4 +164,90 @@ describe('Authentication API', function () {
done();
});
});

it('exchange one time access token on setup', function (done) {
testUtils.fixtures.insertAccessToken({
expires: Date.now() + 3600000,
token: 'one-time-token',
user_id: 1,
client_id: 2
}).then(function () {
request.post(testUtils.API.getApiQuery('authentication/setup/three'))
.set('Origin', config.url)
.send({
token: 'one-time-token',
client_id: 'ghost-admin',
client_secret: 'not_available'
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}

should.exist(res.body.access_token);
should.exist(res.body.refresh_token);
should.exist(res.body.expires_in);

models.Accesstoken.findOne({
token: 'one-time-token'
}).then(function (found) {
should.not.exist(found);
done();
});
});
}).catch(done);
});

it('[failure] wrong AT', function (done) {
request.post(testUtils.API.getApiQuery('authentication/setup/three'))
.set('Origin', config.url)
.send({
token: 'wrong',
client_id: 'ghost-admin',
client_secret: 'not_available'
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.body.access_token);
should.not.exist(res.body.refresh_token);
should.not.exist(res.body.expires_in);

done();
});
});

it('[failure] expired AT', function (done) {
testUtils.fixtures.insertAccessToken({
expires: Date.now() - 1000,
token: 'one-time-token',
user_id: 1,
client_id: 2
}).then(function () {
request.post(testUtils.API.getApiQuery('authentication/setup/three'))
.set('Origin', config.url)
.send({
token: 'one-time-token',
client_id: 'ghost-admin',
client_secret: 'not_available'
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.end(function (err) {
if (err) {
return done(err);
}

done();
});
}).catch(done);
});
});

0 comments on commit af48083

Please sign in to comment.