"use strict"; const Promise = require("bluebird"); const crypto = require("crypto"); const nanoid = require("nanoid"); const defaultValue = require("default-value"); const databaseError = require("database-error"); const errors = require("../errors"); const createDatabaseModule = require("../db-module"); function tokensMatch(a, b) { return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); } module.exports = function ({ knex }) { return createDatabaseModule({ create: function ({ name, deviceId, userId, token }, tx = knex) { return Promise.try(() => { let token_ = defaultValue(token, nanoid()); let deviceId_ = defaultValue(deviceId, nanoid()); return tx("devices").insert({ device_id: deviceId_, user_id: userId, name: name, token: token_, }).returning("*"); }).then((results) => { return results[0]; }); }, byDeviceId: function ({ deviceId }, tx = knex) { return Promise.try(() => { return tx("devices").first().where({ device_id: deviceId }); }).then((result) => { if (result != null) { return result; } else { throw new errors.NotFound("The specified device ID does not exist"); } }); }, byDeviceIdAndToken: function ({ deviceId, token }, _tx = knex) { /* NOTE: This is intentionally a separate method as opposed to conditional logic in byDeviceId, to prevent cases where a bug results in a user-specified token not being passed in - which would fail unsafely by allowing any token, if conditional logic were used. */ return Promise.try(() => { if (token != null) { return this.byDeviceId({ deviceId }); } else { throw new errors.MissingAccessToken("No access token was specified"); } }).then((device) => { if (tokensMatch(token, device.token)) { return device; } else { throw new errors.InvalidAccessToken("Specified token was invalid"); } }); }, updateToken: function ({ userId, deviceId, token }, tx = knex) { return Promise.try(() => { return tx("devices") .update({ token: token }) .where({ device_id: deviceId, user_id: userId }) .returning("*"); }).then((results) => { if (results.length > 0) { return results[0]; } else { throw new errors.NotFound("Specified Device ID does not exist"); } }); }, createDeviceSession: function ({ name, deviceId, userId }, _tx = knex) { return Promise.try(() => { let token = nanoid(); return Promise.try(() => { return this.create({ name, deviceId, userId, token }); }).catch({ name: "UniqueConstraintViolationError", table: "devices", column: "device_id" }, (_error) => { return this.updateToken({ userId, deviceId, token }); }).then((device) => { /* FIXME */ }); }); } }); };