0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added SingleUseToken model (#12215)

no-issue

This is a model for the tokens table, which handles the single use
aspect by customising the `findOne` method to automatically destroy the
model after reading from it
This commit is contained in:
Fabien 'egg' O'Carroll 2020-09-18 15:05:56 +01:00 committed by GitHub
parent f01f088066
commit 812e4b682f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 1 deletions

View file

@ -35,7 +35,8 @@ const models = [
'email',
'email-batch',
'email-recipient',
'label'
'label',
'single-use-token'
];
function init() {

View file

@ -0,0 +1,45 @@
const ghostBookshelf = require('./base');
const crypto = require('crypto');
const SingleUseToken = ghostBookshelf.Model.extend({
tableName: 'tokens',
defaults() {
return {
token: crypto
.randomBytes(192 / 8)
.toString('base64')
// base64url encoding means the tokens are URL safe
.replace('+', '-')
.replace('/', '_')
};
}
}, {
async findOne(data, unfilteredOptions = {}) {
if (!unfilteredOptions.transacting) {
return ghostBookshelf.transaction((transacting) => {
return this.findOne(data, Object.assign({transacting}, unfilteredOptions));
});
}
const model = await ghostBookshelf.Model.findOne.call(this, data, unfilteredOptions);
if (model) {
await this.destroy(Object.assign({
destroyBy: {
id: model.id
}
}, unfilteredOptions));
}
return model;
}
});
const SingleUseTokens = ghostBookshelf.Collection.extend({
model: SingleUseToken
});
module.exports = {
SingleUseToken: ghostBookshelf.model('SingleUseToken', SingleUseToken),
SingleUseTokens: ghostBookshelf.collection('SingleUseTokens', SingleUseTokens)
};

View file

@ -0,0 +1,30 @@
const models = require('../../../core/server/models');
const should = require('should');
describe('Regression: models/single-use-token', function () {
before(function () {
models.init();
});
describe('findOne', function () {
it('Does not allow the same token to be read twice', async function () {
const insertedToken = await models.SingleUseToken.add({
data: 'some_data'
}, {});
const tokenFirstRead = await models.SingleUseToken.findOne({
token: insertedToken.get('token')
});
should.exist(tokenFirstRead);
should.equal(tokenFirstRead.id, insertedToken.id);
const tokenSecondRead = await models.SingleUseToken.findOne({
token: insertedToken.get('token')
});
should.not.exist(tokenSecondRead);
});
});
});