Merge pull request #8704 from Budibase/feature/replace-pouch
PouchDB replacement and context simplification
This commit is contained in:
commit
bb86afe743
|
@ -1,7 +1 @@
|
|||
module.exports = {
|
||||
...require("./src/db/utils"),
|
||||
...require("./src/db/constants"),
|
||||
...require("./src/db"),
|
||||
...require("./src/db/views"),
|
||||
...require("./src/db/pouch"),
|
||||
}
|
||||
module.exports = require("./src/db")
|
||||
|
|
|
@ -12,6 +12,7 @@ if (!process.env.CI) {
|
|||
// use sources when not in CI
|
||||
config.moduleNameMapper = {
|
||||
"@budibase/types": "<rootDir>/../types/src",
|
||||
"^axios.*$": "<rootDir>/node_modules/axios/lib/axios.js",
|
||||
}
|
||||
} else {
|
||||
console.log("Running tests with compiled dependency sources")
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"koa-passport": "4.1.4",
|
||||
"lodash": "4.17.21",
|
||||
"lodash.isarguments": "3.1.0",
|
||||
"nano": "^10.1.0",
|
||||
"node-fetch": "2.6.7",
|
||||
"passport-google-auth": "1.0.2",
|
||||
"passport-google-oauth": "2.0.0",
|
||||
|
|
|
@ -3,7 +3,7 @@ const LocalStrategy = require("passport-local").Strategy
|
|||
const JwtStrategy = require("passport-jwt").Strategy
|
||||
import { getGlobalDB } from "./tenancy"
|
||||
const refresh = require("passport-oauth2-refresh")
|
||||
import { Configs } from "./constants"
|
||||
import { Config } from "./constants"
|
||||
import { getScopedConfig } from "./db/utils"
|
||||
import {
|
||||
jwt,
|
||||
|
@ -76,7 +76,7 @@ async function refreshOIDCAccessToken(
|
|||
|
||||
return new Promise(resolve => {
|
||||
refresh.requestNewAccessToken(
|
||||
Configs.OIDC,
|
||||
Config.OIDC,
|
||||
refreshToken,
|
||||
(err: any, accessToken: string, refreshToken: any, params: any) => {
|
||||
resolve({ err, accessToken, refreshToken, params })
|
||||
|
@ -106,7 +106,7 @@ async function refreshGoogleAccessToken(
|
|||
|
||||
return new Promise(resolve => {
|
||||
refresh.requestNewAccessToken(
|
||||
Configs.GOOGLE,
|
||||
Config.GOOGLE,
|
||||
refreshToken,
|
||||
(err: any, accessToken: string, refreshToken: string, params: any) => {
|
||||
resolve({ err, accessToken, refreshToken, params })
|
||||
|
@ -129,7 +129,7 @@ async function refreshOAuthToken(
|
|||
|
||||
let chosenConfig = {}
|
||||
let refreshResponse
|
||||
if (configType === Configs.OIDC) {
|
||||
if (configType === Config.OIDC) {
|
||||
// configId - retrieved from cookie.
|
||||
chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
||||
if (!chosenConfig) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require("../../../tests")
|
||||
const { Writethrough } = require("../writethrough")
|
||||
const { dangerousGetDB } = require("../../db")
|
||||
const { getDB } = require("../../db")
|
||||
const tk = require("timekeeper")
|
||||
|
||||
const START_DATE = Date.now()
|
||||
|
@ -8,8 +8,8 @@ tk.freeze(START_DATE)
|
|||
|
||||
const DELAY = 5000
|
||||
|
||||
const db = dangerousGetDB("test")
|
||||
const db2 = dangerousGetDB("test2")
|
||||
const db = getDB("test")
|
||||
const db2 = getDB("test2")
|
||||
const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY)
|
||||
|
||||
describe("writethrough", () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import BaseCache from "./base"
|
||||
import { getWritethroughClient } from "../redis/init"
|
||||
import { logWarn } from "../logging"
|
||||
import PouchDB from "pouchdb"
|
||||
import { Database } from "@budibase/types"
|
||||
|
||||
const DEFAULT_WRITE_RATE_MS = 10000
|
||||
let CACHE: BaseCache | null = null
|
||||
|
@ -19,7 +19,7 @@ async function getCache() {
|
|||
return CACHE
|
||||
}
|
||||
|
||||
function makeCacheKey(db: PouchDB.Database, key: string) {
|
||||
function makeCacheKey(db: Database, key: string) {
|
||||
return db.name + key
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ function makeCacheItem(doc: any, lastWrite: number | null = null): CacheItem {
|
|||
}
|
||||
|
||||
export async function put(
|
||||
db: PouchDB.Database,
|
||||
db: Database,
|
||||
doc: any,
|
||||
writeRateMs: number = DEFAULT_WRITE_RATE_MS
|
||||
) {
|
||||
|
@ -64,7 +64,7 @@ export async function put(
|
|||
return { ok: true, id: output._id, rev: output._rev }
|
||||
}
|
||||
|
||||
export async function get(db: PouchDB.Database, id: string): Promise<any> {
|
||||
export async function get(db: Database, id: string): Promise<any> {
|
||||
const cache = await getCache()
|
||||
const cacheKey = makeCacheKey(db, id)
|
||||
let cacheItem: CacheItem = await cache.get(cacheKey)
|
||||
|
@ -77,7 +77,7 @@ export async function get(db: PouchDB.Database, id: string): Promise<any> {
|
|||
}
|
||||
|
||||
export async function remove(
|
||||
db: PouchDB.Database,
|
||||
db: Database,
|
||||
docOrId: any,
|
||||
rev?: any
|
||||
): Promise<void> {
|
||||
|
@ -95,13 +95,10 @@ export async function remove(
|
|||
}
|
||||
|
||||
export class Writethrough {
|
||||
db: PouchDB.Database
|
||||
db: Database
|
||||
writeRateMs: number
|
||||
|
||||
constructor(
|
||||
db: PouchDB.Database,
|
||||
writeRateMs: number = DEFAULT_WRITE_RATE_MS
|
||||
) {
|
||||
constructor(db: Database, writeRateMs: number = DEFAULT_WRITE_RATE_MS) {
|
||||
this.db = db
|
||||
this.writeRateMs = writeRateMs
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import API from "./api"
|
||||
import env from "../environment"
|
||||
import { Headers } from "../constants"
|
||||
import { Header } from "../constants"
|
||||
import { CloudAccount } from "@budibase/types"
|
||||
|
||||
const api = new API(env.ACCOUNT_PORTAL_URL)
|
||||
|
@ -14,7 +14,7 @@ export const getAccount = async (
|
|||
const response = await api.post(`/api/accounts/search`, {
|
||||
body: payload,
|
||||
headers: {
|
||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||
[Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -35,7 +35,7 @@ export const getAccountByTenantId = async (
|
|||
const response = await api.post(`/api/accounts/search`, {
|
||||
body: payload,
|
||||
headers: {
|
||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||
[Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -50,7 +50,7 @@ export const getAccountByTenantId = async (
|
|||
export const getStatus = async () => {
|
||||
const response = await api.get(`/api/status`, {
|
||||
headers: {
|
||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||
[Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||
},
|
||||
})
|
||||
const json = await response.json()
|
||||
|
|
|
@ -1,650 +0,0 @@
|
|||
const util = require("util")
|
||||
const assert = require("assert")
|
||||
const wrapEmitter = require("emitter-listener")
|
||||
const async_hooks = require("async_hooks")
|
||||
|
||||
const CONTEXTS_SYMBOL = "cls@contexts"
|
||||
const ERROR_SYMBOL = "error@context"
|
||||
|
||||
const DEBUG_CLS_HOOKED = process.env.DEBUG_CLS_HOOKED
|
||||
|
||||
let currentUid = -1
|
||||
|
||||
module.exports = {
|
||||
getNamespace: getNamespace,
|
||||
createNamespace: createNamespace,
|
||||
destroyNamespace: destroyNamespace,
|
||||
reset: reset,
|
||||
ERROR_SYMBOL: ERROR_SYMBOL,
|
||||
}
|
||||
|
||||
function Namespace(name) {
|
||||
this.name = name
|
||||
// changed in 2.7: no default context
|
||||
this.active = null
|
||||
this._set = []
|
||||
this.id = null
|
||||
this._contexts = new Map()
|
||||
this._indent = 0
|
||||
this._hook = null
|
||||
}
|
||||
|
||||
Namespace.prototype.set = function set(key, value) {
|
||||
if (!this.active) {
|
||||
throw new Error(
|
||||
"No context available. ns.run() or ns.bind() must be called first."
|
||||
)
|
||||
}
|
||||
|
||||
this.active[key] = value
|
||||
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
indentStr +
|
||||
"CONTEXT-SET KEY:" +
|
||||
key +
|
||||
"=" +
|
||||
value +
|
||||
" in ns:" +
|
||||
this.name +
|
||||
" currentUid:" +
|
||||
currentUid +
|
||||
" active:" +
|
||||
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
Namespace.prototype.get = function get(key) {
|
||||
if (!this.active) {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const asyncHooksCurrentId = async_hooks.currentId()
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
`${indentStr}CONTEXT-GETTING KEY NO ACTIVE NS: (${this.name}) ${key}=undefined currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length}`
|
||||
)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
indentStr +
|
||||
"CONTEXT-GETTING KEY:" +
|
||||
key +
|
||||
"=" +
|
||||
this.active[key] +
|
||||
" (" +
|
||||
this.name +
|
||||
") currentUid:" +
|
||||
currentUid +
|
||||
" active:" +
|
||||
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}CONTEXT-GETTING KEY: (${this.name}) ${key}=${
|
||||
this.active[key]
|
||||
} currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
||||
this._set.length
|
||||
} active:${util.inspect(this.active)}`
|
||||
)
|
||||
}
|
||||
return this.active[key]
|
||||
}
|
||||
|
||||
Namespace.prototype.createContext = function createContext() {
|
||||
// Prototype inherit existing context if created a new child context within existing context.
|
||||
let context = Object.create(this.active ? this.active : Object.prototype)
|
||||
context._ns_name = this.name
|
||||
context.id = currentUid
|
||||
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
`${indentStr}CONTEXT-CREATED Context: (${
|
||||
this.name
|
||||
}) currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
||||
this._set.length
|
||||
} context:${util.inspect(context, {
|
||||
showHidden: true,
|
||||
depth: 2,
|
||||
colors: true,
|
||||
})}`
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
Namespace.prototype.run = function run(fn) {
|
||||
let context = this.createContext()
|
||||
this.enter(context)
|
||||
|
||||
try {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
`${indentStr}CONTEXT-RUN BEGIN: (${
|
||||
this.name
|
||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||
this._set.length
|
||||
} context:${util.inspect(context)}`
|
||||
)
|
||||
}
|
||||
fn(context)
|
||||
return context
|
||||
} catch (exception) {
|
||||
if (exception) {
|
||||
exception[ERROR_SYMBOL] = context
|
||||
}
|
||||
throw exception
|
||||
} finally {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
`${indentStr}CONTEXT-RUN END: (${
|
||||
this.name
|
||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||
this._set.length
|
||||
} ${util.inspect(context)}`
|
||||
)
|
||||
}
|
||||
this.exit(context)
|
||||
}
|
||||
}
|
||||
|
||||
Namespace.prototype.runAndReturn = function runAndReturn(fn) {
|
||||
let value
|
||||
this.run(function (context) {
|
||||
value = fn(context)
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses global Promise and assumes Promise is cls friendly or wrapped already.
|
||||
* @param {function} fn
|
||||
* @returns {*}
|
||||
*/
|
||||
Namespace.prototype.runPromise = function runPromise(fn) {
|
||||
let context = this.createContext()
|
||||
this.enter(context)
|
||||
|
||||
let promise = fn(context)
|
||||
if (!promise || !promise.then || !promise.catch) {
|
||||
throw new Error("fn must return a promise.")
|
||||
}
|
||||
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
debug2(
|
||||
"CONTEXT-runPromise BEFORE: (" +
|
||||
this.name +
|
||||
") currentUid:" +
|
||||
currentUid +
|
||||
" len:" +
|
||||
this._set.length +
|
||||
" " +
|
||||
util.inspect(context)
|
||||
)
|
||||
}
|
||||
|
||||
return promise
|
||||
.then(result => {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
debug2(
|
||||
"CONTEXT-runPromise AFTER then: (" +
|
||||
this.name +
|
||||
") currentUid:" +
|
||||
currentUid +
|
||||
" len:" +
|
||||
this._set.length +
|
||||
" " +
|
||||
util.inspect(context)
|
||||
)
|
||||
}
|
||||
this.exit(context)
|
||||
return result
|
||||
})
|
||||
.catch(err => {
|
||||
err[ERROR_SYMBOL] = context
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
debug2(
|
||||
"CONTEXT-runPromise AFTER catch: (" +
|
||||
this.name +
|
||||
") currentUid:" +
|
||||
currentUid +
|
||||
" len:" +
|
||||
this._set.length +
|
||||
" " +
|
||||
util.inspect(context)
|
||||
)
|
||||
}
|
||||
this.exit(context)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
Namespace.prototype.bind = function bindFactory(fn, context) {
|
||||
if (!context) {
|
||||
if (!this.active) {
|
||||
context = this.createContext()
|
||||
} else {
|
||||
context = this.active
|
||||
}
|
||||
}
|
||||
|
||||
let self = this
|
||||
return function clsBind() {
|
||||
self.enter(context)
|
||||
try {
|
||||
return fn.apply(this, arguments)
|
||||
} catch (exception) {
|
||||
if (exception) {
|
||||
exception[ERROR_SYMBOL] = context
|
||||
}
|
||||
throw exception
|
||||
} finally {
|
||||
self.exit(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Namespace.prototype.enter = function enter(context) {
|
||||
assert.ok(context, "context must be provided for entering")
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
`${indentStr}CONTEXT-ENTER: (${
|
||||
this.name
|
||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||
this._set.length
|
||||
} ${util.inspect(context)}`
|
||||
)
|
||||
}
|
||||
|
||||
this._set.push(this.active)
|
||||
this.active = context
|
||||
}
|
||||
|
||||
Namespace.prototype.exit = function exit(context) {
|
||||
assert.ok(context, "context must be provided for exiting")
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||
debug2(
|
||||
`${indentStr}CONTEXT-EXIT: (${
|
||||
this.name
|
||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||
this._set.length
|
||||
} ${util.inspect(context)}`
|
||||
)
|
||||
}
|
||||
|
||||
// Fast path for most exits that are at the top of the stack
|
||||
if (this.active === context) {
|
||||
assert.ok(this._set.length, "can't remove top context")
|
||||
this.active = this._set.pop()
|
||||
return
|
||||
}
|
||||
|
||||
// Fast search in the stack using lastIndexOf
|
||||
let index = this._set.lastIndexOf(context)
|
||||
|
||||
if (index < 0) {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
debug2(
|
||||
"??ERROR?? context exiting but not entered - ignoring: " +
|
||||
util.inspect(context)
|
||||
)
|
||||
}
|
||||
assert.ok(
|
||||
index >= 0,
|
||||
"context not currently entered; can't exit. \n" +
|
||||
util.inspect(this) +
|
||||
"\n" +
|
||||
util.inspect(context)
|
||||
)
|
||||
} else {
|
||||
assert.ok(index, "can't remove top context")
|
||||
this._set.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
Namespace.prototype.bindEmitter = function bindEmitter(emitter) {
|
||||
assert.ok(
|
||||
emitter.on && emitter.addListener && emitter.emit,
|
||||
"can only bind real EEs"
|
||||
)
|
||||
|
||||
let namespace = this
|
||||
let thisSymbol = "context@" + this.name
|
||||
|
||||
// Capture the context active at the time the emitter is bound.
|
||||
function attach(listener) {
|
||||
if (!listener) {
|
||||
return
|
||||
}
|
||||
if (!listener[CONTEXTS_SYMBOL]) {
|
||||
listener[CONTEXTS_SYMBOL] = Object.create(null)
|
||||
}
|
||||
|
||||
listener[CONTEXTS_SYMBOL][thisSymbol] = {
|
||||
namespace: namespace,
|
||||
context: namespace.active,
|
||||
}
|
||||
}
|
||||
|
||||
// At emit time, bind the listener within the correct context.
|
||||
function bind(unwrapped) {
|
||||
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) {
|
||||
return unwrapped
|
||||
}
|
||||
|
||||
let wrapped = unwrapped
|
||||
let unwrappedContexts = unwrapped[CONTEXTS_SYMBOL]
|
||||
Object.keys(unwrappedContexts).forEach(function (name) {
|
||||
let thunk = unwrappedContexts[name]
|
||||
wrapped = thunk.namespace.bind(wrapped, thunk.context)
|
||||
})
|
||||
return wrapped
|
||||
}
|
||||
|
||||
wrapEmitter(emitter, attach, bind)
|
||||
}
|
||||
|
||||
/**
|
||||
* If an error comes out of a namespace, it will have a context attached to it.
|
||||
* This function knows how to find it.
|
||||
*
|
||||
* @param {Error} exception Possibly annotated error.
|
||||
*/
|
||||
Namespace.prototype.fromException = function fromException(exception) {
|
||||
return exception[ERROR_SYMBOL]
|
||||
}
|
||||
|
||||
function getNamespace(name) {
|
||||
return process.namespaces[name]
|
||||
}
|
||||
|
||||
function createNamespace(name) {
|
||||
assert.ok(name, "namespace must be given a name.")
|
||||
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
debug2(`NS-CREATING NAMESPACE (${name})`)
|
||||
}
|
||||
let namespace = new Namespace(name)
|
||||
namespace.id = currentUid
|
||||
|
||||
const hook = async_hooks.createHook({
|
||||
init(asyncId, type, triggerId, resource) {
|
||||
currentUid = async_hooks.executionAsyncId()
|
||||
|
||||
//CHAIN Parent's Context onto child if none exists. This is needed to pass net-events.spec
|
||||
// let initContext = namespace.active;
|
||||
// if(!initContext && triggerId) {
|
||||
// let parentContext = namespace._contexts.get(triggerId);
|
||||
// if (parentContext) {
|
||||
// namespace.active = parentContext;
|
||||
// namespace._contexts.set(currentUid, parentContext);
|
||||
// if (DEBUG_CLS_HOOKED) {
|
||||
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||
// debug2(`${indentStr}INIT [${type}] (${name}) WITH PARENT CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||
// }
|
||||
// } else if (DEBUG_CLS_HOOKED) {
|
||||
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||
// debug2(`${indentStr}INIT [${type}] (${name}) MISSING CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||
// }
|
||||
// }else {
|
||||
// namespace._contexts.set(currentUid, namespace.active);
|
||||
// if (DEBUG_CLS_HOOKED) {
|
||||
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||
// debug2(`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||
// }
|
||||
// }
|
||||
if (namespace.active) {
|
||||
namespace._contexts.set(asyncId, namespace.active)
|
||||
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} resource:${resource}`
|
||||
)
|
||||
}
|
||||
} else if (currentUid === 0) {
|
||||
// CurrentId will be 0 when triggered from C++. Promise events
|
||||
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const triggerIdContext = namespace._contexts.get(triggerId)
|
||||
if (triggerIdContext) {
|
||||
namespace._contexts.set(asyncId, triggerIdContext)
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}INIT USING CONTEXT FROM TRIGGERID [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} resource:${resource}`
|
||||
)
|
||||
}
|
||||
} else if (DEBUG_CLS_HOOKED) {
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}INIT MISSING CONTEXT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} resource:${resource}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_CLS_HOOKED && type === "PROMISE") {
|
||||
debug2(util.inspect(resource, { showHidden: true }))
|
||||
const parentId = resource.parentId
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}INIT RESOURCE-PROMISE [${type}] (${name}) parentId:${parentId} asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} resource:${resource}`
|
||||
)
|
||||
}
|
||||
},
|
||||
before(asyncId) {
|
||||
currentUid = async_hooks.executionAsyncId()
|
||||
let context
|
||||
|
||||
/*
|
||||
if(currentUid === 0){
|
||||
// CurrentId will be 0 when triggered from C++. Promise events
|
||||
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||
//const triggerId = async_hooks.triggerAsyncId();
|
||||
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
||||
}else{
|
||||
context = namespace._contexts.get(currentUid);
|
||||
}
|
||||
*/
|
||||
|
||||
//HACK to work with promises until they are fixed in node > 8.1.1
|
||||
context =
|
||||
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
||||
|
||||
if (context) {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}BEFORE (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} context:${util.inspect(context)}`
|
||||
)
|
||||
namespace._indent += 2
|
||||
}
|
||||
|
||||
namespace.enter(context)
|
||||
} else if (DEBUG_CLS_HOOKED) {
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}BEFORE MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} namespace._contexts:${util.inspect(namespace._contexts, {
|
||||
showHidden: true,
|
||||
depth: 2,
|
||||
colors: true,
|
||||
})}`
|
||||
)
|
||||
namespace._indent += 2
|
||||
}
|
||||
},
|
||||
after(asyncId) {
|
||||
currentUid = async_hooks.executionAsyncId()
|
||||
let context // = namespace._contexts.get(currentUid);
|
||||
/*
|
||||
if(currentUid === 0){
|
||||
// CurrentId will be 0 when triggered from C++. Promise events
|
||||
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||
//const triggerId = async_hooks.triggerAsyncId();
|
||||
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
||||
}else{
|
||||
context = namespace._contexts.get(currentUid);
|
||||
}
|
||||
*/
|
||||
//HACK to work with promises until they are fixed in node > 8.1.1
|
||||
context =
|
||||
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
||||
|
||||
if (context) {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
namespace._indent -= 2
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}AFTER (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} context:${util.inspect(context)}`
|
||||
)
|
||||
}
|
||||
|
||||
namespace.exit(context)
|
||||
} else if (DEBUG_CLS_HOOKED) {
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
namespace._indent -= 2
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}AFTER MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} context:${util.inspect(context)}`
|
||||
)
|
||||
}
|
||||
},
|
||||
destroy(asyncId) {
|
||||
currentUid = async_hooks.executionAsyncId()
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
const triggerId = async_hooks.triggerAsyncId()
|
||||
const indentStr = " ".repeat(
|
||||
namespace._indent < 0 ? 0 : namespace._indent
|
||||
)
|
||||
debug2(
|
||||
`${indentStr}DESTROY (${name}) currentUid:${currentUid} asyncId:${asyncId} triggerId:${triggerId} active:${util.inspect(
|
||||
namespace.active,
|
||||
{ showHidden: true, depth: 2, colors: true }
|
||||
)} context:${util.inspect(namespace._contexts.get(currentUid))}`
|
||||
)
|
||||
}
|
||||
|
||||
namespace._contexts.delete(asyncId)
|
||||
},
|
||||
})
|
||||
|
||||
hook.enable()
|
||||
namespace._hook = hook
|
||||
|
||||
process.namespaces[name] = namespace
|
||||
return namespace
|
||||
}
|
||||
|
||||
function destroyNamespace(name) {
|
||||
let namespace = getNamespace(name)
|
||||
|
||||
assert.ok(namespace, "can't delete nonexistent namespace! \"" + name + '"')
|
||||
assert.ok(
|
||||
namespace.id,
|
||||
"don't assign to process.namespaces directly! " + util.inspect(namespace)
|
||||
)
|
||||
|
||||
namespace._hook.disable()
|
||||
namespace._contexts = null
|
||||
process.namespaces[name] = null
|
||||
}
|
||||
|
||||
function reset() {
|
||||
// must unregister async listeners
|
||||
if (process.namespaces) {
|
||||
Object.keys(process.namespaces).forEach(function (name) {
|
||||
destroyNamespace(name)
|
||||
})
|
||||
}
|
||||
process.namespaces = Object.create(null)
|
||||
}
|
||||
|
||||
process.namespaces = process.namespaces || {}
|
||||
|
||||
//const fs = require('fs');
|
||||
function debug2(...args) {
|
||||
if (DEBUG_CLS_HOOKED) {
|
||||
//fs.writeSync(1, `${util.format(...args)}\n`);
|
||||
process._rawDebug(`${util.format(...args)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/*function getFunctionName(fn) {
|
||||
if (!fn) {
|
||||
return fn;
|
||||
}
|
||||
if (typeof fn === 'function') {
|
||||
if (fn.name) {
|
||||
return fn.name;
|
||||
}
|
||||
return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
|
||||
} else if (fn.constructor && fn.constructor.name) {
|
||||
return fn.constructor.name;
|
||||
}
|
||||
}*/
|
|
@ -1,44 +0,0 @@
|
|||
exports.UserStatus = {
|
||||
ACTIVE: "active",
|
||||
INACTIVE: "inactive",
|
||||
}
|
||||
|
||||
exports.Cookies = {
|
||||
CurrentApp: "budibase:currentapp",
|
||||
Auth: "budibase:auth",
|
||||
Init: "budibase:init",
|
||||
ACCOUNT_RETURN_URL: "budibase:account:returnurl",
|
||||
DatasourceAuth: "budibase:datasourceauth",
|
||||
OIDC_CONFIG: "budibase:oidc:config",
|
||||
}
|
||||
|
||||
exports.Headers = {
|
||||
API_KEY: "x-budibase-api-key",
|
||||
LICENSE_KEY: "x-budibase-license-key",
|
||||
API_VER: "x-budibase-api-version",
|
||||
APP_ID: "x-budibase-app-id",
|
||||
TYPE: "x-budibase-type",
|
||||
PREVIEW_ROLE: "x-budibase-role",
|
||||
TENANT_ID: "x-budibase-tenant-id",
|
||||
TOKEN: "x-budibase-token",
|
||||
CSRF_TOKEN: "x-csrf-token",
|
||||
}
|
||||
|
||||
exports.GlobalRoles = {
|
||||
OWNER: "owner",
|
||||
ADMIN: "admin",
|
||||
BUILDER: "builder",
|
||||
WORKSPACE_MANAGER: "workspace_manager",
|
||||
}
|
||||
|
||||
exports.Configs = {
|
||||
SETTINGS: "settings",
|
||||
ACCOUNT: "account",
|
||||
SMTP: "smtp",
|
||||
GOOGLE: "google",
|
||||
OIDC: "oidc",
|
||||
OIDC_LOGOS: "logos_oidc",
|
||||
}
|
||||
|
||||
exports.MAX_VALID_DATE = new Date(2147483647000)
|
||||
exports.DEFAULT_TENANT_ID = "default"
|
|
@ -0,0 +1,44 @@
|
|||
export enum UserStatus {
|
||||
ACTIVE = "active",
|
||||
INACTIVE = "inactive",
|
||||
}
|
||||
|
||||
export enum Cookie {
|
||||
CurrentApp = "budibase:currentapp",
|
||||
Auth = "budibase:auth",
|
||||
Init = "budibase:init",
|
||||
ACCOUNT_RETURN_URL = "budibase:account:returnurl",
|
||||
DatasourceAuth = "budibase:datasourceauth",
|
||||
OIDC_CONFIG = "budibase:oidc:config",
|
||||
}
|
||||
|
||||
export enum Header {
|
||||
API_KEY = "x-budibase-api-key",
|
||||
LICENSE_KEY = "x-budibase-license-key",
|
||||
API_VER = "x-budibase-api-version",
|
||||
APP_ID = "x-budibase-app-id",
|
||||
TYPE = "x-budibase-type",
|
||||
PREVIEW_ROLE = "x-budibase-role",
|
||||
TENANT_ID = "x-budibase-tenant-id",
|
||||
TOKEN = "x-budibase-token",
|
||||
CSRF_TOKEN = "x-csrf-token",
|
||||
}
|
||||
|
||||
export enum GlobalRole {
|
||||
OWNER = "owner",
|
||||
ADMIN = "admin",
|
||||
BUILDER = "builder",
|
||||
WORKSPACE_MANAGER = "workspace_manager",
|
||||
}
|
||||
|
||||
export enum Config {
|
||||
SETTINGS = "settings",
|
||||
ACCOUNT = "account",
|
||||
SMTP = "smtp",
|
||||
GOOGLE = "google",
|
||||
OIDC = "oidc",
|
||||
OIDC_LOGOS = "logos_oidc",
|
||||
}
|
||||
|
||||
export const MAX_VALID_DATE = new Date(2147483647000)
|
||||
export const DEFAULT_TENANT_ID = "default"
|
|
@ -0,0 +1,18 @@
|
|||
import { AsyncLocalStorage } from "async_hooks"
|
||||
import { ContextMap } from "./constants"
|
||||
|
||||
export default class Context {
|
||||
static storage = new AsyncLocalStorage<ContextMap>()
|
||||
|
||||
static run(context: ContextMap, func: any) {
|
||||
return Context.storage.run(context, () => func())
|
||||
}
|
||||
|
||||
static get(): ContextMap {
|
||||
return Context.storage.getStore() as ContextMap
|
||||
}
|
||||
|
||||
static set(context: ContextMap) {
|
||||
Context.storage.enterWith(context)
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
const cls = require("../clshooked")
|
||||
const { newid } = require("../hashing")
|
||||
|
||||
const REQUEST_ID_KEY = "requestId"
|
||||
const MAIN_CTX = cls.createNamespace("main")
|
||||
|
||||
function getContextStorage(namespace) {
|
||||
if (namespace && namespace.active) {
|
||||
let contextData = namespace.active
|
||||
delete contextData.id
|
||||
delete contextData._ns_name
|
||||
return contextData
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
class FunctionContext {
|
||||
static run(callback) {
|
||||
return MAIN_CTX.runAndReturn(async () => {
|
||||
const namespaceId = newid()
|
||||
MAIN_CTX.set(REQUEST_ID_KEY, namespaceId)
|
||||
const namespace = cls.createNamespace(namespaceId)
|
||||
let response = await namespace.runAndReturn(callback)
|
||||
cls.destroyNamespace(namespaceId)
|
||||
return response
|
||||
})
|
||||
}
|
||||
|
||||
static setOnContext(key, value) {
|
||||
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
||||
const namespace = cls.getNamespace(namespaceId)
|
||||
namespace.set(key, value)
|
||||
}
|
||||
|
||||
static getFromContext(key) {
|
||||
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
||||
const namespace = cls.getNamespace(namespaceId)
|
||||
const context = getContextStorage(namespace)
|
||||
if (context) {
|
||||
return context[key]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FunctionContext
|
|
@ -1,17 +1,7 @@
|
|||
export enum ContextKey {
|
||||
TENANT_ID = "tenantId",
|
||||
GLOBAL_DB = "globalDb",
|
||||
APP_ID = "appId",
|
||||
IDENTITY = "identity",
|
||||
// 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",
|
||||
// check if something else is using the context, don't close DB
|
||||
TENANCY_IN_USE = "tenancyInUse",
|
||||
APP_IN_USE = "appInUse",
|
||||
IDENTITY_IN_USE = "identityInUse",
|
||||
import { IdentityContext } from "@budibase/types"
|
||||
|
||||
export type ContextMap = {
|
||||
tenantId?: string
|
||||
appId?: string
|
||||
identity?: IdentityContext
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import { getGlobalUserParams, getAllApps } from "../db/utils"
|
||||
import { doWithDB } from "../db"
|
||||
import {
|
||||
getGlobalUserParams,
|
||||
getAllApps,
|
||||
doWithDB,
|
||||
StaticDatabases,
|
||||
} from "../db"
|
||||
import { doWithGlobalDB } from "../tenancy"
|
||||
import { StaticDatabases } from "../db/constants"
|
||||
import { App, Tenants, User } from "@budibase/types"
|
||||
import { App, Tenants, User, Database } from "@budibase/types"
|
||||
|
||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||
|
||||
const removeTenantFromInfoDB = async (tenantId: string) => {
|
||||
async function removeTenantFromInfoDB(tenantId: string) {
|
||||
try {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: Database) => {
|
||||
const tenants = (await infoDb.get(TENANT_DOC)) as Tenants
|
||||
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
|
||||
|
||||
|
@ -21,9 +24,9 @@ const removeTenantFromInfoDB = async (tenantId: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const removeUserFromInfoDB = async (dbUser: User) => {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||
const keys = [dbUser._id, dbUser.email]
|
||||
export async function removeUserFromInfoDB(dbUser: User) {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: Database) => {
|
||||
const keys = [dbUser._id!, dbUser.email]
|
||||
const userDocs = await infoDb.allDocs({
|
||||
keys,
|
||||
include_docs: true,
|
||||
|
@ -38,7 +41,7 @@ export const removeUserFromInfoDB = async (dbUser: User) => {
|
|||
})
|
||||
}
|
||||
|
||||
const removeUsersFromInfoDB = async (tenantId: string) => {
|
||||
async function removeUsersFromInfoDB(tenantId: string) {
|
||||
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||
try {
|
||||
const allUsers = await db.allDocs(
|
||||
|
@ -72,8 +75,8 @@ const removeUsersFromInfoDB = async (tenantId: string) => {
|
|||
})
|
||||
}
|
||||
|
||||
const removeGlobalDB = async (tenantId: string) => {
|
||||
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||
async function removeGlobalDB(tenantId: string) {
|
||||
return doWithGlobalDB(tenantId, async (db: Database) => {
|
||||
try {
|
||||
await db.destroy()
|
||||
} catch (err) {
|
||||
|
@ -83,11 +86,11 @@ const removeGlobalDB = async (tenantId: string) => {
|
|||
})
|
||||
}
|
||||
|
||||
const removeTenantApps = async (tenantId: string) => {
|
||||
async function removeTenantApps(tenantId: string) {
|
||||
try {
|
||||
const apps = (await getAllApps({ all: true })) as App[]
|
||||
const destroyPromises = apps.map(app =>
|
||||
doWithDB(app.appId, (db: any) => db.destroy())
|
||||
doWithDB(app.appId, (db: Database) => db.destroy())
|
||||
)
|
||||
await Promise.allSettled(destroyPromises)
|
||||
} catch (err) {
|
||||
|
@ -97,7 +100,7 @@ const removeTenantApps = async (tenantId: string) => {
|
|||
}
|
||||
|
||||
// can't live in tenancy package due to circular dependency on db/utils
|
||||
export const deleteTenant = async (tenantId: string) => {
|
||||
export async function deleteTenant(tenantId: string) {
|
||||
await removeTenantFromInfoDB(tenantId)
|
||||
await removeUsersFromInfoDB(tenantId)
|
||||
await removeGlobalDB(tenantId)
|
||||
|
|
|
@ -1,47 +1,32 @@
|
|||
import env from "../environment"
|
||||
import { SEPARATOR, DocumentType } from "../db/constants"
|
||||
import cls from "./FunctionContext"
|
||||
import { dangerousGetDB, closeDB } from "../db"
|
||||
import { baseGlobalDBName } from "../db/tenancy"
|
||||
import { IdentityContext } from "@budibase/types"
|
||||
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
||||
import { ContextKey } from "./constants"
|
||||
import PouchDB from "pouchdb"
|
||||
import {
|
||||
updateUsing,
|
||||
closeWithUsing,
|
||||
setAppTenantId,
|
||||
setIdentity,
|
||||
closeAppDBs,
|
||||
getContextDB,
|
||||
} from "./utils"
|
||||
|
||||
SEPARATOR,
|
||||
DocumentType,
|
||||
getDevelopmentAppID,
|
||||
getProdAppID,
|
||||
baseGlobalDBName,
|
||||
getDB,
|
||||
} from "../db"
|
||||
import Context from "./Context"
|
||||
import { IdentityContext, Database } from "@budibase/types"
|
||||
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
||||
import { ContextMap } from "./constants"
|
||||
export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID
|
||||
|
||||
// some test cases call functions directly, need to
|
||||
// store an app ID to pretend there is a context
|
||||
let TEST_APP_ID: string | null = null
|
||||
|
||||
export const closeTenancy = async () => {
|
||||
try {
|
||||
if (env.USE_COUCH) {
|
||||
const db = getGlobalDB()
|
||||
await closeDB(db)
|
||||
}
|
||||
} catch (err) {
|
||||
// no DB found - skip closing
|
||||
return
|
||||
}
|
||||
// clear from context now that database is closed/task is finished
|
||||
cls.setOnContext(ContextKey.TENANT_ID, null)
|
||||
cls.setOnContext(ContextKey.GLOBAL_DB, null)
|
||||
export function isMultiTenant() {
|
||||
return env.MULTI_TENANCY
|
||||
}
|
||||
|
||||
// export const isDefaultTenant = () => {
|
||||
// return getTenantId() === DEFAULT_TENANT_ID
|
||||
// }
|
||||
export function isTenantIdSet() {
|
||||
const context = Context.get()
|
||||
return !!context?.tenantId
|
||||
}
|
||||
|
||||
export const isMultiTenant = () => {
|
||||
export function isTenancyEnabled() {
|
||||
return env.MULTI_TENANCY
|
||||
}
|
||||
|
||||
|
@ -49,9 +34,9 @@ export const isMultiTenant = () => {
|
|||
* Given an app ID this will attempt to retrieve the tenant ID from it.
|
||||
* @return {null|string} The tenant ID found within the app ID.
|
||||
*/
|
||||
export const getTenantIDFromAppID = (appId: string) => {
|
||||
export function getTenantIDFromAppID(appId: string) {
|
||||
if (!appId) {
|
||||
return null
|
||||
return undefined
|
||||
}
|
||||
if (!isMultiTenant()) {
|
||||
return DEFAULT_TENANT_ID
|
||||
|
@ -59,7 +44,7 @@ export const getTenantIDFromAppID = (appId: string) => {
|
|||
const split = appId.split(SEPARATOR)
|
||||
const hasDev = split[1] === DocumentType.DEV
|
||||
if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) {
|
||||
return null
|
||||
return undefined
|
||||
}
|
||||
if (hasDev) {
|
||||
return split[2]
|
||||
|
@ -68,127 +53,125 @@ export const getTenantIDFromAppID = (appId: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const doInContext = async (appId: string, task: any) => {
|
||||
// gets the tenant ID from the app ID
|
||||
const tenantId = getTenantIDFromAppID(appId)
|
||||
return doInTenant(tenantId, async () => {
|
||||
return doInAppContext(appId, async () => {
|
||||
return task()
|
||||
})
|
||||
})
|
||||
function updateContext(updates: ContextMap) {
|
||||
let context: ContextMap
|
||||
try {
|
||||
context = Context.get()
|
||||
} catch (err) {
|
||||
// no context, start empty
|
||||
context = {}
|
||||
}
|
||||
context = {
|
||||
...context,
|
||||
...updates,
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export const doInTenant = (tenantId: string | null, task: any) => {
|
||||
async function newContext(updates: ContextMap, task: any) {
|
||||
// see if there already is a context setup
|
||||
let context: ContextMap = updateContext(updates)
|
||||
return Context.run(context, task)
|
||||
}
|
||||
|
||||
export async function doInContext(appId: string, task: any): Promise<any> {
|
||||
const tenantId = getTenantIDFromAppID(appId)
|
||||
return newContext(
|
||||
{
|
||||
tenantId,
|
||||
appId,
|
||||
},
|
||||
task
|
||||
)
|
||||
}
|
||||
|
||||
export async function doInTenant(
|
||||
tenantId: string | null,
|
||||
task: any
|
||||
): Promise<any> {
|
||||
// make sure default always selected in single tenancy
|
||||
if (!env.MULTI_TENANCY) {
|
||||
tenantId = tenantId || DEFAULT_TENANT_ID
|
||||
}
|
||||
// the internal function is so that we can re-use an existing
|
||||
// context - don't want to close DB on a parent context
|
||||
async function internal(opts = { existing: false }) {
|
||||
// set the tenant id + global db if this is a new context
|
||||
if (!opts.existing) {
|
||||
updateTenantId(tenantId)
|
||||
}
|
||||
|
||||
try {
|
||||
// invoke the task
|
||||
return await task()
|
||||
} finally {
|
||||
await closeWithUsing(ContextKey.TENANCY_IN_USE, () => {
|
||||
return closeTenancy()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const existing = cls.getFromContext(ContextKey.TENANT_ID) === tenantId
|
||||
return updateUsing(ContextKey.TENANCY_IN_USE, existing, internal)
|
||||
const updates = tenantId ? { tenantId } : {}
|
||||
return newContext(updates, task)
|
||||
}
|
||||
|
||||
export const doInAppContext = (appId: string, task: any) => {
|
||||
export async function doInAppContext(appId: string, task: any): Promise<any> {
|
||||
if (!appId) {
|
||||
throw new Error("appId is required")
|
||||
}
|
||||
|
||||
const identity = getIdentity()
|
||||
|
||||
// the internal function is so that we can re-use an existing
|
||||
// context - don't want to close DB on a parent context
|
||||
async function internal(opts = { existing: false }) {
|
||||
// set the app tenant id
|
||||
if (!opts.existing) {
|
||||
setAppTenantId(appId)
|
||||
}
|
||||
// set the app ID
|
||||
cls.setOnContext(ContextKey.APP_ID, appId)
|
||||
|
||||
// preserve the identity
|
||||
if (identity) {
|
||||
setIdentity(identity)
|
||||
}
|
||||
try {
|
||||
// invoke the task
|
||||
return await task()
|
||||
} finally {
|
||||
await closeWithUsing(ContextKey.APP_IN_USE, async () => {
|
||||
await closeAppDBs()
|
||||
await closeTenancy()
|
||||
})
|
||||
}
|
||||
const tenantId = getTenantIDFromAppID(appId)
|
||||
const updates: ContextMap = { appId }
|
||||
if (tenantId) {
|
||||
updates.tenantId = tenantId
|
||||
}
|
||||
const existing = cls.getFromContext(ContextKey.APP_ID) === appId
|
||||
return updateUsing(ContextKey.APP_IN_USE, existing, internal)
|
||||
return newContext(updates, task)
|
||||
}
|
||||
|
||||
export const doInIdentityContext = (identity: IdentityContext, task: any) => {
|
||||
export async function doInIdentityContext(
|
||||
identity: IdentityContext,
|
||||
task: any
|
||||
): Promise<any> {
|
||||
if (!identity) {
|
||||
throw new Error("identity is required")
|
||||
}
|
||||
|
||||
async function internal(opts = { existing: false }) {
|
||||
if (!opts.existing) {
|
||||
cls.setOnContext(ContextKey.IDENTITY, identity)
|
||||
// set the tenant so that doInTenant will preserve identity
|
||||
if (identity.tenantId) {
|
||||
updateTenantId(identity.tenantId)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// invoke the task
|
||||
return await task()
|
||||
} finally {
|
||||
await closeWithUsing(ContextKey.IDENTITY_IN_USE, async () => {
|
||||
setIdentity(null)
|
||||
await closeTenancy()
|
||||
})
|
||||
}
|
||||
const context: ContextMap = {
|
||||
identity,
|
||||
}
|
||||
|
||||
const existing = cls.getFromContext(ContextKey.IDENTITY)
|
||||
return updateUsing(ContextKey.IDENTITY_IN_USE, existing, internal)
|
||||
if (identity.tenantId) {
|
||||
context.tenantId = identity.tenantId
|
||||
}
|
||||
return newContext(context, task)
|
||||
}
|
||||
|
||||
export const getIdentity = (): IdentityContext | undefined => {
|
||||
export function getIdentity(): IdentityContext | undefined {
|
||||
try {
|
||||
return cls.getFromContext(ContextKey.IDENTITY)
|
||||
const context = Context.get()
|
||||
return context?.identity
|
||||
} catch (e) {
|
||||
// do nothing - identity is not in context
|
||||
}
|
||||
}
|
||||
|
||||
export const updateTenantId = (tenantId: string | null) => {
|
||||
cls.setOnContext(ContextKey.TENANT_ID, tenantId)
|
||||
if (env.USE_COUCH) {
|
||||
setGlobalDB(tenantId)
|
||||
export function getTenantId(): string {
|
||||
if (!isMultiTenant()) {
|
||||
return DEFAULT_TENANT_ID
|
||||
}
|
||||
const context = Context.get()
|
||||
const tenantId = context?.tenantId
|
||||
if (!tenantId) {
|
||||
throw new Error("Tenant id not found")
|
||||
}
|
||||
return tenantId
|
||||
}
|
||||
|
||||
export function getAppId(): string | undefined {
|
||||
const context = Context.get()
|
||||
const foundId = context?.appId
|
||||
if (!foundId && env.isTest() && TEST_APP_ID) {
|
||||
return TEST_APP_ID
|
||||
} else {
|
||||
return foundId
|
||||
}
|
||||
}
|
||||
|
||||
export const updateAppId = async (appId: string) => {
|
||||
export function updateTenantId(tenantId?: string) {
|
||||
let context: ContextMap = updateContext({
|
||||
tenantId,
|
||||
})
|
||||
Context.set(context)
|
||||
}
|
||||
|
||||
export function updateAppId(appId: string) {
|
||||
let context: ContextMap = updateContext({
|
||||
appId,
|
||||
})
|
||||
try {
|
||||
// have to close first, before removing the databases from context
|
||||
await closeAppDBs()
|
||||
cls.setOnContext(ContextKey.APP_ID, appId)
|
||||
Context.set(context)
|
||||
} catch (err) {
|
||||
if (env.isTest()) {
|
||||
TEST_APP_ID = appId
|
||||
|
@ -198,70 +181,43 @@ export const updateAppId = async (appId: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const setGlobalDB = (tenantId: string | null) => {
|
||||
const dbName = baseGlobalDBName(tenantId)
|
||||
const db = dangerousGetDB(dbName)
|
||||
cls.setOnContext(ContextKey.GLOBAL_DB, db)
|
||||
return db
|
||||
}
|
||||
|
||||
export const getGlobalDB = () => {
|
||||
const db = cls.getFromContext(ContextKey.GLOBAL_DB)
|
||||
if (!db) {
|
||||
export function getGlobalDB(): Database {
|
||||
const context = Context.get()
|
||||
if (!context || (env.MULTI_TENANCY && !context.tenantId)) {
|
||||
throw new Error("Global DB not found")
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
export const isTenantIdSet = () => {
|
||||
const tenantId = cls.getFromContext(ContextKey.TENANT_ID)
|
||||
return !!tenantId
|
||||
}
|
||||
|
||||
export const getTenantId = () => {
|
||||
if (!isMultiTenant()) {
|
||||
return DEFAULT_TENANT_ID
|
||||
}
|
||||
const tenantId = cls.getFromContext(ContextKey.TENANT_ID)
|
||||
if (!tenantId) {
|
||||
throw new Error("Tenant id not found")
|
||||
}
|
||||
return tenantId
|
||||
}
|
||||
|
||||
export const getAppId = () => {
|
||||
const foundId = cls.getFromContext(ContextKey.APP_ID)
|
||||
if (!foundId && env.isTest() && TEST_APP_ID) {
|
||||
return TEST_APP_ID
|
||||
} else {
|
||||
return foundId
|
||||
}
|
||||
}
|
||||
|
||||
export const isTenancyEnabled = () => {
|
||||
return env.MULTI_TENANCY
|
||||
return getDB(baseGlobalDBName(context?.tenantId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the app database based on whatever the request
|
||||
* Gets the app database based on whatever the request
|
||||
* contained, dev or prod.
|
||||
*/
|
||||
export const getAppDB = (opts?: any) => {
|
||||
return getContextDB(ContextKey.CURRENT_DB, opts)
|
||||
export function getAppDB(opts?: any): Database {
|
||||
const appId = getAppId()
|
||||
return getDB(appId, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* This specifically gets the prod app ID, if the request
|
||||
* contained a development app ID, this will open the prod one.
|
||||
* contained a development app ID, this will get the prod one.
|
||||
*/
|
||||
export const getProdAppDB = (opts?: any) => {
|
||||
return getContextDB(ContextKey.PROD_DB, opts)
|
||||
export function getProdAppDB(opts?: any): Database {
|
||||
const appId = getAppId()
|
||||
if (!appId) {
|
||||
throw new Error("Unable to retrieve prod DB - no app ID.")
|
||||
}
|
||||
return getDB(getProdAppID(appId), opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* This specifically gets the dev app ID, if the request
|
||||
* contained a prod app ID, this will open the dev one.
|
||||
* contained a prod app ID, this will get the dev one.
|
||||
*/
|
||||
export const getDevAppDB = (opts?: any) => {
|
||||
return getContextDB(ContextKey.DEV_DB, opts)
|
||||
export function getDevAppDB(opts?: any): Database {
|
||||
const appId = getAppId()
|
||||
if (!appId) {
|
||||
throw new Error("Unable to retrieve dev DB - no app ID.")
|
||||
}
|
||||
return getDB(getDevelopmentAppID(appId), opts)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,9 @@
|
|||
import "../../../tests"
|
||||
import * as context from ".."
|
||||
import { DEFAULT_TENANT_ID } from "../../constants"
|
||||
import env from "../../environment"
|
||||
|
||||
// must use require to spy index file exports due to known issue in jest
|
||||
const dbUtils = require("../../db")
|
||||
jest.spyOn(dbUtils, "closeDB")
|
||||
jest.spyOn(dbUtils, "dangerousGetDB")
|
||||
require("../../../tests")
|
||||
const context = require("../")
|
||||
const { DEFAULT_TENANT_ID } = require("../../constants")
|
||||
const env = require("../../environment")
|
||||
|
||||
describe("context", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("doInTenant", () => {
|
||||
describe("single-tenancy", () => {
|
||||
it("defaults to the default tenant", () => {
|
||||
|
@ -25,8 +16,6 @@ describe("context", () => {
|
|||
const db = context.getGlobalDB()
|
||||
expect(db.name).toBe("global-db")
|
||||
})
|
||||
expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1)
|
||||
expect(dbUtils.closeDB).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -40,7 +29,7 @@ describe("context", () => {
|
|||
let error
|
||||
try {
|
||||
context.getTenantId()
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
expect(error.message).toBe("Tenant id not found")
|
||||
|
@ -59,7 +48,7 @@ describe("context", () => {
|
|||
let error
|
||||
try {
|
||||
context.getGlobalDB()
|
||||
} catch (e: any) {
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
expect(error.message).toBe("Global DB not found")
|
||||
|
@ -85,8 +74,6 @@ describe("context", () => {
|
|||
const db = context.getGlobalDB()
|
||||
expect(db.name).toBe("test_global-db")
|
||||
})
|
||||
expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1)
|
||||
expect(dbUtils.closeDB).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("sets the tenant id when nested with same tenant id", async () => {
|
||||
|
@ -121,10 +108,6 @@ describe("context", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
// only 1 db is opened and closed
|
||||
expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1)
|
||||
expect(dbUtils.closeDB).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("sets different tenant id inside another context", () => {
|
|
@ -1,109 +0,0 @@
|
|||
import {
|
||||
DEFAULT_TENANT_ID,
|
||||
getAppId,
|
||||
getTenantIDFromAppID,
|
||||
updateTenantId,
|
||||
} from "./index"
|
||||
import cls from "./FunctionContext"
|
||||
import { IdentityContext } from "@budibase/types"
|
||||
import { ContextKey } from "./constants"
|
||||
import { dangerousGetDB, closeDB } from "../db"
|
||||
import { isEqual } from "lodash"
|
||||
import { getDevelopmentAppID, getProdAppID } from "../db/conversions"
|
||||
import env from "../environment"
|
||||
|
||||
export async function updateUsing(
|
||||
usingKey: string,
|
||||
existing: boolean,
|
||||
internal: (opts: { existing: boolean }) => Promise<any>
|
||||
) {
|
||||
const using = cls.getFromContext(usingKey)
|
||||
if (using && existing) {
|
||||
cls.setOnContext(usingKey, using + 1)
|
||||
return internal({ existing: true })
|
||||
} else {
|
||||
return cls.run(async () => {
|
||||
cls.setOnContext(usingKey, 1)
|
||||
return internal({ existing: false })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeWithUsing(
|
||||
usingKey: string,
|
||||
closeFn: () => Promise<any>
|
||||
) {
|
||||
const using = cls.getFromContext(usingKey)
|
||||
if (!using || using <= 1) {
|
||||
await closeFn()
|
||||
} else {
|
||||
cls.setOnContext(usingKey, using - 1)
|
||||
}
|
||||
}
|
||||
|
||||
export const setAppTenantId = (appId: string) => {
|
||||
const appTenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID
|
||||
updateTenantId(appTenantId)
|
||||
}
|
||||
|
||||
export const setIdentity = (identity: IdentityContext | null) => {
|
||||
cls.setOnContext(ContextKey.IDENTITY, identity)
|
||||
}
|
||||
|
||||
// this function makes sure the PouchDB objects are closed and
|
||||
// fully deleted when finished - this protects against memory leaks
|
||||
export async function closeAppDBs() {
|
||||
const dbKeys = [ContextKey.CURRENT_DB, ContextKey.PROD_DB, ContextKey.DEV_DB]
|
||||
for (let dbKey of dbKeys) {
|
||||
const db = cls.getFromContext(dbKey)
|
||||
if (!db) {
|
||||
continue
|
||||
}
|
||||
await closeDB(db)
|
||||
// clear the DB from context, incase someone tries to use it again
|
||||
cls.setOnContext(dbKey, null)
|
||||
}
|
||||
// clear the app ID now that the databases are closed
|
||||
if (cls.getFromContext(ContextKey.APP_ID)) {
|
||||
cls.setOnContext(ContextKey.APP_ID, null)
|
||||
}
|
||||
if (cls.getFromContext(ContextKey.DB_OPTS)) {
|
||||
cls.setOnContext(ContextKey.DB_OPTS, null)
|
||||
}
|
||||
}
|
||||
|
||||
export function getContextDB(key: string, opts: any) {
|
||||
const dbOptsKey = `${key}${ContextKey.DB_OPTS}`
|
||||
let storedOpts = cls.getFromContext(dbOptsKey)
|
||||
let db = cls.getFromContext(key)
|
||||
if (db && isEqual(opts, storedOpts)) {
|
||||
return db
|
||||
}
|
||||
|
||||
const appId = getAppId()
|
||||
let toUseAppId
|
||||
|
||||
switch (key) {
|
||||
case ContextKey.CURRENT_DB:
|
||||
toUseAppId = appId
|
||||
break
|
||||
case ContextKey.PROD_DB:
|
||||
toUseAppId = getProdAppID(appId)
|
||||
break
|
||||
case ContextKey.DEV_DB:
|
||||
toUseAppId = getDevelopmentAppID(appId)
|
||||
break
|
||||
}
|
||||
db = dangerousGetDB(toUseAppId, opts)
|
||||
try {
|
||||
cls.setOnContext(key, db)
|
||||
if (opts) {
|
||||
cls.setOnContext(dbOptsKey, opts)
|
||||
}
|
||||
} catch (err) {
|
||||
if (!env.isTest()) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
return db
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { dangerousGetDB, closeDB } from "."
|
||||
import { getPouchDB, closePouchDB } from "./couch/pouchDB"
|
||||
import { DocumentType } from "./constants"
|
||||
|
||||
class Replication {
|
||||
|
@ -12,12 +12,12 @@ class Replication {
|
|||
* @param {String} target - the DB you want to replicate to, or rollback from
|
||||
*/
|
||||
constructor({ source, target }: any) {
|
||||
this.source = dangerousGetDB(source)
|
||||
this.target = dangerousGetDB(target)
|
||||
this.source = getPouchDB(source)
|
||||
this.target = getPouchDB(target)
|
||||
}
|
||||
|
||||
close() {
|
||||
return Promise.all([closeDB(this.source), closeDB(this.target)])
|
||||
return Promise.all([closePouchDB(this.source), closePouchDB(this.target)])
|
||||
}
|
||||
|
||||
promisify(operation: any, opts = {}) {
|
||||
|
@ -68,7 +68,7 @@ class Replication {
|
|||
async rollback() {
|
||||
await this.target.destroy()
|
||||
// Recreate the DB again
|
||||
this.target = dangerousGetDB(this.target.name)
|
||||
this.target = getPouchDB(this.target.name)
|
||||
// take the opportunity to remove deleted tombstones
|
||||
await this.replicate()
|
||||
}
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
import { APP_DEV_PREFIX, APP_PREFIX } from "./constants"
|
||||
import { App } from "@budibase/types"
|
||||
const NO_APP_ERROR = "No app provided"
|
||||
const { APP_DEV_PREFIX, APP_PREFIX } = require("./constants")
|
||||
|
||||
exports.isDevAppID = appId => {
|
||||
export function isDevAppID(appId?: string) {
|
||||
if (!appId) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return appId.startsWith(APP_DEV_PREFIX)
|
||||
}
|
||||
|
||||
exports.isProdAppID = appId => {
|
||||
export function isProdAppID(appId?: string) {
|
||||
if (!appId) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return appId.startsWith(APP_PREFIX) && !exports.isDevAppID(appId)
|
||||
return appId.startsWith(APP_PREFIX) && !isDevAppID(appId)
|
||||
}
|
||||
|
||||
exports.isDevApp = app => {
|
||||
export function isDevApp(app: App) {
|
||||
if (!app) {
|
||||
throw NO_APP_ERROR
|
||||
}
|
||||
return exports.isDevAppID(app.appId)
|
||||
return isDevAppID(app.appId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a development app ID from a real app ID.
|
||||
* @returns {string} the dev app ID which can be used for dev database.
|
||||
*/
|
||||
exports.getDevelopmentAppID = appId => {
|
||||
export function getDevelopmentAppID(appId: string) {
|
||||
if (!appId || appId.startsWith(APP_DEV_PREFIX)) {
|
||||
return appId
|
||||
}
|
||||
|
@ -36,12 +37,12 @@ exports.getDevelopmentAppID = appId => {
|
|||
const rest = split.join(APP_PREFIX)
|
||||
return `${APP_DEV_PREFIX}${rest}`
|
||||
}
|
||||
exports.getDevAppID = exports.getDevelopmentAppID
|
||||
export const getDevAppID = getDevelopmentAppID
|
||||
|
||||
/**
|
||||
* Convert a development app ID to a deployed app ID.
|
||||
*/
|
||||
exports.getProdAppID = appId => {
|
||||
export function getProdAppID(appId: string) {
|
||||
if (!appId || !appId.startsWith(APP_DEV_PREFIX)) {
|
||||
return appId
|
||||
}
|
||||
|
@ -52,7 +53,7 @@ exports.getProdAppID = appId => {
|
|||
return `${APP_PREFIX}${rest}`
|
||||
}
|
||||
|
||||
exports.extractAppUUID = id => {
|
||||
export function extractAppUUID(id: string) {
|
||||
const split = id?.split("_") || []
|
||||
return split.length ? split[split.length - 1] : null
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
import Nano from "nano"
|
||||
import {
|
||||
AllDocsResponse,
|
||||
AnyDocument,
|
||||
Database,
|
||||
DatabaseOpts,
|
||||
DatabaseQueryOpts,
|
||||
DatabasePutOpts,
|
||||
} from "@budibase/types"
|
||||
import { getCouchInfo } from "./connections"
|
||||
import { directCouchCall } from "./utils"
|
||||
import { getPouchDB } from "./pouchDB"
|
||||
|
||||
export class DatabaseImpl implements Database {
|
||||
public readonly name: string
|
||||
private static nano: Nano.ServerScope
|
||||
private readonly pouchOpts: DatabaseOpts
|
||||
|
||||
constructor(dbName?: string, opts?: DatabaseOpts) {
|
||||
if (dbName == null) {
|
||||
throw new Error("Database name cannot be undefined.")
|
||||
}
|
||||
this.name = dbName
|
||||
this.pouchOpts = opts || {}
|
||||
if (!DatabaseImpl.nano) {
|
||||
DatabaseImpl.init()
|
||||
}
|
||||
}
|
||||
|
||||
static init() {
|
||||
const couchInfo = getCouchInfo()
|
||||
DatabaseImpl.nano = Nano({
|
||||
url: couchInfo.url,
|
||||
requestDefaults: {
|
||||
headers: {
|
||||
Authorization: couchInfo.cookie,
|
||||
},
|
||||
},
|
||||
parseUrl: false,
|
||||
})
|
||||
}
|
||||
|
||||
async exists() {
|
||||
let response = await directCouchCall(`/${this.name}`, "HEAD")
|
||||
return response.status === 200
|
||||
}
|
||||
|
||||
async checkSetup() {
|
||||
let shouldCreate = !this.pouchOpts?.skip_setup
|
||||
// check exists in a lightweight fashion
|
||||
let exists = await this.exists()
|
||||
if (!shouldCreate && !exists) {
|
||||
throw new Error("DB does not exist")
|
||||
}
|
||||
if (!exists) {
|
||||
await DatabaseImpl.nano.db.create(this.name)
|
||||
}
|
||||
return DatabaseImpl.nano.db.use(this.name)
|
||||
}
|
||||
|
||||
private async updateOutput(fnc: any) {
|
||||
try {
|
||||
return await fnc()
|
||||
} catch (err: any) {
|
||||
if (err.statusCode) {
|
||||
err.status = err.statusCode
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(id?: string): Promise<T | any> {
|
||||
const db = await this.checkSetup()
|
||||
if (!id) {
|
||||
throw new Error("Unable to get doc without a valid _id.")
|
||||
}
|
||||
return this.updateOutput(() => db.get(id))
|
||||
}
|
||||
|
||||
async remove(id?: string, rev?: string) {
|
||||
const db = await this.checkSetup()
|
||||
if (!id || !rev) {
|
||||
throw new Error("Unable to remove doc without a valid _id and _rev.")
|
||||
}
|
||||
return this.updateOutput(() => db.destroy(id, rev))
|
||||
}
|
||||
|
||||
async put(document: AnyDocument, opts?: DatabasePutOpts) {
|
||||
if (!document._id) {
|
||||
throw new Error("Cannot store document without _id field.")
|
||||
}
|
||||
const db = await this.checkSetup()
|
||||
if (!document.createdAt) {
|
||||
document.createdAt = new Date().toISOString()
|
||||
}
|
||||
document.updatedAt = new Date().toISOString()
|
||||
if (opts?.force && document._id) {
|
||||
try {
|
||||
const existing = await this.get(document._id)
|
||||
if (existing) {
|
||||
document._rev = existing._rev
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.status !== 404) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.updateOutput(() => db.insert(document))
|
||||
}
|
||||
|
||||
async bulkDocs(documents: AnyDocument[]) {
|
||||
const db = await this.checkSetup()
|
||||
return this.updateOutput(() => db.bulk({ docs: documents }))
|
||||
}
|
||||
|
||||
async allDocs<T>(params: DatabaseQueryOpts): Promise<AllDocsResponse<T>> {
|
||||
const db = await this.checkSetup()
|
||||
return this.updateOutput(() => db.list(params))
|
||||
}
|
||||
|
||||
async query<T>(
|
||||
viewName: string,
|
||||
params: DatabaseQueryOpts
|
||||
): Promise<AllDocsResponse<T>> {
|
||||
const db = await this.checkSetup()
|
||||
const [database, view] = viewName.split("/")
|
||||
return this.updateOutput(() => db.view(database, view, params))
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
try {
|
||||
await DatabaseImpl.nano.db.destroy(this.name)
|
||||
} catch (err: any) {
|
||||
// didn't exist, don't worry
|
||||
if (err.statusCode === 404) {
|
||||
return
|
||||
} else {
|
||||
throw { ...err, status: err.statusCode }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async compact() {
|
||||
const db = await this.checkSetup()
|
||||
return this.updateOutput(() => db.compact())
|
||||
}
|
||||
|
||||
private doWithPouchDB(func: string) {
|
||||
const dbName = this.name
|
||||
return async (args: any[]) => {
|
||||
const pouch = getPouchDB(dbName)
|
||||
// @ts-ignore
|
||||
return pouch[func](...args)
|
||||
}
|
||||
}
|
||||
|
||||
// All below functions are in-frequently called, just utilise PouchDB
|
||||
// for them as it implements them better than we can
|
||||
async dump(...args: any[]) {
|
||||
return this.doWithPouchDB("dump")(args)
|
||||
}
|
||||
|
||||
async load(...args: any[]) {
|
||||
return this.doWithPouchDB("load")(args)
|
||||
}
|
||||
|
||||
async createIndex(...args: any[]) {
|
||||
return this.doWithPouchDB("createIndex")(args)
|
||||
}
|
||||
|
||||
async deleteIndex(...args: any[]) {
|
||||
return this.doWithPouchDB("createIndex")(args)
|
||||
}
|
||||
|
||||
async getIndexes(...args: any[]) {
|
||||
return this.doWithPouchDB("createIndex")(args)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,37 @@
|
|||
import PouchDB from "pouchdb"
|
||||
import env from "../environment"
|
||||
import { PouchOptions } from "@budibase/types"
|
||||
import env from "../../environment"
|
||||
|
||||
export const getCouchInfo = () => {
|
||||
const urlInfo = getUrlInfo()
|
||||
let username
|
||||
let password
|
||||
if (env.COUCH_DB_USERNAME) {
|
||||
// set from env
|
||||
username = env.COUCH_DB_USERNAME
|
||||
} else if (urlInfo.auth.username) {
|
||||
// set from url
|
||||
username = urlInfo.auth.username
|
||||
} else if (!env.isTest()) {
|
||||
throw new Error("CouchDB username not set")
|
||||
}
|
||||
if (env.COUCH_DB_PASSWORD) {
|
||||
// set from env
|
||||
password = env.COUCH_DB_PASSWORD
|
||||
} else if (urlInfo.auth.password) {
|
||||
// set from url
|
||||
password = urlInfo.auth.password
|
||||
} else if (!env.isTest()) {
|
||||
throw new Error("CouchDB password not set")
|
||||
}
|
||||
const authCookie = Buffer.from(`${username}:${password}`).toString("base64")
|
||||
return {
|
||||
url: urlInfo.url!,
|
||||
auth: {
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
cookie: `Basic ${authCookie}`,
|
||||
}
|
||||
}
|
||||
|
||||
export const getUrlInfo = (url = env.COUCH_DB_URL) => {
|
||||
let cleanUrl, username, password, host
|
||||
|
@ -44,85 +75,3 @@ export const getUrlInfo = (url = env.COUCH_DB_URL) => {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const getCouchInfo = () => {
|
||||
const urlInfo = getUrlInfo()
|
||||
let username
|
||||
let password
|
||||
if (env.COUCH_DB_USERNAME) {
|
||||
// set from env
|
||||
username = env.COUCH_DB_USERNAME
|
||||
} else if (urlInfo.auth.username) {
|
||||
// set from url
|
||||
username = urlInfo.auth.username
|
||||
} else if (!env.isTest()) {
|
||||
throw new Error("CouchDB username not set")
|
||||
}
|
||||
if (env.COUCH_DB_PASSWORD) {
|
||||
// set from env
|
||||
password = env.COUCH_DB_PASSWORD
|
||||
} else if (urlInfo.auth.password) {
|
||||
// set from url
|
||||
password = urlInfo.auth.password
|
||||
} else if (!env.isTest()) {
|
||||
throw new Error("CouchDB password not set")
|
||||
}
|
||||
const authCookie = Buffer.from(`${username}:${password}`).toString("base64")
|
||||
return {
|
||||
url: urlInfo.url,
|
||||
auth: {
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
cookie: `Basic ${authCookie}`,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a constructor for PouchDB.
|
||||
* This should be rarely used outside of the main application config.
|
||||
* Exposed for exceptional cases such as in-memory views.
|
||||
*/
|
||||
export const getPouch = (opts: PouchOptions = {}) => {
|
||||
let { url, cookie } = getCouchInfo()
|
||||
let POUCH_DB_DEFAULTS = {
|
||||
prefix: url,
|
||||
fetch: (url: string, opts: any) => {
|
||||
// use a specific authorization cookie - be very explicit about how we authenticate
|
||||
opts.headers.set("Authorization", cookie)
|
||||
return PouchDB.fetch(url, opts)
|
||||
},
|
||||
}
|
||||
|
||||
if (opts.inMemory) {
|
||||
const inMemory = require("pouchdb-adapter-memory")
|
||||
PouchDB.plugin(inMemory)
|
||||
POUCH_DB_DEFAULTS = {
|
||||
prefix: undefined,
|
||||
// @ts-ignore
|
||||
adapter: "memory",
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.onDisk) {
|
||||
POUCH_DB_DEFAULTS = {
|
||||
prefix: undefined,
|
||||
// @ts-ignore
|
||||
adapter: "leveldb",
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.replication) {
|
||||
const replicationStream = require("pouchdb-replication-stream")
|
||||
PouchDB.plugin(replicationStream.plugin)
|
||||
// @ts-ignore
|
||||
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
|
||||
}
|
||||
|
||||
if (opts.find) {
|
||||
const find = require("pouchdb-find")
|
||||
PouchDB.plugin(find)
|
||||
}
|
||||
|
||||
return PouchDB.defaults(POUCH_DB_DEFAULTS)
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./connections"
|
||||
export * from "./DatabaseImpl"
|
||||
export * from "./utils"
|
||||
export { init, getPouch, getPouchDB, closePouchDB } from "./pouchDB"
|
|
@ -0,0 +1,97 @@
|
|||
import PouchDB from "pouchdb"
|
||||
import env from "../../environment"
|
||||
import { PouchOptions } from "@budibase/types"
|
||||
import { getCouchInfo } from "./connections"
|
||||
|
||||
let Pouch: any
|
||||
let initialised = false
|
||||
|
||||
/**
|
||||
* Return a constructor for PouchDB.
|
||||
* This should be rarely used outside of the main application config.
|
||||
* Exposed for exceptional cases such as in-memory views.
|
||||
*/
|
||||
export const getPouch = (opts: PouchOptions = {}) => {
|
||||
let { url, cookie } = getCouchInfo()
|
||||
let POUCH_DB_DEFAULTS = {
|
||||
prefix: url,
|
||||
fetch: (url: string, opts: any) => {
|
||||
// use a specific authorization cookie - be very explicit about how we authenticate
|
||||
opts.headers.set("Authorization", cookie)
|
||||
return PouchDB.fetch(url, opts)
|
||||
},
|
||||
}
|
||||
|
||||
if (opts.inMemory) {
|
||||
const inMemory = require("pouchdb-adapter-memory")
|
||||
PouchDB.plugin(inMemory)
|
||||
POUCH_DB_DEFAULTS = {
|
||||
// @ts-ignore
|
||||
adapter: "memory",
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.onDisk) {
|
||||
POUCH_DB_DEFAULTS = {
|
||||
// @ts-ignore
|
||||
adapter: "leveldb",
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.replication) {
|
||||
const replicationStream = require("pouchdb-replication-stream")
|
||||
PouchDB.plugin(replicationStream.plugin)
|
||||
// @ts-ignore
|
||||
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
|
||||
}
|
||||
|
||||
if (opts.find) {
|
||||
const find = require("pouchdb-find")
|
||||
PouchDB.plugin(find)
|
||||
}
|
||||
|
||||
return PouchDB.defaults(POUCH_DB_DEFAULTS)
|
||||
}
|
||||
|
||||
export function init(opts?: PouchOptions) {
|
||||
Pouch = getPouch(opts)
|
||||
initialised = true
|
||||
}
|
||||
|
||||
const checkInitialised = () => {
|
||||
if (!initialised) {
|
||||
throw new Error("init has not been called")
|
||||
}
|
||||
}
|
||||
|
||||
export function getPouchDB(dbName: string, opts?: any): PouchDB.Database {
|
||||
checkInitialised()
|
||||
const db = new Pouch(dbName, opts)
|
||||
const dbPut = db.put
|
||||
db.put = async (doc: any, options = {}) => {
|
||||
if (!doc.createdAt) {
|
||||
doc.createdAt = new Date().toISOString()
|
||||
}
|
||||
doc.updatedAt = new Date().toISOString()
|
||||
return dbPut(doc, options)
|
||||
}
|
||||
db.exists = async () => {
|
||||
const info = await db.info()
|
||||
return !info.error
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// use this function if you have called getPouchDB - close
|
||||
// the databases you've opened once finished
|
||||
export async function closePouchDB(db: PouchDB.Database) {
|
||||
if (!db || env.isTest()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// specifically await so that if there is an error, it can be ignored
|
||||
return await db.close()
|
||||
} catch (err) {
|
||||
// ignore error, already closed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { getCouchInfo } from "./connections"
|
||||
import fetch from "node-fetch"
|
||||
import { checkSlashesInUrl } from "../../helpers"
|
||||
|
||||
export async function directCouchCall(
|
||||
path: string,
|
||||
method: string = "GET",
|
||||
body?: any
|
||||
) {
|
||||
let { url, cookie } = getCouchInfo()
|
||||
const couchUrl = `${url}/${path}`
|
||||
const params: any = {
|
||||
method: method,
|
||||
headers: {
|
||||
Authorization: cookie,
|
||||
},
|
||||
}
|
||||
if (body && method !== "GET") {
|
||||
params.body = JSON.stringify(body)
|
||||
params.headers["Content-Type"] = "application/json"
|
||||
}
|
||||
return await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params)
|
||||
}
|
||||
|
||||
export async function directCouchQuery(
|
||||
path: string,
|
||||
method: string = "GET",
|
||||
body?: any
|
||||
) {
|
||||
const response = await directCouchCall(path, method, body)
|
||||
if (response.status < 300) {
|
||||
return await response.json()
|
||||
} else {
|
||||
throw "Cannot connect to CouchDB instance"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import env from "../environment"
|
||||
import { directCouchQuery, getPouchDB } from "./couch"
|
||||
import { CouchFindOptions, Database } from "@budibase/types"
|
||||
import { DatabaseImpl } from "../db"
|
||||
|
||||
const dbList = new Set()
|
||||
|
||||
export function getDB(dbName?: string, opts?: any): Database {
|
||||
// TODO: once using the test image, need to remove this
|
||||
if (env.isTest()) {
|
||||
dbList.add(dbName)
|
||||
// @ts-ignore
|
||||
return getPouchDB(dbName, opts)
|
||||
}
|
||||
return new DatabaseImpl(dbName, opts)
|
||||
}
|
||||
|
||||
// we have to use a callback for this so that we can close
|
||||
// the DB when we're done, without this manual requests would
|
||||
// need to close the database when done with it to avoid memory leaks
|
||||
export async function doWithDB(dbName: string, cb: any, opts = {}) {
|
||||
const db = getDB(dbName, opts)
|
||||
// need this to be async so that we can correctly close DB after all
|
||||
// async operations have been completed
|
||||
return await cb(db)
|
||||
}
|
||||
|
||||
export function allDbs() {
|
||||
if (!env.isTest()) {
|
||||
throw new Error("Cannot be used outside test environment.")
|
||||
}
|
||||
return [...dbList]
|
||||
}
|
||||
|
||||
export async function directCouchAllDbs(queryString?: string) {
|
||||
let couchPath = "/_all_dbs"
|
||||
if (queryString) {
|
||||
couchPath += `?${queryString}`
|
||||
}
|
||||
return await directCouchQuery(couchPath)
|
||||
}
|
||||
|
||||
export async function directCouchFind(dbName: string, opts: CouchFindOptions) {
|
||||
const json = await directCouchQuery(`${dbName}/_find`, "POST", opts)
|
||||
return { rows: json.docs, bookmark: json.bookmark }
|
||||
}
|
|
@ -1,133 +1,7 @@
|
|||
import * as pouch from "./pouch"
|
||||
import env from "../environment"
|
||||
import { checkSlashesInUrl } from "../helpers"
|
||||
import fetch from "node-fetch"
|
||||
import { PouchOptions, CouchFindOptions } from "@budibase/types"
|
||||
import PouchDB from "pouchdb"
|
||||
|
||||
const openDbs: string[] = []
|
||||
let Pouch: any
|
||||
let initialised = false
|
||||
const dbList = new Set()
|
||||
|
||||
if (env.MEMORY_LEAK_CHECK) {
|
||||
setInterval(() => {
|
||||
console.log("--- OPEN DBS ---")
|
||||
console.log(openDbs)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
const put =
|
||||
(dbPut: any) =>
|
||||
async (doc: any, options = {}) => {
|
||||
if (!doc.createdAt) {
|
||||
doc.createdAt = new Date().toISOString()
|
||||
}
|
||||
doc.updatedAt = new Date().toISOString()
|
||||
return dbPut(doc, options)
|
||||
}
|
||||
|
||||
const checkInitialised = () => {
|
||||
if (!initialised) {
|
||||
throw new Error("init has not been called")
|
||||
}
|
||||
}
|
||||
|
||||
export function init(opts?: PouchOptions) {
|
||||
Pouch = pouch.getPouch(opts)
|
||||
initialised = true
|
||||
}
|
||||
|
||||
// NOTE: THIS IS A DANGEROUS FUNCTION - USE WITH CAUTION
|
||||
// this function is prone to leaks, should only be used
|
||||
// in situations that using the function doWithDB does not work
|
||||
export function dangerousGetDB(dbName: string, opts?: any): PouchDB.Database {
|
||||
checkInitialised()
|
||||
if (env.isTest()) {
|
||||
dbList.add(dbName)
|
||||
}
|
||||
const db = new Pouch(dbName, opts)
|
||||
if (env.MEMORY_LEAK_CHECK) {
|
||||
openDbs.push(db.name)
|
||||
}
|
||||
const dbPut = db.put
|
||||
db.put = put(dbPut)
|
||||
return db
|
||||
}
|
||||
|
||||
// use this function if you have called dangerousGetDB - close
|
||||
// the databases you've opened once finished
|
||||
export async function closeDB(db: PouchDB.Database) {
|
||||
if (!db || env.isTest()) {
|
||||
return
|
||||
}
|
||||
if (env.MEMORY_LEAK_CHECK) {
|
||||
openDbs.splice(openDbs.indexOf(db.name), 1)
|
||||
}
|
||||
try {
|
||||
// specifically await so that if there is an error, it can be ignored
|
||||
return await db.close()
|
||||
} catch (err) {
|
||||
// ignore error, already closed
|
||||
}
|
||||
}
|
||||
|
||||
// we have to use a callback for this so that we can close
|
||||
// the DB when we're done, without this manual requests would
|
||||
// need to close the database when done with it to avoid memory leaks
|
||||
export async function doWithDB(dbName: string, cb: any, opts = {}) {
|
||||
const db = dangerousGetDB(dbName, opts)
|
||||
// need this to be async so that we can correctly close DB after all
|
||||
// async operations have been completed
|
||||
try {
|
||||
return await cb(db)
|
||||
} finally {
|
||||
await closeDB(db)
|
||||
}
|
||||
}
|
||||
|
||||
export function allDbs() {
|
||||
if (!env.isTest()) {
|
||||
throw new Error("Cannot be used outside test environment.")
|
||||
}
|
||||
checkInitialised()
|
||||
return [...dbList]
|
||||
}
|
||||
|
||||
export async function directCouchQuery(
|
||||
path: string,
|
||||
method: string = "GET",
|
||||
body?: any
|
||||
) {
|
||||
let { url, cookie } = pouch.getCouchInfo()
|
||||
const couchUrl = `${url}/${path}`
|
||||
const params: any = {
|
||||
method: method,
|
||||
headers: {
|
||||
Authorization: cookie,
|
||||
},
|
||||
}
|
||||
if (body && method !== "GET") {
|
||||
params.body = JSON.stringify(body)
|
||||
params.headers["Content-Type"] = "application/json"
|
||||
}
|
||||
const response = await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params)
|
||||
if (response.status < 300) {
|
||||
return await response.json()
|
||||
} else {
|
||||
throw "Cannot connect to CouchDB instance"
|
||||
}
|
||||
}
|
||||
|
||||
export async function directCouchAllDbs(queryString?: string) {
|
||||
let couchPath = "/_all_dbs"
|
||||
if (queryString) {
|
||||
couchPath += `?${queryString}`
|
||||
}
|
||||
return await directCouchQuery(couchPath)
|
||||
}
|
||||
|
||||
export async function directCouchFind(dbName: string, opts: CouchFindOptions) {
|
||||
const json = await directCouchQuery(`${dbName}/_find`, "POST", opts)
|
||||
return { rows: json.docs, bookmark: json.bookmark }
|
||||
}
|
||||
export * from "./couch"
|
||||
export * from "./db"
|
||||
export * from "./utils"
|
||||
export * from "./views"
|
||||
export * from "./constants"
|
||||
export * from "./conversions"
|
||||
export * from "./tenancy"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
require("../../../tests")
|
||||
const { dangerousGetDB } = require("../")
|
||||
const { getDB } = require("../")
|
||||
|
||||
describe("db", () => {
|
||||
|
||||
describe("getDB", () => {
|
||||
it("returns a db", async () => {
|
||||
const db = dangerousGetDB("test")
|
||||
const db = getDB("test")
|
||||
expect(db).toBeDefined()
|
||||
expect(db._adapter).toBe("memory")
|
||||
expect(db.prefix).toBe("_pouch_")
|
||||
|
@ -13,7 +13,7 @@ describe("db", () => {
|
|||
})
|
||||
|
||||
it("uses the custom put function", async () => {
|
||||
const db = dangerousGetDB("test")
|
||||
const db = getDB("test")
|
||||
let doc = { _id: "test" }
|
||||
await db.put(doc)
|
||||
doc = await db.get(doc._id)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
require("../../../tests")
|
||||
const getUrlInfo = require("../pouch").getUrlInfo
|
||||
const getUrlInfo = require("../couch").getUrlInfo
|
||||
|
||||
describe("pouch", () => {
|
||||
describe("Couch DB URL parsing", () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require("../../../tests");
|
||||
require("../../../tests")
|
||||
const {
|
||||
generateAppID,
|
||||
getDevelopmentAppID,
|
||||
|
@ -8,8 +8,8 @@ const {
|
|||
getPlatformUrl,
|
||||
getScopedConfig
|
||||
} = require("../utils")
|
||||
const tenancy = require("../../tenancy");
|
||||
const { Configs, DEFAULT_TENANT_ID } = require("../../constants");
|
||||
const tenancy = require("../../tenancy")
|
||||
const { Config, DEFAULT_TENANT_ID } = require("../../constants")
|
||||
const env = require("../../environment")
|
||||
|
||||
describe("utils", () => {
|
||||
|
@ -77,7 +77,7 @@ const setDbPlatformUrl = async () => {
|
|||
const db = tenancy.getGlobalDB()
|
||||
db.put({
|
||||
_id: "config_settings",
|
||||
type: Configs.SETTINGS,
|
||||
type: Config.SETTINGS,
|
||||
config: {
|
||||
platformUrl: DB_URL
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ describe("getScopedConfig", () => {
|
|||
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||
await setDbPlatformUrl()
|
||||
const db = tenancy.getGlobalDB()
|
||||
const config = await getScopedConfig(db, { type: Configs.SETTINGS })
|
||||
const config = await getScopedConfig(db, { type: Config.SETTINGS })
|
||||
expect(config.platformUrl).toBe(DB_URL)
|
||||
})
|
||||
})
|
||||
|
@ -186,7 +186,7 @@ describe("getScopedConfig", () => {
|
|||
it("returns the platform url without an existing config", async () => {
|
||||
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||
const db = tenancy.getGlobalDB()
|
||||
const config = await getScopedConfig(db, { type: Configs.SETTINGS })
|
||||
const config = await getScopedConfig(db, { type: Config.SETTINGS })
|
||||
expect(config.platformUrl).toBe(DEFAULT_URL)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { newid } from "../hashing"
|
||||
import { DEFAULT_TENANT_ID, Configs } from "../constants"
|
||||
import { DEFAULT_TENANT_ID, Config } from "../constants"
|
||||
import env from "../environment"
|
||||
import {
|
||||
SEPARATOR,
|
||||
|
@ -10,12 +10,12 @@ import {
|
|||
} from "./constants"
|
||||
import { getTenantId, getGlobalDB } from "../context"
|
||||
import { getGlobalDBName } from "./tenancy"
|
||||
import { doWithDB, allDbs, directCouchAllDbs } from "./index"
|
||||
import { doWithDB, allDbs, directCouchAllDbs } from "./db"
|
||||
import { getAppMetadata } from "../cache/appMetadata"
|
||||
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
||||
import { APP_PREFIX } from "./constants"
|
||||
import * as events from "../events"
|
||||
import { App } from "@budibase/types"
|
||||
import { App, Database } from "@budibase/types"
|
||||
|
||||
export * from "./constants"
|
||||
export * from "./conversions"
|
||||
|
@ -26,7 +26,7 @@ export * from "./tenancy"
|
|||
* Generates a new app ID.
|
||||
* @returns {string} The new app ID which the app doc can be stored under.
|
||||
*/
|
||||
export const generateAppID = (tenantId = null) => {
|
||||
export const generateAppID = (tenantId?: string | null) => {
|
||||
let id = APP_PREFIX
|
||||
if (tenantId) {
|
||||
id += `${tenantId}${SEPARATOR}`
|
||||
|
@ -251,11 +251,11 @@ export function generateRoleID(id: any) {
|
|||
/**
|
||||
* Gets parameters for retrieving a role, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
export function getRoleParams(roleId = null, otherProps = {}) {
|
||||
export function getRoleParams(roleId?: string | null, otherProps = {}) {
|
||||
return getDocParams(DocumentType.ROLE, roleId, otherProps)
|
||||
}
|
||||
|
||||
export function getStartEndKeyURL(baseKey: any, tenantId = null) {
|
||||
export function getStartEndKeyURL(baseKey: any, tenantId?: string) {
|
||||
const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : ""
|
||||
return `startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"`
|
||||
}
|
||||
|
@ -392,20 +392,10 @@ export async function getDevAppIDs() {
|
|||
}
|
||||
|
||||
export async function dbExists(dbName: any) {
|
||||
let exists = false
|
||||
return doWithDB(
|
||||
dbName,
|
||||
async (db: any) => {
|
||||
try {
|
||||
// check if database exists
|
||||
const info = await db.info()
|
||||
if (info && !info.error) {
|
||||
exists = true
|
||||
}
|
||||
} catch (err) {
|
||||
exists = false
|
||||
}
|
||||
return exists
|
||||
async (db: Database) => {
|
||||
return await db.exists()
|
||||
},
|
||||
{ skip_setup: true }
|
||||
)
|
||||
|
@ -504,7 +494,7 @@ export const getScopedFullConfig = async function (
|
|||
)[0]
|
||||
|
||||
// custom logic for settings doc
|
||||
if (type === Configs.SETTINGS) {
|
||||
if (type === Config.SETTINGS) {
|
||||
if (scopedConfig && scopedConfig.doc) {
|
||||
// overrides affected by environment variables
|
||||
scopedConfig.doc.config.platformUrl = await getPlatformUrl({
|
||||
|
@ -543,7 +533,7 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => {
|
|||
// get the doc directly instead of with getScopedConfig to prevent loop
|
||||
let settings
|
||||
try {
|
||||
settings = await db.get(generateConfigID({ type: Configs.SETTINGS }))
|
||||
settings = await db.get(generateConfigID({ type: Config.SETTINGS }))
|
||||
} catch (e: any) {
|
||||
if (e.status !== 404) {
|
||||
throw e
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { DocumentType, ViewName, DeprecatedViews, SEPARATOR } from "./utils"
|
||||
import { getGlobalDB } from "../context"
|
||||
import PouchDB from "pouchdb"
|
||||
import { StaticDatabases } from "./constants"
|
||||
import { doWithDB } from "./"
|
||||
import { Database, DatabaseQueryOpts } from "@budibase/types"
|
||||
|
||||
const DESIGN_DB = "_design/database"
|
||||
|
||||
|
@ -19,7 +19,7 @@ interface DesignDocument {
|
|||
views: any
|
||||
}
|
||||
|
||||
async function removeDeprecated(db: PouchDB.Database, viewName: ViewName) {
|
||||
async function removeDeprecated(db: Database, viewName: ViewName) {
|
||||
// @ts-ignore
|
||||
if (!DeprecatedViews[viewName]) {
|
||||
return
|
||||
|
@ -70,16 +70,13 @@ export const createAccountEmailView = async () => {
|
|||
emit(doc.email.toLowerCase(), doc._id)
|
||||
}
|
||||
}`
|
||||
await doWithDB(
|
||||
StaticDatabases.PLATFORM_INFO.name,
|
||||
async (db: PouchDB.Database) => {
|
||||
await createView(db, viewJs, ViewName.ACCOUNT_BY_EMAIL)
|
||||
}
|
||||
)
|
||||
await doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => {
|
||||
await createView(db, viewJs, ViewName.ACCOUNT_BY_EMAIL)
|
||||
})
|
||||
}
|
||||
|
||||
export const createUserAppView = async () => {
|
||||
const db = getGlobalDB() as PouchDB.Database
|
||||
const db = getGlobalDB()
|
||||
const viewJs = `function(doc) {
|
||||
if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) {
|
||||
for (let prodAppId of Object.keys(doc.roles)) {
|
||||
|
@ -117,12 +114,9 @@ export const createPlatformUserView = async () => {
|
|||
emit(doc._id.toLowerCase(), doc._id)
|
||||
}
|
||||
}`
|
||||
await doWithDB(
|
||||
StaticDatabases.PLATFORM_INFO.name,
|
||||
async (db: PouchDB.Database) => {
|
||||
await createView(db, viewJs, ViewName.PLATFORM_USERS_LOWERCASE)
|
||||
}
|
||||
)
|
||||
await doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => {
|
||||
await createView(db, viewJs, ViewName.PLATFORM_USERS_LOWERCASE)
|
||||
})
|
||||
}
|
||||
|
||||
export interface QueryViewOptions {
|
||||
|
@ -131,22 +125,24 @@ export interface QueryViewOptions {
|
|||
|
||||
export const queryView = async <T>(
|
||||
viewName: ViewName,
|
||||
params: PouchDB.Query.Options<T, T>,
|
||||
db: PouchDB.Database,
|
||||
params: DatabaseQueryOpts,
|
||||
db: Database,
|
||||
createFunc: any,
|
||||
opts?: QueryViewOptions
|
||||
): Promise<T[] | T | undefined> => {
|
||||
try {
|
||||
let response = await db.query<T, T>(`database/${viewName}`, params)
|
||||
let response = await db.query<T>(`database/${viewName}`, params)
|
||||
const rows = response.rows
|
||||
const docs = rows.map(row => (params.include_docs ? row.doc : row.value))
|
||||
const docs = rows.map((row: any) =>
|
||||
params.include_docs ? row.doc : row.value
|
||||
)
|
||||
|
||||
// if arrayResponse has been requested, always return array regardless of length
|
||||
if (opts?.arrayResponse) {
|
||||
return docs
|
||||
return docs as T[]
|
||||
} else {
|
||||
// return the single document if there is only one
|
||||
return docs.length <= 1 ? docs[0] : docs
|
||||
return docs.length <= 1 ? (docs[0] as T) : (docs as T[])
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err != null && err.name === "not_found") {
|
||||
|
@ -161,7 +157,7 @@ export const queryView = async <T>(
|
|||
|
||||
export const queryPlatformView = async <T>(
|
||||
viewName: ViewName,
|
||||
params: PouchDB.Query.Options<T, T>,
|
||||
params: DatabaseQueryOpts,
|
||||
opts?: QueryViewOptions
|
||||
): Promise<T[] | T | undefined> => {
|
||||
const CreateFuncByName: any = {
|
||||
|
@ -169,19 +165,16 @@ export const queryPlatformView = async <T>(
|
|||
[ViewName.PLATFORM_USERS_LOWERCASE]: createPlatformUserView,
|
||||
}
|
||||
|
||||
return doWithDB(
|
||||
StaticDatabases.PLATFORM_INFO.name,
|
||||
async (db: PouchDB.Database) => {
|
||||
const createFn = CreateFuncByName[viewName]
|
||||
return queryView(viewName, params, db, createFn, opts)
|
||||
}
|
||||
)
|
||||
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => {
|
||||
const createFn = CreateFuncByName[viewName]
|
||||
return queryView(viewName, params, db, createFn, opts)
|
||||
})
|
||||
}
|
||||
|
||||
export const queryGlobalView = async <T>(
|
||||
viewName: ViewName,
|
||||
params: PouchDB.Query.Options<T, T>,
|
||||
db?: PouchDB.Database,
|
||||
params: DatabaseQueryOpts,
|
||||
db?: Database,
|
||||
opts?: QueryViewOptions
|
||||
): Promise<T[] | T | undefined> => {
|
||||
const CreateFuncByName: any = {
|
||||
|
@ -192,8 +185,8 @@ export const queryGlobalView = async <T>(
|
|||
}
|
||||
// can pass DB in if working with something specific
|
||||
if (!db) {
|
||||
db = getGlobalDB() as PouchDB.Database
|
||||
db = getGlobalDB()
|
||||
}
|
||||
const createFn = CreateFuncByName[viewName]
|
||||
return queryView(viewName, params, db, createFn, opts)
|
||||
return queryView(viewName, params, db!, createFn, opts)
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ const env = {
|
|||
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
|
||||
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,
|
||||
SERVICE: process.env.SERVICE || "budibase",
|
||||
MEMORY_LEAK_CHECK: process.env.MEMORY_LEAK_CHECK || false,
|
||||
LOG_LEVEL: process.env.LOG_LEVEL,
|
||||
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
||||
DEPLOYMENT_ENVIRONMENT:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import env from "../environment"
|
||||
import tenancy from "../tenancy"
|
||||
import * as dbUtils from "../db/utils"
|
||||
import { Configs } from "../constants"
|
||||
import { Config } from "../constants"
|
||||
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
||||
|
||||
export const enabled = async () => {
|
||||
|
@ -45,9 +45,7 @@ const getSettingsDoc = async () => {
|
|||
const db = tenancy.getGlobalDB()
|
||||
let settings
|
||||
try {
|
||||
settings = await db.get(
|
||||
dbUtils.generateConfigID({ type: Configs.SETTINGS })
|
||||
)
|
||||
settings = await db.get(dbUtils.generateConfigID({ type: Config.SETTINGS }))
|
||||
} catch (e: any) {
|
||||
if (e.status !== 404) {
|
||||
throw e
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
} from "@budibase/types"
|
||||
import { processors } from "./processors"
|
||||
import * as dbUtils from "../db/utils"
|
||||
import { Configs } from "../constants"
|
||||
import { Config } from "../constants"
|
||||
import * as hashing from "../hashing"
|
||||
import * as installation from "../installation"
|
||||
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
||||
|
@ -273,7 +273,7 @@ const getUniqueTenantId = async (tenantId: string): Promise<string> => {
|
|||
return withCache(CacheKeys.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => {
|
||||
const db = context.getGlobalDB()
|
||||
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, {
|
||||
type: Configs.SETTINGS,
|
||||
type: Config.SETTINGS,
|
||||
})
|
||||
|
||||
let uniqueTenantId: string
|
||||
|
|
|
@ -13,7 +13,7 @@ import featureFlags from "./featureFlags"
|
|||
import * as sessions from "./security/sessions"
|
||||
import * as deprovisioning from "./context/deprovision"
|
||||
import auth from "./auth"
|
||||
import constants from "./constants"
|
||||
import * as constants from "./constants"
|
||||
import * as dbConstants from "./db/constants"
|
||||
import * as logging from "./logging"
|
||||
import pino from "./pino"
|
||||
|
@ -21,9 +21,9 @@ import * as middleware from "./middleware"
|
|||
import plugins from "./plugin"
|
||||
import encryption from "./security/encryption"
|
||||
import * as queue from "./queue"
|
||||
import * as db from "./db"
|
||||
|
||||
// mimic the outer package exports
|
||||
import * as db from "./pkg/db"
|
||||
import * as objectStore from "./pkg/objectStore"
|
||||
import * as utils from "./pkg/utils"
|
||||
import redis from "./pkg/redis"
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { Cookies, Headers } from "../constants"
|
||||
import { Cookie, Header } from "../constants"
|
||||
import { getCookie, clearCookie, openJwt } from "../utils"
|
||||
import { getUser } from "../cache/user"
|
||||
import { getSession, updateSessionTTL } from "../security/sessions"
|
||||
import { buildMatcherRegex, matches } from "./matchers"
|
||||
import { SEPARATOR } from "../db/constants"
|
||||
import { ViewName } from "../db/utils"
|
||||
import { queryGlobalView } from "../db/views"
|
||||
import { SEPARATOR, queryGlobalView, ViewName } from "../db"
|
||||
import { getGlobalDB, doInTenant } from "../tenancy"
|
||||
import { decrypt } from "../security/encryption"
|
||||
const identity = require("../context/identity")
|
||||
|
@ -74,7 +72,7 @@ export = (
|
|||
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
|
||||
return async (ctx: any, next: any) => {
|
||||
let publicEndpoint = false
|
||||
const version = ctx.request.headers[Headers.API_VER]
|
||||
const version = ctx.request.headers[Header.API_VER]
|
||||
// the path is not authenticated
|
||||
const found = matches(ctx, noAuthOptions)
|
||||
if (found) {
|
||||
|
@ -82,10 +80,10 @@ export = (
|
|||
}
|
||||
try {
|
||||
// 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)
|
||||
const apiKey = ctx.request.headers[Headers.API_KEY]
|
||||
const tenantId = ctx.request.headers[Headers.TENANT_ID]
|
||||
const headerToken = ctx.request.headers[Header.TOKEN]
|
||||
const authCookie = getCookie(ctx, Cookie.Auth) || openJwt(headerToken)
|
||||
const apiKey = ctx.request.headers[Header.API_KEY]
|
||||
const tenantId = ctx.request.headers[Header.TENANT_ID]
|
||||
let authenticated = false,
|
||||
user = null,
|
||||
internal = false
|
||||
|
@ -116,7 +114,7 @@ export = (
|
|||
authenticated = false
|
||||
console.error("Auth Error", err?.message || err)
|
||||
// remove the cookie as the user does not exist anymore
|
||||
clearCookie(ctx, Cookies.Auth)
|
||||
clearCookie(ctx, Cookie.Auth)
|
||||
}
|
||||
}
|
||||
// this is an internal request, no user made it
|
||||
|
@ -140,7 +138,7 @@ export = (
|
|||
delete user.password
|
||||
}
|
||||
// be explicit
|
||||
if (authenticated !== true) {
|
||||
if (!authenticated) {
|
||||
authenticated = false
|
||||
}
|
||||
// isAuthenticated is a function, so use a variable to be able to check authed state
|
||||
|
@ -155,7 +153,7 @@ export = (
|
|||
console.error("Auth Error", err?.message || err)
|
||||
// invalid token, clear the cookie
|
||||
if (err && err.name === "JsonWebTokenError") {
|
||||
clearCookie(ctx, Cookies.Auth)
|
||||
clearCookie(ctx, Cookie.Auth)
|
||||
}
|
||||
// allow configuring for public access
|
||||
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const { Headers } = require("../constants")
|
||||
const { Header } = require("../constants")
|
||||
const { buildMatcherRegex, matches } = require("./matchers")
|
||||
|
||||
/**
|
||||
|
@ -68,7 +68,7 @@ module.exports = (opts = { noCsrfPatterns: [] }) => {
|
|||
}
|
||||
|
||||
// reject if no token in request or mismatch
|
||||
const requestToken = ctx.get(Headers.CSRF_TOKEN)
|
||||
const requestToken = ctx.get(Header.CSRF_TOKEN)
|
||||
if (!requestToken || requestToken !== userToken) {
|
||||
ctx.throw(403, "Invalid CSRF token")
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const env = require("../environment")
|
||||
const { Headers } = require("../constants")
|
||||
const { Header } = require("../constants")
|
||||
|
||||
/**
|
||||
* API Key only endpoint.
|
||||
*/
|
||||
module.exports = async (ctx, next) => {
|
||||
const apiKey = ctx.request.headers[Headers.API_KEY]
|
||||
const apiKey = ctx.request.headers[Header.API_KEY]
|
||||
if (apiKey !== env.INTERNAL_API_KEY) {
|
||||
ctx.throw(403, "Unauthorized")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const google = require("../google")
|
||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||
const { Cookies, Configs } = require("../../../constants")
|
||||
const { Cookie, Config } = require("../../../constants")
|
||||
const { clearCookie, getCookie } = require("../../../utils")
|
||||
const { getScopedConfig, getPlatformUrl } = require("../../../db/utils")
|
||||
const { doWithDB } = require("../../../db")
|
||||
|
@ -11,7 +11,7 @@ async function fetchGoogleCreds() {
|
|||
// try and get the config from the tenant
|
||||
const db = getGlobalDB()
|
||||
const googleConfig = await getScopedConfig(db, {
|
||||
type: Configs.GOOGLE,
|
||||
type: Config.GOOGLE,
|
||||
})
|
||||
// or fall back to env variables
|
||||
return (
|
||||
|
@ -47,7 +47,7 @@ async function postAuth(passport, ctx, next) {
|
|||
const platformUrl = await getPlatformUrl({ tenantAware: false })
|
||||
|
||||
let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
|
||||
const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth)
|
||||
const authStateCookie = getCookie(ctx, Cookie.DatasourceAuth)
|
||||
|
||||
return passport.authenticate(
|
||||
new GoogleStrategy(
|
||||
|
@ -57,7 +57,7 @@ async function postAuth(passport, ctx, next) {
|
|||
callbackURL: callbackUrl,
|
||||
},
|
||||
(accessToken, refreshToken, profile, done) => {
|
||||
clearCookie(ctx, Cookies.DatasourceAuth)
|
||||
clearCookie(ctx, Cookie.DatasourceAuth)
|
||||
done(null, { accessToken, refreshToken })
|
||||
}
|
||||
),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||
const { ssoCallbackUrl } = require("./utils")
|
||||
const { authenticateThirdParty } = require("./third-party-common")
|
||||
const { Configs } = require("../../../constants")
|
||||
const { Config } = require("../../../constants")
|
||||
|
||||
const buildVerifyFn = saveUserFn => {
|
||||
return (accessToken, refreshToken, profile, done) => {
|
||||
|
@ -60,7 +60,7 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
|
|||
}
|
||||
|
||||
exports.getCallbackUrl = async function (db, config) {
|
||||
return ssoCallbackUrl(db, config, Configs.GOOGLE)
|
||||
return ssoCallbackUrl(db, config, Config.GOOGLE)
|
||||
}
|
||||
|
||||
// expose for testing
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const { Cookies } = require("../../constants")
|
||||
const { Cookie } = require("../../constants")
|
||||
const env = require("../../environment")
|
||||
const { authError } = require("./utils")
|
||||
|
||||
exports.options = {
|
||||
secretOrKey: env.JWT_SECRET,
|
||||
jwtFromRequest: function (ctx) {
|
||||
return ctx.cookies.get(Cookies.Auth)
|
||||
return ctx.cookies.get(Cookie.Auth)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ const fetch = require("node-fetch")
|
|||
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
|
||||
const { authenticateThirdParty } = require("./third-party-common")
|
||||
const { ssoCallbackUrl } = require("./utils")
|
||||
const { Configs } = require("../../../constants")
|
||||
const { Config } = require("../../../constants")
|
||||
|
||||
const buildVerifyFn = saveUserFn => {
|
||||
/**
|
||||
|
@ -140,7 +140,7 @@ exports.fetchStrategyConfig = async function (enrichedConfig, callbackUrl) {
|
|||
}
|
||||
|
||||
exports.getCallbackUrl = async function (db, config) {
|
||||
return ssoCallbackUrl(db, config, Configs.OIDC)
|
||||
return ssoCallbackUrl(db, config, Config.OIDC)
|
||||
}
|
||||
|
||||
// expose for testing
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { isMultiTenant, getTenantId } = require("../../tenancy")
|
||||
const { getScopedConfig } = require("../../db/utils")
|
||||
const { Configs } = require("../../constants")
|
||||
const { Config } = require("../../constants")
|
||||
|
||||
/**
|
||||
* Utility to handle authentication errors.
|
||||
|
@ -24,7 +24,7 @@ exports.ssoCallbackUrl = async (db, config, type) => {
|
|||
return config.callbackURL
|
||||
}
|
||||
const publicConfig = await getScopedConfig(db, {
|
||||
type: Configs.SETTINGS,
|
||||
type: Config.SETTINGS,
|
||||
})
|
||||
|
||||
let callbackUrl = `/api/global/auth`
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { doInTenant, getTenantIDFromCtx } from "../tenancy"
|
||||
import { buildMatcherRegex, matches } from "./matchers"
|
||||
import { Headers } from "../constants"
|
||||
import { Header } from "../constants"
|
||||
import {
|
||||
BBContext,
|
||||
EndpointMatcher,
|
||||
|
@ -29,7 +29,7 @@ const tenancy = (
|
|||
}
|
||||
|
||||
const tenantId = getTenantIDFromCtx(ctx, tenantOpts)
|
||||
ctx.set(Headers.TENANT_ID, tenantId as string)
|
||||
ctx.set(Header.TENANT_ID, tenantId as string)
|
||||
return doInTenant(tenantId, next)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ export const runMigration = async (
|
|||
options: MigrationOptions = {}
|
||||
) => {
|
||||
const migrationType = migration.type
|
||||
let tenantId: string
|
||||
let tenantId: string | undefined
|
||||
if (migrationType !== MigrationType.INSTALLATION) {
|
||||
tenantId = getTenantId()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require("../../../tests")
|
||||
const { runMigrations, getMigrationsDoc } = require("../index")
|
||||
const { dangerousGetDB } = require("../../db")
|
||||
const { getDB } = require("../../db")
|
||||
const {
|
||||
StaticDatabases,
|
||||
} = require("../../db/utils")
|
||||
|
@ -18,7 +18,7 @@ describe("migrations", () => {
|
|||
}]
|
||||
|
||||
beforeEach(() => {
|
||||
db = dangerousGetDB(StaticDatabases.GLOBAL.name)
|
||||
db = getDB(StaticDatabases.GLOBAL.name)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
import * as generic from "../cache/generic"
|
||||
import * as user from "../cache/user"
|
||||
import * as app from "../cache/appMetadata"
|
||||
import * as writethrough from "../cache/writethrough"
|
||||
|
||||
export = {
|
||||
app,
|
||||
user,
|
||||
writethrough,
|
||||
...generic,
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
// Mimic the outer package export for usage in index.ts
|
||||
// The outer exports can't be used as they now reference dist directly
|
||||
export * from "../db"
|
||||
export * from "../db/utils"
|
||||
export * from "../db/views"
|
||||
export * from "../db/pouch"
|
||||
export * from "../db/constants"
|
|
@ -1,164 +0,0 @@
|
|||
const { flatten } = require("lodash")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
|
||||
const PermissionLevels = {
|
||||
READ: "read",
|
||||
WRITE: "write",
|
||||
EXECUTE: "execute",
|
||||
ADMIN: "admin",
|
||||
}
|
||||
|
||||
// these are the global types, that govern the underlying default behaviour
|
||||
const PermissionTypes = {
|
||||
APP: "app",
|
||||
TABLE: "table",
|
||||
USER: "user",
|
||||
AUTOMATION: "automation",
|
||||
WEBHOOK: "webhook",
|
||||
BUILDER: "builder",
|
||||
VIEW: "view",
|
||||
QUERY: "query",
|
||||
}
|
||||
|
||||
function Permission(type, level) {
|
||||
this.level = level
|
||||
this.type = type
|
||||
}
|
||||
|
||||
function levelToNumber(perm) {
|
||||
switch (perm) {
|
||||
// not everything has execute privileges
|
||||
case PermissionLevels.EXECUTE:
|
||||
return 0
|
||||
case PermissionLevels.READ:
|
||||
return 1
|
||||
case PermissionLevels.WRITE:
|
||||
return 2
|
||||
case PermissionLevels.ADMIN:
|
||||
return 3
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the specified permission level for the user return the levels they are allowed to carry out.
|
||||
* @param {string} userPermLevel The permission level of the user.
|
||||
* @return {string[]} All the permission levels this user is allowed to carry out.
|
||||
*/
|
||||
function getAllowedLevels(userPermLevel) {
|
||||
switch (userPermLevel) {
|
||||
case PermissionLevels.EXECUTE:
|
||||
return [PermissionLevels.EXECUTE]
|
||||
case PermissionLevels.READ:
|
||||
return [PermissionLevels.EXECUTE, PermissionLevels.READ]
|
||||
case PermissionLevels.WRITE:
|
||||
case PermissionLevels.ADMIN:
|
||||
return [
|
||||
PermissionLevels.READ,
|
||||
PermissionLevels.WRITE,
|
||||
PermissionLevels.EXECUTE,
|
||||
]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
exports.BUILTIN_PERMISSION_IDS = {
|
||||
PUBLIC: "public",
|
||||
READ_ONLY: "read_only",
|
||||
WRITE: "write",
|
||||
ADMIN: "admin",
|
||||
POWER: "power",
|
||||
}
|
||||
|
||||
const BUILTIN_PERMISSIONS = {
|
||||
PUBLIC: {
|
||||
_id: exports.BUILTIN_PERMISSION_IDS.PUBLIC,
|
||||
name: "Public",
|
||||
permissions: [
|
||||
new Permission(PermissionTypes.WEBHOOK, PermissionLevels.EXECUTE),
|
||||
],
|
||||
},
|
||||
READ_ONLY: {
|
||||
_id: exports.BUILTIN_PERMISSION_IDS.READ_ONLY,
|
||||
name: "Read only",
|
||||
permissions: [
|
||||
new Permission(PermissionTypes.QUERY, PermissionLevels.READ),
|
||||
new Permission(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||
],
|
||||
},
|
||||
WRITE: {
|
||||
_id: exports.BUILTIN_PERMISSION_IDS.WRITE,
|
||||
name: "Read/Write",
|
||||
permissions: [
|
||||
new Permission(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
||||
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||
new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
||||
],
|
||||
},
|
||||
POWER: {
|
||||
_id: exports.BUILTIN_PERMISSION_IDS.POWER,
|
||||
name: "Power",
|
||||
permissions: [
|
||||
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
new Permission(PermissionTypes.USER, PermissionLevels.READ),
|
||||
new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||
new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ),
|
||||
],
|
||||
},
|
||||
ADMIN: {
|
||||
_id: exports.BUILTIN_PERMISSION_IDS.ADMIN,
|
||||
name: "Admin",
|
||||
permissions: [
|
||||
new Permission(PermissionTypes.TABLE, PermissionLevels.ADMIN),
|
||||
new Permission(PermissionTypes.USER, PermissionLevels.ADMIN),
|
||||
new Permission(PermissionTypes.AUTOMATION, PermissionLevels.ADMIN),
|
||||
new Permission(PermissionTypes.VIEW, PermissionLevels.ADMIN),
|
||||
new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ),
|
||||
new Permission(PermissionTypes.QUERY, PermissionLevels.ADMIN),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
exports.getBuiltinPermissions = () => {
|
||||
return cloneDeep(BUILTIN_PERMISSIONS)
|
||||
}
|
||||
|
||||
exports.getBuiltinPermissionByID = id => {
|
||||
const perms = Object.values(BUILTIN_PERMISSIONS)
|
||||
return perms.find(perm => perm._id === id)
|
||||
}
|
||||
|
||||
exports.doesHaveBasePermission = (permType, permLevel, rolesHierarchy) => {
|
||||
const basePermissions = [
|
||||
...new Set(rolesHierarchy.map(role => role.permissionId)),
|
||||
]
|
||||
const builtins = Object.values(BUILTIN_PERMISSIONS)
|
||||
let permissions = flatten(
|
||||
builtins
|
||||
.filter(builtin => basePermissions.indexOf(builtin._id) !== -1)
|
||||
.map(builtin => builtin.permissions)
|
||||
)
|
||||
for (let permission of permissions) {
|
||||
if (
|
||||
permission.type === permType &&
|
||||
getAllowedLevels(permission.level).indexOf(permLevel) !== -1
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
exports.isPermissionLevelHigherThanRead = level => {
|
||||
return levelToNumber(level) > 1
|
||||
}
|
||||
|
||||
// utility as a lot of things need simply the builder permission
|
||||
exports.BUILDER = PermissionTypes.BUILDER
|
||||
exports.PermissionTypes = PermissionTypes
|
||||
exports.PermissionLevels = PermissionLevels
|
|
@ -0,0 +1,175 @@
|
|||
const { flatten } = require("lodash")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
|
||||
export type RoleHierarchy = {
|
||||
permissionId: string
|
||||
}[]
|
||||
|
||||
export enum PermissionLevel {
|
||||
READ = "read",
|
||||
WRITE = "write",
|
||||
EXECUTE = "execute",
|
||||
ADMIN = "admin",
|
||||
}
|
||||
|
||||
// these are the global types, that govern the underlying default behaviour
|
||||
export enum PermissionType {
|
||||
APP = "app",
|
||||
TABLE = "table",
|
||||
USER = "user",
|
||||
AUTOMATION = "automation",
|
||||
WEBHOOK = "webhook",
|
||||
BUILDER = "builder",
|
||||
VIEW = "view",
|
||||
QUERY = "query",
|
||||
}
|
||||
|
||||
class Permission {
|
||||
type: PermissionType
|
||||
level: PermissionLevel
|
||||
|
||||
constructor(type: PermissionType, level: PermissionLevel) {
|
||||
this.type = type
|
||||
this.level = level
|
||||
}
|
||||
}
|
||||
|
||||
function levelToNumber(perm: PermissionLevel) {
|
||||
switch (perm) {
|
||||
// not everything has execute privileges
|
||||
case PermissionLevel.EXECUTE:
|
||||
return 0
|
||||
case PermissionLevel.READ:
|
||||
return 1
|
||||
case PermissionLevel.WRITE:
|
||||
return 2
|
||||
case PermissionLevel.ADMIN:
|
||||
return 3
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the specified permission level for the user return the levels they are allowed to carry out.
|
||||
* @param {string} userPermLevel The permission level of the user.
|
||||
* @return {string[]} All the permission levels this user is allowed to carry out.
|
||||
*/
|
||||
function getAllowedLevels(userPermLevel: PermissionLevel) {
|
||||
switch (userPermLevel) {
|
||||
case PermissionLevel.EXECUTE:
|
||||
return [PermissionLevel.EXECUTE]
|
||||
case PermissionLevel.READ:
|
||||
return [PermissionLevel.EXECUTE, PermissionLevel.READ]
|
||||
case PermissionLevel.WRITE:
|
||||
case PermissionLevel.ADMIN:
|
||||
return [
|
||||
PermissionLevel.READ,
|
||||
PermissionLevel.WRITE,
|
||||
PermissionLevel.EXECUTE,
|
||||
]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export enum BuiltinPermissionID {
|
||||
PUBLIC = "public",
|
||||
READ_ONLY = "read_only",
|
||||
WRITE = "write",
|
||||
ADMIN = "admin",
|
||||
POWER = "power",
|
||||
}
|
||||
|
||||
const BUILTIN_PERMISSIONS = {
|
||||
PUBLIC: {
|
||||
_id: BuiltinPermissionID.PUBLIC,
|
||||
name: "Public",
|
||||
permissions: [
|
||||
new Permission(PermissionType.WEBHOOK, PermissionLevel.EXECUTE),
|
||||
],
|
||||
},
|
||||
READ_ONLY: {
|
||||
_id: BuiltinPermissionID.READ_ONLY,
|
||||
name: "Read only",
|
||||
permissions: [
|
||||
new Permission(PermissionType.QUERY, PermissionLevel.READ),
|
||||
new Permission(PermissionType.TABLE, PermissionLevel.READ),
|
||||
new Permission(PermissionType.VIEW, PermissionLevel.READ),
|
||||
],
|
||||
},
|
||||
WRITE: {
|
||||
_id: BuiltinPermissionID.WRITE,
|
||||
name: "Read/Write",
|
||||
permissions: [
|
||||
new Permission(PermissionType.QUERY, PermissionLevel.WRITE),
|
||||
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
new Permission(PermissionType.VIEW, PermissionLevel.READ),
|
||||
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||
],
|
||||
},
|
||||
POWER: {
|
||||
_id: BuiltinPermissionID.POWER,
|
||||
name: "Power",
|
||||
permissions: [
|
||||
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
new Permission(PermissionType.USER, PermissionLevel.READ),
|
||||
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||
new Permission(PermissionType.VIEW, PermissionLevel.READ),
|
||||
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
||||
],
|
||||
},
|
||||
ADMIN: {
|
||||
_id: BuiltinPermissionID.ADMIN,
|
||||
name: "Admin",
|
||||
permissions: [
|
||||
new Permission(PermissionType.TABLE, PermissionLevel.ADMIN),
|
||||
new Permission(PermissionType.USER, PermissionLevel.ADMIN),
|
||||
new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN),
|
||||
new Permission(PermissionType.VIEW, PermissionLevel.ADMIN),
|
||||
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
||||
new Permission(PermissionType.QUERY, PermissionLevel.ADMIN),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export function getBuiltinPermissions() {
|
||||
return cloneDeep(BUILTIN_PERMISSIONS)
|
||||
}
|
||||
|
||||
export function getBuiltinPermissionByID(id: string) {
|
||||
const perms = Object.values(BUILTIN_PERMISSIONS)
|
||||
return perms.find(perm => perm._id === id)
|
||||
}
|
||||
|
||||
export function doesHaveBasePermission(
|
||||
permType: PermissionType,
|
||||
permLevel: PermissionLevel,
|
||||
rolesHierarchy: RoleHierarchy
|
||||
) {
|
||||
const basePermissions = [
|
||||
...new Set(rolesHierarchy.map(role => role.permissionId)),
|
||||
]
|
||||
const builtins = Object.values(BUILTIN_PERMISSIONS)
|
||||
let permissions = flatten(
|
||||
builtins
|
||||
.filter(builtin => basePermissions.indexOf(builtin._id) !== -1)
|
||||
.map(builtin => builtin.permissions)
|
||||
)
|
||||
for (let permission of permissions) {
|
||||
if (
|
||||
permission.type === permType &&
|
||||
getAllowedLevels(permission.level).indexOf(permLevel) !== -1
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function isPermissionLevelHigherThanRead(level: PermissionLevel) {
|
||||
return levelToNumber(level) > 1
|
||||
}
|
||||
|
||||
// utility as a lot of things need simply the builder permission
|
||||
export const BUILDER = PermissionType.BUILDER
|
|
@ -1,4 +1,4 @@
|
|||
import { BUILTIN_PERMISSION_IDS, PermissionLevels } from "./permissions"
|
||||
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
||||
import {
|
||||
generateRoleID,
|
||||
getRoleParams,
|
||||
|
@ -54,19 +54,19 @@ export class Role {
|
|||
|
||||
const BUILTIN_ROLES = {
|
||||
ADMIN: new Role(BUILTIN_IDS.ADMIN, "Admin")
|
||||
.addPermission(BUILTIN_PERMISSION_IDS.ADMIN)
|
||||
.addPermission(BuiltinPermissionID.ADMIN)
|
||||
.addInheritance(BUILTIN_IDS.POWER),
|
||||
POWER: new Role(BUILTIN_IDS.POWER, "Power")
|
||||
.addPermission(BUILTIN_PERMISSION_IDS.POWER)
|
||||
.addPermission(BuiltinPermissionID.POWER)
|
||||
.addInheritance(BUILTIN_IDS.BASIC),
|
||||
BASIC: new Role(BUILTIN_IDS.BASIC, "Basic")
|
||||
.addPermission(BUILTIN_PERMISSION_IDS.WRITE)
|
||||
.addPermission(BuiltinPermissionID.WRITE)
|
||||
.addInheritance(BUILTIN_IDS.PUBLIC),
|
||||
PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public").addPermission(
|
||||
BUILTIN_PERMISSION_IDS.PUBLIC
|
||||
BuiltinPermissionID.PUBLIC
|
||||
),
|
||||
BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder").addPermission(
|
||||
BUILTIN_PERMISSION_IDS.ADMIN
|
||||
BuiltinPermissionID.ADMIN
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -147,9 +147,9 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string) {
|
|||
* @param {string|null} roleId The level ID to lookup.
|
||||
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
|
||||
*/
|
||||
export async function getRole(roleId?: string) {
|
||||
export async function getRole(roleId?: string): Promise<RoleDoc | undefined> {
|
||||
if (!roleId) {
|
||||
return null
|
||||
return undefined
|
||||
}
|
||||
let role: any = {}
|
||||
// built in roles mostly come from the in-code implementation,
|
||||
|
@ -193,7 +193,9 @@ async function getAllUserRoles(userRoleId?: string): Promise<RoleDoc[]> {
|
|||
) {
|
||||
roleIds.push(currentRole.inherits)
|
||||
currentRole = await getRole(currentRole.inherits)
|
||||
roles.push(currentRole)
|
||||
if (currentRole) {
|
||||
roles.push(currentRole)
|
||||
}
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
@ -225,8 +227,8 @@ export function checkForRoleResourceArray(
|
|||
if (rolePerms && !Array.isArray(rolePerms[resourceId])) {
|
||||
const permLevel = rolePerms[resourceId] as any
|
||||
rolePerms[resourceId] = [permLevel]
|
||||
if (permLevel === PermissionLevels.WRITE) {
|
||||
rolePerms[resourceId].push(PermissionLevels.READ)
|
||||
if (permLevel === PermissionLevel.WRITE) {
|
||||
rolePerms[resourceId].push(PermissionLevel.READ)
|
||||
}
|
||||
}
|
||||
return rolePerms
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { doWithDB } from "../db"
|
||||
import { queryPlatformView } from "../db/views"
|
||||
import { StaticDatabases, ViewName } from "../db/constants"
|
||||
import { getGlobalDBName } from "../db/tenancy"
|
||||
import {
|
||||
getTenantId,
|
||||
doWithDB,
|
||||
queryPlatformView,
|
||||
StaticDatabases,
|
||||
getGlobalDBName,
|
||||
ViewName,
|
||||
} from "../db"
|
||||
import {
|
||||
DEFAULT_TENANT_ID,
|
||||
isMultiTenant,
|
||||
getTenantId,
|
||||
getTenantIDFromAppID,
|
||||
isMultiTenant,
|
||||
} from "../context"
|
||||
import env from "../environment"
|
||||
import {
|
||||
|
@ -15,12 +18,12 @@ import {
|
|||
TenantResolutionStrategy,
|
||||
GetTenantIdOptions,
|
||||
} from "@budibase/types"
|
||||
import { Headers } from "../constants"
|
||||
import { Header } from "../constants"
|
||||
|
||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||
|
||||
export const addTenantToUrl = (url: string) => {
|
||||
export function addTenantToUrl(url: string) {
|
||||
const tenantId = getTenantId()
|
||||
|
||||
if (isMultiTenant()) {
|
||||
|
@ -31,7 +34,7 @@ export const addTenantToUrl = (url: string) => {
|
|||
return url
|
||||
}
|
||||
|
||||
export const doesTenantExist = async (tenantId: string) => {
|
||||
export async function doesTenantExist(tenantId: string) {
|
||||
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||
let tenants
|
||||
try {
|
||||
|
@ -48,12 +51,12 @@ export const doesTenantExist = async (tenantId: string) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const tryAddTenant = async (
|
||||
export async function tryAddTenant(
|
||||
tenantId: string,
|
||||
userId: string,
|
||||
email: string,
|
||||
afterCreateTenant: () => Promise<void>
|
||||
) => {
|
||||
) {
|
||||
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||
const getDoc = async (id: string) => {
|
||||
if (!id) {
|
||||
|
@ -95,11 +98,11 @@ export const tryAddTenant = async (
|
|||
})
|
||||
}
|
||||
|
||||
export const doWithGlobalDB = (tenantId: string, cb: any) => {
|
||||
export function doWithGlobalDB(tenantId: string, cb: any) {
|
||||
return doWithDB(getGlobalDBName(tenantId), cb)
|
||||
}
|
||||
|
||||
export const lookupTenantId = async (userId: string) => {
|
||||
export async function lookupTenantId(userId: string) {
|
||||
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => {
|
||||
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null
|
||||
try {
|
||||
|
@ -115,19 +118,26 @@ export const lookupTenantId = async (userId: string) => {
|
|||
}
|
||||
|
||||
// lookup, could be email or userId, either will return a doc
|
||||
export const getTenantUser = async (
|
||||
export async function getTenantUser(
|
||||
identifier: string
|
||||
): Promise<PlatformUser | null> => {
|
||||
): Promise<PlatformUser | undefined> {
|
||||
// use the view here and allow to find anyone regardless of casing
|
||||
// Use lowercase to ensure email login is case insensitive
|
||||
const response = queryPlatformView(ViewName.PLATFORM_USERS_LOWERCASE, {
|
||||
keys: [identifier.toLowerCase()],
|
||||
include_docs: true,
|
||||
}) as Promise<PlatformUser>
|
||||
return response
|
||||
// Use lowercase to ensure email login is case-insensitive
|
||||
const users = await queryPlatformView<PlatformUser>(
|
||||
ViewName.PLATFORM_USERS_LOWERCASE,
|
||||
{
|
||||
keys: [identifier.toLowerCase()],
|
||||
include_docs: true,
|
||||
}
|
||||
)
|
||||
if (Array.isArray(users)) {
|
||||
return users[0]
|
||||
} else {
|
||||
return users
|
||||
}
|
||||
}
|
||||
|
||||
export const isUserInAppTenant = (appId: string, user?: any) => {
|
||||
export function isUserInAppTenant(appId: string, user?: any) {
|
||||
let userTenantId
|
||||
if (user) {
|
||||
userTenantId = user.tenantId || DEFAULT_TENANT_ID
|
||||
|
@ -138,7 +148,7 @@ export const isUserInAppTenant = (appId: string, user?: any) => {
|
|||
return tenantId === userTenantId
|
||||
}
|
||||
|
||||
export const getTenantIds = async () => {
|
||||
export async function getTenantIds() {
|
||||
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||
let tenants
|
||||
try {
|
||||
|
@ -193,7 +203,7 @@ export const getTenantIDFromCtx = (
|
|||
|
||||
// header
|
||||
if (isAllowed(TenantResolutionStrategy.HEADER)) {
|
||||
const headerTenantId = ctx.request.headers[Headers.TENANT_ID]
|
||||
const headerTenantId = ctx.request.headers[Header.TENANT_ID]
|
||||
if (headerTenantId) {
|
||||
return headerTenantId as string
|
||||
}
|
||||
|
|
|
@ -3,15 +3,14 @@ import {
|
|||
getUsersByAppParams,
|
||||
getProdAppID,
|
||||
generateAppUserID,
|
||||
} from "./db/utils"
|
||||
import { queryGlobalView } from "./db/views"
|
||||
import { UNICODE_MAX } from "./db/constants"
|
||||
queryGlobalView,
|
||||
UNICODE_MAX,
|
||||
} from "./db"
|
||||
import { BulkDocsResponse, User } from "@budibase/types"
|
||||
import { getGlobalDB } from "./context"
|
||||
import PouchDB from "pouchdb"
|
||||
|
||||
export const bulkGetGlobalUsersById = async (userIds: string[]) => {
|
||||
const db = getGlobalDB() as PouchDB.Database
|
||||
const db = getGlobalDB()
|
||||
return (
|
||||
await db.allDocs({
|
||||
keys: userIds,
|
||||
|
@ -21,7 +20,7 @@ export const bulkGetGlobalUsersById = async (userIds: string[]) => {
|
|||
}
|
||||
|
||||
export const bulkUpdateGlobalUsers = async (users: User[]) => {
|
||||
const db = getGlobalDB() as PouchDB.Database
|
||||
const db = getGlobalDB()
|
||||
return (await db.bulkDocs(users)) as BulkDocsResponse
|
||||
}
|
||||
|
||||
|
@ -69,7 +68,7 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
|||
if (!user) {
|
||||
return
|
||||
}
|
||||
return generateAppUserID(getProdAppID(appId), user._id!)
|
||||
return generateAppUserID(getProdAppID(appId)!, user._id!)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { DocumentType, SEPARATOR, ViewName, getAllApps } from "./db/utils"
|
||||
const jwt = require("jsonwebtoken")
|
||||
import {
|
||||
DocumentType,
|
||||
SEPARATOR,
|
||||
ViewName,
|
||||
getAllApps,
|
||||
queryGlobalView,
|
||||
} from "./db"
|
||||
import { options } from "./middleware/passport/jwt"
|
||||
import { queryGlobalView } from "./db/views"
|
||||
import { Headers, Cookies, MAX_VALID_DATE } from "./constants"
|
||||
import { Header, Cookie, MAX_VALID_DATE } from "./constants"
|
||||
import env from "./environment"
|
||||
import userCache from "./cache/user"
|
||||
import { getSessionsForUser, invalidateSessions } from "./security/sessions"
|
||||
|
@ -15,6 +19,7 @@ import {
|
|||
TenantResolutionStrategy,
|
||||
} from "@budibase/types"
|
||||
import { SetOption } from "cookies"
|
||||
const jwt = require("jsonwebtoken")
|
||||
|
||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||
const PROD_APP_PREFIX = "/app/"
|
||||
|
@ -29,7 +34,7 @@ async function resolveAppUrl(ctx: BBContext) {
|
|||
const appUrl = ctx.path.split("/")[2]
|
||||
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
||||
|
||||
let tenantId = tenancy.getTenantId()
|
||||
let tenantId: string | null = tenancy.getTenantId()
|
||||
if (env.MULTI_TENANCY) {
|
||||
// always use the tenant id from the subdomain in multi tenancy
|
||||
// this ensures the logged-in user tenant id doesn't overwrite
|
||||
|
@ -50,7 +55,7 @@ async function resolveAppUrl(ctx: BBContext) {
|
|||
return app && app.appId ? app.appId : undefined
|
||||
}
|
||||
|
||||
export const isServingApp = (ctx: BBContext) => {
|
||||
export function isServingApp(ctx: BBContext) {
|
||||
// dev app
|
||||
if (ctx.path.startsWith(`/${APP_PREFIX}`)) {
|
||||
return true
|
||||
|
@ -67,9 +72,9 @@ export const isServingApp = (ctx: BBContext) => {
|
|||
* @param {object} ctx The main request body to look through.
|
||||
* @returns {string|undefined} If an appId was found it will be returned.
|
||||
*/
|
||||
export const getAppIdFromCtx = async (ctx: BBContext) => {
|
||||
export async function getAppIdFromCtx(ctx: BBContext) {
|
||||
// look in headers
|
||||
const options = [ctx.headers[Headers.APP_ID]]
|
||||
const options = [ctx.headers[Header.APP_ID]]
|
||||
let appId
|
||||
for (let option of options) {
|
||||
appId = confirmAppId(option as string)
|
||||
|
@ -103,7 +108,7 @@ export const getAppIdFromCtx = async (ctx: BBContext) => {
|
|||
* opens the contents of the specified encrypted JWT.
|
||||
* @return {object} the contents of the token.
|
||||
*/
|
||||
export const openJwt = (token: string) => {
|
||||
export function openJwt(token: string) {
|
||||
if (!token) {
|
||||
return token
|
||||
}
|
||||
|
@ -115,7 +120,7 @@ export const openJwt = (token: string) => {
|
|||
* @param {object} ctx The request which is to be manipulated.
|
||||
* @param {string} name The name of the cookie to get.
|
||||
*/
|
||||
export const getCookie = (ctx: BBContext, name: string) => {
|
||||
export function getCookie(ctx: BBContext, name: string) {
|
||||
const cookie = ctx.cookies.get(name)
|
||||
|
||||
if (!cookie) {
|
||||
|
@ -132,12 +137,12 @@ export const getCookie = (ctx: BBContext, name: string) => {
|
|||
* @param {string|object} value The value of cookie which will be set.
|
||||
* @param {object} opts options like whether to sign.
|
||||
*/
|
||||
export const setCookie = (
|
||||
export function setCookie(
|
||||
ctx: BBContext,
|
||||
value: any,
|
||||
name = "builder",
|
||||
opts = { sign: true }
|
||||
) => {
|
||||
) {
|
||||
if (value && opts && opts.sign) {
|
||||
value = jwt.sign(value, options.secretOrKey)
|
||||
}
|
||||
|
@ -159,7 +164,7 @@ export const setCookie = (
|
|||
/**
|
||||
* Utility function, simply calls setCookie with an empty string for value
|
||||
*/
|
||||
export const clearCookie = (ctx: BBContext, name: string) => {
|
||||
export function clearCookie(ctx: BBContext, name: string) {
|
||||
setCookie(ctx, null, name)
|
||||
}
|
||||
|
||||
|
@ -169,11 +174,11 @@ export const clearCookie = (ctx: BBContext, name: string) => {
|
|||
* @param {object} ctx The koa context object to be tested.
|
||||
* @return {boolean} returns true if the call is from the client lib (a built app rather than the builder).
|
||||
*/
|
||||
export const isClient = (ctx: BBContext) => {
|
||||
return ctx.headers[Headers.TYPE] === "client"
|
||||
export function isClient(ctx: BBContext) {
|
||||
return ctx.headers[Header.TYPE] === "client"
|
||||
}
|
||||
|
||||
const getBuilders = async () => {
|
||||
async function getBuilders() {
|
||||
const builders = await queryGlobalView(ViewName.USER_BY_BUILDERS, {
|
||||
include_docs: false,
|
||||
})
|
||||
|
@ -189,7 +194,7 @@ const getBuilders = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getBuildersCount = async () => {
|
||||
export async function getBuildersCount() {
|
||||
const builders = await getBuilders()
|
||||
return builders.length
|
||||
}
|
||||
|
@ -197,14 +202,14 @@ export const getBuildersCount = async () => {
|
|||
/**
|
||||
* Logs a user out from budibase. Re-used across account portal and builder.
|
||||
*/
|
||||
export const platformLogout = async (opts: PlatformLogoutOpts) => {
|
||||
export async function platformLogout(opts: PlatformLogoutOpts) {
|
||||
const ctx = opts.ctx
|
||||
const userId = opts.userId
|
||||
const keepActiveSession = opts.keepActiveSession
|
||||
|
||||
if (!ctx) throw new Error("Koa context must be supplied to logout.")
|
||||
|
||||
const currentSession = getCookie(ctx, Cookies.Auth)
|
||||
const currentSession = getCookie(ctx, Cookie.Auth)
|
||||
let sessions = await getSessionsForUser(userId)
|
||||
|
||||
if (keepActiveSession) {
|
||||
|
@ -213,8 +218,8 @@ export const platformLogout = async (opts: PlatformLogoutOpts) => {
|
|||
)
|
||||
} else {
|
||||
// clear cookies
|
||||
clearCookie(ctx, Cookies.Auth)
|
||||
clearCookie(ctx, Cookies.CurrentApp)
|
||||
clearCookie(ctx, Cookie.Auth)
|
||||
clearCookie(ctx, Cookie.CurrentApp)
|
||||
}
|
||||
|
||||
const sessionIds = sessions.map(({ sessionId }) => sessionId)
|
||||
|
@ -223,6 +228,6 @@ export const platformLogout = async (opts: PlatformLogoutOpts) => {
|
|||
await userCache.invalidateUser(userId)
|
||||
}
|
||||
|
||||
export const timeout = (timeMs: number) => {
|
||||
export function timeout(timeMs: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, timeMs))
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"types": [ "node", "jest" ],
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
},
|
||||
"include": [
|
||||
"**/*.js",
|
||||
|
|
|
@ -1335,6 +1335,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/tough-cookie@^4.0.2":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
|
||||
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
|
||||
|
||||
"@types/uuid@8.3.4":
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
|
@ -1411,7 +1416,7 @@ acorn@^8.4.1:
|
|||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
|
||||
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
|
||||
|
||||
agent-base@6:
|
||||
agent-base@6, agent-base@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||
|
@ -1568,6 +1573,15 @@ axios@0.24.0:
|
|||
dependencies:
|
||||
follow-redirects "^1.14.4"
|
||||
|
||||
axios@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35"
|
||||
integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
babel-jest@^28.1.3:
|
||||
version "28.1.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5"
|
||||
|
@ -1814,6 +1828,14 @@ cacheable-request@^6.0.0:
|
|||
normalize-url "^4.1.0"
|
||||
responselike "^1.0.2"
|
||||
|
||||
call-bind@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.0.2"
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
|
@ -2472,6 +2494,11 @@ follow-redirects@^1.14.4:
|
|||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
|
||||
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
|
||||
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
|
@ -2486,6 +2513,15 @@ form-data@^3.0.0:
|
|||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
|
@ -2557,6 +2593,15 @@ get-caller-file@^2.0.5:
|
|||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-intrinsic@^1.0.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
|
||||
integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
get-package-type@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||
|
@ -2705,7 +2750,7 @@ has-flag@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
has-symbols@^1.0.2:
|
||||
has-symbols@^1.0.2, has-symbols@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||
|
@ -2752,6 +2797,13 @@ http-cache-semantics@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
||||
|
||||
http-cookie-agent@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/http-cookie-agent/-/http-cookie-agent-4.0.2.tgz#dcdaae18ed1f7452d81ae4d5cd80b227d6831b69"
|
||||
integrity sha512-noTmxdH5CuytTnLj/Qv3Z84e/YFq8yLXAw3pqIYZ25Edhb9pQErIAC+ednw40Cic6Le/h9ryph5/TqsvkOaUCw==
|
||||
dependencies:
|
||||
agent-base "^6.0.2"
|
||||
|
||||
http-errors@^1.6.3, http-errors@~1.8.0:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
|
||||
|
@ -4018,6 +4070,18 @@ msgpackr@^1.5.2:
|
|||
optionalDependencies:
|
||||
msgpackr-extract "^2.1.2"
|
||||
|
||||
nano@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.0.tgz#afdd5a7440e62f09a8e23f41fcea328d27383922"
|
||||
integrity sha512-COeN2TpLcHuSN44QLnPmfZCoCsKAg8/aelPOVqqm/2/MvRHDEA11/Kld5C4sLzDlWlhFZ3SO2WGJGevCsvcEzQ==
|
||||
dependencies:
|
||||
"@types/tough-cookie" "^4.0.2"
|
||||
axios "^1.1.3"
|
||||
http-cookie-agent "^4.0.2"
|
||||
node-abort-controller "^3.0.1"
|
||||
qs "^6.11.0"
|
||||
tough-cookie "^4.1.2"
|
||||
|
||||
napi-macros@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
|
||||
|
@ -4043,6 +4107,11 @@ negotiator@0.6.3:
|
|||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
|
||||
node-abort-controller@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e"
|
||||
integrity sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw==
|
||||
|
||||
node-addon-api@^3.1.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
||||
|
@ -4178,6 +4247,11 @@ object-assign@^4.1.1:
|
|||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-inspect@^1.9.0:
|
||||
version "1.12.2"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
|
||||
integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
|
||||
|
||||
on-finished@^2.3.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||
|
@ -4675,6 +4749,11 @@ prompts@^2.0.1:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
prr@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||
|
@ -4715,6 +4794,13 @@ pupa@^2.1.1:
|
|||
dependencies:
|
||||
escape-goat "^2.0.0"
|
||||
|
||||
qs@^6.11.0:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
||||
|
@ -4725,6 +4811,11 @@ querystring@0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
range-parser@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
|
@ -4878,6 +4969,11 @@ require-directory@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
|
||||
|
||||
resolve-cwd@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
||||
|
@ -4999,6 +5095,15 @@ shimmer@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
|
||||
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
|
@ -5329,6 +5434,16 @@ touch@^3.1.0:
|
|||
punycode "^2.1.1"
|
||||
universalify "^0.1.2"
|
||||
|
||||
tough-cookie@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
|
||||
integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==
|
||||
dependencies:
|
||||
psl "^1.1.33"
|
||||
punycode "^2.1.1"
|
||||
universalify "^0.2.0"
|
||||
url-parse "^1.5.3"
|
||||
|
||||
tough-cookie@~2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
||||
|
@ -5458,6 +5573,11 @@ universalify@^0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
|
||||
|
||||
update-browserslist-db@^1.0.9:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
|
||||
|
@ -5500,6 +5620,14 @@ url-parse-lax@^3.0.0:
|
|||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
url-parse@^1.5.3:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
url@0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||
|
|
|
@ -2,7 +2,7 @@ module FetchMock {
|
|||
const fetch = jest.requireActual("node-fetch")
|
||||
let failCount = 0
|
||||
|
||||
module.exports = async (url: any, opts: any) => {
|
||||
const func = async (url: any, opts: any) => {
|
||||
function json(body: any, status = 200) {
|
||||
return {
|
||||
status,
|
||||
|
@ -106,4 +106,8 @@ module FetchMock {
|
|||
}
|
||||
return fetch(url, opts)
|
||||
}
|
||||
|
||||
func.Headers = fetch.Headers
|
||||
|
||||
module.exports = func
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ if (!process.env.CI) {
|
|||
"@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
|
||||
"@budibase/backend-core": "<rootDir>/../backend-core/src",
|
||||
"@budibase/types": "<rootDir>/../types/src",
|
||||
"^axios.*$": "<rootDir>/node_modules/axios/lib/axios.js",
|
||||
}
|
||||
// add pro sources if they exist
|
||||
if (fs.existsSync("../../../budibase-pro")) {
|
||||
|
|
|
@ -14,17 +14,16 @@ import {
|
|||
DocumentType,
|
||||
AppStatus,
|
||||
} from "../../db/utils"
|
||||
const {
|
||||
BUILTIN_ROLE_IDS,
|
||||
AccessController,
|
||||
} = require("@budibase/backend-core/roles")
|
||||
const { CacheKeys, bustCache } = require("@budibase/backend-core/cache")
|
||||
const {
|
||||
getAllApps,
|
||||
isDevAppID,
|
||||
getProdAppID,
|
||||
Replication,
|
||||
} = require("@budibase/backend-core/db")
|
||||
import {
|
||||
db as dbCore,
|
||||
roles,
|
||||
cache,
|
||||
tenancy,
|
||||
context,
|
||||
errors,
|
||||
events,
|
||||
migrations,
|
||||
} from "@budibase/backend-core"
|
||||
import { USERS_TABLE_SCHEMA } from "../../constants"
|
||||
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||
import { clientLibraryPath, stringToReadStream } from "../../utilities"
|
||||
|
@ -34,15 +33,11 @@ import {
|
|||
backupClientLibrary,
|
||||
revertClientLibrary,
|
||||
} from "../../utilities/fileSystem/clientLibrary"
|
||||
const { getTenantId, isMultiTenant } = require("@budibase/backend-core/tenancy")
|
||||
import { syncGlobalUsers } from "./user"
|
||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
||||
import { cleanupAutomations } from "../../automations/utils"
|
||||
import { context } from "@budibase/backend-core"
|
||||
import { checkAppMetadata } from "../../automations/logging"
|
||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||
import { quotas, groups } from "@budibase/pro"
|
||||
import { errors, events, migrations } from "@budibase/backend-core"
|
||||
import { App, Layout, Screen, MigrationType } from "@budibase/types"
|
||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||
import { enrichPluginURLs } from "../../utilities/plugins"
|
||||
|
@ -75,7 +70,7 @@ async function getScreens() {
|
|||
|
||||
function getUserRoleId(ctx: any) {
|
||||
return !ctx.user.role || !ctx.user.role._id
|
||||
? BUILTIN_ROLE_IDS.PUBLIC
|
||||
? roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||
: ctx.user.role._id
|
||||
}
|
||||
|
||||
|
@ -123,7 +118,7 @@ const checkAppName = (
|
|||
}
|
||||
|
||||
async function createInstance(template: any) {
|
||||
const tenantId = isMultiTenant() ? getTenantId() : null
|
||||
const tenantId = tenancy.isMultiTenant() ? tenancy.getTenantId() : null
|
||||
const baseAppId = generateAppID(tenantId)
|
||||
const appId = generateDevAppID(baseAppId)
|
||||
await context.updateAppId(appId)
|
||||
|
@ -162,7 +157,7 @@ async function createInstance(template: any) {
|
|||
export const fetch = async (ctx: any) => {
|
||||
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
||||
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
||||
const apps = await getAllApps({ dev, all })
|
||||
const apps = (await dbCore.getAllApps({ dev, all })) as App[]
|
||||
|
||||
const appIds = apps
|
||||
.filter((app: any) => app.status === "development")
|
||||
|
@ -187,7 +182,7 @@ export const fetch = async (ctx: any) => {
|
|||
export const fetchAppDefinition = async (ctx: any) => {
|
||||
const layouts = await getLayouts()
|
||||
const userRoleId = getUserRoleId(ctx)
|
||||
const accessController = new AccessController()
|
||||
const accessController = new roles.AccessController()
|
||||
const screens = await accessController.checkScreensAccess(
|
||||
await getScreens(),
|
||||
userRoleId
|
||||
|
@ -211,7 +206,7 @@ export const fetchAppPackage = async (ctx: any) => {
|
|||
// Only filter screens if the user is not a builder
|
||||
if (!(ctx.user.builder && ctx.user.builder.global)) {
|
||||
const userRoleId = getUserRoleId(ctx)
|
||||
const accessController = new AccessController()
|
||||
const accessController = new roles.AccessController()
|
||||
screens = await accessController.checkScreensAccess(screens, userRoleId)
|
||||
}
|
||||
|
||||
|
@ -224,7 +219,7 @@ export const fetchAppPackage = async (ctx: any) => {
|
|||
}
|
||||
|
||||
const performAppCreate = async (ctx: any) => {
|
||||
const apps = await getAllApps({ dev: true })
|
||||
const apps = await dbCore.getAllApps({ dev: true })
|
||||
const name = ctx.request.body.name
|
||||
checkAppName(ctx, apps, name)
|
||||
const url = getAppUrl(ctx)
|
||||
|
@ -254,7 +249,7 @@ const performAppCreate = async (ctx: any) => {
|
|||
url: url,
|
||||
template: templateKey,
|
||||
instance,
|
||||
tenantId: getTenantId(),
|
||||
tenantId: tenancy.getTenantId(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
status: AppStatus.DEV,
|
||||
|
@ -313,7 +308,7 @@ const performAppCreate = async (ctx: any) => {
|
|||
await createApp(appId)
|
||||
}
|
||||
|
||||
await appCache.invalidateAppMetadata(appId, newApplication)
|
||||
await cache.app.invalidateAppMetadata(appId, newApplication)
|
||||
return newApplication
|
||||
}
|
||||
|
||||
|
@ -343,7 +338,7 @@ const creationEvents = async (request: any, app: App) => {
|
|||
}
|
||||
|
||||
const appPostCreate = async (ctx: any, app: App) => {
|
||||
const tenantId = getTenantId()
|
||||
const tenantId = tenancy.getTenantId()
|
||||
await migrations.backPopulateMigrations({
|
||||
type: MigrationType.APP,
|
||||
tenantId,
|
||||
|
@ -356,7 +351,9 @@ const appPostCreate = async (ctx: any, app: App) => {
|
|||
const rowCount = rows ? rows.length : 0
|
||||
if (rowCount) {
|
||||
try {
|
||||
await quotas.addRows(rowCount)
|
||||
await context.doInAppContext(app.appId, () => {
|
||||
return quotas.addRows(rowCount)
|
||||
})
|
||||
} catch (err: any) {
|
||||
if (err.code && err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||
// this import resulted in row usage exceeding the quota
|
||||
|
@ -374,7 +371,7 @@ const appPostCreate = async (ctx: any, app: App) => {
|
|||
export const create = async (ctx: any) => {
|
||||
const newApplication = await quotas.addApp(() => performAppCreate(ctx))
|
||||
await appPostCreate(ctx, newApplication)
|
||||
await bustCache(CacheKeys.CHECKLIST)
|
||||
await cache.bustCache(cache.CacheKeys.CHECKLIST)
|
||||
ctx.body = newApplication
|
||||
ctx.status = 200
|
||||
}
|
||||
|
@ -382,7 +379,7 @@ export const create = async (ctx: any) => {
|
|||
// This endpoint currently operates as a PATCH rather than a PUT
|
||||
// Thus name and url fields are handled only if present
|
||||
export const update = async (ctx: any) => {
|
||||
const apps = await getAllApps({ dev: true })
|
||||
const apps = await dbCore.getAllApps({ dev: true })
|
||||
// validation
|
||||
const name = ctx.request.body.name
|
||||
if (name) {
|
||||
|
@ -455,7 +452,7 @@ const destroyApp = async (ctx: any) => {
|
|||
let isUnpublish = ctx.query && ctx.query.unpublish
|
||||
|
||||
if (isUnpublish) {
|
||||
appId = getProdAppID(appId)
|
||||
appId = dbCore.getProdAppID(appId)
|
||||
}
|
||||
|
||||
const db = isUnpublish ? context.getProdAppDB() : context.getAppDB()
|
||||
|
@ -481,7 +478,7 @@ const destroyApp = async (ctx: any) => {
|
|||
else {
|
||||
await removeAppFromUserRoles(ctx, appId)
|
||||
}
|
||||
await appCache.invalidateAppMetadata(appId)
|
||||
await cache.app.invalidateAppMetadata(appId)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -517,19 +514,17 @@ export const sync = async (ctx: any, next: any) => {
|
|||
}
|
||||
|
||||
const appId = ctx.params.appId
|
||||
if (!isDevAppID(appId)) {
|
||||
if (!dbCore.isDevAppID(appId)) {
|
||||
ctx.throw(400, "This action cannot be performed for production apps")
|
||||
}
|
||||
|
||||
// replicate prod to dev
|
||||
const prodAppId = getProdAppID(appId)
|
||||
const prodAppId = dbCore.getProdAppID(appId)
|
||||
|
||||
try {
|
||||
// specific case, want to make sure setup is skipped
|
||||
const prodDb = context.getProdAppDB({ skip_setup: true })
|
||||
const info = await prodDb.info()
|
||||
if (info.error) throw info.error
|
||||
} catch (err) {
|
||||
// specific case, want to make sure setup is skipped
|
||||
const prodDb = context.getProdAppDB({ skip_setup: true })
|
||||
const exists = await prodDb.exists()
|
||||
if (!exists) {
|
||||
// the database doesn't exist. Don't replicate
|
||||
ctx.status = 200
|
||||
ctx.body = {
|
||||
|
@ -538,7 +533,7 @@ export const sync = async (ctx: any, next: any) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
const replication = new Replication({
|
||||
const replication = new dbCore.Replication({
|
||||
source: prodAppId,
|
||||
target: appId,
|
||||
})
|
||||
|
@ -579,7 +574,7 @@ export const updateAppPackage = async (appPackage: any, appId: any) => {
|
|||
|
||||
await db.put(newAppPackage)
|
||||
// remove any cached metadata, so that it will be updated
|
||||
await appCache.invalidateAppMetadata(appId)
|
||||
await cache.app.invalidateAppMetadata(appId)
|
||||
return newAppPackage
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { InternalTables } from "../../db/utils"
|
|||
import { getFullUser } from "../../utilities/users"
|
||||
import { roles, context } from "@budibase/backend-core"
|
||||
import { groups } from "@budibase/pro"
|
||||
import { ContextUser, User } from "@budibase/types"
|
||||
|
||||
const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||
|
||||
|
@ -24,7 +25,7 @@ export async function fetchSelf(ctx: any) {
|
|||
}
|
||||
|
||||
const appId = context.getAppId()
|
||||
const user = await getFullUser(ctx, userId)
|
||||
const user: ContextUser = await getFullUser(ctx, userId)
|
||||
// this shouldn't be returned by the app self
|
||||
delete user.roles
|
||||
// forward the csrf token from the session
|
||||
|
@ -34,7 +35,7 @@ export async function fetchSelf(ctx: any) {
|
|||
const db = context.getAppDB()
|
||||
// check for group permissions
|
||||
if (!user.roleId || user.roleId === PUBLIC_ROLE) {
|
||||
const groupRoleId = await groups.getGroupRoleId(user, appId)
|
||||
const groupRoleId = await groups.getGroupRoleId(user as User, appId)
|
||||
user.roleId = groupRoleId || user.roleId
|
||||
}
|
||||
// remove the full roles structure
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
import Deployment from "./Deployment"
|
||||
import {
|
||||
getDevelopmentAppID,
|
||||
getProdAppID,
|
||||
Replication,
|
||||
} from "@budibase/backend-core/db"
|
||||
import { context, db as dbCore, events, cache } from "@budibase/backend-core"
|
||||
import { DocumentType, getAutomationParams } from "../../../db/utils"
|
||||
import {
|
||||
clearMetadata,
|
||||
disableAllCrons,
|
||||
enableCronTrigger,
|
||||
} from "../../../automations/utils"
|
||||
import { app as appCache } from "@budibase/backend-core/cache"
|
||||
import {
|
||||
getAppDB,
|
||||
getAppId,
|
||||
getDevAppDB,
|
||||
getProdAppDB,
|
||||
} from "@budibase/backend-core/context"
|
||||
import { events } from "@budibase/backend-core"
|
||||
import { backups } from "@budibase/pro"
|
||||
import { AppBackupTrigger } from "@budibase/types"
|
||||
|
||||
|
@ -49,7 +37,7 @@ async function checkAllDeployments(deployments: any) {
|
|||
|
||||
async function storeDeploymentHistory(deployment: any) {
|
||||
const deploymentJSON = deployment.getJSON()
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
|
||||
let deploymentDoc
|
||||
try {
|
||||
|
@ -77,7 +65,7 @@ async function storeDeploymentHistory(deployment: any) {
|
|||
}
|
||||
|
||||
async function initDeployedApp(prodAppId: any) {
|
||||
const db = getProdAppDB()
|
||||
const db = context.getProdAppDB()
|
||||
console.log("Reading automation docs")
|
||||
const automations = (
|
||||
await db.allDocs(
|
||||
|
@ -103,9 +91,9 @@ async function initDeployedApp(prodAppId: any) {
|
|||
async function deployApp(deployment: any, userId: string) {
|
||||
let replication
|
||||
try {
|
||||
const appId = getAppId()
|
||||
const devAppId = getDevelopmentAppID(appId)
|
||||
const productionAppId = getProdAppID(appId)
|
||||
const appId = context.getAppId()!
|
||||
const devAppId = dbCore.getDevelopmentAppID(appId)
|
||||
const productionAppId = dbCore.getProdAppID(appId)
|
||||
|
||||
// don't try this if feature isn't allowed, will error
|
||||
if (await backups.isEnabled()) {
|
||||
|
@ -122,8 +110,8 @@ async function deployApp(deployment: any, userId: string) {
|
|||
source: devAppId,
|
||||
target: productionAppId,
|
||||
}
|
||||
replication = new Replication(config)
|
||||
const devDb = getDevAppDB()
|
||||
replication = new dbCore.Replication(config)
|
||||
const devDb = context.getDevAppDB()
|
||||
console.log("Compacting development DB")
|
||||
await devDb.compact()
|
||||
console.log("Replication object created")
|
||||
|
@ -131,7 +119,7 @@ async function deployApp(deployment: any, userId: string) {
|
|||
console.log("replication complete.. replacing app meta doc")
|
||||
// app metadata is excluded as it is likely to be in conflict
|
||||
// replicate the app metadata document manually
|
||||
const db = getProdAppDB()
|
||||
const db = context.getProdAppDB()
|
||||
const appDoc = await devDb.get(DocumentType.APP_METADATA)
|
||||
try {
|
||||
const prodAppDoc = await db.get(DocumentType.APP_METADATA)
|
||||
|
@ -147,7 +135,7 @@ async function deployApp(deployment: any, userId: string) {
|
|||
// remove automation errors if they exist
|
||||
delete appDoc.automationErrors
|
||||
await db.put(appDoc)
|
||||
await appCache.invalidateAppMetadata(productionAppId)
|
||||
await cache.app.invalidateAppMetadata(productionAppId)
|
||||
console.log("New app doc written successfully.")
|
||||
await initDeployedApp(productionAppId)
|
||||
console.log("Deployed app initialised, setting deployment to successful")
|
||||
|
@ -170,7 +158,7 @@ async function deployApp(deployment: any, userId: string) {
|
|||
|
||||
export async function fetchDeployments(ctx: any) {
|
||||
try {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||
const { updated, deployments } = await checkAllDeployments(deploymentDoc)
|
||||
if (updated) {
|
||||
|
@ -184,7 +172,7 @@ export async function fetchDeployments(ctx: any) {
|
|||
|
||||
export async function deploymentProgress(ctx: any) {
|
||||
try {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
||||
} catch (err) {
|
||||
|
@ -197,7 +185,7 @@ export async function deploymentProgress(ctx: any) {
|
|||
|
||||
const isFirstDeploy = async () => {
|
||||
try {
|
||||
const db = getProdAppDB()
|
||||
const db = context.getProdAppDB()
|
||||
await db.get(DocumentType.APP_METADATA)
|
||||
} catch (e: any) {
|
||||
if (e.status === 404) {
|
||||
|
|
|
@ -1,29 +1,23 @@
|
|||
const fetch = require("node-fetch")
|
||||
const env = require("../../environment")
|
||||
const { checkSlashesInUrl } = require("../../utilities")
|
||||
const { request } = require("../../utilities/workerRequests")
|
||||
const { clearLock } = require("../../utilities/redis")
|
||||
const { Replication, getProdAppID } = require("@budibase/backend-core/db")
|
||||
const { DocumentType } = require("../../db/utils")
|
||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
||||
const { getProdAppDB, getAppDB } = require("@budibase/backend-core/context")
|
||||
const { events } = require("@budibase/backend-core")
|
||||
import fetch from "node-fetch"
|
||||
import env from "../../environment"
|
||||
import { checkSlashesInUrl } from "../../utilities"
|
||||
import { request } from "../../utilities/workerRequests"
|
||||
import { clearLock as redisClearLock } from "../../utilities/redis"
|
||||
import { DocumentType } from "../../db/utils"
|
||||
import { context } from "@budibase/backend-core"
|
||||
import { events, db as dbCore, cache } from "@budibase/backend-core"
|
||||
|
||||
async function redirect(ctx, method, path = "global") {
|
||||
async function redirect(ctx: any, method: string, path: string = "global") {
|
||||
const { devPath } = ctx.params
|
||||
const queryString = ctx.originalUrl.split("?")[1] || ""
|
||||
const response = await fetch(
|
||||
checkSlashesInUrl(
|
||||
`${env.WORKER_URL}/api/${path}/${devPath}?${queryString}`
|
||||
),
|
||||
request(
|
||||
ctx,
|
||||
{
|
||||
method,
|
||||
body: ctx.request.body,
|
||||
},
|
||||
true
|
||||
)
|
||||
request(ctx, {
|
||||
method,
|
||||
body: ctx.request.body,
|
||||
})
|
||||
)
|
||||
if (response.status !== 200) {
|
||||
const err = await response.text()
|
||||
|
@ -46,28 +40,28 @@ async function redirect(ctx, method, path = "global") {
|
|||
ctx.cookies
|
||||
}
|
||||
|
||||
exports.buildRedirectGet = path => {
|
||||
return async ctx => {
|
||||
export function buildRedirectGet(path: string) {
|
||||
return async (ctx: any) => {
|
||||
await redirect(ctx, "GET", path)
|
||||
}
|
||||
}
|
||||
|
||||
exports.buildRedirectPost = path => {
|
||||
return async ctx => {
|
||||
export function buildRedirectPost(path: string) {
|
||||
return async (ctx: any) => {
|
||||
await redirect(ctx, "POST", path)
|
||||
}
|
||||
}
|
||||
|
||||
exports.buildRedirectDelete = path => {
|
||||
return async ctx => {
|
||||
export function buildRedirectDelete(path: string) {
|
||||
return async (ctx: any) => {
|
||||
await redirect(ctx, "DELETE", path)
|
||||
}
|
||||
}
|
||||
|
||||
exports.clearLock = async ctx => {
|
||||
export async function clearLock(ctx: any) {
|
||||
const { appId } = ctx.params
|
||||
try {
|
||||
await clearLock(appId, ctx.user)
|
||||
await redisClearLock(appId, ctx.user)
|
||||
} catch (err) {
|
||||
ctx.throw(400, `Unable to remove lock. ${err}`)
|
||||
}
|
||||
|
@ -76,16 +70,16 @@ exports.clearLock = async ctx => {
|
|||
}
|
||||
}
|
||||
|
||||
exports.revert = async ctx => {
|
||||
export async function revert(ctx: any) {
|
||||
const { appId } = ctx.params
|
||||
const productionAppId = getProdAppID(appId)
|
||||
const productionAppId = dbCore.getProdAppID(appId)
|
||||
|
||||
// App must have been deployed first
|
||||
try {
|
||||
const db = getProdAppDB({ skip_setup: true })
|
||||
const info = await db.info()
|
||||
if (info.error) {
|
||||
throw info.error
|
||||
const db = context.getProdAppDB({ skip_setup: true })
|
||||
const exists = await db.exists()
|
||||
if (!exists) {
|
||||
throw new Error("App must be deployed to be reverted.")
|
||||
}
|
||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||
if (
|
||||
|
@ -98,7 +92,7 @@ exports.revert = async ctx => {
|
|||
return ctx.throw(400, "App has not yet been deployed")
|
||||
}
|
||||
|
||||
const replication = new Replication({
|
||||
const replication = new dbCore.Replication({
|
||||
source: productionAppId,
|
||||
target: appId,
|
||||
})
|
||||
|
@ -109,12 +103,12 @@ exports.revert = async ctx => {
|
|||
}
|
||||
|
||||
// update appID in reverted app to be dev version again
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
const appDoc = await db.get(DocumentType.APP_METADATA)
|
||||
appDoc.appId = appId
|
||||
appDoc.instance._id = appId
|
||||
await db.put(appDoc)
|
||||
await appCache.invalidateAppMetadata(appId)
|
||||
await cache.app.invalidateAppMetadata(appId)
|
||||
ctx.body = {
|
||||
message: "Reverted changes successfully.",
|
||||
}
|
||||
|
@ -126,7 +120,7 @@ exports.revert = async ctx => {
|
|||
}
|
||||
}
|
||||
|
||||
exports.getBudibaseVersion = async ctx => {
|
||||
export async function getBudibaseVersion(ctx: any) {
|
||||
const version = require("../../../package.json").version
|
||||
ctx.body = {
|
||||
version,
|
|
@ -1,5 +1,4 @@
|
|||
const { getAllApps } = require("@budibase/backend-core/db")
|
||||
const { doInAppContext } = require("@budibase/backend-core/context")
|
||||
import { db as dbCore, context } from "@budibase/backend-core"
|
||||
import { search as stringSearch, addRev } from "./utils"
|
||||
import * as controller from "../application"
|
||||
import { Application } from "../../../definitions/common"
|
||||
|
@ -15,15 +14,22 @@ function fixAppID(app: Application, params: any) {
|
|||
}
|
||||
|
||||
async function setResponseApp(ctx: any) {
|
||||
if (ctx.body && ctx.body.appId && (!ctx.params || !ctx.params.appId)) {
|
||||
ctx.params = { appId: ctx.body.appId }
|
||||
const appId = ctx.body?.appId
|
||||
if (appId && (!ctx.params || !ctx.params.appId)) {
|
||||
ctx.params = { appId }
|
||||
}
|
||||
if (appId) {
|
||||
await context.doInContext(appId, () => {
|
||||
return controller.fetchAppPackage(ctx)
|
||||
})
|
||||
} else {
|
||||
return controller.fetchAppPackage(ctx)
|
||||
}
|
||||
await controller.fetchAppPackage(ctx)
|
||||
}
|
||||
|
||||
export async function search(ctx: any, next: any) {
|
||||
const { name } = ctx.request.body
|
||||
const apps = await getAllApps({ all: true })
|
||||
const apps = await dbCore.getAllApps({ all: true })
|
||||
ctx.body = stringSearch(apps, name)
|
||||
await next()
|
||||
}
|
||||
|
@ -41,7 +47,7 @@ export async function create(ctx: any, next: any) {
|
|||
}
|
||||
|
||||
export async function read(ctx: any, next: any) {
|
||||
await doInAppContext(ctx.params.appId, async () => {
|
||||
await context.doInAppContext(ctx.params.appId, async () => {
|
||||
await setResponseApp(ctx)
|
||||
await next()
|
||||
})
|
||||
|
@ -49,7 +55,7 @@ export async function read(ctx: any, next: any) {
|
|||
|
||||
export async function update(ctx: any, next: any) {
|
||||
ctx.request.body = await addRev(fixAppID(ctx.request.body, ctx.params))
|
||||
await doInAppContext(ctx.params.appId, async () => {
|
||||
await context.doInAppContext(ctx.params.appId, async () => {
|
||||
await controller.update(ctx)
|
||||
await setResponseApp(ctx)
|
||||
await next()
|
||||
|
@ -57,7 +63,7 @@ export async function update(ctx: any, next: any) {
|
|||
}
|
||||
|
||||
export async function destroy(ctx: any, next: any) {
|
||||
await doInAppContext(ctx.params.appId, async () => {
|
||||
await context.doInAppContext(ctx.params.appId, async () => {
|
||||
// get the app before deleting it
|
||||
await setResponseApp(ctx)
|
||||
const body = ctx.body
|
||||
|
|
|
@ -5,11 +5,8 @@ import { save as saveDatasource } from "../datasource"
|
|||
import { RestImporter } from "./import"
|
||||
import { invalidateDynamicVariables } from "../../../threads/utils"
|
||||
import { QUERY_THREAD_TIMEOUT } from "../../../environment"
|
||||
import { getAppDB } from "@budibase/backend-core/context"
|
||||
import { quotas } from "@budibase/pro"
|
||||
import { events } from "@budibase/backend-core"
|
||||
import { getCookie } from "@budibase/backend-core/utils"
|
||||
import { Cookies, Configs } from "@budibase/backend-core/constants"
|
||||
import { events, context, utils, constants } from "@budibase/backend-core"
|
||||
|
||||
const Runner = new Thread(ThreadType.QUERY, {
|
||||
timeoutMs: QUERY_THREAD_TIMEOUT || 10000,
|
||||
|
@ -28,7 +25,7 @@ function enrichQueries(input: any) {
|
|||
}
|
||||
|
||||
export async function fetch(ctx: any) {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
|
||||
const body = await db.allDocs(
|
||||
getQueryParams(null, {
|
||||
|
@ -81,7 +78,7 @@ const _import = async (ctx: any) => {
|
|||
export { _import as import }
|
||||
|
||||
export async function save(ctx: any) {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
const query = ctx.request.body
|
||||
|
||||
const datasource = await db.get(query.datasourceId)
|
||||
|
@ -103,7 +100,7 @@ export async function save(ctx: any) {
|
|||
}
|
||||
|
||||
export async function find(ctx: any) {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
const query = enrichQueries(await db.get(ctx.params.queryId))
|
||||
// remove properties that could be dangerous in real app
|
||||
if (isProdAppID(ctx.appId)) {
|
||||
|
@ -115,13 +112,13 @@ export async function find(ctx: any) {
|
|||
|
||||
//Required to discern between OIDC OAuth config entries
|
||||
function getOAuthConfigCookieId(ctx: any) {
|
||||
if (ctx.user.providerType === Configs.OIDC) {
|
||||
return getCookie(ctx, Cookies.OIDC_CONFIG)
|
||||
if (ctx.user.providerType === constants.Config.OIDC) {
|
||||
return utils.getCookie(ctx, constants.Cookie.OIDC_CONFIG)
|
||||
}
|
||||
}
|
||||
|
||||
function getAuthConfig(ctx: any) {
|
||||
const authCookie = getCookie(ctx, Cookies.Auth)
|
||||
const authCookie = utils.getCookie(ctx, constants.Cookie.Auth)
|
||||
let authConfigCtx: any = {}
|
||||
authConfigCtx["configId"] = getOAuthConfigCookieId(ctx)
|
||||
authConfigCtx["sessionId"] = authCookie ? authCookie.sessionId : null
|
||||
|
@ -129,7 +126,7 @@ function getAuthConfig(ctx: any) {
|
|||
}
|
||||
|
||||
export async function preview(ctx: any) {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
|
||||
const datasource = await db.get(ctx.request.body.datasourceId)
|
||||
const query = ctx.request.body
|
||||
|
@ -201,7 +198,7 @@ async function execute(
|
|||
ctx: any,
|
||||
opts: any = { rowsOnly: false, isAutomation: false }
|
||||
) {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
|
||||
const query = await db.get(ctx.params.queryId)
|
||||
const datasource = await db.get(query.datasourceId)
|
||||
|
@ -267,7 +264,7 @@ export async function executeV2(
|
|||
}
|
||||
|
||||
const removeDynamicVariables = async (queryId: any) => {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
const query = await db.get(queryId)
|
||||
const datasource = await db.get(query.datasourceId)
|
||||
const dynamicVariables = datasource.config.dynamicVariables
|
||||
|
@ -288,7 +285,7 @@ const removeDynamicVariables = async (queryId: any) => {
|
|||
}
|
||||
|
||||
export async function destroy(ctx: any) {
|
||||
const db = getAppDB()
|
||||
const db = context.getAppDB()
|
||||
const queryId = ctx.params.queryId
|
||||
await removeDynamicVariables(queryId)
|
||||
const query = await db.get(queryId)
|
||||
|
|
|
@ -6,7 +6,7 @@ const {
|
|||
DocumentType,
|
||||
InternalTables,
|
||||
} = require("../../../db/utils")
|
||||
const { dangerousGetDB } = require("@budibase/backend-core/db")
|
||||
const { getDB } = require("@budibase/backend-core/db")
|
||||
const userController = require("../user")
|
||||
const {
|
||||
inputProcessing,
|
||||
|
@ -251,7 +251,7 @@ exports.fetch = async ctx => {
|
|||
}
|
||||
|
||||
exports.find = async ctx => {
|
||||
const db = dangerousGetDB(ctx.appId)
|
||||
const db = getDB(ctx.appId)
|
||||
const table = await db.get(ctx.params.tableId)
|
||||
let row = await findRow(ctx, ctx.params.tableId, ctx.params.rowId)
|
||||
row = await outputProcessing(table, row)
|
||||
|
|
|
@ -3,8 +3,8 @@ const controller = require("../controllers/automation")
|
|||
const authorized = require("../../middleware/authorized")
|
||||
const {
|
||||
BUILDER,
|
||||
PermissionLevels,
|
||||
PermissionTypes,
|
||||
PermissionLevel,
|
||||
PermissionType,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const { bodyResource, paramResource } = require("../../middleware/resourceId")
|
||||
const {
|
||||
|
@ -71,14 +71,14 @@ router
|
|||
"/api/automations/:id/trigger",
|
||||
appInfoMiddleware({ appType: AppType.PROD }),
|
||||
paramResource("id"),
|
||||
authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
||||
authorized(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||
controller.trigger
|
||||
)
|
||||
.post(
|
||||
"/api/automations/:id/test",
|
||||
appInfoMiddleware({ appType: AppType.DEV }),
|
||||
paramResource("id"),
|
||||
authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
||||
authorized(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||
controller.test
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ const datasourceController = require("../controllers/datasource")
|
|||
const authorized = require("../../middleware/authorized")
|
||||
const {
|
||||
BUILDER,
|
||||
PermissionLevels,
|
||||
PermissionTypes,
|
||||
PermissionLevel,
|
||||
PermissionType,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const {
|
||||
datasourceValidator,
|
||||
|
@ -17,17 +17,17 @@ router
|
|||
.get("/api/datasources", authorized(BUILDER), datasourceController.fetch)
|
||||
.get(
|
||||
"/api/datasources/:datasourceId",
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
datasourceController.find
|
||||
)
|
||||
.put(
|
||||
"/api/datasources/:datasourceId",
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
datasourceController.update
|
||||
)
|
||||
.post(
|
||||
"/api/datasources/query",
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
datasourceQueryValidator(),
|
||||
datasourceController.query
|
||||
)
|
||||
|
|
|
@ -13,8 +13,8 @@ import env from "../../../environment"
|
|||
const Router = require("@koa/router")
|
||||
const { RateLimit, Stores } = require("koa2-ratelimit")
|
||||
const {
|
||||
PermissionLevels,
|
||||
PermissionTypes,
|
||||
PermissionLevel,
|
||||
PermissionType,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const { getRedisOptions } = require("@budibase/backend-core/redis").utils
|
||||
|
||||
|
@ -105,7 +105,7 @@ function applyRoutes(
|
|||
: paramResource(resource)
|
||||
const publicApiMiddleware = publicApi({
|
||||
requiresAppId:
|
||||
permType !== PermissionTypes.APP && permType !== PermissionTypes.USER,
|
||||
permType !== PermissionType.APP && permType !== PermissionType.USER,
|
||||
})
|
||||
addMiddleware(endpoints.read, publicApiMiddleware)
|
||||
addMiddleware(endpoints.write, publicApiMiddleware)
|
||||
|
@ -113,8 +113,8 @@ function applyRoutes(
|
|||
addMiddleware(endpoints.read, paramMiddleware)
|
||||
addMiddleware(endpoints.write, paramMiddleware)
|
||||
// add the authorization middleware, using the correct perm type
|
||||
addMiddleware(endpoints.read, authorized(permType, PermissionLevels.READ))
|
||||
addMiddleware(endpoints.write, authorized(permType, PermissionLevels.WRITE))
|
||||
addMiddleware(endpoints.read, authorized(permType, PermissionLevel.READ))
|
||||
addMiddleware(endpoints.write, authorized(permType, PermissionLevel.WRITE))
|
||||
// add the output mapper middleware
|
||||
addMiddleware(endpoints.read, mapperMiddleware, { output: true })
|
||||
addMiddleware(endpoints.write, mapperMiddleware, { output: true })
|
||||
|
@ -122,12 +122,12 @@ function applyRoutes(
|
|||
addToRouter(endpoints.write)
|
||||
}
|
||||
|
||||
applyRoutes(appEndpoints, PermissionTypes.APP, "appId")
|
||||
applyRoutes(tableEndpoints, PermissionTypes.TABLE, "tableId")
|
||||
applyRoutes(userEndpoints, PermissionTypes.USER, "userId")
|
||||
applyRoutes(queryEndpoints, PermissionTypes.QUERY, "queryId")
|
||||
applyRoutes(appEndpoints, PermissionType.APP, "appId")
|
||||
applyRoutes(tableEndpoints, PermissionType.TABLE, "tableId")
|
||||
applyRoutes(userEndpoints, PermissionType.USER, "userId")
|
||||
applyRoutes(queryEndpoints, PermissionType.QUERY, "queryId")
|
||||
// needs to be applied last for routing purposes, don't override other endpoints
|
||||
applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId")
|
||||
applyRoutes(rowEndpoints, PermissionType.TABLE, "tableId", "rowId")
|
||||
|
||||
export default publicRouter
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ async function makeRequest(method, endpoint, body, appId = config.getAppId()) {
|
|||
if (body) {
|
||||
req.send(body)
|
||||
}
|
||||
const res = await req.expect("Content-Type", /json/).expect(200)
|
||||
const res = await req
|
||||
expect(res.body).toBeDefined()
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ const Router = require("@koa/router")
|
|||
const queryController = require("../controllers/query")
|
||||
const authorized = require("../../middleware/authorized")
|
||||
const {
|
||||
PermissionLevels,
|
||||
PermissionTypes,
|
||||
PermissionLevel,
|
||||
PermissionType,
|
||||
BUILDER,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const {
|
||||
|
@ -38,20 +38,20 @@ router
|
|||
.get(
|
||||
"/api/queries/:queryId",
|
||||
paramResource("queryId"),
|
||||
authorized(PermissionTypes.QUERY, PermissionLevels.READ),
|
||||
authorized(PermissionType.QUERY, PermissionLevel.READ),
|
||||
queryController.find
|
||||
)
|
||||
// DEPRECATED - use new query endpoint for future work
|
||||
.post(
|
||||
"/api/queries/:queryId",
|
||||
paramResource("queryId"),
|
||||
authorized(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.QUERY, PermissionLevel.WRITE),
|
||||
queryController.executeV1
|
||||
)
|
||||
.post(
|
||||
"/api/v2/queries/:queryId",
|
||||
paramResource("queryId"),
|
||||
authorized(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.QUERY, PermissionLevel.WRITE),
|
||||
queryController.executeV2
|
||||
)
|
||||
.delete(
|
||||
|
|
|
@ -3,8 +3,8 @@ import * as rowController from "../controllers/row"
|
|||
import authorized from "../../middleware/authorized"
|
||||
import { paramResource, paramSubResource } from "../../middleware/resourceId"
|
||||
const {
|
||||
PermissionLevels,
|
||||
PermissionTypes,
|
||||
PermissionLevel,
|
||||
PermissionType,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const { internalSearchValidator } = require("./utils/validators")
|
||||
|
||||
|
@ -28,7 +28,7 @@ router
|
|||
.get(
|
||||
"/api/:tableId/:rowId/enrich",
|
||||
paramSubResource("tableId", "rowId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
rowController.fetchEnrichedRow
|
||||
)
|
||||
/**
|
||||
|
@ -48,7 +48,7 @@ router
|
|||
.get(
|
||||
"/api/:tableId/rows",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
rowController.fetch
|
||||
)
|
||||
/**
|
||||
|
@ -67,7 +67,7 @@ router
|
|||
.get(
|
||||
"/api/:tableId/rows/:rowId",
|
||||
paramSubResource("tableId", "rowId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
rowController.find
|
||||
)
|
||||
/**
|
||||
|
@ -137,7 +137,7 @@ router
|
|||
"/api/:tableId/search",
|
||||
internalSearchValidator(),
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
rowController.search
|
||||
)
|
||||
// DEPRECATED - this is an old API, but for backwards compat it needs to be
|
||||
|
@ -145,7 +145,7 @@ router
|
|||
.post(
|
||||
"/api/search/:tableId/rows",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
rowController.search
|
||||
)
|
||||
/**
|
||||
|
@ -175,7 +175,7 @@ router
|
|||
.post(
|
||||
"/api/:tableId/rows",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
rowController.save
|
||||
)
|
||||
/**
|
||||
|
@ -189,7 +189,7 @@ router
|
|||
.patch(
|
||||
"/api/:tableId/rows",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
rowController.patch
|
||||
)
|
||||
/**
|
||||
|
@ -215,7 +215,7 @@ router
|
|||
.post(
|
||||
"/api/:tableId/rows/validate",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
rowController.validate
|
||||
)
|
||||
/**
|
||||
|
@ -241,7 +241,7 @@ router
|
|||
.delete(
|
||||
"/api/:tableId/rows",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
rowController.destroy
|
||||
)
|
||||
|
||||
|
@ -261,7 +261,7 @@ router
|
|||
.post(
|
||||
"/api/:tableId/rows/exportRows",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
rowController.exportRows
|
||||
)
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import { budibaseTempDir } from "../../utilities/budibaseDir"
|
|||
import authorized from "../../middleware/authorized"
|
||||
import {
|
||||
BUILDER,
|
||||
PermissionTypes,
|
||||
PermissionLevels,
|
||||
PermissionType,
|
||||
PermissionLevel,
|
||||
} from "@budibase/backend-core/permissions"
|
||||
import * as env from "../../environment"
|
||||
import { paramResource } from "../../middleware/resourceId"
|
||||
|
@ -47,13 +47,13 @@ router
|
|||
.post(
|
||||
"/api/attachments/:tableId/upload",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
controller.uploadFile
|
||||
)
|
||||
.post(
|
||||
"/api/attachments/:tableId/delete",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||
controller.deleteObjects
|
||||
)
|
||||
.get("/app/preview", authorized(BUILDER), controller.serveBuilderPreview)
|
||||
|
@ -61,7 +61,7 @@ router
|
|||
.get("/app/:appUrl/:path*", controller.serveApp)
|
||||
.post(
|
||||
"/api/attachments/:datasourceId/url",
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ),
|
||||
controller.getSignedUploadURL
|
||||
)
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ const authorized = require("../../middleware/authorized")
|
|||
const { paramResource, bodyResource } = require("../../middleware/resourceId")
|
||||
const {
|
||||
BUILDER,
|
||||
PermissionLevels,
|
||||
PermissionTypes,
|
||||
PermissionLevel,
|
||||
PermissionType,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const { tableValidator } = require("./utils/validators")
|
||||
|
||||
|
@ -40,7 +40,7 @@ router
|
|||
.get(
|
||||
"/api/tables/:tableId",
|
||||
paramResource("tableId"),
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ, { schema: true }),
|
||||
authorized(PermissionType.TABLE, PermissionLevel.READ, { schema: true }),
|
||||
tableController.find
|
||||
)
|
||||
/**
|
||||
|
|
|
@ -43,7 +43,7 @@ describe("/static", () => {
|
|||
|
||||
it("should ping from app", async () => {
|
||||
const headers = config.defaultHeaders()
|
||||
headers[constants.Headers.APP_ID] = config.prodAppId
|
||||
headers[constants.Header.APP_ID] = config.prodAppId
|
||||
|
||||
await request
|
||||
.post("/api/bbtel/ping")
|
||||
|
|
|
@ -4,16 +4,21 @@ jest.mock("node-fetch")
|
|||
|
||||
// Mock isProdAppID to we can later mock the implementation and pretend we are
|
||||
// using prod app IDs
|
||||
const authDb = require("@budibase/backend-core/db")
|
||||
const { isProdAppID } = authDb
|
||||
const mockIsProdAppID = jest.fn(isProdAppID)
|
||||
authDb.isProdAppID = mockIsProdAppID
|
||||
|
||||
jest.mock("@budibase/backend-core", () => {
|
||||
const core = jest.requireActual("@budibase/backend-core")
|
||||
return {
|
||||
...core,
|
||||
db: {
|
||||
...core.db,
|
||||
isProdAppID: jest.fn(),
|
||||
}
|
||||
}
|
||||
})
|
||||
const setup = require("./utilities")
|
||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const { checkCacheForDynamicVariable } = require("../../../threads/utils")
|
||||
const { basicQuery, basicDatasource } = setup.structures
|
||||
const { events } = require("@budibase/backend-core")
|
||||
const { events, db: dbCore } = require("@budibase/backend-core")
|
||||
|
||||
describe("/queries", () => {
|
||||
let request = setup.getRequest()
|
||||
|
@ -152,8 +157,8 @@ describe("/queries", () => {
|
|||
|
||||
it("should remove sensitive info for prod apps", async () => {
|
||||
// Mock isProdAppID to pretend we are using a prod app
|
||||
mockIsProdAppID.mockClear()
|
||||
mockIsProdAppID.mockImplementation(() => true)
|
||||
dbCore.isProdAppID.mockClear()
|
||||
dbCore.isProdAppID.mockImplementation(() => true)
|
||||
|
||||
const query = await config.createQuery()
|
||||
const res = await request
|
||||
|
@ -167,8 +172,8 @@ describe("/queries", () => {
|
|||
expect(res.body.schema).toBeDefined()
|
||||
|
||||
// Reset isProdAppID mock
|
||||
expect(mockIsProdAppID).toHaveBeenCalledTimes(1)
|
||||
mockIsProdAppID.mockImplementation(isProdAppID)
|
||||
expect(dbCore.isProdAppID).toHaveBeenCalledTimes(1)
|
||||
dbCore.isProdAppID.mockImplementation(() => false)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
|
||||
const {
|
||||
BUILTIN_PERMISSION_IDS,
|
||||
BuiltinPermissionID,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const setup = require("./utilities")
|
||||
const { basicRole } = setup.structures
|
||||
|
@ -76,18 +76,18 @@ describe("/roles", () => {
|
|||
const adminRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.ADMIN)
|
||||
expect(adminRole).toBeDefined()
|
||||
expect(adminRole.inherits).toEqual(BUILTIN_ROLE_IDS.POWER)
|
||||
expect(adminRole.permissionId).toEqual(BUILTIN_PERMISSION_IDS.ADMIN)
|
||||
expect(adminRole.permissionId).toEqual(BuiltinPermissionID.ADMIN)
|
||||
|
||||
const powerUserRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.POWER)
|
||||
expect(powerUserRole).toBeDefined()
|
||||
expect(powerUserRole.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
||||
expect(powerUserRole.permissionId).toEqual(BUILTIN_PERMISSION_IDS.POWER)
|
||||
expect(powerUserRole.permissionId).toEqual(BuiltinPermissionID.POWER)
|
||||
|
||||
const customRoleFetched = res.body.find(r => r._id === customRole._id)
|
||||
expect(customRoleFetched).toBeDefined()
|
||||
expect(customRoleFetched.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC)
|
||||
expect(customRoleFetched.permissionId).toEqual(
|
||||
BUILTIN_PERMISSION_IDS.READ_ONLY
|
||||
BuiltinPermissionID.READ_ONLY
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -109,7 +109,7 @@ describe("/roles", () => {
|
|||
it("should delete custom roles", async () => {
|
||||
const customRole = await config.createRole({
|
||||
name: "user",
|
||||
permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY,
|
||||
permissionId: BuiltinPermissionID.READ_ONLY,
|
||||
inherits: BUILTIN_ROLE_IDS.BASIC,
|
||||
})
|
||||
delete customRole._rev_tree
|
||||
|
|
|
@ -46,7 +46,7 @@ describe("/static", () => {
|
|||
|
||||
it("should serve the app by id", async () => {
|
||||
const headers = config.defaultHeaders()
|
||||
delete headers[constants.Headers.APP_ID]
|
||||
delete headers[constants.Header.APP_ID]
|
||||
|
||||
const res = await request
|
||||
.get(`/${config.prodAppId}`)
|
||||
|
@ -58,7 +58,7 @@ describe("/static", () => {
|
|||
|
||||
it("should serve the app by url", async () => {
|
||||
const headers = config.defaultHeaders()
|
||||
delete headers[constants.Headers.APP_ID]
|
||||
delete headers[constants.Header.APP_ID]
|
||||
|
||||
const res = await request
|
||||
.get(`/app${config.prodApp.url}`)
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const { getAppDB } = require("@budibase/backend-core/context")
|
||||
const setup = require("./utilities")
|
||||
const { basicTable } = setup.structures
|
||||
const { events } = require("@budibase/backend-core")
|
||||
const { events, context } = require("@budibase/backend-core")
|
||||
|
||||
describe("/tables", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let appId
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
const app = await config.init()
|
||||
appId = app.appId
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
|
@ -199,38 +200,6 @@ describe("/tables", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("indexing", () => {
|
||||
it("should be able to create a table with indexes", async () => {
|
||||
const db = getAppDB(config)
|
||||
const indexCount = (await db.getIndexes()).total_rows
|
||||
const table = basicTable()
|
||||
table.indexes = ["name"]
|
||||
const res = await request
|
||||
.post(`/api/tables`)
|
||||
.send(table)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body._id).toBeDefined()
|
||||
expect(res.body._rev).toBeDefined()
|
||||
expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1)
|
||||
// update index to see what happens
|
||||
table.indexes = ["name", "description"]
|
||||
await request
|
||||
.post(`/api/tables`)
|
||||
.send({
|
||||
...table,
|
||||
_id: res.body._id,
|
||||
_rev: res.body._rev,
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
// shouldn't have created a new index
|
||||
expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validate csv", () => {
|
||||
it("should be able to validate a CSV layout", async () => {
|
||||
const res = await request
|
||||
|
@ -249,6 +218,40 @@ describe("/tables", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("indexing", () => {
|
||||
it("should be able to create a table with indexes", async () => {
|
||||
await context.doInAppContext(appId, async () => {
|
||||
const db = context.getAppDB()
|
||||
const indexCount = (await db.getIndexes()).total_rows
|
||||
const table = basicTable()
|
||||
table.indexes = ["name"]
|
||||
const res = await request
|
||||
.post(`/api/tables`)
|
||||
.send(table)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body._id).toBeDefined()
|
||||
expect(res.body._rev).toBeDefined()
|
||||
expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1)
|
||||
// update index to see what happens
|
||||
table.indexes = ["name", "description"]
|
||||
await request
|
||||
.post(`/api/tables`)
|
||||
.send({
|
||||
...table,
|
||||
_id: res.body._id,
|
||||
_rev: res.body._rev,
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
// shouldn't have created a new index
|
||||
expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
let testTable
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ const Router = require("@koa/router")
|
|||
const controller = require("../controllers/user")
|
||||
const authorized = require("../../middleware/authorized")
|
||||
const {
|
||||
PermissionLevels,
|
||||
PermissionTypes,
|
||||
PermissionLevel,
|
||||
PermissionType,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
|
||||
const router = new Router()
|
||||
|
@ -11,42 +11,42 @@ const router = new Router()
|
|||
router
|
||||
.get(
|
||||
"/api/users/metadata",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
||||
authorized(PermissionType.USER, PermissionLevel.READ),
|
||||
controller.fetchMetadata
|
||||
)
|
||||
.get(
|
||||
"/api/users/metadata/:id",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
||||
authorized(PermissionType.USER, PermissionLevel.READ),
|
||||
controller.findMetadata
|
||||
)
|
||||
.put(
|
||||
"/api/users/metadata",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
||||
controller.updateMetadata
|
||||
)
|
||||
.post(
|
||||
"/api/users/metadata/self",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
||||
controller.updateSelfMetadata
|
||||
)
|
||||
.delete(
|
||||
"/api/users/metadata/:id",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
||||
controller.destroyMetadata
|
||||
)
|
||||
.post(
|
||||
"/api/users/metadata/sync/:id",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
||||
controller.syncUser
|
||||
)
|
||||
.post(
|
||||
"/api/users/flags",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
||||
controller.setFlag
|
||||
)
|
||||
.get(
|
||||
"/api/users/flags",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.READ),
|
||||
authorized(PermissionType.USER, PermissionLevel.READ),
|
||||
controller.getFlags
|
||||
)
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
const { joiValidator } = require("@budibase/backend-core/auth")
|
||||
const { DataSourceOperation } = require("../../../constants")
|
||||
const {
|
||||
BUILTIN_PERMISSION_IDS,
|
||||
PermissionLevels,
|
||||
BuiltinPermissionID,
|
||||
PermissionLevel,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const { WebhookActionType } = require("@budibase/types")
|
||||
const Joi = require("joi")
|
||||
|
@ -133,14 +133,14 @@ exports.webhookValidator = () => {
|
|||
}
|
||||
|
||||
exports.roleValidator = () => {
|
||||
const permLevelArray = Object.values(PermissionLevels)
|
||||
const permLevelArray = Object.values(PermissionLevel)
|
||||
// prettier-ignore
|
||||
return joiValidator.body(Joi.object({
|
||||
_id: OPTIONAL_STRING,
|
||||
_rev: OPTIONAL_STRING,
|
||||
name: Joi.string().required(),
|
||||
// this is the base permission ID (for now a built in)
|
||||
permissionId: Joi.string().valid(...Object.values(BUILTIN_PERMISSION_IDS)).required(),
|
||||
permissionId: Joi.string().valid(...Object.values(BuiltinPermissionID)).required(),
|
||||
permissions: Joi.object()
|
||||
.pattern(/.*/, [Joi.string().valid(...permLevelArray)])
|
||||
.optional(),
|
||||
|
@ -149,7 +149,7 @@ exports.roleValidator = () => {
|
|||
}
|
||||
|
||||
exports.permissionValidator = () => {
|
||||
const permLevelArray = Object.values(PermissionLevels)
|
||||
const permLevelArray = Object.values(PermissionLevel)
|
||||
// prettier-ignore
|
||||
return joiValidator.params(Joi.object({
|
||||
level: Joi.string().valid(...permLevelArray).required(),
|
||||
|
|
|
@ -5,8 +5,8 @@ const authorized = require("../../middleware/authorized")
|
|||
const { paramResource } = require("../../middleware/resourceId")
|
||||
const {
|
||||
BUILDER,
|
||||
PermissionTypes,
|
||||
PermissionLevels,
|
||||
PermissionType,
|
||||
PermissionLevel,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
|
||||
const router = new Router()
|
||||
|
@ -16,7 +16,7 @@ router
|
|||
.get(
|
||||
"/api/views/:viewName",
|
||||
paramResource("viewName"),
|
||||
authorized(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||
authorized(PermissionType.VIEW, PermissionLevel.READ),
|
||||
rowController.fetchView
|
||||
)
|
||||
.get("/api/views", authorized(BUILDER), viewController.fetch)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const env = require("../../environment")
|
||||
const setup = require("./utilities")
|
||||
|
||||
describe("test the update row action", () => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const TestConfig = require("../../../tests/utilities/TestConfiguration")
|
||||
const { TENANT_ID } = require("../../../tests/utilities/structures")
|
||||
const { doInTenant } = require("@budibase/backend-core/tenancy")
|
||||
const { context } = require("@budibase/backend-core")
|
||||
const actions = require("../../actions")
|
||||
const emitter = require("../../../events/index")
|
||||
const env = require("../../../environment")
|
||||
|
@ -33,7 +32,7 @@ exports.runInProd = async fn => {
|
|||
}
|
||||
|
||||
exports.runStep = async function runStep(stepId, inputs) {
|
||||
return doInTenant(TENANT_ID, async () => {
|
||||
async function run() {
|
||||
let step = await actions.getAction(stepId)
|
||||
expect(step).toBeDefined()
|
||||
return step({
|
||||
|
@ -43,7 +42,14 @@ exports.runStep = async function runStep(stepId, inputs) {
|
|||
apiKey: exports.apiKey,
|
||||
emitter,
|
||||
})
|
||||
})
|
||||
}
|
||||
if (config?.appId) {
|
||||
return context.doInContext(config?.appId, async () => {
|
||||
return run()
|
||||
})
|
||||
} else {
|
||||
return run()
|
||||
}
|
||||
}
|
||||
|
||||
exports.apiKey = "test"
|
||||
|
|
|
@ -4,15 +4,9 @@ import { automationQueue } from "./bullboard"
|
|||
import newid from "../db/newid"
|
||||
import { updateEntityMetadata } from "../utilities"
|
||||
import { MetadataTypes } from "../constants"
|
||||
import { getProdAppID, doWithDB } from "@budibase/backend-core/db"
|
||||
import { db as dbCore, context } from "@budibase/backend-core"
|
||||
import { getAutomationMetadataParams } from "../db/utils"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import {
|
||||
getAppDB,
|
||||
getAppId,
|
||||
getProdAppDB,
|
||||
} from "@budibase/backend-core/context"
|
||||
import { context } from "@budibase/backend-core"
|
||||
import { quotas } from "@budibase/pro"
|
||||
import { Automation, WebhookActionType } from "@budibase/types"
|
||||
import sdk from "../sdk"
|
||||
|
@ -102,7 +96,7 @@ export async function disableCronById(jobId: number | string) {
|
|||
}
|
||||
|
||||
export async function clearMetadata() {
|
||||
const db = getProdAppDB()
|
||||
const db = context.getProdAppDB()
|
||||
const automationMetadata = (
|
||||
await db.allDocs(
|
||||
getAutomationMetadataParams({
|
||||
|
@ -157,7 +151,7 @@ export async function enableCronTrigger(appId: any, automation: Automation) {
|
|||
// can't use getAppDB here as this is likely to be called from dev app,
|
||||
// but this call could be for dev app or prod app, need to just use what
|
||||
// was passed in
|
||||
await doWithDB(appId, async (db: any) => {
|
||||
await dbCore.doWithDB(appId, async (db: any) => {
|
||||
const response = await db.put(automation)
|
||||
automation._id = response.id
|
||||
automation._rev = response.rev
|
||||
|
@ -175,7 +169,10 @@ export async function enableCronTrigger(appId: any, automation: Automation) {
|
|||
* written to DB (this does not write to DB as it would be wasteful to repeat).
|
||||
*/
|
||||
export async function checkForWebhooks({ oldAuto, newAuto }: any) {
|
||||
const appId = getAppId()
|
||||
const appId = context.getAppId()
|
||||
if (!appId) {
|
||||
throw new Error("Unable to check webhooks - no app ID in context.")
|
||||
}
|
||||
const oldTrigger = oldAuto ? oldAuto.definition.trigger : null
|
||||
const newTrigger = newAuto ? newAuto.definition.trigger : null
|
||||
const triggerChanged =
|
||||
|
@ -194,7 +191,7 @@ export async function checkForWebhooks({ oldAuto, newAuto }: any) {
|
|||
oldTrigger.webhookId
|
||||
) {
|
||||
try {
|
||||
let db = getAppDB()
|
||||
let db = context.getAppDB()
|
||||
// need to get the webhook to get the rev
|
||||
const webhook = await db.get(oldTrigger.webhookId)
|
||||
// might be updating - reset the inputs to remove the URLs
|
||||
|
@ -224,7 +221,7 @@ export async function checkForWebhooks({ oldAuto, newAuto }: any) {
|
|||
// the app ID has to be development for this endpoint
|
||||
// it can only be used when building the app
|
||||
// but the trigger endpoint will always be used in production
|
||||
const prodAppId = getProdAppID(appId)
|
||||
const prodAppId = dbCore.getProdAppID(appId)
|
||||
newTrigger.inputs = {
|
||||
schemaUrl: `api/webhooks/schema/${appId}/${id}`,
|
||||
triggerUrl: `api/webhooks/trigger/${prodAppId}/${id}`,
|
||||
|
|
|
@ -2,7 +2,7 @@ const newid = require("./newid")
|
|||
|
||||
// bypass the main application db config
|
||||
// use in memory pouchdb directly
|
||||
const { getPouch, closeDB } = require("@budibase/backend-core/db")
|
||||
const { getPouch, closePouchDB } = require("@budibase/backend-core/db")
|
||||
const Pouch = getPouch({ inMemory: true })
|
||||
|
||||
exports.runView = async (view, calculation, group, data) => {
|
||||
|
@ -44,6 +44,6 @@ exports.runView = async (view, calculation, group, data) => {
|
|||
return response
|
||||
} finally {
|
||||
await db.destroy()
|
||||
await closeDB(db)
|
||||
await closePouchDB(db)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
const TestConfig = require("../../tests/utilities/TestConfiguration")
|
||||
const { basicRow, basicLinkedRow, basicTable } = require("../../tests/utilities/structures")
|
||||
const LinkController = require("../linkedRows/LinkController")
|
||||
const { context } = require("@budibase/backend-core")
|
||||
const { RelationshipTypes } = require("../../constants")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
|
||||
describe("test the link controller", () => {
|
||||
let config = new TestConfig(false)
|
||||
let table1, table2
|
||||
let table1, table2, appId
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
const app = await config.init()
|
||||
appId = app.appId
|
||||
const { _id } = await config.createTable()
|
||||
table2 = await config.createLinkedTable(RelationshipTypes.MANY_TO_MANY, ["link", "link2"])
|
||||
// update table after creating link
|
||||
|
@ -18,18 +20,20 @@ describe("test the link controller", () => {
|
|||
|
||||
afterAll(config.end)
|
||||
|
||||
function createLinkController(table, row = null, oldTable = null) {
|
||||
const linkConfig = {
|
||||
tableId: table._id,
|
||||
table,
|
||||
}
|
||||
if (row) {
|
||||
linkConfig.row = row
|
||||
}
|
||||
if (oldTable) {
|
||||
linkConfig.oldTable = oldTable
|
||||
}
|
||||
return new LinkController(linkConfig)
|
||||
async function createLinkController(table, row = null, oldTable = null) {
|
||||
return context.doInAppContext(appId, () => {
|
||||
const linkConfig = {
|
||||
tableId: table._id,
|
||||
table,
|
||||
}
|
||||
if (row) {
|
||||
linkConfig.row = row
|
||||
}
|
||||
if (oldTable) {
|
||||
linkConfig.oldTable = oldTable
|
||||
}
|
||||
return new LinkController(linkConfig)
|
||||
})
|
||||
}
|
||||
|
||||
async function createLinkedRow(linkField = "link", t1 = table1, t2 = table2) {
|
||||
|
@ -38,16 +42,16 @@ describe("test the link controller", () => {
|
|||
return config.getRow(t1._id, _id)
|
||||
}
|
||||
|
||||
it("should be able to confirm if two table schemas are equal", () => {
|
||||
const controller = createLinkController(table1)
|
||||
it("should be able to confirm if two table schemas are equal", async () => {
|
||||
const controller = await createLinkController(table1)
|
||||
let equal = controller.areLinkSchemasEqual(table2.schema.link, table2.schema.link)
|
||||
expect(equal).toEqual(true)
|
||||
equal = controller.areLinkSchemasEqual(table1.schema.link, table2.schema.link)
|
||||
expect(equal).toEqual(false)
|
||||
})
|
||||
|
||||
it("should be able to check the relationship types across two fields", () => {
|
||||
const controller = createLinkController(table1)
|
||||
it("should be able to check the relationship types across two fields", async () => {
|
||||
const controller = await createLinkController(table1)
|
||||
// empty case
|
||||
let output = controller.handleRelationshipType({}, {})
|
||||
expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY)
|
||||
|
@ -65,29 +69,33 @@ describe("test the link controller", () => {
|
|||
|
||||
it("should be able to delete a row", async () => {
|
||||
const row = await createLinkedRow()
|
||||
const controller = createLinkController(table1, row)
|
||||
// get initial count
|
||||
const beforeLinks = await controller.getRowLinkDocs(row._id)
|
||||
await controller.rowDeleted()
|
||||
let afterLinks = await controller.getRowLinkDocs(row._id)
|
||||
expect(beforeLinks.length).toEqual(1)
|
||||
expect(afterLinks.length).toEqual(0)
|
||||
const controller = await createLinkController(table1, row)
|
||||
await context.doInAppContext(appId, async () => {
|
||||
// get initial count
|
||||
const beforeLinks = await controller.getRowLinkDocs(row._id)
|
||||
await controller.rowDeleted()
|
||||
let afterLinks = await controller.getRowLinkDocs(row._id)
|
||||
expect(beforeLinks.length).toEqual(1)
|
||||
expect(afterLinks.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
it("shouldn't throw an error when deleting a row with no links", async () => {
|
||||
const row = await config.createRow(basicRow(table1._id))
|
||||
const controller = createLinkController(table1, row)
|
||||
let error
|
||||
try {
|
||||
await controller.rowDeleted()
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
expect(error).toBeUndefined()
|
||||
const controller = await createLinkController(table1, row)
|
||||
await context.doInAppContext(appId, async () => {
|
||||
let error
|
||||
try {
|
||||
await controller.rowDeleted()
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
expect(error).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it("should throw an error when validating a table which is invalid", () => {
|
||||
const controller = createLinkController(table1)
|
||||
it("should throw an error when validating a table which is invalid", async () => {
|
||||
const controller = await createLinkController(table1)
|
||||
const copyTable = {
|
||||
...table1
|
||||
}
|
||||
|
@ -110,32 +118,38 @@ describe("test the link controller", () => {
|
|||
const row = await createLinkedRow()
|
||||
// remove the link from the row
|
||||
row.link = []
|
||||
const controller = createLinkController(table1, row)
|
||||
await controller.rowSaved()
|
||||
let links = await controller.getRowLinkDocs(row._id)
|
||||
expect(links.length).toEqual(0)
|
||||
const controller = await createLinkController(table1, row)
|
||||
await context.doInAppContext(appId, async () => {
|
||||
await controller.rowSaved()
|
||||
let links = await controller.getRowLinkDocs(row._id)
|
||||
expect(links.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
it("should be able to delete a table and have links deleted", async () => {
|
||||
await createLinkedRow()
|
||||
const controller = createLinkController(table1)
|
||||
let before = await controller.getTableLinkDocs()
|
||||
await controller.tableDeleted()
|
||||
let after = await controller.getTableLinkDocs()
|
||||
expect(before.length).toEqual(1)
|
||||
expect(after.length).toEqual(0)
|
||||
const controller = await createLinkController(table1)
|
||||
await context.doInAppContext(appId, async () => {
|
||||
let before = await controller.getTableLinkDocs()
|
||||
await controller.tableDeleted()
|
||||
let after = await controller.getTableLinkDocs()
|
||||
expect(before.length).toEqual(1)
|
||||
expect(after.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
it("should be able to remove a linked field from a table", async () => {
|
||||
await createLinkedRow()
|
||||
await createLinkedRow("link2")
|
||||
const controller = createLinkController(table1, null, table1)
|
||||
let before = await controller.getTableLinkDocs()
|
||||
await controller.removeFieldFromTable("link")
|
||||
let after = await controller.getTableLinkDocs()
|
||||
expect(before.length).toEqual(2)
|
||||
// shouldn't delete the other field
|
||||
expect(after.length).toEqual(1)
|
||||
const controller = await createLinkController(table1, null, table1)
|
||||
await context.doInAppContext(appId, async () => {
|
||||
let before = await controller.getTableLinkDocs()
|
||||
await controller.removeFieldFromTable("link")
|
||||
let after = await controller.getTableLinkDocs()
|
||||
expect(before.length).toEqual(2)
|
||||
// shouldn't delete the other field
|
||||
expect(after.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
it("should throw an error when overwriting a link column", async () => {
|
||||
|
@ -143,7 +157,7 @@ describe("test the link controller", () => {
|
|||
update.schema.link.relationshipType = RelationshipTypes.MANY_TO_ONE
|
||||
let error
|
||||
try {
|
||||
const controller = createLinkController(update)
|
||||
const controller = await createLinkController(update)
|
||||
await controller.tableSaved()
|
||||
} catch (err) {
|
||||
error = err
|
||||
|
@ -156,10 +170,12 @@ describe("test the link controller", () => {
|
|||
await createLinkedRow()
|
||||
const newTable = cloneDeep(table1)
|
||||
delete newTable.schema.link
|
||||
const controller = createLinkController(newTable, null, table1)
|
||||
await controller.tableUpdated()
|
||||
const links = await controller.getTableLinkDocs()
|
||||
expect(links.length).toEqual(0)
|
||||
const controller = await createLinkController(newTable, null, table1)
|
||||
await context.doInAppContext(appId, async () => {
|
||||
await controller.tableUpdated()
|
||||
const links = await controller.getTableLinkDocs()
|
||||
expect(links.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
it("shouldn't allow one to many having many relationships against it", async () => {
|
||||
|
|
|
@ -1,30 +1,34 @@
|
|||
const TestConfig = require("../../tests/utilities/TestConfiguration")
|
||||
const { basicTable } = require("../../tests/utilities/structures")
|
||||
const linkUtils = require("../linkedRows/linkUtils")
|
||||
const { getAppDB } = require("@budibase/backend-core/context")
|
||||
const { doWithDB } = require("@budibase/backend-core/db")
|
||||
const { context } = require("@budibase/backend-core")
|
||||
|
||||
describe("test link functionality", () => {
|
||||
const config = new TestConfig(false)
|
||||
let appId
|
||||
|
||||
describe("getLinkedTable", () => {
|
||||
let db, table
|
||||
let table
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
db = getAppDB()
|
||||
const app = await config.init()
|
||||
appId = app.appId
|
||||
table = await config.createTable()
|
||||
})
|
||||
|
||||
it("should be able to retrieve a linked table from a list", async () => {
|
||||
const retrieved = await linkUtils.getLinkedTable(table._id, [table])
|
||||
expect(retrieved._id).toBe(table._id)
|
||||
await context.doInAppContext(appId, async () => {
|
||||
const retrieved = await linkUtils.getLinkedTable(table._id, [table])
|
||||
expect(retrieved._id).toBe(table._id)
|
||||
})
|
||||
})
|
||||
|
||||
it("should be able to retrieve a table from DB and update list", async () => {
|
||||
const tables = []
|
||||
const retrieved = await linkUtils.getLinkedTable(table._id, tables)
|
||||
expect(retrieved._id).toBe(table._id)
|
||||
expect(tables[0]).toBeDefined()
|
||||
await context.doInAppContext(appId, async () => {
|
||||
const retrieved = await linkUtils.getLinkedTable(table._id, tables)
|
||||
expect(retrieved._id).toBe(table._id)
|
||||
expect(tables[0]).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -48,15 +52,14 @@ describe("test link functionality", () => {
|
|||
describe("getLinkDocuments", () => {
|
||||
it("should create the link view when it doesn't exist", async () => {
|
||||
// create the DB and a very basic app design DB
|
||||
const output = await doWithDB("test", async db => {
|
||||
await db.put({ _id: "_design/database", views: {} })
|
||||
return await linkUtils.getLinkDocuments({
|
||||
await context.doInAppContext(appId, async () => {
|
||||
const output = await linkUtils.getLinkDocuments({
|
||||
tableId: "test",
|
||||
rowId: "test",
|
||||
includeDocs: false,
|
||||
})
|
||||
expect(Array.isArray(output)).toBe(true)
|
||||
})
|
||||
expect(Array.isArray(output)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,44 +1,19 @@
|
|||
const newid = require("./newid")
|
||||
const {
|
||||
DocumentType: CoreDocType,
|
||||
InternalTable,
|
||||
getRoleParams,
|
||||
generateRoleID,
|
||||
APP_DEV_PREFIX,
|
||||
APP_PREFIX,
|
||||
SEPARATOR,
|
||||
StaticDatabases,
|
||||
isDevAppID,
|
||||
isProdAppID,
|
||||
getDevelopmentAppID,
|
||||
generateAppID,
|
||||
getQueryIndex,
|
||||
ViewName,
|
||||
getDocParams,
|
||||
getRowParams,
|
||||
generateRowID,
|
||||
getUserMetadataParams,
|
||||
generateUserMetadataID,
|
||||
getGlobalIDFromUserMetadataID,
|
||||
} = require("@budibase/backend-core/db")
|
||||
import newid from "./newid"
|
||||
import { db as dbCore } from "@budibase/backend-core"
|
||||
|
||||
const UNICODE_MAX = "\ufff0"
|
||||
type Optional = string | null
|
||||
|
||||
const AppStatus = {
|
||||
export const AppStatus = {
|
||||
DEV: "development",
|
||||
ALL: "all",
|
||||
DEPLOYED: "published",
|
||||
}
|
||||
|
||||
const DocumentType = CoreDocType
|
||||
|
||||
const SearchIndexes = {
|
||||
export const SearchIndexes = {
|
||||
ROWS: "rows",
|
||||
}
|
||||
|
||||
exports.StaticDatabases = StaticDatabases
|
||||
|
||||
const BudibaseInternalDB = {
|
||||
export const BudibaseInternalDB = {
|
||||
_id: "bb_internal",
|
||||
type: "budibase",
|
||||
name: "Budibase DB",
|
||||
|
@ -46,37 +21,36 @@ const BudibaseInternalDB = {
|
|||
config: {},
|
||||
}
|
||||
|
||||
exports.APP_PREFIX = APP_PREFIX
|
||||
exports.APP_DEV_PREFIX = APP_DEV_PREFIX
|
||||
exports.isDevAppID = isDevAppID
|
||||
exports.isProdAppID = isProdAppID
|
||||
exports.USER_METDATA_PREFIX = `${DocumentType.ROW}${SEPARATOR}${InternalTable.USER_METADATA}${SEPARATOR}`
|
||||
exports.LINK_USER_METADATA_PREFIX = `${DocumentType.LINK}${SEPARATOR}${InternalTable.USER_METADATA}${SEPARATOR}`
|
||||
exports.TABLE_ROW_PREFIX = `${DocumentType.ROW}${SEPARATOR}${DocumentType.TABLE}`
|
||||
exports.ViewName = ViewName
|
||||
exports.InternalTables = InternalTable
|
||||
exports.DocumentType = DocumentType
|
||||
exports.SEPARATOR = SEPARATOR
|
||||
exports.UNICODE_MAX = UNICODE_MAX
|
||||
exports.SearchIndexes = SearchIndexes
|
||||
exports.AppStatus = AppStatus
|
||||
exports.BudibaseInternalDB = BudibaseInternalDB
|
||||
exports.generateAppID = generateAppID
|
||||
exports.generateDevAppID = getDevelopmentAppID
|
||||
exports.generateRoleID = generateRoleID
|
||||
exports.getRoleParams = getRoleParams
|
||||
exports.getQueryIndex = getQueryIndex
|
||||
exports.getDocParams = getDocParams
|
||||
exports.getRowParams = getRowParams
|
||||
exports.generateRowID = generateRowID
|
||||
exports.getUserMetadataParams = getUserMetadataParams
|
||||
exports.generateUserMetadataID = generateUserMetadataID
|
||||
exports.getGlobalIDFromUserMetadataID = getGlobalIDFromUserMetadataID
|
||||
export const SEPARATOR = dbCore.SEPARATOR
|
||||
export const StaticDatabases = dbCore.StaticDatabases
|
||||
export const DocumentType = dbCore.DocumentType
|
||||
export const APP_PREFIX = dbCore.APP_PREFIX
|
||||
export const APP_DEV_PREFIX = dbCore.APP_DEV_PREFIX
|
||||
export const isDevAppID = dbCore.isDevAppID
|
||||
export const isProdAppID = dbCore.isProdAppID
|
||||
export const USER_METDATA_PREFIX = `${DocumentType.ROW}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
||||
export const LINK_USER_METADATA_PREFIX = `${DocumentType.LINK}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
||||
export const TABLE_ROW_PREFIX = `${DocumentType.ROW}${SEPARATOR}${DocumentType.TABLE}`
|
||||
export const ViewName = dbCore.ViewName
|
||||
export const InternalTables = dbCore.InternalTable
|
||||
export const UNICODE_MAX = dbCore.UNICODE_MAX
|
||||
export const generateAppID = dbCore.generateAppID
|
||||
export const generateDevAppID = dbCore.getDevelopmentAppID
|
||||
export const generateRoleID = dbCore.generateRoleID
|
||||
export const getRoleParams = dbCore.getRoleParams
|
||||
export const getQueryIndex = dbCore.getQueryIndex
|
||||
export const getDocParams = dbCore.getDocParams
|
||||
export const getRowParams = dbCore.getRowParams
|
||||
export const generateRowID = dbCore.generateRowID
|
||||
export const getUserMetadataParams = dbCore.getUserMetadataParams
|
||||
export const generateUserMetadataID = dbCore.generateUserMetadataID
|
||||
export const getGlobalIDFromUserMetadataID =
|
||||
dbCore.getGlobalIDFromUserMetadataID
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving tables, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getTableParams = (tableId = null, otherProps = {}) => {
|
||||
export function getTableParams(tableId?: Optional, otherProps = {}) {
|
||||
return getDocParams(DocumentType.TABLE, tableId, otherProps)
|
||||
}
|
||||
|
||||
|
@ -84,7 +58,7 @@ exports.getTableParams = (tableId = null, otherProps = {}) => {
|
|||
* Generates a new table ID.
|
||||
* @returns {string} The new table ID which the table doc can be stored under.
|
||||
*/
|
||||
exports.generateTableID = () => {
|
||||
export function generateTableID() {
|
||||
return `${DocumentType.TABLE}${SEPARATOR}${newid()}`
|
||||
}
|
||||
|
||||
|
@ -93,7 +67,7 @@ exports.generateTableID = () => {
|
|||
* @param {string} rowId The ID of the row.
|
||||
* @returns {string} The table ID.
|
||||
*/
|
||||
exports.getTableIDFromRowID = rowId => {
|
||||
export function getTableIDFromRowID(rowId: string) {
|
||||
const components = rowId
|
||||
.split(DocumentType.TABLE + SEPARATOR)[1]
|
||||
.split(SEPARATOR)
|
||||
|
@ -103,7 +77,10 @@ exports.getTableIDFromRowID = rowId => {
|
|||
/**
|
||||
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getAutomationParams = (automationId = null, otherProps = {}) => {
|
||||
export function getAutomationParams(
|
||||
automationId?: Optional,
|
||||
otherProps: any = {}
|
||||
) {
|
||||
return getDocParams(DocumentType.AUTOMATION, automationId, otherProps)
|
||||
}
|
||||
|
||||
|
@ -111,7 +88,7 @@ exports.getAutomationParams = (automationId = null, otherProps = {}) => {
|
|||
* Generates a new automation ID.
|
||||
* @returns {string} The new automation ID which the automation doc can be stored under.
|
||||
*/
|
||||
exports.generateAutomationID = () => {
|
||||
export function generateAutomationID() {
|
||||
return `${DocumentType.AUTOMATION}${SEPARATOR}${newid()}`
|
||||
}
|
||||
|
||||
|
@ -126,14 +103,14 @@ exports.generateAutomationID = () => {
|
|||
* @param {string} fieldName2 the name of the field in the linked row.
|
||||
* @returns {string} The new link doc ID which the automation doc can be stored under.
|
||||
*/
|
||||
exports.generateLinkID = (
|
||||
tableId1,
|
||||
tableId2,
|
||||
rowId1,
|
||||
rowId2,
|
||||
fieldName1,
|
||||
fieldName2
|
||||
) => {
|
||||
export function generateLinkID(
|
||||
tableId1: string,
|
||||
tableId2: string,
|
||||
rowId1: string,
|
||||
rowId2: string,
|
||||
fieldName1: string,
|
||||
fieldName2: string
|
||||
) {
|
||||
const tables = `${SEPARATOR}${tableId1}${SEPARATOR}${tableId2}`
|
||||
const rows = `${SEPARATOR}${rowId1}${SEPARATOR}${rowId2}`
|
||||
const fields = `${SEPARATOR}${fieldName1}${SEPARATOR}${fieldName2}`
|
||||
|
@ -143,7 +120,7 @@ exports.generateLinkID = (
|
|||
/**
|
||||
* Gets parameters for retrieving link docs, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getLinkParams = (otherProps = {}) => {
|
||||
export function getLinkParams(otherProps: any = {}) {
|
||||
return getDocParams(DocumentType.LINK, null, otherProps)
|
||||
}
|
||||
|
||||
|
@ -151,14 +128,14 @@ exports.getLinkParams = (otherProps = {}) => {
|
|||
* Generates a new layout ID.
|
||||
* @returns {string} The new layout ID which the layout doc can be stored under.
|
||||
*/
|
||||
exports.generateLayoutID = id => {
|
||||
export function generateLayoutID(id: string) {
|
||||
return `${DocumentType.LAYOUT}${SEPARATOR}${id || newid()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving layout, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getLayoutParams = (layoutId = null, otherProps = {}) => {
|
||||
export function getLayoutParams(layoutId?: Optional, otherProps: any = {}) {
|
||||
return getDocParams(DocumentType.LAYOUT, layoutId, otherProps)
|
||||
}
|
||||
|
||||
|
@ -166,14 +143,14 @@ exports.getLayoutParams = (layoutId = null, otherProps = {}) => {
|
|||
* Generates a new screen ID.
|
||||
* @returns {string} The new screen ID which the screen doc can be stored under.
|
||||
*/
|
||||
exports.generateScreenID = () => {
|
||||
export function generateScreenID() {
|
||||
return `${DocumentType.SCREEN}${SEPARATOR}${newid()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving screens, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getScreenParams = (screenId = null, otherProps = {}) => {
|
||||
export function getScreenParams(screenId?: Optional, otherProps: any = {}) {
|
||||
return getDocParams(DocumentType.SCREEN, screenId, otherProps)
|
||||
}
|
||||
|
||||
|
@ -181,14 +158,14 @@ exports.getScreenParams = (screenId = null, otherProps = {}) => {
|
|||
* Generates a new webhook ID.
|
||||
* @returns {string} The new webhook ID which the webhook doc can be stored under.
|
||||
*/
|
||||
exports.generateWebhookID = () => {
|
||||
export function generateWebhookID() {
|
||||
return `${DocumentType.WEBHOOK}${SEPARATOR}${newid()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving a webhook, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getWebhookParams = (webhookId = null, otherProps = {}) => {
|
||||
export function getWebhookParams(webhookId?: Optional, otherProps: any = {}) {
|
||||
return getDocParams(DocumentType.WEBHOOK, webhookId, otherProps)
|
||||
}
|
||||
|
||||
|
@ -196,7 +173,7 @@ exports.getWebhookParams = (webhookId = null, otherProps = {}) => {
|
|||
* Generates a new datasource ID.
|
||||
* @returns {string} The new datasource ID which the webhook doc can be stored under.
|
||||
*/
|
||||
exports.generateDatasourceID = ({ plus = false } = {}) => {
|
||||
export function generateDatasourceID({ plus = false } = {}) {
|
||||
return `${
|
||||
plus ? DocumentType.DATASOURCE_PLUS : DocumentType.DATASOURCE
|
||||
}${SEPARATOR}${newid()}`
|
||||
|
@ -205,7 +182,10 @@ exports.generateDatasourceID = ({ plus = false } = {}) => {
|
|||
/**
|
||||
* Gets parameters for retrieving a datasource, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getDatasourceParams = (datasourceId = null, otherProps = {}) => {
|
||||
export function getDatasourceParams(
|
||||
datasourceId?: Optional,
|
||||
otherProps: any = {}
|
||||
) {
|
||||
return getDocParams(DocumentType.DATASOURCE, datasourceId, otherProps)
|
||||
}
|
||||
|
||||
|
@ -213,7 +193,7 @@ exports.getDatasourceParams = (datasourceId = null, otherProps = {}) => {
|
|||
* Generates a new query ID.
|
||||
* @returns {string} The new query ID which the query doc can be stored under.
|
||||
*/
|
||||
exports.generateQueryID = datasourceId => {
|
||||
export function generateQueryID(datasourceId: string) {
|
||||
return `${
|
||||
DocumentType.QUERY
|
||||
}${SEPARATOR}${datasourceId}${SEPARATOR}${newid()}`
|
||||
|
@ -223,21 +203,21 @@ exports.generateQueryID = datasourceId => {
|
|||
* Generates a metadata ID for automations, used to track errors in recurring
|
||||
* automations etc.
|
||||
*/
|
||||
exports.generateAutomationMetadataID = automationId => {
|
||||
export function generateAutomationMetadataID(automationId: string) {
|
||||
return `${DocumentType.AUTOMATION_METADATA}${SEPARATOR}${automationId}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all automation metadata in an app database.
|
||||
*/
|
||||
exports.getAutomationMetadataParams = (otherProps = {}) => {
|
||||
export function getAutomationMetadataParams(otherProps: any = {}) {
|
||||
return getDocParams(DocumentType.AUTOMATION_METADATA, null, otherProps)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving a query, this is a utility function for the getDocParams function.
|
||||
*/
|
||||
exports.getQueryParams = (datasourceId = null, otherProps = {}) => {
|
||||
export function getQueryParams(datasourceId?: Optional, otherProps: any = {}) {
|
||||
if (datasourceId == null) {
|
||||
return getDocParams(DocumentType.QUERY, null, otherProps)
|
||||
}
|
||||
|
@ -253,15 +233,19 @@ exports.getQueryParams = (datasourceId = null, otherProps = {}) => {
|
|||
* Generates a new flag document ID.
|
||||
* @returns {string} The ID of the flag document that was generated.
|
||||
*/
|
||||
exports.generateUserFlagID = userId => {
|
||||
export function generateUserFlagID(userId: string) {
|
||||
return `${DocumentType.USER_FLAG}${SEPARATOR}${userId}`
|
||||
}
|
||||
|
||||
exports.generateMetadataID = (type, entityId) => {
|
||||
export function generateMetadataID(type: string, entityId: string) {
|
||||
return `${DocumentType.METADATA}${SEPARATOR}${type}${SEPARATOR}${entityId}`
|
||||
}
|
||||
|
||||
exports.getMetadataParams = (type, entityId = null, otherProps = {}) => {
|
||||
export function getMetadataParams(
|
||||
type: string,
|
||||
entityId?: Optional,
|
||||
otherProps: any = {}
|
||||
) {
|
||||
let docId = `${type}${SEPARATOR}`
|
||||
if (entityId != null) {
|
||||
docId += entityId
|
||||
|
@ -269,22 +253,22 @@ exports.getMetadataParams = (type, entityId = null, otherProps = {}) => {
|
|||
return getDocParams(DocumentType.METADATA, docId, otherProps)
|
||||
}
|
||||
|
||||
exports.generateMemoryViewID = viewName => {
|
||||
export function generateMemoryViewID(viewName: string) {
|
||||
return `${DocumentType.MEM_VIEW}${SEPARATOR}${viewName}`
|
||||
}
|
||||
|
||||
exports.getMemoryViewParams = (otherProps = {}) => {
|
||||
export function getMemoryViewParams(otherProps: any = {}) {
|
||||
return getDocParams(DocumentType.MEM_VIEW, null, otherProps)
|
||||
}
|
||||
|
||||
exports.generatePluginID = name => {
|
||||
export function generatePluginID(name: string) {
|
||||
return `${DocumentType.PLUGIN}${SEPARATOR}${name}`
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used with the db.allDocs to get a list of IDs
|
||||
*/
|
||||
exports.getMultiIDParams = ids => {
|
||||
export function getMultiIDParams(ids: string[]) {
|
||||
return {
|
||||
keys: ids,
|
||||
include_docs: true,
|
|
@ -12,10 +12,7 @@ import { buildExternalTableId } from "./utils"
|
|||
import { DataSourceOperation, FieldTypes } from "../constants"
|
||||
import { GoogleSpreadsheet } from "google-spreadsheet"
|
||||
import env from "../environment"
|
||||
|
||||
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
||||
const { getScopedConfig } = require("@budibase/backend-core/db")
|
||||
const { Configs } = require("@budibase/backend-core/constants")
|
||||
import { tenancy, db as dbCore, constants } from "@budibase/backend-core"
|
||||
const fetch = require("node-fetch")
|
||||
|
||||
interface GoogleSheetsConfig {
|
||||
|
@ -176,9 +173,9 @@ class GoogleSheetsIntegration implements DatasourcePlus {
|
|||
async connect() {
|
||||
try {
|
||||
// Initialise oAuth client
|
||||
const db = getGlobalDB()
|
||||
let googleConfig = await getScopedConfig(db, {
|
||||
type: Configs.GOOGLE,
|
||||
const db = tenancy.getGlobalDB()
|
||||
let googleConfig = await dbCore.getScopedConfig(db, {
|
||||
type: constants.Config.GOOGLE,
|
||||
})
|
||||
|
||||
if (!googleConfig) {
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
const { isDevAppID, isProdAppID } = require("../db/utils")
|
||||
|
||||
exports.AppType = {
|
||||
DEV: "dev",
|
||||
PROD: "prod",
|
||||
}
|
||||
|
||||
exports.middleware =
|
||||
({ appType } = {}) =>
|
||||
(ctx, next) => {
|
||||
const appId = ctx.appId
|
||||
if (appType === exports.AppType.DEV && appId && !isDevAppID(appId)) {
|
||||
ctx.throw(400, "Only apps in development support this endpoint")
|
||||
}
|
||||
if (appType === exports.AppType.PROD && appId && !isProdAppID(appId)) {
|
||||
ctx.throw(400, "Only apps in production support this endpoint")
|
||||
}
|
||||
return next()
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { isDevAppID, isProdAppID } from "../db/utils"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
export enum AppType {
|
||||
DEV = "dev",
|
||||
PROD = "prod",
|
||||
}
|
||||
|
||||
export function middleware({ appType }: { appType?: AppType } = {}) {
|
||||
return (ctx: BBContext, next: any) => {
|
||||
const appId = ctx.appId
|
||||
if (appType === AppType.DEV && appId && !isDevAppID(appId)) {
|
||||
ctx.throw(400, "Only apps in development support this endpoint")
|
||||
}
|
||||
if (appType === AppType.PROD && appId && !isProdAppID(appId)) {
|
||||
ctx.throw(400, "Only apps in production support this endpoint")
|
||||
}
|
||||
return next()
|
||||
}
|
||||
}
|
|
@ -4,8 +4,8 @@ import {
|
|||
BUILTIN_ROLE_IDS,
|
||||
} from "@budibase/backend-core/roles"
|
||||
const {
|
||||
PermissionTypes,
|
||||
PermissionLevels,
|
||||
PermissionType,
|
||||
PermissionLevel,
|
||||
doesHaveBasePermission,
|
||||
} = require("@budibase/backend-core/permissions")
|
||||
const builderMiddleware = require("./builder")
|
||||
|
@ -33,7 +33,7 @@ const checkAuthorized = async (
|
|||
) => {
|
||||
// check if this is a builder api and the user is not a builder
|
||||
const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
|
||||
const isBuilderApi = permType === PermissionTypes.BUILDER
|
||||
const isBuilderApi = permType === PermissionType.BUILDER
|
||||
if (isBuilderApi && !isBuilder) {
|
||||
return ctx.throw(403, "Not Authorized")
|
||||
}
|
||||
|
@ -91,9 +91,9 @@ export = (permType: any, permLevel: any = null, opts = { schema: false }) =>
|
|||
let resourceRoles: any = []
|
||||
let otherLevelRoles: any = []
|
||||
const otherLevel =
|
||||
permLevel === PermissionLevels.READ
|
||||
? PermissionLevels.WRITE
|
||||
: PermissionLevels.READ
|
||||
permLevel === PermissionLevel.READ
|
||||
? PermissionLevel.WRITE
|
||||
: PermissionLevel.READ
|
||||
const appId = getAppId()
|
||||
if (appId && hasResource(ctx)) {
|
||||
resourceRoles = await getRequiredResourceRole(permLevel, ctx)
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
const { APP_DEV_PREFIX } = require("../db/utils")
|
||||
const {
|
||||
import {
|
||||
APP_DEV_PREFIX,
|
||||
DocumentType,
|
||||
getGlobalIDFromUserMetadataID,
|
||||
} from "../db/utils"
|
||||
import {
|
||||
doesUserHaveLock,
|
||||
updateLock,
|
||||
checkDebounce,
|
||||
setDebounce,
|
||||
} = require("../utilities/redis")
|
||||
const { doWithDB } = require("@budibase/backend-core/db")
|
||||
const { DocumentType, getGlobalIDFromUserMetadataID } = require("../db/utils")
|
||||
const { PermissionTypes } = require("@budibase/backend-core/permissions")
|
||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
||||
} from "../utilities/redis"
|
||||
import { db as dbCore, cache, permissions } from "@budibase/backend-core"
|
||||
import { BBContext, Database } from "@budibase/types"
|
||||
|
||||
const DEBOUNCE_TIME_SEC = 30
|
||||
|
||||
|
@ -21,11 +23,11 @@ const DEBOUNCE_TIME_SEC = 30
|
|||
* through the authorized middleware *
|
||||
****************************************************/
|
||||
|
||||
async function checkDevAppLocks(ctx) {
|
||||
async function checkDevAppLocks(ctx: BBContext) {
|
||||
const appId = ctx.appId
|
||||
|
||||
// if any public usage, don't proceed
|
||||
if (!ctx.user._id && !ctx.user.userId) {
|
||||
if (!ctx.user?._id && !ctx.user?.userId) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -41,34 +43,34 @@ async function checkDevAppLocks(ctx) {
|
|||
await updateLock(appId, ctx.user)
|
||||
}
|
||||
|
||||
async function updateAppUpdatedAt(ctx) {
|
||||
async function updateAppUpdatedAt(ctx: BBContext) {
|
||||
const appId = ctx.appId
|
||||
// if debouncing skip this update
|
||||
// get methods also aren't updating
|
||||
if (ctx.method === "GET" || (await checkDebounce(appId))) {
|
||||
return
|
||||
}
|
||||
await doWithDB(appId, async db => {
|
||||
await dbCore.doWithDB(appId, async (db: Database) => {
|
||||
const metadata = await db.get(DocumentType.APP_METADATA)
|
||||
metadata.updatedAt = new Date().toISOString()
|
||||
|
||||
metadata.updatedBy = getGlobalIDFromUserMetadataID(ctx.user.userId)
|
||||
metadata.updatedBy = getGlobalIDFromUserMetadataID(ctx.user?.userId!)
|
||||
|
||||
const response = await db.put(metadata)
|
||||
metadata._rev = response.rev
|
||||
await appCache.invalidateAppMetadata(appId, metadata)
|
||||
await cache.app.invalidateAppMetadata(appId, metadata)
|
||||
// set a new debounce record with a short TTL
|
||||
await setDebounce(appId, DEBOUNCE_TIME_SEC)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = async (ctx, permType) => {
|
||||
export = async function builder(ctx: BBContext, permType: string) {
|
||||
const appId = ctx.appId
|
||||
// this only functions within an app context
|
||||
if (!appId) {
|
||||
return
|
||||
}
|
||||
const isBuilderApi = permType === PermissionTypes.BUILDER
|
||||
const isBuilderApi = permType === permissions.PermissionType.BUILDER
|
||||
const referer = ctx.headers["referer"]
|
||||
|
||||
const overviewPath = "/builder/portal/overview/"
|
|
@ -1,29 +1,26 @@
|
|||
const {
|
||||
getAppIdFromCtx,
|
||||
setCookie,
|
||||
getCookie,
|
||||
clearCookie,
|
||||
} = require("@budibase/backend-core/utils")
|
||||
const { Cookies, Headers } = require("@budibase/backend-core/constants")
|
||||
const { getRole } = require("@budibase/backend-core/roles")
|
||||
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
|
||||
const { generateUserMetadataID, isDevAppID } = require("../db/utils")
|
||||
const { dbExists } = require("@budibase/backend-core/db")
|
||||
const { isUserInAppTenant } = require("@budibase/backend-core/tenancy")
|
||||
const { getCachedSelf } = require("../utilities/global")
|
||||
const env = require("../environment")
|
||||
const { isWebhookEndpoint } = require("./utils")
|
||||
const { doInAppContext } = require("@budibase/backend-core/context")
|
||||
import {
|
||||
utils,
|
||||
constants,
|
||||
roles,
|
||||
db as dbCore,
|
||||
tenancy,
|
||||
context,
|
||||
} from "@budibase/backend-core"
|
||||
import { generateUserMetadataID, isDevAppID } from "../db/utils"
|
||||
import { getCachedSelf } from "../utilities/global"
|
||||
import env from "../environment"
|
||||
import { isWebhookEndpoint } from "./utils"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
module.exports = async (ctx, next) => {
|
||||
export = async (ctx: BBContext, next: any) => {
|
||||
// try to get the appID from the request
|
||||
let requestAppId = await getAppIdFromCtx(ctx)
|
||||
let requestAppId = await utils.getAppIdFromCtx(ctx)
|
||||
// get app cookie if it exists
|
||||
let appCookie = null
|
||||
let appCookie: { appId?: string } | undefined
|
||||
try {
|
||||
appCookie = getCookie(ctx, Cookies.CurrentApp)
|
||||
appCookie = utils.getCookie(ctx, constants.Cookie.CurrentApp)
|
||||
} catch (err) {
|
||||
clearCookie(ctx, Cookies.CurrentApp)
|
||||
utils.clearCookie(ctx, constants.Cookie.CurrentApp)
|
||||
}
|
||||
if (!appCookie && !requestAppId) {
|
||||
return next()
|
||||
|
@ -31,9 +28,9 @@ module.exports = async (ctx, next) => {
|
|||
// check the app exists referenced in cookie
|
||||
if (appCookie) {
|
||||
const appId = appCookie.appId
|
||||
const exists = await dbExists(appId)
|
||||
const exists = await dbCore.dbExists(appId)
|
||||
if (!exists) {
|
||||
clearCookie(ctx, Cookies.CurrentApp)
|
||||
utils.clearCookie(ctx, constants.Cookie.CurrentApp)
|
||||
return next()
|
||||
}
|
||||
// if the request app ID wasn't set, update it with the cookie
|
||||
|
@ -47,13 +44,13 @@ module.exports = async (ctx, next) => {
|
|||
!isWebhookEndpoint(ctx) &&
|
||||
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
|
||||
) {
|
||||
clearCookie(ctx, Cookies.CurrentApp)
|
||||
utils.clearCookie(ctx, constants.Cookie.CurrentApp)
|
||||
return ctx.redirect("/")
|
||||
}
|
||||
}
|
||||
|
||||
let appId,
|
||||
roleId = BUILTIN_ROLE_IDS.PUBLIC
|
||||
let appId: string | undefined,
|
||||
roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||
if (!ctx.user) {
|
||||
// not logged in, try to set a cookie for public apps
|
||||
appId = requestAppId
|
||||
|
@ -68,16 +65,20 @@ module.exports = async (ctx, next) => {
|
|||
const isBuilder =
|
||||
globalUser && globalUser.builder && globalUser.builder.global
|
||||
const isDevApp = appId && isDevAppID(appId)
|
||||
const roleHeader = ctx.request && ctx.request.headers[Headers.PREVIEW_ROLE]
|
||||
const roleHeader =
|
||||
ctx.request &&
|
||||
(ctx.request.headers[constants.Header.PREVIEW_ROLE] as string)
|
||||
if (isBuilder && isDevApp && roleHeader) {
|
||||
// Ensure the role is valid by ensuring a definition exists
|
||||
try {
|
||||
await getRole(roleHeader)
|
||||
roleId = roleHeader
|
||||
if (roleHeader) {
|
||||
await roles.getRole(roleHeader)
|
||||
roleId = roleHeader
|
||||
|
||||
// Delete admin and builder flags so that the specified role is honoured
|
||||
delete ctx.user.builder
|
||||
delete ctx.user.admin
|
||||
// Delete admin and builder flags so that the specified role is honoured
|
||||
delete ctx.user.builder
|
||||
delete ctx.user.admin
|
||||
}
|
||||
} catch (error) {
|
||||
// Swallow error and do nothing
|
||||
}
|
||||
|
@ -89,7 +90,7 @@ module.exports = async (ctx, next) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
return doInAppContext(appId, async () => {
|
||||
return context.doInAppContext(appId, async () => {
|
||||
let skipCookie = false
|
||||
// if the user not in the right tenant then make sure they have no permissions
|
||||
// need to judge this only based on the request app ID,
|
||||
|
@ -97,14 +98,14 @@ module.exports = async (ctx, next) => {
|
|||
env.MULTI_TENANCY &&
|
||||
ctx.user &&
|
||||
requestAppId &&
|
||||
!isUserInAppTenant(requestAppId, ctx.user)
|
||||
!tenancy.isUserInAppTenant(requestAppId, ctx.user)
|
||||
) {
|
||||
// don't error, simply remove the users rights (they are a public user)
|
||||
delete ctx.user.builder
|
||||
delete ctx.user.admin
|
||||
delete ctx.user.roles
|
||||
ctx.isAuthenticated = false
|
||||
roleId = BUILTIN_ROLE_IDS.PUBLIC
|
||||
roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||
skipCookie = true
|
||||
}
|
||||
|
||||
|
@ -112,15 +113,17 @@ module.exports = async (ctx, next) => {
|
|||
if (roleId) {
|
||||
ctx.roleId = roleId
|
||||
const globalId = ctx.user ? ctx.user._id : undefined
|
||||
const userId = ctx.user ? generateUserMetadataID(ctx.user._id) : null
|
||||
const userId = ctx.user
|
||||
? generateUserMetadataID(ctx.user._id!)
|
||||
: undefined
|
||||
ctx.user = {
|
||||
...ctx.user,
|
||||
...ctx.user!,
|
||||
// override userID with metadata one
|
||||
_id: userId,
|
||||
userId,
|
||||
globalId,
|
||||
roleId,
|
||||
role: await getRole(roleId),
|
||||
role: await roles.getRole(roleId),
|
||||
}
|
||||
}
|
||||
if (
|
||||
|
@ -129,7 +132,7 @@ module.exports = async (ctx, next) => {
|
|||
appCookie.appId !== requestAppId) &&
|
||||
!skipCookie
|
||||
) {
|
||||
setCookie(ctx, { appId }, Cookies.CurrentApp)
|
||||
utils.setCookie(ctx, { appId }, constants.Cookie.CurrentApp)
|
||||
}
|
||||
|
||||
return next()
|
|
@ -1,20 +1,21 @@
|
|||
const Joi = require("joi")
|
||||
import Joi from "joi"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
function validate(schema, property) {
|
||||
function validate(schema: Joi.Schema, property: string) {
|
||||
// Return a Koa middleware function
|
||||
return (ctx, next) => {
|
||||
return (ctx: BBContext, next: any) => {
|
||||
if (!schema) {
|
||||
return next()
|
||||
}
|
||||
let params = null
|
||||
if (ctx[property] != null) {
|
||||
params = ctx[property]
|
||||
} else if (ctx.request[property] != null) {
|
||||
params = ctx.request[property]
|
||||
} else if (ctx.request.get(property) != null) {
|
||||
params = ctx.request.get(property)
|
||||
}
|
||||
|
||||
// not all schemas have the append property e.g. array schemas
|
||||
if (schema.append) {
|
||||
if ("append" in schema && schema.append) {
|
||||
schema = schema.append({
|
||||
createdAt: Joi.any().optional(),
|
||||
updatedAt: Joi.any().optional(),
|
||||
|
@ -30,10 +31,10 @@ function validate(schema, property) {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports.body = schema => {
|
||||
export function body(schema: Joi.Schema) {
|
||||
return validate(schema, "body")
|
||||
}
|
||||
|
||||
module.exports.params = schema => {
|
||||
export function params(schema: Joi.Schema) {
|
||||
return validate(schema, "params")
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
const { Headers } = require("@budibase/backend-core/constants")
|
||||
const { getAppIdFromCtx } = require("@budibase/backend-core/utils")
|
||||
|
||||
module.exports = function ({ requiresAppId } = {}) {
|
||||
return async (ctx, next) => {
|
||||
const appId = await getAppIdFromCtx(ctx)
|
||||
if (requiresAppId && !appId) {
|
||||
ctx.throw(
|
||||
400,
|
||||
`Invalid app ID provided, please check the ${Headers.APP_ID} header.`
|
||||
)
|
||||
}
|
||||
if (!ctx.headers[Headers.API_KEY]) {
|
||||
ctx.throw(
|
||||
400,
|
||||
`Invalid API key provided, please check the ${Headers.API_KEY} header.`
|
||||
)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { constants, utils } from "@budibase/backend-core"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
export = function ({ requiresAppId }: { requiresAppId?: boolean } = {}) {
|
||||
return async (ctx: BBContext, next: any) => {
|
||||
const appId = await utils.getAppIdFromCtx(ctx)
|
||||
if (requiresAppId && !appId) {
|
||||
ctx.throw(
|
||||
400,
|
||||
`Invalid app ID provided, please check the ${constants.Header.APP_ID} header.`
|
||||
)
|
||||
}
|
||||
if (!ctx.headers[constants.Header.API_KEY]) {
|
||||
ctx.throw(
|
||||
400,
|
||||
`Invalid API key provided, please check the ${constants.Header.API_KEY} header.`
|
||||
)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
}
|
|
@ -1,17 +1,23 @@
|
|||
class ResourceIdGetter {
|
||||
constructor(ctxProperty) {
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
export class ResourceIdGetter {
|
||||
parameter: string
|
||||
main: string | null
|
||||
sub: string | null
|
||||
|
||||
constructor(ctxProperty: string) {
|
||||
this.parameter = ctxProperty
|
||||
this.main = null
|
||||
this.sub = null
|
||||
return this
|
||||
}
|
||||
|
||||
mainResource(field) {
|
||||
mainResource(field: string) {
|
||||
this.main = field
|
||||
return this
|
||||
}
|
||||
|
||||
subResource(field) {
|
||||
subResource(field: string) {
|
||||
this.sub = field
|
||||
return this
|
||||
}
|
||||
|
@ -20,7 +26,8 @@ class ResourceIdGetter {
|
|||
const parameter = this.parameter,
|
||||
main = this.main,
|
||||
sub = this.sub
|
||||
return (ctx, next) => {
|
||||
return (ctx: BBContext, next: any) => {
|
||||
// @ts-ignore
|
||||
const request = ctx.request[parameter] || ctx[parameter]
|
||||
if (request == null) {
|
||||
return next()
|
||||
|
@ -36,24 +43,22 @@ class ResourceIdGetter {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports.ResourceIdGetter = ResourceIdGetter
|
||||
|
||||
module.exports.paramResource = main => {
|
||||
export function paramResource(main: string) {
|
||||
return new ResourceIdGetter("params").mainResource(main).build()
|
||||
}
|
||||
|
||||
module.exports.paramSubResource = (main, sub) => {
|
||||
export function paramSubResource(main: string, sub: string) {
|
||||
return new ResourceIdGetter("params")
|
||||
.mainResource(main)
|
||||
.subResource(sub)
|
||||
.build()
|
||||
}
|
||||
|
||||
module.exports.bodyResource = main => {
|
||||
export function bodyResource(main: string) {
|
||||
return new ResourceIdGetter("body").mainResource(main).build()
|
||||
}
|
||||
|
||||
module.exports.bodySubResource = (main, sub) => {
|
||||
export function bodySubResource(main: string, sub: string) {
|
||||
return new ResourceIdGetter("body")
|
||||
.mainResource(main)
|
||||
.subResource(sub)
|
|
@ -1,7 +1,9 @@
|
|||
const env = require("../environment")
|
||||
import env from "../environment"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
// if added as a middleware will stop requests unless builder is in self host mode
|
||||
// or cloud is in self host
|
||||
module.exports = async (ctx, next) => {
|
||||
export = async (ctx: BBContext, next: any) => {
|
||||
if (env.SELF_HOSTED) {
|
||||
await next()
|
||||
return
|
|
@ -9,7 +9,7 @@ jest.mock("../../environment", () => ({
|
|||
)
|
||||
const authorizedMiddleware = require("../authorized")
|
||||
const env = require("../../environment")
|
||||
const { PermissionTypes, PermissionLevels } = require("@budibase/backend-core/permissions")
|
||||
const { PermissionType, PermissionLevel } = require("@budibase/backend-core/permissions")
|
||||
const { doInAppContext } = require("@budibase/backend-core/context")
|
||||
|
||||
const APP_ID = ""
|
||||
|
@ -113,7 +113,7 @@ describe("Authorization middleware", () => {
|
|||
|
||||
it("throws if the user does not have builder permissions", async () => {
|
||||
config.setEnvironment(false)
|
||||
config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER)
|
||||
config.setMiddlewareRequiredPermission(PermissionType.BUILDER)
|
||||
config.setUser({
|
||||
role: {
|
||||
_id: ""
|
||||
|
@ -125,13 +125,13 @@ describe("Authorization middleware", () => {
|
|||
})
|
||||
|
||||
it("passes on to next() middleware if the user has resource permission", async () => {
|
||||
config.setResourceId(PermissionTypes.QUERY)
|
||||
config.setResourceId(PermissionType.QUERY)
|
||||
config.setUser({
|
||||
role: {
|
||||
_id: ""
|
||||
}
|
||||
})
|
||||
config.setMiddlewareRequiredPermission(PermissionTypes.QUERY)
|
||||
config.setMiddlewareRequiredPermission(PermissionType.QUERY)
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
|
@ -155,7 +155,7 @@ describe("Authorization middleware", () => {
|
|||
_id: ""
|
||||
},
|
||||
})
|
||||
config.setMiddlewareRequiredPermission(PermissionTypes.ADMIN, PermissionLevels.BASIC)
|
||||
config.setMiddlewareRequiredPermission(PermissionType.ADMIN, PermissionLevel.BASIC)
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.throw).toHaveBeenCalledWith(403, "User does not have permission")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue