diff --git a/migrations/20210501044940-secrets-add-table.js b/migrations/20210501044940-secrets-add-table.js new file mode 100644 index 000000000..56e520855 --- /dev/null +++ b/migrations/20210501044940-secrets-add-table.js @@ -0,0 +1,20 @@ +'use strict'; + +exports.up = function(db, callback) { + const cmd = 'CREATE TABLE secrets(' + + 'name VARCHAR(128) NOT NULL UNIQUE,' + + 'VALUE MEDIUMBLOB,' + + 'PRIMARY KEY (name)) CHARACTER SET utf8 COLLATE utf8_bin'; + + db.runSql(cmd, function (error) { + if (error) console.error(error); + callback(error); + }); +}; + +exports.down = function(db, callback) { + db.runSql('DROP TABLE secrets', function (error) { + if (error) console.error(error); + callback(error); + }); +}; \ No newline at end of file diff --git a/migrations/schema.sql b/migrations/schema.sql index f4c6552da..a4d9def1d 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -264,4 +264,9 @@ CREATE TABLE IF NOT EXISTS appMounts( FOREIGN KEY(appId) REFERENCES apps(id), FOREIGN KEY(volumeId) REFERENCES volumes(id)); +CREATE TABLE IF NOT EXISTS secrets( + name VARCHAR(128) NOT NULL UNIQUE, + value TEXT, + PRIMARY KEY(name)); + CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/src/secrets.js b/src/secrets.js new file mode 100644 index 000000000..7b0e796b0 --- /dev/null +++ b/src/secrets.js @@ -0,0 +1,32 @@ +'use strict'; + +exports = module.exports = { + get, + set, +}; + +const assert = require('assert'), + secretsdb = require('./secretsdb.js'); + +function get(name, callback) { + assert.strictEqual(typeof name, 'string'); + assert.strictEqual(typeof callback, 'function'); + + secretsdb.get(name, function (error, name) { + if (error) return callback(error); + + callback(null, name); + }); +} + +function set(name, value, callback) { + assert.strictEqual(typeof name, 'string'); + assert(Buffer.isBuffer(value)); + assert.strictEqual(typeof callback, 'function'); + + secretsdb.set(name, value, function (error) { + if (error) return callback(error); + + return callback(null); + }); +} diff --git a/src/secretsdb.js b/src/secretsdb.js new file mode 100644 index 000000000..81b759d5b --- /dev/null +++ b/src/secretsdb.js @@ -0,0 +1,47 @@ +/* jslint node:true */ + +'use strict'; + +exports = module.exports = { + get, + set, + _clear: clear +}; + +const assert = require('assert'), + BoxError = require('./boxerror.js'), + database = require('./database.js'); + +const SECRETS_FIELDS = [ 'name', 'value' ].join(','); + +function get(key, callback) { + assert.strictEqual(typeof key, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query(`SELECT ${SECRETS_FIELDS} FROM secrets WHERE name = ?`, [ key ], function (error, result) { + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Setting not found')); + + callback(null, result[0].value); + }); +} + +function set(key, value, callback) { + assert.strictEqual(typeof key, 'string'); + assert(value === null || Buffer.isBuffer(value)); + assert.strictEqual(typeof callback, 'function'); + + database.query('INSERT INTO secrets (name, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value=VALUES(value)', [ key, value ], function (error) { + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); // don't rely on affectedRows here since it gives 2 + + callback(null); + }); +} + +function clear(callback) { + database.query('DELETE FROM secrets', function (error) { + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + + callback(error); + }); +} diff --git a/src/test/database-test.js b/src/test/database-test.js index c92a79f60..a716757ad 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -20,6 +20,7 @@ var appdb = require('../appdb.js'), mailboxdb = require('../mailboxdb.js'), maildb = require('../maildb.js'), notificationdb = require('../notificationdb.js'), + secretsdb = require('../secretsdb.js'), settingsdb = require('../settingsdb.js'), taskdb = require('../taskdb.js'), tokendb = require('../tokendb.js'), @@ -1327,6 +1328,36 @@ describe('database', function () { }); + describe('secrets', function () { + it('can set value', function (done) { + secretsdb.set('somekey', Buffer.from('somevalue'), function (error) { + expect(error).to.be(null); + done(); + }); + }); + it('can get the set value', function (done) { + secretsdb.get('somekey', function (error, value) { + expect(error).to.be(null); + expect(value).to.eql(Buffer.from('somevalue')); + done(); + }); + }); + it('can update a value', function (done) { + secretsdb.set('somekey', Buffer.from('someothervalue'), function (error) { + expect(error).to.be(null); + done(); + }); + }); + it('can get updated value', function (done) { + secretsdb.get('somekey', function (error, value) { + expect(error).to.be(null); + expect(value).to.eql(Buffer.from('someothervalue')); + done(); + }); + }); + + }); + describe('backup', function () { it('add succeeds', function (done) {