Implementing some changes to how context gets set for tenancy, after testing, as well as updating server.
This commit is contained in:
parent
f3ce979230
commit
e7974f7e86
|
@ -1,22 +1,25 @@
|
||||||
const redis = require("../redis/authRedis")
|
const redis = require("../redis/authRedis")
|
||||||
const {
|
const {
|
||||||
updateTenantId,
|
getTenantId,
|
||||||
lookupTenantId,
|
lookupTenantId,
|
||||||
getGlobalDB,
|
getGlobalDB,
|
||||||
isTenantIdSet,
|
|
||||||
} = require("../tenancy")
|
} = require("../tenancy")
|
||||||
|
|
||||||
const EXPIRY_SECONDS = 3600
|
const EXPIRY_SECONDS = 3600
|
||||||
|
|
||||||
exports.getUser = async userId => {
|
exports.getUser = async (userId, tenantId = null) => {
|
||||||
if (!isTenantIdSet()) {
|
if (!tenantId) {
|
||||||
updateTenantId(await lookupTenantId(userId))
|
try {
|
||||||
|
tenantId = getTenantId()
|
||||||
|
} catch (err) {
|
||||||
|
tenantId = await lookupTenantId(userId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const client = await redis.getUserClient()
|
const client = await redis.getUserClient()
|
||||||
// try cache
|
// try cache
|
||||||
let user = await client.get(userId)
|
let user = await client.get(userId)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await getGlobalDB().get(userId)
|
user = await getGlobalDB(tenantId).get(userId)
|
||||||
client.store(userId, user, EXPIRY_SECONDS)
|
client.store(userId, user, EXPIRY_SECONDS)
|
||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
|
|
|
@ -3,7 +3,6 @@ const { getCookie, clearCookie } = require("../utils")
|
||||||
const { getUser } = require("../cache/user")
|
const { getUser } = require("../cache/user")
|
||||||
const { getSession, updateSessionTTL } = require("../security/sessions")
|
const { getSession, updateSessionTTL } = require("../security/sessions")
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
const { buildMatcherRegex, matches } = require("./matchers")
|
||||||
const { isTenantIdSet, updateTenantId } = require("../tenancy")
|
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
|
||||||
function finalise(
|
function finalise(
|
||||||
|
@ -17,6 +16,11 @@ function finalise(
|
||||||
ctx.version = version
|
ctx.version = version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This middleware is tenancy aware, so that it does not depend on other middlewares being used.
|
||||||
|
* The tenancy modules should not be used here and it should be assumed that the tenancy context
|
||||||
|
* has not yet been populated.
|
||||||
|
*/
|
||||||
module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => {
|
module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => {
|
||||||
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
|
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
|
@ -42,10 +46,7 @@ module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => {
|
||||||
error = "No session found"
|
error = "No session found"
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (session.tenantId && !isTenantIdSet()) {
|
user = await getUser(userId, session.tenantId)
|
||||||
updateTenantId(session.tenantId)
|
|
||||||
}
|
|
||||||
user = await getUser(userId)
|
|
||||||
delete user.password
|
delete user.password
|
||||||
authenticated = true
|
authenticated = true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
|
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
|
||||||
|
|
||||||
exports.buildMatcherRegex = patterns => {
|
exports.buildMatcherRegex = patterns => {
|
||||||
|
if (!patterns) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
return patterns.map(pattern => {
|
return patterns.map(pattern => {
|
||||||
const isObj = typeof pattern === "object" && pattern.route
|
const isObj = typeof pattern === "object" && pattern.route
|
||||||
const method = isObj ? pattern.method : "GET"
|
const method = isObj ? pattern.method : "GET"
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
const { createTenancyContext, setTenantId } = require("../tenancy")
|
const { setTenantId } = require("../tenancy")
|
||||||
|
const ContextFactory = require("../tenancy/FunctionContext")
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
const { buildMatcherRegex, matches } = require("./matchers")
|
||||||
|
|
||||||
module.exports = (allowQueryStringPatterns, noTenancyPatterns) => {
|
module.exports = (allowQueryStringPatterns, noTenancyPatterns) => {
|
||||||
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
||||||
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
||||||
|
|
||||||
return (ctx, next) => {
|
return ContextFactory.getMiddleware(ctx => {
|
||||||
// always run in context
|
const allowNoTenant = !!matches(ctx, noTenancyOptions)
|
||||||
return createTenancyContext().runAndReturn(() => {
|
const allowQs = !!matches(ctx, allowQsOptions)
|
||||||
if (matches(ctx, noTenancyOptions)) {
|
setTenantId(ctx, { allowQs, allowNoTenant })
|
||||||
return next()
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const allowQs = !!matches(ctx, allowQsOptions)
|
|
||||||
setTenantId(ctx, { allowQs })
|
|
||||||
return next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
const cls = require("cls-hooked")
|
||||||
|
const { newid } = require("../hashing")
|
||||||
|
|
||||||
|
const REQUEST_ID_KEY = "requestId"
|
||||||
|
|
||||||
|
class FunctionContext {
|
||||||
|
static getMiddleware(updateCtxFn = null) {
|
||||||
|
const namespace = this.createNamespace()
|
||||||
|
|
||||||
|
return async function(ctx, next) {
|
||||||
|
await new Promise(namespace.bind(function(resolve, reject) {
|
||||||
|
// store a contextual request ID that can be used anywhere (audit logs)
|
||||||
|
namespace.set(REQUEST_ID_KEY, newid())
|
||||||
|
namespace.bindEmitter(ctx.req)
|
||||||
|
namespace.bindEmitter(ctx.res)
|
||||||
|
|
||||||
|
if (updateCtxFn) {
|
||||||
|
updateCtxFn(ctx)
|
||||||
|
}
|
||||||
|
next().then(resolve).catch(reject)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static run(callback) {
|
||||||
|
const namespace = this.createNamespace()
|
||||||
|
|
||||||
|
return namespace.runAndReturn(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
static setOnContext(key, value) {
|
||||||
|
const namespace = this.createNamespace()
|
||||||
|
namespace.set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
static getContextStorage() {
|
||||||
|
if (this._namespace && this._namespace.active) {
|
||||||
|
const { id, _ns_name, ...contextData } = this._namespace.active
|
||||||
|
return contextData
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getFromContext(key) {
|
||||||
|
const context = this.getContextStorage()
|
||||||
|
if (context) {
|
||||||
|
return context[key]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static destroyNamespace() {
|
||||||
|
if (this._namespace) {
|
||||||
|
cls.destroyNamespace("session")
|
||||||
|
this._namespace = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static createNamespace() {
|
||||||
|
if (!this._namespace) {
|
||||||
|
this._namespace = cls.createNamespace("session")
|
||||||
|
}
|
||||||
|
return this._namespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FunctionContext
|
|
@ -1,6 +1,6 @@
|
||||||
const cls = require("cls-hooked")
|
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { Headers } = require("../../constants")
|
const { Headers } = require("../../constants")
|
||||||
|
const cls = require("./FunctionContext")
|
||||||
|
|
||||||
exports.DEFAULT_TENANT_ID = "default"
|
exports.DEFAULT_TENANT_ID = "default"
|
||||||
|
|
||||||
|
@ -12,66 +12,61 @@ exports.isMultiTenant = () => {
|
||||||
return env.MULTI_TENANCY
|
return env.MULTI_TENANCY
|
||||||
}
|
}
|
||||||
|
|
||||||
// continuation local storage
|
|
||||||
const CONTEXT_NAME = "tenancy"
|
|
||||||
const TENANT_ID = "tenantId"
|
const TENANT_ID = "tenantId"
|
||||||
|
|
||||||
exports.createTenancyContext = () => {
|
|
||||||
return cls.createNamespace(CONTEXT_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTenancyContext = () => {
|
|
||||||
return cls.getNamespace(CONTEXT_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
// used for automations, API endpoints should always be in context already
|
// used for automations, API endpoints should always be in context already
|
||||||
exports.doInTenant = (tenantId, task) => {
|
exports.doInTenant = (tenantId, task) => {
|
||||||
const context = getTenancyContext()
|
return cls.run(() => {
|
||||||
return getTenancyContext().runAndReturn(() => {
|
|
||||||
// set the tenant id
|
// set the tenant id
|
||||||
context.set(TENANT_ID, tenantId)
|
cls.setOnContext(TENANT_ID, tenantId)
|
||||||
|
|
||||||
// invoke the task
|
// invoke the task
|
||||||
const result = task()
|
const result = task()
|
||||||
|
|
||||||
// clear down the tenant id manually for extra safety
|
// clear down the tenant id manually for extra safety
|
||||||
// this should also happen automatically when the call exits
|
// this should also happen automatically when the call exits
|
||||||
context.set(TENANT_ID, null)
|
cls.setOnContext(TENANT_ID, null)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updateTenantId = tenantId => {
|
exports.updateTenantId = tenantId => {
|
||||||
getTenancyContext().set(TENANT_ID, tenantId)
|
cls.setOnContext(TENANT_ID, tenantId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.setTenantId = (ctx, opts = { allowQs: false }) => {
|
exports.setTenantId = (ctx, opts = { allowQs: false, allowNoTenant: false }) => {
|
||||||
let tenantId
|
let tenantId
|
||||||
// exit early if not multi-tenant
|
// exit early if not multi-tenant
|
||||||
if (!exports.isMultiTenant()) {
|
if (!exports.isMultiTenant()) {
|
||||||
getTenancyContext().set(TENANT_ID, this.DEFAULT_TENANT_ID)
|
cls.setOnContext(TENANT_ID, this.DEFAULT_TENANT_ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = ctx.request.params || {}
|
const allowQs = opts && opts.allowQs
|
||||||
|
const allowNoTenant = opts && opts.allowNoTenant
|
||||||
const header = ctx.request.headers[Headers.TENANT_ID]
|
const header = ctx.request.headers[Headers.TENANT_ID]
|
||||||
const user = ctx.request.user || {}
|
const user = ctx.user || {}
|
||||||
tenantId = user.tenantId || params.tenantId || header
|
if (allowQs) {
|
||||||
if (opts.allowQs && !tenantId) {
|
|
||||||
const query = ctx.request.query || {}
|
const query = ctx.request.query || {}
|
||||||
tenantId = query.tenantId
|
tenantId = query.tenantId
|
||||||
}
|
}
|
||||||
|
// override query string (if allowed) by user, or header
|
||||||
|
// URL params cannot be used in a middleware, as they are
|
||||||
|
// processed later in the chain
|
||||||
|
tenantId = user.tenantId || header || tenantId
|
||||||
|
|
||||||
if (!tenantId) {
|
if (!tenantId && !allowNoTenant) {
|
||||||
ctx.throw(403, "Tenant id not set")
|
ctx.throw(403, "Tenant id not set")
|
||||||
}
|
}
|
||||||
|
// check tenant ID just incase no tenant was allowed
|
||||||
getTenancyContext().set(TENANT_ID, tenantId)
|
if (tenantId) {
|
||||||
|
cls.setOnContext(TENANT_ID, tenantId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isTenantIdSet = () => {
|
exports.isTenantIdSet = () => {
|
||||||
const tenantId = getTenancyContext().get(TENANT_ID)
|
const tenantId = cls.getFromContext(TENANT_ID)
|
||||||
return !!tenantId
|
return !!tenantId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +74,7 @@ exports.getTenantId = () => {
|
||||||
if (!exports.isMultiTenant()) {
|
if (!exports.isMultiTenant()) {
|
||||||
return exports.DEFAULT_TENANT_ID
|
return exports.DEFAULT_TENANT_ID
|
||||||
}
|
}
|
||||||
const tenantId = getTenancyContext().get(TENANT_ID)
|
const tenantId = cls.getFromContext(TENANT_ID)
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw Error("Tenant id not found")
|
throw Error("Tenant id not found")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { getDB } = require("../../db")
|
const { getDB } = require("../db")
|
||||||
const { SEPARATOR, StaticDatabases } = require("../db/constants")
|
const { SEPARATOR, StaticDatabases } = require("../db/constants")
|
||||||
const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context")
|
const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
|
|
@ -94,6 +94,9 @@
|
||||||
requireAuth = smtpConfig.config.auth != null
|
requireAuth = smtpConfig.config.auth != null
|
||||||
// always attach the auth for the forms purpose -
|
// always attach the auth for the forms purpose -
|
||||||
// this will be removed later if required
|
// this will be removed later if required
|
||||||
|
if (!smtpDoc.config) {
|
||||||
|
smtpDoc.config = {}
|
||||||
|
}
|
||||||
if (!smtpDoc.config.auth) {
|
if (!smtpDoc.config.auth) {
|
||||||
smtpConfig.config.auth = {
|
smtpConfig.config.auth = {
|
||||||
type: "login",
|
type: "login",
|
||||||
|
|
|
@ -38,6 +38,7 @@ const {
|
||||||
backupClientLibrary,
|
backupClientLibrary,
|
||||||
revertClientLibrary,
|
revertClientLibrary,
|
||||||
} = require("../../utilities/fileSystem/clientLibrary")
|
} = require("../../utilities/fileSystem/clientLibrary")
|
||||||
|
const { getTenantId, isMultiTenant } = require("@budibase/auth/tenancy")
|
||||||
|
|
||||||
const URL_REGEX_SLASH = /\/|\\/g
|
const URL_REGEX_SLASH = /\/|\\/g
|
||||||
|
|
||||||
|
@ -92,8 +93,9 @@ async function getAppUrlIfNotInUse(ctx) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createInstance(tenantId, template) {
|
async function createInstance(template) {
|
||||||
const baseAppId = generateAppID(env.MULTI_TENANCY ? tenantId : null)
|
const tenantId = isMultiTenant() ? getTenantId() : null
|
||||||
|
const baseAppId = generateAppID(tenantId)
|
||||||
const appId = generateDevAppID(baseAppId)
|
const appId = generateDevAppID(baseAppId)
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
|
@ -128,8 +130,7 @@ async function createInstance(tenantId, template) {
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
||||||
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
||||||
const tenantId = ctx.user.tenantId
|
const apps = await getAllApps(CouchDB, { dev, all })
|
||||||
const apps = await getAllApps(CouchDB, { tenantId, dev, all })
|
|
||||||
|
|
||||||
// get the locks for all the dev apps
|
// get the locks for all the dev apps
|
||||||
if (dev || all) {
|
if (dev || all) {
|
||||||
|
@ -189,7 +190,6 @@ exports.fetchAppPackage = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = async function (ctx) {
|
exports.create = async function (ctx) {
|
||||||
const tenantId = ctx.user.tenantId
|
|
||||||
const { useTemplate, templateKey } = ctx.request.body
|
const { useTemplate, templateKey } = ctx.request.body
|
||||||
const instanceConfig = {
|
const instanceConfig = {
|
||||||
useTemplate,
|
useTemplate,
|
||||||
|
@ -198,7 +198,7 @@ exports.create = async function (ctx) {
|
||||||
if (ctx.request.files && ctx.request.files.templateFile) {
|
if (ctx.request.files && ctx.request.files.templateFile) {
|
||||||
instanceConfig.file = ctx.request.files.templateFile
|
instanceConfig.file = ctx.request.files.templateFile
|
||||||
}
|
}
|
||||||
const instance = await createInstance(tenantId, instanceConfig)
|
const instance = await createInstance(instanceConfig)
|
||||||
const appId = instance._id
|
const appId = instance._id
|
||||||
|
|
||||||
const url = await getAppUrlIfNotInUse(ctx)
|
const url = await getAppUrlIfNotInUse(ctx)
|
||||||
|
@ -222,7 +222,7 @@ exports.create = async function (ctx) {
|
||||||
url: url,
|
url: url,
|
||||||
template: ctx.request.body.template,
|
template: ctx.request.body.template,
|
||||||
instance: instance,
|
instance: instance,
|
||||||
tenantId,
|
tenantId: getTenantId(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,13 @@ const env = require("../environment")
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
|
const NO_TENANCY_ENDPOINTS = [
|
||||||
|
{
|
||||||
|
route: "/api/analytics",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
router
|
router
|
||||||
.use(
|
.use(
|
||||||
compress({
|
compress({
|
||||||
|
@ -32,12 +39,13 @@ router
|
||||||
})
|
})
|
||||||
.use("/health", ctx => (ctx.status = 200))
|
.use("/health", ctx => (ctx.status = 200))
|
||||||
.use("/version", ctx => (ctx.body = pkg.version))
|
.use("/version", ctx => (ctx.body = pkg.version))
|
||||||
.use(buildTenancyMiddleware())
|
|
||||||
.use(
|
.use(
|
||||||
buildAuthMiddleware(null, {
|
buildAuthMiddleware(null, {
|
||||||
publicAllowed: true,
|
publicAllowed: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
// nothing in the server should allow query string tenants
|
||||||
|
.use(buildTenancyMiddleware(null, NO_TENANCY_ENDPOINTS))
|
||||||
.use(currentApp)
|
.use(currentApp)
|
||||||
.use(auditLog)
|
.use(auditLog)
|
||||||
|
|
||||||
|
|
|
@ -46,13 +46,13 @@ module.exports.definition = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.run = async function ({ inputs, tenantId }) {
|
module.exports.run = async function ({ inputs }) {
|
||||||
let { to, from, subject, contents } = inputs
|
let { to, from, subject, contents } = inputs
|
||||||
if (!contents) {
|
if (!contents) {
|
||||||
contents = "<h1>No content</h1>"
|
contents = "<h1>No content</h1>"
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let response = await sendSmtpEmail(tenantId, to, from, subject, contents)
|
let response = await sendSmtpEmail(to, from, subject, contents)
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
response,
|
response,
|
||||||
|
|
|
@ -6,6 +6,7 @@ const { processObject } = require("@budibase/string-templates")
|
||||||
const { DEFAULT_TENANT_ID } = require("@budibase/auth").constants
|
const { DEFAULT_TENANT_ID } = require("@budibase/auth").constants
|
||||||
const CouchDB = require("../db")
|
const CouchDB = require("../db")
|
||||||
const { DocumentTypes } = require("../db/utils")
|
const { DocumentTypes } = require("../db/utils")
|
||||||
|
const { doInTenant } = require("@budibase/auth/tenancy")
|
||||||
|
|
||||||
const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId
|
const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ class Orchestrator {
|
||||||
|
|
||||||
async execute() {
|
async execute() {
|
||||||
let automation = this._automation
|
let automation = this._automation
|
||||||
const app = this.getApp()
|
const app = await this.getApp()
|
||||||
for (let step of automation.definition.steps) {
|
for (let step of automation.definition.steps) {
|
||||||
let stepFn = await this.getStepFunctionality(step.type, step.stepId)
|
let stepFn = await this.getStepFunctionality(step.type, step.stepId)
|
||||||
step.inputs = await processObject(step.inputs, this._context)
|
step.inputs = await processObject(step.inputs, this._context)
|
||||||
|
@ -66,13 +67,15 @@ class Orchestrator {
|
||||||
)
|
)
|
||||||
// appId is always passed
|
// appId is always passed
|
||||||
try {
|
try {
|
||||||
const outputs = await stepFn({
|
let tenantId = app.tenantId || DEFAULT_TENANT_ID
|
||||||
inputs: step.inputs,
|
const outputs = await doInTenant(tenantId, () => {
|
||||||
appId: this._appId,
|
return stepFn({
|
||||||
apiKey: automation.apiKey,
|
inputs: step.inputs,
|
||||||
emitter: this._emitter,
|
appId: this._appId,
|
||||||
context: this._context,
|
apiKey: automation.apiKey,
|
||||||
tenantId: app.tenantId || DEFAULT_TENANT_ID,
|
emitter: this._emitter,
|
||||||
|
context: this._context,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
if (step.stepId === FILTER_STEP_ID && !outputs.success) {
|
if (step.stepId === FILTER_STEP_ID && !outputs.success) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -68,5 +68,6 @@ module.exports = async (ctx, next) => {
|
||||||
) {
|
) {
|
||||||
setCookie(ctx, { appId }, Cookies.CurrentApp)
|
setCookie(ctx, { appId }, Cookies.CurrentApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,9 @@ function processUser(appId, user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getCachedSelf = async (ctx, appId) => {
|
exports.getCachedSelf = async (ctx, appId) => {
|
||||||
const user = await userCache.getUser(ctx.user._id, ctx.user.tenantId)
|
// this has to be tenant aware, can't depend on the context to find it out
|
||||||
|
// running some middlewares before the tenancy causes context to break
|
||||||
|
const user = await userCache.getUser(ctx.user._id)
|
||||||
return processUser(appId, user)
|
return processUser(appId, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ const { checkSlashesInUrl } = require("./index")
|
||||||
const { getDeployedAppID } = require("@budibase/auth/db")
|
const { getDeployedAppID } = require("@budibase/auth/db")
|
||||||
const { updateAppRole, getGlobalUser } = require("./global")
|
const { updateAppRole, getGlobalUser } = require("./global")
|
||||||
const { Headers } = require("@budibase/auth/constants")
|
const { Headers } = require("@budibase/auth/constants")
|
||||||
|
const { getTenantId, isTenantIdSet } = require("@budibase/auth/tenancy")
|
||||||
|
|
||||||
function request(ctx, request) {
|
function request(ctx, request) {
|
||||||
if (!request.headers) {
|
if (!request.headers) {
|
||||||
|
@ -11,6 +12,9 @@ function request(ctx, request) {
|
||||||
}
|
}
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
request.headers[Headers.API_KEY] = env.INTERNAL_API_KEY
|
request.headers[Headers.API_KEY] = env.INTERNAL_API_KEY
|
||||||
|
if (isTenantIdSet()) {
|
||||||
|
request.headers[Headers.TENANT_ID] = getTenantId()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (request.body && Object.keys(request.body).length > 0) {
|
if (request.body && Object.keys(request.body).length > 0) {
|
||||||
request.headers["Content-Type"] = "application/json"
|
request.headers["Content-Type"] = "application/json"
|
||||||
|
@ -29,13 +33,14 @@ function request(ctx, request) {
|
||||||
|
|
||||||
exports.request = request
|
exports.request = request
|
||||||
|
|
||||||
exports.sendSmtpEmail = async (tenantId, to, from, subject, contents) => {
|
// have to pass in the tenant ID as this could be coming from an automation
|
||||||
|
exports.sendSmtpEmail = async (to, from, subject, contents) => {
|
||||||
|
// tenant ID will be set in header
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`),
|
checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`),
|
||||||
request(null, {
|
request(null, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
tenantId,
|
|
||||||
email: to,
|
email: to,
|
||||||
from,
|
from,
|
||||||
contents,
|
contents,
|
||||||
|
|
|
@ -13,65 +13,9 @@ const { user: userCache } = require("@budibase/auth/cache")
|
||||||
const { invalidateSessions } = require("@budibase/auth/sessions")
|
const { invalidateSessions } = require("@budibase/auth/sessions")
|
||||||
const CouchDB = require("../../../db")
|
const CouchDB = require("../../../db")
|
||||||
const env = require("../../../environment")
|
const env = require("../../../environment")
|
||||||
const { getGlobalDB, getTenantId } = require("@budibase/auth/tenancy")
|
const { getGlobalDB, getTenantId, doesTenantExist, tryAddTenant, updateTenantId } = require("@budibase/auth/tenancy")
|
||||||
|
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
|
||||||
|
|
||||||
async function tryAddTenant(tenantId, userId, email) {
|
|
||||||
const db = new CouchDB(PLATFORM_INFO_DB)
|
|
||||||
const getDoc = async id => {
|
|
||||||
if (!id) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return await db.get(id)
|
|
||||||
} catch (err) {
|
|
||||||
return { _id: id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let [tenants, userIdDoc, emailDoc] = await Promise.all([
|
|
||||||
getDoc(TENANT_DOC),
|
|
||||||
getDoc(userId),
|
|
||||||
getDoc(email),
|
|
||||||
])
|
|
||||||
if (!Array.isArray(tenants.tenantIds)) {
|
|
||||||
tenants = {
|
|
||||||
_id: TENANT_DOC,
|
|
||||||
tenantIds: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let promises = []
|
|
||||||
if (userIdDoc) {
|
|
||||||
userIdDoc.tenantId = tenantId
|
|
||||||
promises.push(db.put(userIdDoc))
|
|
||||||
}
|
|
||||||
if (emailDoc) {
|
|
||||||
emailDoc.tenantId = tenantId
|
|
||||||
promises.push(db.put(emailDoc))
|
|
||||||
}
|
|
||||||
if (tenants.tenantIds.indexOf(tenantId) === -1) {
|
|
||||||
tenants.tenantIds.push(tenantId)
|
|
||||||
promises.push(db.put(tenants))
|
|
||||||
}
|
|
||||||
await Promise.all(promises)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doesTenantExist(tenantId) {
|
|
||||||
const db = new CouchDB(PLATFORM_INFO_DB)
|
|
||||||
let tenants
|
|
||||||
try {
|
|
||||||
tenants = await db.get(TENANT_DOC)
|
|
||||||
} catch (err) {
|
|
||||||
// if theres an error the doc doesn't exist, no tenants exist
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
tenants &&
|
|
||||||
Array.isArray(tenants.tenantIds) &&
|
|
||||||
tenants.tenantIds.indexOf(tenantId) !== -1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function allUsers() {
|
async function allUsers() {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
|
@ -87,6 +31,8 @@ async function saveUser(user, tenantId) {
|
||||||
if (!tenantId) {
|
if (!tenantId) {
|
||||||
throw "No tenancy specified."
|
throw "No tenancy specified."
|
||||||
}
|
}
|
||||||
|
// need to set the context for this request, as specified
|
||||||
|
updateTenantId(tenantId)
|
||||||
// specify the tenancy incase we're making a new admin user (public)
|
// specify the tenancy incase we're making a new admin user (public)
|
||||||
const db = getGlobalDB(tenantId)
|
const db = getGlobalDB(tenantId)
|
||||||
let { email, password, _id } = user
|
let { email, password, _id } = user
|
||||||
|
@ -162,7 +108,7 @@ exports.adminUser = async ctx => {
|
||||||
ctx.throw(403, "Organisation already exists.")
|
ctx.throw(403, "Organisation already exists.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB(tenantId)
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
getGlobalUserParams(null, {
|
getGlobalUserParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
|
|
|
@ -5,17 +5,6 @@ const { routes } = require("./routes")
|
||||||
const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } =
|
const { buildAuthMiddleware, auditLog, buildTenancyMiddleware } =
|
||||||
require("@budibase/auth").auth
|
require("@budibase/auth").auth
|
||||||
|
|
||||||
const NO_TENANCY_ENDPOINTS = [
|
|
||||||
{
|
|
||||||
route: "/api/system",
|
|
||||||
method: "ALL",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: "/api/global/users/self",
|
|
||||||
method: "GET",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const PUBLIC_ENDPOINTS = [
|
const PUBLIC_ENDPOINTS = [
|
||||||
{
|
{
|
||||||
// this covers all of the POST auth routes
|
// this covers all of the POST auth routes
|
||||||
|
@ -32,10 +21,6 @@ const PUBLIC_ENDPOINTS = [
|
||||||
route: "/api/global/configs/public",
|
route: "/api/global/configs/public",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
route: "api/global/flags",
|
|
||||||
method: "GET",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
route: "/api/global/configs/checklist",
|
route: "/api/global/configs/checklist",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -48,6 +33,22 @@ const PUBLIC_ENDPOINTS = [
|
||||||
route: "/api/global/users/invite/accept",
|
route: "/api/global/users/invite/accept",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
route: "api/system/flags",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const NO_TENANCY_ENDPOINTS = [
|
||||||
|
...PUBLIC_ENDPOINTS,
|
||||||
|
{
|
||||||
|
route: "/api/system",
|
||||||
|
method: "ALL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: "/api/global/users/self",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
@ -65,8 +66,8 @@ router
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.use("/health", ctx => (ctx.status = 200))
|
.use("/health", ctx => (ctx.status = 200))
|
||||||
.use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS))
|
|
||||||
.use(buildAuthMiddleware(PUBLIC_ENDPOINTS))
|
.use(buildAuthMiddleware(PUBLIC_ENDPOINTS))
|
||||||
|
.use(buildTenancyMiddleware(PUBLIC_ENDPOINTS, NO_TENANCY_ENDPOINTS))
|
||||||
// for now no public access is allowed to worker (bar health check)
|
// for now no public access is allowed to worker (bar health check)
|
||||||
.use((ctx, next) => {
|
.use((ctx, next) => {
|
||||||
if (!ctx.isAuthenticated && !ctx.publicEndpoint) {
|
if (!ctx.isAuthenticated && !ctx.publicEndpoint) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ const Router = require("@koa/router")
|
||||||
const authController = require("../../controllers/global/auth")
|
const authController = require("../../controllers/global/auth")
|
||||||
const joiValidator = require("../../../middleware/joi-validator")
|
const joiValidator = require("../../../middleware/joi-validator")
|
||||||
const Joi = require("joi")
|
const Joi = require("joi")
|
||||||
|
const { updateTenantId } = require("@budibase/auth/tenancy")
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
@ -28,34 +29,41 @@ function buildResetUpdateValidation() {
|
||||||
}).required().unknown(false))
|
}).required().unknown(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateTenant(ctx, next) {
|
||||||
|
updateTenantId(ctx.params.tenantId)
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
router
|
router
|
||||||
.post(
|
.post(
|
||||||
"/api/global/auth/:tenantId/login",
|
"/api/global/auth/:tenantId/login",
|
||||||
buildAuthValidation(),
|
buildAuthValidation(),
|
||||||
|
updateTenant,
|
||||||
authController.authenticate
|
authController.authenticate
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/global/auth/:tenantId/reset",
|
"/api/global/auth/:tenantId/reset",
|
||||||
buildResetValidation(),
|
buildResetValidation(),
|
||||||
|
updateTenant,
|
||||||
authController.reset
|
authController.reset
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/global/auth/:tenantId/reset/update",
|
"/api/global/auth/:tenantId/reset/update",
|
||||||
buildResetUpdateValidation(),
|
buildResetUpdateValidation(),
|
||||||
|
updateTenant,
|
||||||
authController.resetUpdate
|
authController.resetUpdate
|
||||||
)
|
)
|
||||||
.post("/api/global/auth/logout", authController.logout)
|
.post("/api/global/auth/logout", authController.logout)
|
||||||
.get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
|
.get("/api/global/auth/:tenantId/google", updateTenant, authController.googlePreAuth)
|
||||||
.get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
|
.get("/api/global/auth/:tenantId/google/callback", updateTenant, authController.googleAuth)
|
||||||
.get(
|
.get(
|
||||||
"/api/global/auth/:tenantId/oidc/configs/:configId",
|
"/api/global/auth/:tenantId/oidc/configs/:configId",
|
||||||
|
updateTenant,
|
||||||
authController.oidcPreAuth
|
authController.oidcPreAuth
|
||||||
)
|
)
|
||||||
.get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth)
|
.get("/api/global/auth/:tenantId/oidc/callback", updateTenant, authController.oidcAuth)
|
||||||
// deprecated - used by the default system before tenancy
|
// deprecated - used by the default system before tenancy
|
||||||
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||||
.get("/api/global/auth/google/callback", authController.googleAuth)
|
|
||||||
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
|
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
|
||||||
.get("/api/global/auth/oidc/callback", authController.oidcAuth)
|
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
Loading…
Reference in New Issue