2021-08-05 10:59:08 +02:00
|
|
|
const env = require("../environment")
|
|
|
|
const { Headers } = require("../../constants")
|
|
|
|
const cls = require("./FunctionContext")
|
2022-01-27 19:18:31 +01:00
|
|
|
const { getCouch } = require("../db")
|
|
|
|
const { getDeployedAppID, getDevelopmentAppID } = require("../db/utils")
|
|
|
|
const { isEqual } = require("lodash")
|
|
|
|
|
|
|
|
// some test cases call functions directly, need to
|
|
|
|
// store an app ID to pretend there is a context
|
|
|
|
let TEST_APP_ID = null
|
|
|
|
|
|
|
|
const ContextKeys = {
|
|
|
|
TENANT_ID: "tenantId",
|
|
|
|
APP_ID: "appId",
|
|
|
|
// whatever the request app DB was
|
|
|
|
CURRENT_DB: "currentDb",
|
|
|
|
// get the prod app DB from the request
|
|
|
|
PROD_DB: "prodDb",
|
|
|
|
// get the dev app DB from the request
|
|
|
|
DEV_DB: "devDb",
|
|
|
|
DB_OPTS: "dbOpts",
|
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
|
|
|
|
exports.DEFAULT_TENANT_ID = "default"
|
|
|
|
|
|
|
|
exports.isDefaultTenant = () => {
|
|
|
|
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.isMultiTenant = () => {
|
|
|
|
return env.MULTI_TENANCY
|
|
|
|
}
|
|
|
|
|
|
|
|
// used for automations, API endpoints should always be in context already
|
|
|
|
exports.doInTenant = (tenantId, task) => {
|
|
|
|
return cls.run(() => {
|
|
|
|
// set the tenant id
|
2022-01-27 19:18:31 +01:00
|
|
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
2021-08-05 10:59:08 +02:00
|
|
|
|
|
|
|
// invoke the task
|
2021-09-09 14:27:18 +02:00
|
|
|
return task()
|
2021-08-05 10:59:08 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.updateTenantId = tenantId => {
|
2022-01-27 19:18:31 +01:00
|
|
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.updateAppId = appId => {
|
|
|
|
try {
|
|
|
|
cls.setOnContext(ContextKeys.APP_ID, appId)
|
|
|
|
} catch (err) {
|
|
|
|
if (env.isTest()) {
|
|
|
|
TEST_APP_ID = appId
|
|
|
|
} else {
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
exports.setTenantId = (
|
|
|
|
ctx,
|
|
|
|
opts = { allowQs: false, allowNoTenant: false }
|
|
|
|
) => {
|
|
|
|
let tenantId
|
|
|
|
// exit early if not multi-tenant
|
|
|
|
if (!exports.isMultiTenant()) {
|
2022-01-27 19:18:31 +01:00
|
|
|
cls.setOnContext(ContextKeys.TENANT_ID, this.DEFAULT_TENANT_ID)
|
2021-08-05 10:59:08 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const allowQs = opts && opts.allowQs
|
|
|
|
const allowNoTenant = opts && opts.allowNoTenant
|
|
|
|
const header = ctx.request.headers[Headers.TENANT_ID]
|
|
|
|
const user = ctx.user || {}
|
|
|
|
if (allowQs) {
|
|
|
|
const query = ctx.request.query || {}
|
|
|
|
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
|
|
|
|
|
2021-09-28 17:40:03 +02:00
|
|
|
// Set the tenantId from the subdomain
|
|
|
|
if (!tenantId) {
|
|
|
|
tenantId = ctx.subdomains && ctx.subdomains[0]
|
|
|
|
}
|
|
|
|
|
2021-08-05 10:59:08 +02:00
|
|
|
if (!tenantId && !allowNoTenant) {
|
|
|
|
ctx.throw(403, "Tenant id not set")
|
|
|
|
}
|
|
|
|
// check tenant ID just incase no tenant was allowed
|
|
|
|
if (tenantId) {
|
2022-01-27 19:18:31 +01:00
|
|
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.isTenantIdSet = () => {
|
2022-01-27 19:18:31 +01:00
|
|
|
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID)
|
2021-08-05 10:59:08 +02:00
|
|
|
return !!tenantId
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.getTenantId = () => {
|
|
|
|
if (!exports.isMultiTenant()) {
|
|
|
|
return exports.DEFAULT_TENANT_ID
|
|
|
|
}
|
2022-01-27 19:18:31 +01:00
|
|
|
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID)
|
2021-08-05 10:59:08 +02:00
|
|
|
if (!tenantId) {
|
|
|
|
throw Error("Tenant id not found")
|
|
|
|
}
|
|
|
|
return tenantId
|
|
|
|
}
|
2022-01-27 19:18:31 +01:00
|
|
|
|
|
|
|
exports.getAppId = () => {
|
|
|
|
const foundId = cls.getFromContext(ContextKeys.APP_ID)
|
|
|
|
if (!foundId && env.isTest() && TEST_APP_ID) {
|
|
|
|
return TEST_APP_ID
|
|
|
|
} else {
|
|
|
|
return foundId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDB(key, opts) {
|
|
|
|
const dbOptsKey = `${key}${ContextKeys.DB_OPTS}`
|
|
|
|
let storedOpts = cls.getFromContext(dbOptsKey)
|
|
|
|
let db = cls.getFromContext(key)
|
|
|
|
if (db && isEqual(opts, storedOpts)) {
|
|
|
|
return db
|
|
|
|
}
|
|
|
|
const appId = exports.getAppId()
|
|
|
|
const CouchDB = getCouch()
|
|
|
|
let toUseAppId
|
|
|
|
switch (key) {
|
|
|
|
case ContextKeys.CURRENT_DB:
|
|
|
|
toUseAppId = appId
|
|
|
|
break
|
|
|
|
case ContextKeys.PROD_DB:
|
|
|
|
toUseAppId = getDeployedAppID(appId)
|
|
|
|
break
|
|
|
|
case ContextKeys.DEV_DB:
|
|
|
|
toUseAppId = getDevelopmentAppID(appId)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
db = new CouchDB(toUseAppId, opts)
|
|
|
|
try {
|
|
|
|
cls.setOnContext(key, db)
|
|
|
|
if (opts) {
|
|
|
|
cls.setOnContext(dbOptsKey, opts)
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
if (!env.isTest()) {
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return db
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opens the app database based on whatever the request
|
|
|
|
* contained, dev or prod.
|
|
|
|
*/
|
|
|
|
exports.getAppDB = opts => {
|
|
|
|
return getDB(ContextKeys.CURRENT_DB, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This specifically gets the prod app ID, if the request
|
|
|
|
* contained a development app ID, this will open the prod one.
|
|
|
|
*/
|
|
|
|
exports.getProdAppDB = opts => {
|
|
|
|
return getDB(ContextKeys.PROD_DB, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This specifically gets the dev app ID, if the request
|
|
|
|
* contained a prod app ID, this will open the dev one.
|
|
|
|
*/
|
|
|
|
exports.getDevAppDB = opts => {
|
|
|
|
return getDB(ContextKeys.DEV_DB, opts)
|
|
|
|
}
|