mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Added the Admin API and tests for Personal Tokens.
no issue
This commit is contained in:
parent
be4146e324
commit
4edccfd2f7
5 changed files with 134 additions and 1 deletions
|
@ -8,6 +8,30 @@ const permissionsService = require('../../services/permissions');
|
||||||
const ALLOWED_INCLUDES = ['count.posts', 'permissions', 'roles', 'roles.permissions'];
|
const ALLOWED_INCLUDES = ['count.posts', 'permissions', 'roles', 'roles.permissions'];
|
||||||
const UNSAFE_ATTRS = ['status', 'roles'];
|
const UNSAFE_ATTRS = ['status', 'roles'];
|
||||||
|
|
||||||
|
function permissionOnlySelf(frame) {
|
||||||
|
const targetId = getTargetId(frame);
|
||||||
|
const userId = frame.user.id;
|
||||||
|
if (targetId !== userId) {
|
||||||
|
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.permissions.noPermissionToAction')}));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTargetId(frame) {
|
||||||
|
return frame.options.id === 'me' ? frame.user.id : frame.options.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchOrCreatePersonalToken(userId) {
|
||||||
|
const token = await models.ApiKey.findOne({user_id: userId}, {});
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
const newToken = await models.ApiKey.add({user_id: userId, type: 'admin'});
|
||||||
|
return newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
docName: 'users',
|
docName: 'users',
|
||||||
|
|
||||||
|
@ -176,5 +200,46 @@ module.exports = {
|
||||||
query(frame) {
|
query(frame) {
|
||||||
return models.User.transferOwnership(frame.data.owner[0], frame.options);
|
return models.User.transferOwnership(frame.data.owner[0], frame.options);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
readToken: {
|
||||||
|
options: [
|
||||||
|
'id'
|
||||||
|
],
|
||||||
|
validation: {
|
||||||
|
options: {
|
||||||
|
id: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
permissions: permissionOnlySelf,
|
||||||
|
query(frame) {
|
||||||
|
const targetId = getTargetId(frame);
|
||||||
|
return fetchOrCreatePersonalToken(targetId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
regenerateToken: {
|
||||||
|
headers: {
|
||||||
|
cacheInvalidate: true
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
'id'
|
||||||
|
],
|
||||||
|
validation: {
|
||||||
|
options: {
|
||||||
|
id: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
permissions: permissionOnlySelf,
|
||||||
|
query(frame) {
|
||||||
|
const targetId = getTargetId(frame);
|
||||||
|
return fetchOrCreatePersonalToken(targetId).then((model) => {
|
||||||
|
return models.ApiKey.refreshSecret(model.toJSON(), Object.assign({}, {id: model.id}));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,5 +49,22 @@ module.exports = {
|
||||||
frame.response = {
|
frame.response = {
|
||||||
users: models.map(model => model.toJSON(frame.options))
|
users: models.map(model => model.toJSON(frame.options))
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
readToken(model, apiConfig, frame) {
|
||||||
|
debug('readToken');
|
||||||
|
|
||||||
|
frame.response = {
|
||||||
|
apiKey: model.toJSON(frame.options)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
regenerateToken(model, apiConfig, frame) {
|
||||||
|
debug('regenerateToken');
|
||||||
|
|
||||||
|
frame.response = {
|
||||||
|
apiKey: model.toJSON(frame.options)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,10 +71,12 @@ module.exports = function apiRoutes() {
|
||||||
router.get('/users/slug/:slug', mw.authAdminApi, http(apiCanary.users.read));
|
router.get('/users/slug/:slug', mw.authAdminApi, http(apiCanary.users.read));
|
||||||
// NOTE: We don't expose any email addresses via the public api.
|
// NOTE: We don't expose any email addresses via the public api.
|
||||||
router.get('/users/email/:email', mw.authAdminApi, http(apiCanary.users.read));
|
router.get('/users/email/:email', mw.authAdminApi, http(apiCanary.users.read));
|
||||||
|
router.get('/users/:id/token', mw.authAdminApi, http(apiCanary.users.readToken));
|
||||||
|
|
||||||
router.put('/users/password', mw.authAdminApi, http(apiCanary.users.changePassword));
|
router.put('/users/password', mw.authAdminApi, http(apiCanary.users.changePassword));
|
||||||
router.put('/users/owner', mw.authAdminApi, http(apiCanary.users.transferOwnership));
|
router.put('/users/owner', mw.authAdminApi, http(apiCanary.users.transferOwnership));
|
||||||
router.put('/users/:id', mw.authAdminApi, http(apiCanary.users.edit));
|
router.put('/users/:id', mw.authAdminApi, http(apiCanary.users.edit));
|
||||||
|
router.put('/users/:id/token', mw.authAdminApi, http(apiCanary.users.regenerateToken));
|
||||||
router.del('/users/:id', mw.authAdminApi, http(apiCanary.users.destroy));
|
router.del('/users/:id', mw.authAdminApi, http(apiCanary.users.destroy));
|
||||||
|
|
||||||
// ## Tags
|
// ## Tags
|
||||||
|
|
|
@ -334,4 +334,53 @@ describe('User API', function () {
|
||||||
.set('Origin', config.get('url'))
|
.set('Origin', config.get('url'))
|
||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Can read the user\'s Personal Token', async function () {
|
||||||
|
await request
|
||||||
|
.get(localUtils.API.getApiQuery('users/me/token/'))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(200)
|
||||||
|
.then((res) => {
|
||||||
|
should.exist(res.body.apiKey);
|
||||||
|
should.exist(res.body.apiKey.id);
|
||||||
|
should.exist(res.body.apiKey.secret);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can\'t read another user\'s Personal Token', async function () {
|
||||||
|
const userNotAdmin = testUtils.existingData.users.find(user => user.email === 'ghost-author@example.com');
|
||||||
|
await request
|
||||||
|
.get(localUtils.API.getApiQuery('users/' + userNotAdmin.id + '/token/'))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(403)
|
||||||
|
.then((res) => {
|
||||||
|
should.exist(res.body.errors);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can re-generate the user\'s Personal Token', async function () {
|
||||||
|
const {body: {apiKey: {id, secret}}} = await request
|
||||||
|
.get(localUtils.API.getApiQuery('users/me/token/'))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
await request
|
||||||
|
.put(localUtils.API.getApiQuery('users/me/token'))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(200)
|
||||||
|
.then((res) => {
|
||||||
|
should.exist(res.body.apiKey);
|
||||||
|
should.exist(res.body.apiKey.id);
|
||||||
|
should.exist(res.body.apiKey.secret);
|
||||||
|
|
||||||
|
should(res.body.id).not.be.equal(id);
|
||||||
|
should(res.body.secret).not.be.equal(secret);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,7 +32,7 @@ const defaultSettings = require('../../../../core/server/data/schema/default-set
|
||||||
*/
|
*/
|
||||||
describe('DB version integrity', function () {
|
describe('DB version integrity', function () {
|
||||||
// Only these variables should need updating
|
// Only these variables should need updating
|
||||||
const currentSchemaHash = 'cd8820283c865acf610f97252addfa99';
|
const currentSchemaHash = '6a5b00d987f045f364c6581082cd0b03';
|
||||||
const currentFixturesHash = 'd46d696c94d03e41a5903500547fea77';
|
const currentFixturesHash = 'd46d696c94d03e41a5903500547fea77';
|
||||||
const currentSettingsHash = 'b7c0c2c6a4c61561dfefe642470d30f8';
|
const currentSettingsHash = 'b7c0c2c6a4c61561dfefe642470d30f8';
|
||||||
const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01';
|
const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01';
|
||||||
|
|
Loading…
Add table
Reference in a new issue