From 651947bb498a3eced9ab6feae36c3a314581df92 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 4 Oct 2021 13:40:50 +0100 Subject: [PATCH] Add API keys between account portal and budibase --- .../templates/worker-service-deployment.yaml | 2 ++ hosting/kubernetes/budibase/values.yaml | 1 + packages/auth/src/cloud/accounts.js | 6 ++++-- packages/auth/src/environment.js | 1 + packages/auth/src/middleware/matchers.js | 13 ++++++++++--- .../src/api/controllers/row/ExternalRequest.ts | 8 ++++++-- packages/worker/scripts/dev/manage.js | 1 + packages/worker/src/api/routes/global/users.js | 2 ++ .../worker/src/middleware/cloudRestricted.js | 17 +++++++++++++++++ 9 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 packages/worker/src/middleware/cloudRestricted.js diff --git a/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml b/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml index 98a921a8a6..08b40d3b6b 100644 --- a/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml +++ b/hosting/kubernetes/budibase/templates/worker-service-deployment.yaml @@ -89,6 +89,8 @@ spec: value: {{ .Values.globals.selfHosted | quote }} - name: ACCOUNT_PORTAL_URL value: {{ .Values.globals.accountPortalUrl | quote }} + - name: ACCOUNT_PORTAL_API_KEY + value: {{ .Values.globals.accountPortalApiKey | quote }} - name: COOKIE_DOMAIN value: {{ .Values.globals.cookieDomain | quote }} image: budibase/worker diff --git a/hosting/kubernetes/budibase/values.yaml b/hosting/kubernetes/budibase/values.yaml index c9b2549b30..5999f9c4bc 100644 --- a/hosting/kubernetes/budibase/values.yaml +++ b/hosting/kubernetes/budibase/values.yaml @@ -90,6 +90,7 @@ globals: logLevel: info selfHosted: 1 accountPortalUrL: "" + accountPortalApiKey: "" cookieDomain: "" createSecrets: true # creates an internal API key, JWT secrets and redis password for you diff --git a/packages/auth/src/cloud/accounts.js b/packages/auth/src/cloud/accounts.js index a102df8920..a02fe60926 100644 --- a/packages/auth/src/cloud/accounts.js +++ b/packages/auth/src/cloud/accounts.js @@ -1,16 +1,18 @@ const API = require("./api") const env = require("../environment") +const { Headers } = require("../constants") const api = new API(env.ACCOUNT_PORTAL_URL) -// TODO: Authorization - exports.getAccount = async email => { const payload = { email, } const response = await api.post(`/api/accounts/search`, { body: payload, + headers: { + [Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY, + }, }) const json = await response.json() diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js index 7f822090d7..c36b469c4e 100644 --- a/packages/auth/src/environment.js +++ b/packages/auth/src/environment.js @@ -21,6 +21,7 @@ module.exports = { INTERNAL_API_KEY: process.env.INTERNAL_API_KEY, MULTI_TENANCY: process.env.MULTI_TENANCY, ACCOUNT_PORTAL_URL: process.env.ACCOUNT_PORTAL_URL, + ACCOUNT_PORTAL_API_KEY: process.env.ACCOUNT_PORTAL_API_KEY, DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL, SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED), COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, diff --git a/packages/auth/src/middleware/matchers.js b/packages/auth/src/middleware/matchers.js index a555823136..3d5065c069 100644 --- a/packages/auth/src/middleware/matchers.js +++ b/packages/auth/src/middleware/matchers.js @@ -7,6 +7,7 @@ exports.buildMatcherRegex = patterns => { return patterns.map(pattern => { const isObj = typeof pattern === "object" && pattern.route const method = isObj ? pattern.method : "GET" + const strict = pattern.strict ? pattern.strict : false let route = isObj ? pattern.route : pattern const matches = route.match(PARAM_REGEX) @@ -16,13 +17,19 @@ exports.buildMatcherRegex = patterns => { route = route.replace(match, pattern) } } - return { regex: new RegExp(route), method } + return { regex: new RegExp(route), method, strict, route } }) } exports.matches = (ctx, options) => { - return options.find(({ regex, method }) => { - const urlMatch = regex.test(ctx.request.url) + return options.find(({ regex, method, strict, route }) => { + let urlMatch + if (strict) { + urlMatch = ctx.request.url === route + } else { + urlMatch = regex.test(ctx.request.url) + } + const methodMatch = method === "ALL" ? true diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 03daf4a90b..3e9c707e69 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -205,9 +205,13 @@ module External { } else { // we're not inserting a doc, will be a bunch of update calls const isUpdate = !field.through - const thisKey: string = isUpdate ? "id" : (field.throughTo || linkTablePrimary) + const thisKey: string = isUpdate + ? "id" + : field.throughTo || linkTablePrimary // @ts-ignore - const otherKey: string = isUpdate ? field.fieldName : (field.throughFrom || tablePrimary) + const otherKey: string = isUpdate + ? field.fieldName + : field.throughFrom || tablePrimary row[key].map((relationship: any) => { // we don't really support composite keys for relationships, this is why [0] is used manyRelationships.push({ diff --git a/packages/worker/scripts/dev/manage.js b/packages/worker/scripts/dev/manage.js index be6d724716..e0b8c3586a 100644 --- a/packages/worker/scripts/dev/manage.js +++ b/packages/worker/scripts/dev/manage.js @@ -23,6 +23,7 @@ async function init() { MULTI_TENANCY: "", DISABLE_ACCOUNT_PORTAL: "", ACCOUNT_PORTAL_URL: "http://localhost:10001", + ACCOUNT_PORTAL_API_KEY: "budibase", PLATFORM_URL: "http://localhost:10000", } let envFile = "" diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js index 1a04944a30..d2a6bece9f 100644 --- a/packages/worker/src/api/routes/global/users.js +++ b/packages/worker/src/api/routes/global/users.js @@ -3,6 +3,7 @@ const controller = require("../../controllers/global/users") const joiValidator = require("../../../middleware/joi-validator") const adminOnly = require("../../../middleware/adminOnly") const Joi = require("joi") +const cloudRestricted = require("../../../middleware/cloudRestricted") const router = Router() @@ -90,6 +91,7 @@ router ) .post( "/api/global/users/init", + cloudRestricted, buildAdminInitValidation(), controller.adminUser ) diff --git a/packages/worker/src/middleware/cloudRestricted.js b/packages/worker/src/middleware/cloudRestricted.js new file mode 100644 index 0000000000..10cdeaebd4 --- /dev/null +++ b/packages/worker/src/middleware/cloudRestricted.js @@ -0,0 +1,17 @@ +const env = require("../environment") +const { Headers } = require("@budibase/auth").constants + +/** + * This is a restricted endpoint in the cloud. + * Ensure that the correct API key has been supplied. + */ +module.exports = async (ctx, next) => { + if (!env.SELF_HOSTED) { + const apiKey = ctx.request.headers[Headers.API_KEY] + if (apiKey !== env.INTERNAL_API_KEY) { + ctx.throw(403, "Unauthorized") + } + } + + return next() +}