2021-04-23 04:57:38 +12:00
|
|
|
const {
|
|
|
|
generateConfigID,
|
|
|
|
getConfigParams,
|
2021-05-06 07:58:31 +12:00
|
|
|
getGlobalUserParams,
|
2021-05-06 21:51:21 +12:00
|
|
|
getScopedFullConfig,
|
2021-08-05 20:59:08 +12:00
|
|
|
getAllApps,
|
2022-01-11 08:33:00 +13:00
|
|
|
} = require("@budibase/backend-core/db")
|
2021-04-24 00:49:47 +12:00
|
|
|
const { Configs } = require("../../../constants")
|
|
|
|
const email = require("../../../utilities/email")
|
2022-01-13 00:32:14 +13:00
|
|
|
const {
|
|
|
|
upload,
|
|
|
|
ObjectStoreBuckets,
|
|
|
|
} = require("@budibase/backend-core/objectStore")
|
2021-08-05 20:59:08 +12:00
|
|
|
const CouchDB = require("../../../db")
|
2022-01-11 08:33:00 +13:00
|
|
|
const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy")
|
2021-09-24 03:22:12 +12:00
|
|
|
const env = require("../../../environment")
|
2021-11-13 02:31:55 +13:00
|
|
|
const { googleCallbackUrl, oidcCallbackUrl } = require("./auth")
|
|
|
|
|
|
|
|
const BB_TENANT_CDN = "https://tenants.cdn.budi.live"
|
2021-05-06 07:58:31 +12:00
|
|
|
|
2021-05-03 19:31:09 +12:00
|
|
|
exports.save = async function (ctx) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
|
|
|
const { type, workspace, user, config } = ctx.request.body
|
2021-04-21 05:14:36 +12:00
|
|
|
|
|
|
|
// Config does not exist yet
|
2021-05-05 04:31:06 +12:00
|
|
|
if (!ctx.request.body._id) {
|
|
|
|
ctx.request.body._id = generateConfigID({
|
2021-04-22 22:45:22 +12:00
|
|
|
type,
|
2021-08-05 20:59:08 +12:00
|
|
|
workspace,
|
2021-04-22 22:45:22 +12:00
|
|
|
user,
|
|
|
|
})
|
2021-04-21 05:14:36 +12:00
|
|
|
}
|
|
|
|
|
2021-06-10 02:45:54 +12:00
|
|
|
try {
|
|
|
|
// verify the configuration
|
|
|
|
switch (type) {
|
|
|
|
case Configs.SMTP:
|
|
|
|
await email.verifyConfig(config)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
ctx.throw(400, err)
|
2021-04-24 00:49:47 +12:00
|
|
|
}
|
|
|
|
|
2021-04-21 05:14:36 +12:00
|
|
|
try {
|
2021-05-05 04:31:06 +12:00
|
|
|
const response = await db.put(ctx.request.body)
|
2021-04-21 05:14:36 +12:00
|
|
|
ctx.body = {
|
2021-04-22 22:45:22 +12:00
|
|
|
type,
|
2021-04-21 05:14:36 +12:00
|
|
|
_id: response.id,
|
|
|
|
_rev: response.rev,
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2021-06-10 02:45:54 +12:00
|
|
|
ctx.throw(400, err)
|
2021-04-21 05:14:36 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 19:31:09 +12:00
|
|
|
exports.fetch = async function (ctx) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-04-21 05:14:36 +12:00
|
|
|
const response = await db.allDocs(
|
2021-05-06 03:00:15 +12:00
|
|
|
getConfigParams(
|
|
|
|
{ type: ctx.params.type },
|
|
|
|
{
|
|
|
|
include_docs: true,
|
|
|
|
}
|
|
|
|
)
|
2021-04-21 05:14:36 +12:00
|
|
|
)
|
2021-05-04 22:32:22 +12:00
|
|
|
ctx.body = response.rows.map(row => row.doc)
|
2021-04-21 05:14:36 +12:00
|
|
|
}
|
|
|
|
|
2021-04-22 22:45:22 +12:00
|
|
|
/**
|
|
|
|
* Gets the most granular config for a particular configuration type.
|
2021-08-05 20:59:08 +12:00
|
|
|
* The hierarchy is type -> workspace -> user.
|
2021-04-22 22:45:22 +12:00
|
|
|
*/
|
2021-05-03 19:31:09 +12:00
|
|
|
exports.find = async function (ctx) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
|
|
|
|
|
|
|
const { userId, workspaceId } = ctx.query
|
|
|
|
if (workspaceId && userId) {
|
|
|
|
const workspace = await db.get(workspaceId)
|
|
|
|
const userInWorkspace = workspace.users.some(
|
|
|
|
workspaceUser => workspaceUser === userId
|
|
|
|
)
|
|
|
|
if (!ctx.user.admin && !userInWorkspace) {
|
|
|
|
ctx.throw(400, `User is not in specified workspace: ${workspace}.`)
|
2021-04-22 22:45:22 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-21 05:14:36 +12:00
|
|
|
try {
|
2021-04-22 22:45:22 +12:00
|
|
|
// Find the config with the most granular scope based on context
|
2021-05-06 21:51:21 +12:00
|
|
|
const scopedConfig = await getScopedFullConfig(db, {
|
2021-04-23 00:46:54 +12:00
|
|
|
type: ctx.params.type,
|
|
|
|
user: userId,
|
2021-08-05 20:59:08 +12:00
|
|
|
workspace: workspaceId,
|
2021-04-22 22:45:22 +12:00
|
|
|
})
|
|
|
|
|
|
|
|
if (scopedConfig) {
|
|
|
|
ctx.body = scopedConfig
|
|
|
|
} else {
|
2021-06-22 04:13:06 +12:00
|
|
|
// don't throw an error, there simply is nothing to return
|
|
|
|
ctx.body = {}
|
2021-04-22 22:45:22 +12:00
|
|
|
}
|
2021-04-21 05:14:36 +12:00
|
|
|
} catch (err) {
|
|
|
|
ctx.throw(err.status, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-14 01:54:20 +12:00
|
|
|
exports.publicOidc = async function (ctx) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-07-14 01:54:20 +12:00
|
|
|
try {
|
|
|
|
// Find the config with the most granular scope based on context
|
|
|
|
const oidcConfig = await getScopedFullConfig(db, {
|
|
|
|
type: Configs.OIDC,
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!oidcConfig) {
|
|
|
|
ctx.body = {}
|
|
|
|
} else {
|
2021-08-05 20:59:08 +12:00
|
|
|
ctx.body = oidcConfig.config.configs.map(config => ({
|
|
|
|
logo: config.logo,
|
|
|
|
name: config.name,
|
|
|
|
uuid: config.uuid,
|
|
|
|
}))
|
2021-07-14 01:54:20 +12:00
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
ctx.throw(err.status, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-22 05:01:25 +12:00
|
|
|
exports.publicSettings = async function (ctx) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-07-16 03:50:57 +12:00
|
|
|
|
2021-06-22 05:01:25 +12:00
|
|
|
try {
|
|
|
|
// Find the config with the most granular scope based on context
|
2021-07-09 20:49:16 +12:00
|
|
|
const publicConfig = await getScopedFullConfig(db, {
|
2021-06-22 05:01:25 +12:00
|
|
|
type: Configs.SETTINGS,
|
|
|
|
})
|
2021-07-09 20:49:16 +12:00
|
|
|
|
2021-07-16 02:49:06 +12:00
|
|
|
const googleConfig = await getScopedFullConfig(db, {
|
|
|
|
type: Configs.GOOGLE,
|
|
|
|
})
|
|
|
|
|
|
|
|
const oidcConfig = await getScopedFullConfig(db, {
|
|
|
|
type: Configs.OIDC,
|
|
|
|
})
|
|
|
|
|
2021-08-05 20:59:08 +12:00
|
|
|
let config
|
2021-07-16 03:50:57 +12:00
|
|
|
if (!publicConfig) {
|
|
|
|
config = {
|
|
|
|
config: {},
|
2021-07-16 02:49:06 +12:00
|
|
|
}
|
2021-06-22 05:37:14 +12:00
|
|
|
} else {
|
2021-07-16 03:50:57 +12:00
|
|
|
config = publicConfig
|
2021-06-22 05:37:14 +12:00
|
|
|
}
|
2021-07-16 03:50:57 +12:00
|
|
|
|
2021-07-23 22:38:17 +12:00
|
|
|
// google button flag
|
2021-07-24 01:40:22 +12:00
|
|
|
if (googleConfig && googleConfig.config) {
|
2021-08-05 20:59:08 +12:00
|
|
|
// activated by default for configs pre-activated flag
|
|
|
|
config.config.google =
|
|
|
|
googleConfig.config.activated == null || googleConfig.config.activated
|
2021-07-24 01:40:22 +12:00
|
|
|
} else {
|
|
|
|
config.config.google = false
|
|
|
|
}
|
2021-07-23 22:38:17 +12:00
|
|
|
|
2021-11-13 02:31:55 +13:00
|
|
|
// callback urls
|
|
|
|
config.config.oidcCallbackUrl = await oidcCallbackUrl()
|
|
|
|
config.config.googleCallbackUrl = await googleCallbackUrl()
|
|
|
|
|
2021-07-23 22:38:17 +12:00
|
|
|
// oidc button flag
|
2021-07-24 01:40:22 +12:00
|
|
|
if (oidcConfig && oidcConfig.config) {
|
2021-08-05 20:59:08 +12:00
|
|
|
config.config.oidc = oidcConfig.config.configs[0].activated
|
2021-07-24 01:40:22 +12:00
|
|
|
} else {
|
|
|
|
config.config.oidc = false
|
|
|
|
}
|
2021-07-23 22:38:17 +12:00
|
|
|
|
2021-07-16 03:50:57 +12:00
|
|
|
ctx.body = config
|
2021-06-22 05:01:25 +12:00
|
|
|
} catch (err) {
|
|
|
|
ctx.throw(err.status, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-08 00:55:30 +12:00
|
|
|
exports.upload = async function (ctx) {
|
|
|
|
if (ctx.request.files == null || ctx.request.files.file.length > 1) {
|
|
|
|
ctx.throw(400, "One file must be uploaded.")
|
|
|
|
}
|
|
|
|
const file = ctx.request.files.file
|
|
|
|
const { type, name } = ctx.params
|
|
|
|
|
2021-09-24 03:22:12 +12:00
|
|
|
let bucket
|
|
|
|
if (env.SELF_HOSTED) {
|
|
|
|
bucket = ObjectStoreBuckets.GLOBAL
|
|
|
|
} else {
|
|
|
|
bucket = ObjectStoreBuckets.GLOBAL_CLOUD
|
|
|
|
}
|
|
|
|
|
2021-11-13 02:31:55 +13:00
|
|
|
let key
|
|
|
|
if (env.MULTI_TENANCY) {
|
|
|
|
key = `${getTenantId()}/${type}/${name}`
|
|
|
|
} else {
|
|
|
|
key = `${type}/${name}`
|
|
|
|
}
|
|
|
|
|
2021-05-08 00:55:30 +12:00
|
|
|
await upload({
|
|
|
|
bucket,
|
|
|
|
filename: key,
|
|
|
|
path: file.path,
|
|
|
|
type: file.type,
|
|
|
|
})
|
|
|
|
|
|
|
|
// add to configuration structure
|
|
|
|
// TODO: right now this only does a global level
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-05-13 01:27:33 +12:00
|
|
|
let cfgStructure = await getScopedFullConfig(db, { type })
|
|
|
|
if (!cfgStructure) {
|
|
|
|
cfgStructure = {
|
2021-05-08 00:55:30 +12:00
|
|
|
_id: generateConfigID({ type }),
|
2021-05-13 01:27:33 +12:00
|
|
|
config: {},
|
2021-05-08 00:55:30 +12:00
|
|
|
}
|
|
|
|
}
|
2021-11-13 02:31:55 +13:00
|
|
|
let url
|
|
|
|
if (env.SELF_HOSTED) {
|
|
|
|
url = `/${bucket}/${key}`
|
|
|
|
} else {
|
|
|
|
url = `${BB_TENANT_CDN}/${key}`
|
|
|
|
}
|
|
|
|
|
2021-07-08 00:41:09 +12:00
|
|
|
cfgStructure.config[`${name}`] = url
|
2021-05-08 00:55:30 +12:00
|
|
|
// write back to db with url updated
|
2021-05-13 01:27:33 +12:00
|
|
|
await db.put(cfgStructure)
|
2021-05-08 00:55:30 +12:00
|
|
|
|
|
|
|
ctx.body = {
|
|
|
|
message: "File has been uploaded and url stored to config.",
|
|
|
|
url,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 19:31:09 +12:00
|
|
|
exports.destroy = async function (ctx) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-04-21 05:14:36 +12:00
|
|
|
const { id, rev } = ctx.params
|
|
|
|
|
|
|
|
try {
|
|
|
|
await db.remove(id, rev)
|
|
|
|
ctx.body = { message: "Config deleted successfully" }
|
|
|
|
} catch (err) {
|
|
|
|
ctx.throw(err.status, err)
|
|
|
|
}
|
|
|
|
}
|
2021-05-06 07:58:31 +12:00
|
|
|
|
2021-05-06 21:57:24 +12:00
|
|
|
exports.configChecklist = async function (ctx) {
|
2021-08-05 20:59:08 +12:00
|
|
|
const db = getGlobalDB()
|
2021-05-06 07:58:31 +12:00
|
|
|
|
|
|
|
try {
|
|
|
|
// TODO: Watch get started video
|
|
|
|
|
|
|
|
// Apps exist
|
2021-08-07 04:14:31 +12:00
|
|
|
const apps = await getAllApps(CouchDB, { idsOnly: true })
|
2021-05-06 07:58:31 +12:00
|
|
|
|
|
|
|
// They have set up SMTP
|
2021-05-06 23:09:35 +12:00
|
|
|
const smtpConfig = await getScopedFullConfig(db, {
|
2021-05-06 21:57:24 +12:00
|
|
|
type: Configs.SMTP,
|
2021-05-06 07:58:31 +12:00
|
|
|
})
|
|
|
|
|
2021-05-22 01:55:11 +12:00
|
|
|
// They have set up Google Auth
|
2021-07-14 04:30:17 +12:00
|
|
|
const googleConfig = await getScopedFullConfig(db, {
|
2021-05-22 01:55:11 +12:00
|
|
|
type: Configs.GOOGLE,
|
|
|
|
})
|
|
|
|
|
2021-07-06 01:27:19 +12:00
|
|
|
// They have set up OIDC
|
|
|
|
const oidcConfig = await getScopedFullConfig(db, {
|
|
|
|
type: Configs.OIDC,
|
|
|
|
})
|
2021-08-05 20:59:08 +12:00
|
|
|
// They have set up an global user
|
2021-05-06 07:58:31 +12:00
|
|
|
const users = await db.allDocs(
|
|
|
|
getGlobalUserParams(null, {
|
|
|
|
include_docs: true,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
const adminUser = users.rows.some(row => row.doc.admin)
|
|
|
|
|
2021-05-06 21:57:24 +12:00
|
|
|
ctx.body = {
|
2021-09-07 01:16:21 +12:00
|
|
|
apps: {
|
|
|
|
checked: apps.length > 0,
|
|
|
|
label: "Create your first app",
|
|
|
|
link: "/builder/portal/apps",
|
|
|
|
},
|
|
|
|
smtp: {
|
|
|
|
checked: !!smtpConfig,
|
|
|
|
label: "Set up email",
|
|
|
|
link: "/builder/portal/manage/email",
|
|
|
|
},
|
|
|
|
adminUser: {
|
2021-09-07 06:23:33 +12:00
|
|
|
checked: adminUser,
|
2021-09-07 01:16:21 +12:00
|
|
|
label: "Create your first user",
|
|
|
|
link: "/builder/portal/manage/users",
|
|
|
|
},
|
|
|
|
sso: {
|
|
|
|
checked: !!googleConfig || !!oidcConfig,
|
|
|
|
label: "Set up single sign-on",
|
|
|
|
link: "/builder/portal/manage/auth",
|
|
|
|
},
|
2021-05-06 07:58:31 +12:00
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
ctx.throw(err.status, err)
|
|
|
|
}
|
|
|
|
}
|