diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 58526cdf4c..a5ef3132af 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -9,6 +9,7 @@ const env = require("./environment") const eventEmitter = require("./events") const automations = require("./automations/index") const Sentry = require("@sentry/node") +const selfhost = require("./selfhost") const app = new Koa() @@ -49,9 +50,12 @@ destroyable(server) server.on("close", () => console.log("Server Closed")) -module.exports = server.listen(env.PORT || 4001, () => { +module.exports = server.listen(env.PORT || 4001, async () => { console.log(`Budibase running on ${JSON.stringify(server.address())}`) automations.init() + if (env.SELF_HOSTED) { + await selfhost.init() + } }) process.on("uncaughtException", err => { diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index e959e05f9d..fff66a68d6 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -7,7 +7,7 @@ const { doesHavePermission, } = require("../utilities/security/permissions") const env = require("../environment") -const { apiKeyTable } = require("../db/dynamoClient") +const { getAPIKey } = require("../utilities/security/apikey") const { AuthTypes } = require("../constants") const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER] @@ -21,9 +21,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { } if (env.CLOUD && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) { // api key header passed by external webhook - const apiKeyInfo = await apiKeyTable.get({ - primary: ctx.headers["x-api-key"], - }) + const apiKeyInfo = await getAPIKey(ctx.headers["x-api-key"]) if (apiKeyInfo) { ctx.auth = { diff --git a/packages/server/src/middleware/usageQuota.js b/packages/server/src/middleware/usageQuota.js index 39e387bd5a..e980afe678 100644 --- a/packages/server/src/middleware/usageQuota.js +++ b/packages/server/src/middleware/usageQuota.js @@ -43,6 +43,10 @@ module.exports = async (ctx, next) => { return } } + // if running in builder or a self hosted cloud usage quotas should not be executed + if (!env.CLOUD || env.SELF_HOSTED) { + return next() + } // update usage for uploads to be the total size if (property === usageQuota.Properties.UPLOAD) { const files = @@ -51,9 +55,6 @@ module.exports = async (ctx, next) => { : [ctx.request.files.file] usage = files.map(file => file.size).reduce((total, size) => total + size) } - if (!env.CLOUD) { - return next() - } try { await usageQuota.update(ctx.auth.apiKey, property, usage) return next() diff --git a/packages/server/src/selfhost/README.md b/packages/server/src/selfhost/README.md new file mode 100644 index 0000000000..a02743a58c --- /dev/null +++ b/packages/server/src/selfhost/README.md @@ -0,0 +1,7 @@ +### Self hosting +This directory contains utilities that are needed for self hosted platforms to operate. +These will mostly be utilities, necessary to the operation of the server e.g. storing self +hosting specific options and attributes to CouchDB. + +All the internal operations should be exposed through the `index.js` so importing +the self host directory should give you everything you need. \ No newline at end of file diff --git a/packages/server/src/selfhost/index.js b/packages/server/src/selfhost/index.js new file mode 100644 index 0000000000..05c9bdc6b2 --- /dev/null +++ b/packages/server/src/selfhost/index.js @@ -0,0 +1,39 @@ +const CouchDB = require("../db") +const env = require("../environment") +const newid = require("../db/newid") + +const SELF_HOST_DB = "self-host-db" +const SELF_HOST_DOC = "self-host-info" + +async function createSelfHostDB(db) { + await db.put({ + _id: "_design/database", + views: {}, + }) + const selfHostInfo = { + _id: SELF_HOST_DOC, + apiKeyId: newid(), + } + await db.put(selfHostInfo) + return selfHostInfo +} + +exports.init = async () => { + if (!env.SELF_HOSTED) { + return + } + const db = new CouchDB(SELF_HOST_DB) + try { + await db.get(SELF_HOST_DOC) + } catch (err) { + // failed to retrieve + if (err.status === 404) { + await createSelfHostDB(db) + } + } +} + +exports.getSelfHostInfo = async () => { + const db = new CouchDB(SELF_HOST_DB) + return db.get(SELF_HOST_DOC) +} diff --git a/packages/server/src/utilities/security/apikey.js b/packages/server/src/utilities/security/apikey.js new file mode 100644 index 0000000000..b2fd230130 --- /dev/null +++ b/packages/server/src/utilities/security/apikey.js @@ -0,0 +1,22 @@ +const { apiKeyTable } = require("../../db/dynamoClient") +const env = require("../../environment") +const { getSelfHostInfo } = require("../../selfhost") + +/** + * This file purely exists so that we can centralise all logic pertaining to API keys, as their usage differs + * in our Cloud environment versus self hosted. + */ + +exports.getAPIKey = async apiKeyId => { + if (env.CLOUD && !env.SELF_HOSTED) { + return apiKeyTable.get({ + primary: apiKeyId, + }) + } + if (env.SELF_HOSTED) { + const selfHostInfo = await getSelfHostInfo() + // if the api key supplied is correct then return structure similar + return apiKeyId === selfHostInfo.apiKeyId ? { pk: apiKeyId } : null + } + return null +}