diff --git a/packages/auth/src/constants.js b/packages/auth/src/constants.js index 363274eda5..28b9ced49b 100644 --- a/packages/auth/src/constants.js +++ b/packages/auth/src/constants.js @@ -16,6 +16,7 @@ exports.Headers = { APP_ID: "x-budibase-app-id", TYPE: "x-budibase-type", TENANT_ID: "x-budibase-tenant-id", + TOKEN: "x-budibase-token", } exports.GlobalRoles = { diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index f0fb6e21c5..87bd4d35ce 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -1,5 +1,5 @@ const { Cookies, Headers } = require("../constants") -const { getCookie, clearCookie } = require("../utils") +const { getCookie, clearCookie, openJwt } = require("../utils") const { getUser } = require("../cache/user") const { getSession, updateSessionTTL } = require("../security/sessions") const { buildMatcherRegex, matches } = require("./matchers") @@ -35,8 +35,9 @@ module.exports = ( publicEndpoint = true } try { - // check the actual user is authenticated first - const authCookie = getCookie(ctx, Cookies.Auth) + // check the actual user is authenticated first, try header or cookie + const headerToken = ctx.request.headers[Headers.TOKEN] + const authCookie = getCookie(ctx, Cookies.Auth) || openJwt(headerToken) let authenticated = false, user = null, internal = false diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index b8fa7b9588..8c00f2a8b8 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -63,6 +63,17 @@ exports.getAppId = ctx => { return appId } +/** + * opens the contents of the specified encrypted JWT. + * @return {object} the contents of the token. + */ +exports.openJwt = token => { + if (!token) { + return token + } + return jwt.verify(token, options.secretOrKey) +} + /** * Get a cookie from context, and decrypt if necessary. * @param {object} ctx The request which is to be manipulated. @@ -75,7 +86,7 @@ exports.getCookie = (ctx, name) => { return cookie } - return jwt.verify(cookie, options.secretOrKey) + return exports.openJwt(cookie) } /** diff --git a/packages/builder/src/stores/backend/queries.js b/packages/builder/src/stores/backend/queries.js index e4e6732871..0e767fa5c0 100644 --- a/packages/builder/src/stores/backend/queries.js +++ b/packages/builder/src/stores/backend/queries.js @@ -91,6 +91,7 @@ export function createQueriesStore() { {} ), datasourceId: query.datasourceId, + queryId: query._id || undefined, }) if (response.status !== 200) { diff --git a/packages/server/src/api/controllers/query/index.js b/packages/server/src/api/controllers/query/index.js index d0992d037a..4c7c7398be 100644 --- a/packages/server/src/api/controllers/query/index.js +++ b/packages/server/src/api/controllers/query/index.js @@ -104,8 +104,10 @@ exports.preview = async function (ctx) { const db = new CouchDB(ctx.appId) const datasource = await db.get(ctx.request.body.datasourceId) - - const { fields, parameters, queryVerb, transformer } = ctx.request.body + // preview may not have a queryId as it hasn't been saved, but if it does + // this stops dynamic variables from calling the same query + const { fields, parameters, queryVerb, transformer, queryId } = + ctx.request.body try { const { rows, keys, info, extra } = await Runner.run({ @@ -115,6 +117,7 @@ exports.preview = async function (ctx) { fields, parameters, transformer, + queryId, }) ctx.body = { @@ -143,6 +146,7 @@ async function execute(ctx, opts = { rowsOnly: false }) { fields: query.fields, parameters: ctx.request.body.parameter, transformer: query.transformer, + queryId: ctx.params.queryId, }) if (opts && opts.rowsOnly) { ctx.body = rows diff --git a/packages/server/src/api/controllers/query/validation.js b/packages/server/src/api/controllers/query/validation.js index a17a752ca5..4958433849 100644 --- a/packages/server/src/api/controllers/query/validation.js +++ b/packages/server/src/api/controllers/query/validation.js @@ -36,6 +36,7 @@ exports.generateQueryPreviewValidation = () => { extra: Joi.object().optional(), datasourceId: Joi.string().required(), transformer: Joi.string().optional(), - parameters: Joi.object({}).required().unknown(true) + parameters: Joi.object({}).required().unknown(true), + queryId: Joi.string().optional(), })) } diff --git a/packages/server/src/threads/query.js b/packages/server/src/threads/query.js index b86c1a49fd..0a46cc0271 100644 --- a/packages/server/src/threads/query.js +++ b/packages/server/src/threads/query.js @@ -13,6 +13,7 @@ class QueryRunner { this.fields = input.fields this.parameters = input.parameters this.transformer = input.transformer + this.queryId = input.queryId this.noRecursiveQuery = flags.noRecursiveQuery } @@ -108,6 +109,10 @@ class QueryRunner { // need to see if this uses any variables const stringFields = JSON.stringify(fields) const foundVars = dynamicVars.filter(variable => { + // don't allow a query to use its own dynamic variable (loop) + if (variable.queryId === this.queryId) { + return false + } // look for {{ variable }} but allow spaces between handlebars const regex = new RegExp(`{{[ ]*${variable.name}[ ]*}}`) return regex.test(stringFields) diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index cd7d8abcee..93a15a6514 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -12,7 +12,7 @@ const { hash, platformLogout, } = authPkg.utils -const { Cookies } = authPkg.constants +const { Cookies, Headers } = authPkg.constants const { passport } = authPkg.auth const { checkResetPasswordCode } = require("../../../utilities/redis") const { @@ -60,7 +60,10 @@ async function authInternal(ctx, user, err = null, info = null) { return ctx.throw(403, info ? info : "Unauthorized") } + // set a cookie for browser access setCookie(ctx, user.token, Cookies.Auth, { sign: false }) + // set the token in a header as well for APIs + ctx.set(Headers.TOKEN, user.token) // get rid of any app cookies on login // have to check test because this breaks cypress if (!env.isTest()) {