diff --git a/packages/backend-core/src/auth.ts b/packages/backend-core/src/auth.ts index 5e1959e0c8..91a4b750ad 100644 --- a/packages/backend-core/src/auth.ts +++ b/packages/backend-core/src/auth.ts @@ -4,7 +4,7 @@ const JwtStrategy = require("passport-jwt").Strategy import { getGlobalDB } from "./tenancy" const refresh = require("passport-oauth2-refresh") import { Config } from "./constants" -import { getScopedConfig } from "./db/utils" +import { getScopedConfig } from "./db" import { jwt, local, diff --git a/packages/cli/src/backups/objectStore.js b/packages/cli/src/backups/objectStore.js index 8d616d276a..407659d54a 100644 --- a/packages/cli/src/backups/objectStore.js +++ b/packages/cli/src/backups/objectStore.js @@ -1,14 +1,15 @@ +const { objectStore } = require("@budibase/backend-core") +const fs = require("fs") +const { join } = require("path") +const { TEMP_DIR, MINIO_DIR } = require("./utils") +const { progressBar } = require("../utils") const { ObjectStoreBuckets, ObjectStore, retrieve, uploadDirectory, makeSureBucketExists, -} = require("@budibase/backend-core/objectStore") -const fs = require("fs") -const { join } = require("path") -const { TEMP_DIR, MINIO_DIR } = require("./utils") -const { progressBar } = require("../utils") +} = objectStore const bucketList = Object.values(ObjectStoreBuckets) diff --git a/packages/cli/src/plugins/index.js b/packages/cli/src/plugins/index.js index 873be10612..5786c521b2 100644 --- a/packages/cli/src/plugins/index.js +++ b/packages/cli/src/plugins/index.js @@ -4,7 +4,7 @@ const { getSkeleton, fleshOutSkeleton } = require("./skeleton") const questions = require("../questions") const fs = require("fs") const { PLUGIN_TYPE_ARR } = require("@budibase/types") -const { validate } = require("@budibase/backend-core/plugins") +const { plugins } = require("@budibase/backend-core") const { runPkgCommand } = require("../exec") const { join } = require("path") const { success, error, info, moveDirectory } = require("../utils") @@ -107,7 +107,7 @@ async function verify() { } name = pkgJson.name version = pkgJson.version - validate(schemaJson) + plugins.validate(schemaJson) return { name, version } } catch (err) { if (err && err.message && err.message.includes("not valid JSON")) { diff --git a/packages/types/src/documents/global/config.ts b/packages/types/src/documents/global/config.ts index f62f0a12a8..7718b62733 100644 --- a/packages/types/src/documents/global/config.ts +++ b/packages/types/src/documents/global/config.ts @@ -62,7 +62,9 @@ export const isOIDCConfig = (config: Config): config is OIDCConfig => export enum ConfigType { SETTINGS = "settings", + ACCOUNT = "account", SMTP = "smtp", GOOGLE = "google", OIDC = "oidc", + OIDC_LOGOS = "logos_oidc", } diff --git a/packages/types/src/documents/global/index.ts b/packages/types/src/documents/global/index.ts index 9f779d4937..40a30ee477 100644 --- a/packages/types/src/documents/global/index.ts +++ b/packages/types/src/documents/global/index.ts @@ -4,3 +4,4 @@ export * from "./userGroup" export * from "./plugin" export * from "./quotas" export * from "./schedule" +export * from "./templates" diff --git a/packages/types/src/documents/global/templates.ts b/packages/types/src/documents/global/templates.ts new file mode 100644 index 0000000000..df620fc7b0 --- /dev/null +++ b/packages/types/src/documents/global/templates.ts @@ -0,0 +1,9 @@ +import { Document } from "../document" + +export interface Template extends Document { + ownerId?: string + name?: string + contents: string + purpose: string + type?: string +} diff --git a/packages/worker/src/api/controllers/global/auth.ts b/packages/worker/src/api/controllers/global/auth.ts index 8d36024634..0b34c9c3ef 100644 --- a/packages/worker/src/api/controllers/global/auth.ts +++ b/packages/worker/src/api/controllers/global/auth.ts @@ -1,25 +1,25 @@ -import core from "@budibase/backend-core" +import { utils, constants, auth, db as dbCore } from "@budibase/backend-core" import { events, users as usersCore, context, tenancy, } from "@budibase/backend-core" -import { Config, EmailTemplatePurpose } from "../../../constants" +import { EmailTemplatePurpose } from "../../../constants" import { sendEmail, isEmailConfigured } from "../../../utilities/email" import { checkResetPasswordCode } from "../../../utilities/redis" import env from "../../../environment" import sdk from "../../../sdk" -import { User } from "@budibase/types" -const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils -const { Cookie, Header } = core.constants -const { passport, ssoCallbackUrl, google, oidc } = core.auth +import { User, Config, ConfigType } from "@budibase/types" +const { setCookie, getCookie, clearCookie, hash, platformLogout } = utils +const { Cookie, Header } = constants +const { passport, ssoCallbackUrl, google, oidc } = auth -export const googleCallbackUrl = async (config: any) => { +export async function googleCallbackUrl(config?: Config) { return ssoCallbackUrl(tenancy.getGlobalDB(), config, "google") } -export const oidcCallbackUrl = async (config: any) => { +export async function oidcCallbackUrl(config?: Config) { return ssoCallbackUrl(tenancy.getGlobalDB(), config, "oidc") } @@ -135,7 +135,7 @@ export const logout = async (ctx: any) => { export const datasourcePreAuth = async (ctx: any, next: any) => { const provider = ctx.params.provider - const middleware = require(`@budibase/backend-core/middleware`) + const { middleware } = require(`@budibase/backend-core`) const handler = middleware.datasource[provider] setCookie( @@ -154,7 +154,7 @@ export const datasourcePreAuth = async (ctx: any, next: any) => { export const datasourceAuth = async (ctx: any, next: any) => { const authStateCookie = getCookie(ctx, Cookie.DatasourceAuth) const provider = authStateCookie.provider - const middleware = require(`@budibase/backend-core/middleware`) + const { middleware } = require(`@budibase/backend-core`) const handler = middleware.datasource[provider] return handler.postAuth(passport, ctx, next) } @@ -166,11 +166,11 @@ export const datasourceAuth = async (ctx: any, next: any) => { export const googlePreAuth = async (ctx: any, next: any) => { const db = tenancy.getGlobalDB() - const config = await core.db.getScopedConfig(db, { - type: Config.GOOGLE, + const config = await dbCore.getScopedConfig(db, { + type: ConfigType.GOOGLE, workspace: ctx.query.workspace, }) - let callbackUrl = await exports.googleCallbackUrl(config) + let callbackUrl = await googleCallbackUrl(config) const strategy = await google.strategyFactory( config, callbackUrl, @@ -187,11 +187,11 @@ export const googlePreAuth = async (ctx: any, next: any) => { export const googleAuth = async (ctx: any, next: any) => { const db = tenancy.getGlobalDB() - const config = await core.db.getScopedConfig(db, { - type: Config.GOOGLE, + const config = await dbCore.getScopedConfig(db, { + type: ConfigType.GOOGLE, workspace: ctx.query.workspace, }) - const callbackUrl = await exports.googleCallbackUrl(config) + const callbackUrl = await googleCallbackUrl(config) const strategy = await google.strategyFactory( config, callbackUrl, @@ -213,13 +213,13 @@ export const googleAuth = async (ctx: any, next: any) => { export const oidcStrategyFactory = async (ctx: any, configId: any) => { const db = tenancy.getGlobalDB() - const config = await core.db.getScopedConfig(db, { - type: Config.OIDC, + const config = await dbCore.getScopedConfig(db, { + type: ConfigType.OIDC, group: ctx.query.group, }) const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0] - let callbackUrl = await exports.oidcCallbackUrl(chosenConfig) + let callbackUrl = await oidcCallbackUrl(chosenConfig) //Remote Config const enrichedConfig = await oidc.fetchStrategyConfig( @@ -240,8 +240,8 @@ export const oidcPreAuth = async (ctx: any, next: any) => { setCookie(ctx, configId, Cookie.OIDC_CONFIG) const db = tenancy.getGlobalDB() - const config = await core.db.getScopedConfig(db, { - type: Config.OIDC, + const config = await dbCore.getScopedConfig(db, { + type: ConfigType.OIDC, group: ctx.query.group, }) diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.ts similarity index 57% rename from packages/worker/src/api/controllers/global/configs.js rename to packages/worker/src/api/controllers/global/configs.ts index cb8dc48c25..a8ecd78a00 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.ts @@ -1,28 +1,26 @@ -const { - generateConfigID, - getConfigParams, - getScopedFullConfig, - getAllApps, -} = require("@budibase/backend-core/db") -const { Config } = require("../../../constants") -const email = require("../../../utilities/email") -const { - upload, - ObjectStoreBuckets, -} = require("@budibase/backend-core/objectStore") -const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy") -const env = require("../../../environment") -const { googleCallbackUrl, oidcCallbackUrl } = require("./auth") -const { - withCache, - CacheKeys, - bustCache, +import * as email from "../../../utilities/email" +import env from "../../../environment" +import { googleCallbackUrl, oidcCallbackUrl } from "./auth" +import { + events, cache, -} = require("@budibase/backend-core/cache") -const { events } = require("@budibase/backend-core") -const { checkAnyUserExists } = require("../../../utilities/users") + objectStore, + tenancy, + db as dbCore, +} from "@budibase/backend-core" +import { checkAnyUserExists } from "../../../utilities/users" +import { + Database, + Config as ConfigDoc, + ConfigType, + SSOType, + GoogleConfig, + OIDCConfig, + SettingsConfig, + BBContext, +} from "@budibase/types" -const getEventFns = async (db, config) => { +const getEventFns = async (db: Database, config: ConfigDoc) => { const fns = [] const type = config.type @@ -31,41 +29,45 @@ const getEventFns = async (db, config) => { existing = await db.get(config._id) } + const ssoType = type as SSOType if (!existing) { switch (config.type) { - case Config.SMTP: { + case ConfigType.SMTP: { fns.push(events.email.SMTPCreated) break } - case Config.GOOGLE: { - fns.push(() => events.auth.SSOCreated(type)) - if (config.config.activated) { - fns.push(() => events.auth.SSOActivated(type)) + case ConfigType.GOOGLE: { + const googleCfg = config as GoogleConfig + fns.push(() => events.auth.SSOCreated(ssoType)) + if (googleCfg.config.activated) { + fns.push(() => events.auth.SSOActivated(ssoType)) } break } - case Config.OIDC: { - fns.push(() => events.auth.SSOCreated(type)) - if (config.config.configs[0].activated) { - fns.push(() => events.auth.SSOActivated(type)) + case ConfigType.OIDC: { + const oidcCfg = config as OIDCConfig + fns.push(() => events.auth.SSOCreated(ssoType)) + if (oidcCfg.config.configs[0].activated) { + fns.push(() => events.auth.SSOActivated(ssoType)) } break } - case Config.SETTINGS: { + case ConfigType.SETTINGS: { // company - const company = config.config.company + const settingsCfg = config as SettingsConfig + const company = settingsCfg.config.company if (company && company !== "Budibase") { fns.push(events.org.nameUpdated) } // logo - const logoUrl = config.config.logoUrl + const logoUrl = settingsCfg.config.logoUrl if (logoUrl) { fns.push(events.org.logoUpdated) } // platform url - const platformUrl = config.config.platformUrl + const platformUrl = settingsCfg.config.platformUrl if ( platformUrl && platformUrl !== "http://localhost:10000" && @@ -78,52 +80,55 @@ const getEventFns = async (db, config) => { } } else { switch (config.type) { - case Config.SMTP: { + case ConfigType.SMTP: { fns.push(events.email.SMTPUpdated) break } - case Config.GOOGLE: { - fns.push(() => events.auth.SSOUpdated(type)) - if (!existing.config.activated && config.config.activated) { - fns.push(() => events.auth.SSOActivated(type)) - } else if (existing.config.activated && !config.config.activated) { - fns.push(() => events.auth.SSODeactivated(type)) + case ConfigType.GOOGLE: { + const googleCfg = config as GoogleConfig + fns.push(() => events.auth.SSOUpdated(ssoType)) + if (!existing.config.activated && googleCfg.config.activated) { + fns.push(() => events.auth.SSOActivated(ssoType)) + } else if (existing.config.activated && !googleCfg.config.activated) { + fns.push(() => events.auth.SSODeactivated(ssoType)) } break } - case Config.OIDC: { - fns.push(() => events.auth.SSOUpdated(type)) + case ConfigType.OIDC: { + const oidcCfg = config as OIDCConfig + fns.push(() => events.auth.SSOUpdated(ssoType)) if ( !existing.config.configs[0].activated && - config.config.configs[0].activated + oidcCfg.config.configs[0].activated ) { - fns.push(() => events.auth.SSOActivated(type)) + fns.push(() => events.auth.SSOActivated(ssoType)) } else if ( existing.config.configs[0].activated && - !config.config.configs[0].activated + !oidcCfg.config.configs[0].activated ) { - fns.push(() => events.auth.SSODeactivated(type)) + fns.push(() => events.auth.SSODeactivated(ssoType)) } break } - case Config.SETTINGS: { + case ConfigType.SETTINGS: { // company + const settingsCfg = config as SettingsConfig const existingCompany = existing.config.company - const company = config.config.company + const company = settingsCfg.config.company if (company && company !== "Budibase" && existingCompany !== company) { fns.push(events.org.nameUpdated) } // logo const existingLogoUrl = existing.config.logoUrl - const logoUrl = config.config.logoUrl + const logoUrl = settingsCfg.config.logoUrl if (logoUrl && existingLogoUrl !== logoUrl) { fns.push(events.org.logoUpdated) } // platform url const existingPlatformUrl = existing.config.platformUrl - const platformUrl = config.config.platformUrl + const platformUrl = settingsCfg.config.platformUrl if ( platformUrl && platformUrl !== "http://localhost:10000" && @@ -140,13 +145,13 @@ const getEventFns = async (db, config) => { return fns } -exports.save = async function (ctx) { - const db = getGlobalDB() +export async function save(ctx: BBContext) { + const db = tenancy.getGlobalDB() const { type, workspace, user, config } = ctx.request.body let eventFns = await getEventFns(db, ctx.request.body) // Config does not exist yet if (!ctx.request.body._id) { - ctx.request.body._id = generateConfigID({ + ctx.request.body._id = dbCore.generateConfigID({ type, workspace, user, @@ -155,18 +160,18 @@ exports.save = async function (ctx) { try { // verify the configuration switch (type) { - case Config.SMTP: + case ConfigType.SMTP: await email.verifyConfig(config) break } - } catch (err) { + } catch (err: any) { ctx.throw(400, err) } try { const response = await db.put(ctx.request.body) - await bustCache(CacheKeys.CHECKLIST) - await bustCache(CacheKeys.ANALYTICS_ENABLED) + await cache.bustCache(cache.CacheKeys.CHECKLIST) + await cache.bustCache(cache.CacheKeys.ANALYTICS_ENABLED) for (const fn of eventFns) { await fn() @@ -177,15 +182,15 @@ exports.save = async function (ctx) { _id: response.id, _rev: response.rev, } - } catch (err) { + } catch (err: any) { ctx.throw(400, err) } } -exports.fetch = async function (ctx) { - const db = getGlobalDB() +export async function fetch(ctx: BBContext) { + const db = tenancy.getGlobalDB() const response = await db.allDocs( - getConfigParams( + dbCore.getConfigParams( { type: ctx.params.type }, { include_docs: true, @@ -199,23 +204,23 @@ exports.fetch = async function (ctx) { * Gets the most granular config for a particular configuration type. * The hierarchy is type -> workspace -> user. */ -exports.find = async function (ctx) { - const db = getGlobalDB() +export async function find(ctx: BBContext) { + const db = tenancy.getGlobalDB() const { userId, workspaceId } = ctx.query if (workspaceId && userId) { - const workspace = await db.get(workspaceId) + const workspace = await db.get(workspaceId as string) const userInWorkspace = workspace.users.some( - workspaceUser => workspaceUser === userId + (workspaceUser: any) => workspaceUser === userId ) - if (!ctx.user.admin && !userInWorkspace) { + if (!ctx.user!.admin && !userInWorkspace) { ctx.throw(400, `User is not in specified workspace: ${workspace}.`) } } try { // Find the config with the most granular scope based on context - const scopedConfig = await getScopedFullConfig(db, { + const scopedConfig = await dbCore.getScopedFullConfig(db, { type: ctx.params.type, user: userId, workspace: workspaceId, @@ -227,48 +232,48 @@ exports.find = async function (ctx) { // don't throw an error, there simply is nothing to return ctx.body = {} } - } catch (err) { - ctx.throw(err.status, err) + } catch (err: any) { + ctx.throw(err?.status || 400, err) } } -exports.publicOidc = async function (ctx) { - const db = getGlobalDB() +export async function publicOidc(ctx: BBContext) { + const db = tenancy.getGlobalDB() try { // Find the config with the most granular scope based on context - const oidcConfig = await getScopedFullConfig(db, { - type: Config.OIDC, + const oidcConfig = await dbCore.getScopedFullConfig(db, { + type: ConfigType.OIDC, }) if (!oidcConfig) { ctx.body = {} } else { - ctx.body = oidcConfig.config.configs.map(config => ({ + ctx.body = oidcConfig.config.configs.map((config: any) => ({ logo: config.logo, name: config.name, uuid: config.uuid, })) } - } catch (err) { + } catch (err: any) { ctx.throw(err.status, err) } } -exports.publicSettings = async function (ctx) { - const db = getGlobalDB() +export async function publicSettings(ctx: BBContext) { + const db = tenancy.getGlobalDB() try { // Find the config with the most granular scope based on context - const publicConfig = await getScopedFullConfig(db, { - type: Config.SETTINGS, + const publicConfig = await dbCore.getScopedFullConfig(db, { + type: ConfigType.SETTINGS, }) - const googleConfig = await getScopedFullConfig(db, { - type: Config.GOOGLE, + const googleConfig = await dbCore.getScopedFullConfig(db, { + type: ConfigType.GOOGLE, }) - const oidcConfig = await getScopedFullConfig(db, { - type: Config.OIDC, + const oidcConfig = await dbCore.getScopedFullConfig(db, { + type: ConfigType.OIDC, }) let config @@ -301,12 +306,12 @@ exports.publicSettings = async function (ctx) { } ctx.body = config - } catch (err) { + } catch (err: any) { ctx.throw(err.status, err) } } -exports.upload = async function (ctx) { +export async function upload(ctx: BBContext) { if (ctx.request.files == null || ctx.request.files.file.length > 1) { ctx.throw(400, "One file must be uploaded.") } @@ -315,19 +320,19 @@ exports.upload = async function (ctx) { let bucket if (env.SELF_HOSTED) { - bucket = ObjectStoreBuckets.GLOBAL + bucket = objectStore.ObjectStoreBuckets.GLOBAL } else { - bucket = ObjectStoreBuckets.GLOBAL_CLOUD + bucket = objectStore.ObjectStoreBuckets.GLOBAL_CLOUD } let key if (env.MULTI_TENANCY) { - key = `${getTenantId()}/${type}/${name}` + key = `${tenancy.getTenantId()}/${type}/${name}` } else { key = `${type}/${name}` } - await upload({ + await objectStore.upload({ bucket, filename: key, path: file.path, @@ -336,11 +341,11 @@ exports.upload = async function (ctx) { // add to configuration structure // TODO: right now this only does a global level - const db = getGlobalDB() - let cfgStructure = await getScopedFullConfig(db, { type }) + const db = tenancy.getGlobalDB() + let cfgStructure = await dbCore.getScopedFullConfig(db, { type }) if (!cfgStructure) { cfgStructure = { - _id: generateConfigID({ type }), + _id: dbCore.generateConfigID({ type }), config: {}, } } @@ -361,49 +366,49 @@ exports.upload = async function (ctx) { } } -exports.destroy = async function (ctx) { - const db = getGlobalDB() +export async function destroy(ctx: BBContext) { + const db = tenancy.getGlobalDB() const { id, rev } = ctx.params try { await db.remove(id, rev) - await cache.delete(CacheKeys.CHECKLIST) + await cache.delete(cache.CacheKeys.CHECKLIST) ctx.body = { message: "Config deleted successfully" } - } catch (err) { + } catch (err: any) { ctx.throw(err.status, err) } } -exports.configChecklist = async function (ctx) { - const db = getGlobalDB() - const tenantId = getTenantId() +export async function configChecklist(ctx: BBContext) { + const db = tenancy.getGlobalDB() + const tenantId = tenancy.getTenantId() try { - ctx.body = await withCache( - CacheKeys.CHECKLIST, + ctx.body = await cache.withCache( + cache.CacheKeys.CHECKLIST, env.CHECKLIST_CACHE_TTL, async () => { let apps = [] if (!env.MULTI_TENANCY || tenantId) { // Apps exist - apps = await getAllApps({ idsOnly: true, efficient: true }) + apps = await dbCore.getAllApps({ idsOnly: true, efficient: true }) } // They have set up SMTP - const smtpConfig = await getScopedFullConfig(db, { - type: Config.SMTP, + const smtpConfig = await dbCore.getScopedFullConfig(db, { + type: ConfigType.SMTP, }) // They have set up Google Auth - const googleConfig = await getScopedFullConfig(db, { - type: Config.GOOGLE, + const googleConfig = await dbCore.getScopedFullConfig(db, { + type: ConfigType.GOOGLE, }) // They have set up OIDC - const oidcConfig = await getScopedFullConfig(db, { - type: Config.OIDC, + const oidcConfig = await dbCore.getScopedFullConfig(db, { + type: ConfigType.OIDC, }) - // They have set up an global user + // They have set up a global user const userExists = await checkAnyUserExists() return { apps: { @@ -429,7 +434,7 @@ exports.configChecklist = async function (ctx) { } } ) - } catch (err) { + } catch (err: any) { ctx.throw(err.status, err) } } diff --git a/packages/worker/src/api/controllers/global/email.js b/packages/worker/src/api/controllers/global/email.ts similarity index 56% rename from packages/worker/src/api/controllers/global/email.js rename to packages/worker/src/api/controllers/global/email.ts index 85e39be0da..f5acad9a66 100644 --- a/packages/worker/src/api/controllers/global/email.js +++ b/packages/worker/src/api/controllers/global/email.ts @@ -1,7 +1,8 @@ -const { sendEmail } = require("../../../utilities/email") -const { getGlobalDB } = require("@budibase/backend-core/tenancy") +import { sendEmail as sendEmailFn } from "../../../utilities/email" +import { tenancy } from "@budibase/backend-core" +import { BBContext } from "@budibase/types" -exports.sendEmail = async ctx => { +export async function sendEmail(ctx: BBContext) { let { workspaceId, email, @@ -16,10 +17,10 @@ exports.sendEmail = async ctx => { } = ctx.request.body let user if (userId) { - const db = getGlobalDB() + const db = tenancy.getGlobalDB() user = await db.get(userId) } - const response = await sendEmail(email, purpose, { + const response = await sendEmailFn(email, purpose, { workspaceId, user, contents, diff --git a/packages/worker/src/api/controllers/global/roles.js b/packages/worker/src/api/controllers/global/roles.js deleted file mode 100644 index d63f1a62b5..0000000000 --- a/packages/worker/src/api/controllers/global/roles.js +++ /dev/null @@ -1,68 +0,0 @@ -const { getAllRoles } = require("@budibase/backend-core/roles") -const { - getAllApps, - getProdAppID, - getDevAppID, - DocumentType, -} = require("@budibase/backend-core/db") -const { doInAppContext, getAppDB } = require("@budibase/backend-core/context") -const { user: userCache } = require("@budibase/backend-core/cache") -const { getGlobalDB } = require("@budibase/backend-core/tenancy") -const { allUsers } = require("../../../sdk/users") - -exports.fetch = async ctx => { - const tenantId = ctx.user.tenantId - // always use the dev apps as they'll be most up to date (true) - const apps = await getAllApps({ tenantId, all: true }) - const promises = [] - for (let app of apps) { - // use dev app IDs - promises.push(getAllRoles(app.appId)) - } - const roles = await Promise.all(promises) - const response = {} - for (let app of apps) { - const deployedAppId = getProdAppID(app.appId) - response[deployedAppId] = { - roles: roles.shift(), - name: app.name, - version: app.version, - url: app.url, - } - } - ctx.body = response -} - -exports.find = async ctx => { - const appId = ctx.params.appId - await doInAppContext(getDevAppID(appId), async () => { - const db = getAppDB() - const app = await db.get(DocumentType.APP_METADATA) - ctx.body = { - roles: await getAllRoles(), - name: app.name, - version: app.version, - url: app.url, - } - }) -} - -exports.removeAppRole = async ctx => { - const { appId } = ctx.params - const db = getGlobalDB() - const users = await allUsers(ctx) - const bulk = [] - const cacheInvalidations = [] - for (let user of users) { - if (user.roles[appId]) { - cacheInvalidations.push(userCache.invalidateUser(user._id)) - delete user.roles[appId] - bulk.push(user) - } - } - await db.bulkDocs(bulk) - await Promise.all(cacheInvalidations) - ctx.body = { - message: "App role removed from all users", - } -} diff --git a/packages/worker/src/api/controllers/global/roles.ts b/packages/worker/src/api/controllers/global/roles.ts new file mode 100644 index 0000000000..e0cb52e1b0 --- /dev/null +++ b/packages/worker/src/api/controllers/global/roles.ts @@ -0,0 +1,66 @@ +import { + db as dbCore, + roles, + context, + cache, + tenancy, +} from "@budibase/backend-core" +import { BBContext, App } from "@budibase/types" +import { allUsers } from "../../../sdk/users" + +export async function fetch(ctx: BBContext) { + const tenantId = ctx.user!.tenantId + // always use the dev apps as they'll be most up to date (true) + const apps = (await dbCore.getAllApps({ tenantId, all: true })) as App[] + const promises = [] + for (let app of apps) { + // use dev app IDs + promises.push(roles.getAllRoles(app.appId)) + } + const roleList = await Promise.all(promises) + const response: any = {} + for (let app of apps) { + const deployedAppId = dbCore.getProdAppID(app.appId) + response[deployedAppId] = { + roles: roleList.shift(), + name: app.name, + version: app.version, + url: app.url, + } + } + ctx.body = response +} + +export async function find(ctx: BBContext) { + const appId = ctx.params.appId + await context.doInAppContext(dbCore.getDevAppID(appId), async () => { + const db = context.getAppDB() + const app = await db.get(dbCore.DocumentType.APP_METADATA) + ctx.body = { + roles: await roles.getAllRoles(), + name: app.name, + version: app.version, + url: app.url, + } + }) +} + +export async function removeAppRole(ctx: BBContext) { + const { appId } = ctx.params + const db = tenancy.getGlobalDB() + const users = await allUsers() + const bulk = [] + const cacheInvalidations = [] + for (let user of users) { + if (user.roles[appId]) { + cacheInvalidations.push(cache.user.invalidateUser(user._id)) + delete user.roles[appId] + bulk.push(user) + } + } + await db.bulkDocs(bulk) + await Promise.all(cacheInvalidations) + ctx.body = { + message: "App role removed from all users", + } +} diff --git a/packages/worker/src/api/controllers/global/workspaces.js b/packages/worker/src/api/controllers/global/workspaces.ts similarity index 52% rename from packages/worker/src/api/controllers/global/workspaces.js rename to packages/worker/src/api/controllers/global/workspaces.ts index d5d1037d9e..cf6486fec9 100644 --- a/packages/worker/src/api/controllers/global/workspaces.js +++ b/packages/worker/src/api/controllers/global/workspaces.ts @@ -1,16 +1,13 @@ -const { - getWorkspaceParams, - generateWorkspaceID, -} = require("@budibase/backend-core/db") -const { getGlobalDB } = require("@budibase/backend-core/tenancy") +import { tenancy, db as dbCore } from "@budibase/backend-core" +import { BBContext } from "@budibase/types" -exports.save = async function (ctx) { - const db = getGlobalDB() +export async function save(ctx: BBContext) { + const db = tenancy.getGlobalDB() const workspaceDoc = ctx.request.body // workspace does not exist yet if (!workspaceDoc._id) { - workspaceDoc._id = generateWorkspaceID() + workspaceDoc._id = dbCore.generateWorkspaceID() } try { @@ -19,38 +16,38 @@ exports.save = async function (ctx) { _id: response.id, _rev: response.rev, } - } catch (err) { + } catch (err: any) { ctx.throw(err.status, err) } } -exports.fetch = async function (ctx) { - const db = getGlobalDB() +export async function fetch(ctx: BBContext) { + const db = tenancy.getGlobalDB() const response = await db.allDocs( - getWorkspaceParams(undefined, { + dbCore.getWorkspaceParams(undefined, { include_docs: true, }) ) ctx.body = response.rows.map(row => row.doc) } -exports.find = async function (ctx) { - const db = getGlobalDB() +export async function find(ctx: BBContext) { + const db = tenancy.getGlobalDB() try { ctx.body = await db.get(ctx.params.id) - } catch (err) { + } catch (err: any) { ctx.throw(err.status, err) } } -exports.destroy = async function (ctx) { - const db = getGlobalDB() +export async function destroy(ctx: BBContext) { + const db = tenancy.getGlobalDB() const { id, rev } = ctx.params try { await db.remove(id, rev) ctx.body = { message: "Workspace deleted successfully" } - } catch (err) { + } catch (err: any) { ctx.throw(err.status, err) } } diff --git a/packages/worker/src/api/index.ts b/packages/worker/src/api/index.ts index 9a32792691..073235c3ba 100644 --- a/packages/worker/src/api/index.ts +++ b/packages/worker/src/api/index.ts @@ -104,7 +104,7 @@ const NO_TENANCY_ENDPOINTS = [ // add them all to be safe const NO_CSRF_ENDPOINTS = [...PUBLIC_ENDPOINTS] -const router = new Router() +const router: Router = new Router() router .use( compress({ @@ -163,4 +163,4 @@ for (let route of routes) { router.use(route.allowedMethods()) } -module.exports = router +export = router diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.ts similarity index 85% rename from packages/worker/src/api/routes/global/auth.js rename to packages/worker/src/api/routes/global/auth.ts index 2bf6bb68bf..05c799beb0 100644 --- a/packages/worker/src/api/routes/global/auth.js +++ b/packages/worker/src/api/routes/global/auth.ts @@ -1,13 +1,13 @@ -const Router = require("@koa/router") -const authController = require("../../controllers/global/auth") -const { joiValidator } = require("@budibase/backend-core/auth") -const Joi = require("joi") +import Router from "@koa/router" +import * as authController from "../../controllers/global/auth" +import { auth } from "@budibase/backend-core" +import Joi from "joi" -const router = new Router() +const router: Router = new Router() function buildAuthValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ username: Joi.string().required(), password: Joi.string().required(), }).required().unknown(false)) @@ -15,14 +15,14 @@ function buildAuthValidation() { function buildResetValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ email: Joi.string().required(), }).required().unknown(false)) } function buildResetUpdateValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ resetCode: Joi.string().required(), password: Joi.string().required(), }).required().unknown(false)) @@ -85,4 +85,4 @@ router .get("/api/global/auth/oidc/callback", authController.oidcAuth) .get("/api/admin/auth/oidc/callback", authController.oidcAuth) -module.exports = router +export = router diff --git a/packages/worker/src/api/routes/global/configs.js b/packages/worker/src/api/routes/global/configs.ts similarity index 70% rename from packages/worker/src/api/routes/global/configs.js rename to packages/worker/src/api/routes/global/configs.ts index fe8b1f97af..a4794abcc6 100644 --- a/packages/worker/src/api/routes/global/configs.js +++ b/packages/worker/src/api/routes/global/configs.ts @@ -1,11 +1,10 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/global/configs") -const { joiValidator } = require("@budibase/backend-core/auth") -const { adminOnly } = require("@budibase/backend-core/auth") -const Joi = require("joi") -const { Config } = require("@budibase/backend-core/constants") +import Router from "@koa/router" +import * as controller from "../../controllers/global/configs" +import { auth } from "@budibase/backend-core" +import Joi from "joi" +import { ConfigType } from "@budibase/types" -const router = new Router() +const router: Router = new Router() function smtpValidation() { // prettier-ignore @@ -55,27 +54,27 @@ function oidcValidation() { activated: Joi.boolean().required(), scopes: Joi.array().optional() }) - ).required(true) + ).required() }).unknown(true) } function buildConfigSaveValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ _id: Joi.string().optional(), _rev: Joi.string().optional(), workspace: Joi.string().optional(), - type: Joi.string().valid(...Object.values(Config)).required(), + type: Joi.string().valid(...Object.values(ConfigType)).required(), createdAt: Joi.string().optional(), updatedAt: Joi.string().optional(), config: Joi.alternatives() .conditional("type", { switch: [ - { is: Config.SMTP, then: smtpValidation() }, - { is: Config.SETTINGS, then: settingValidation() }, - { is: Config.ACCOUNT, then: Joi.object().unknown(true) }, - { is: Config.GOOGLE, then: googleValidation() }, - { is: Config.OIDC, then: oidcValidation() } + { is: ConfigType.SMTP, then: smtpValidation() }, + { is: ConfigType.SETTINGS, then: settingValidation() }, + { is: ConfigType.ACCOUNT, then: Joi.object().unknown(true) }, + { is: ConfigType.GOOGLE, then: googleValidation() }, + { is: ConfigType.OIDC, then: oidcValidation() } ], }), }).required().unknown(true), @@ -84,27 +83,27 @@ function buildConfigSaveValidation() { function buildUploadValidation() { // prettier-ignore - return joiValidator.params(Joi.object({ - type: Joi.string().valid(...Object.values(Config)).required(), + return auth.joiValidator.params(Joi.object({ + type: Joi.string().valid(...Object.values(ConfigType)).required(), name: Joi.string().required(), }).required().unknown(true)) } function buildConfigGetValidation() { // prettier-ignore - return joiValidator.params(Joi.object({ - type: Joi.string().valid(...Object.values(Config)).required() + return auth.joiValidator.params(Joi.object({ + type: Joi.string().valid(...Object.values(ConfigType)).required() }).required().unknown(true)) } router .post( "/api/global/configs", - adminOnly, + auth.adminOnly, buildConfigSaveValidation(), controller.save ) - .delete("/api/global/configs/:id/:rev", adminOnly, controller.destroy) + .delete("/api/global/configs/:id/:rev", auth.adminOnly, controller.destroy) .get("/api/global/configs", controller.fetch) .get("/api/global/configs/checklist", controller.configChecklist) .get( @@ -117,9 +116,9 @@ router .get("/api/global/configs/:type", buildConfigGetValidation(), controller.find) .post( "/api/global/configs/upload/:type/:name", - adminOnly, + auth.adminOnly, buildUploadValidation(), controller.upload ) -module.exports = router +export = router diff --git a/packages/worker/src/api/routes/global/email.js b/packages/worker/src/api/routes/global/email.ts similarity index 60% rename from packages/worker/src/api/routes/global/email.js rename to packages/worker/src/api/routes/global/email.ts index 962aea8d14..b49e5b5db2 100644 --- a/packages/worker/src/api/routes/global/email.js +++ b/packages/worker/src/api/routes/global/email.ts @@ -1,15 +1,14 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/global/email") -const { EmailTemplatePurpose } = require("../../../constants") -const { joiValidator } = require("@budibase/backend-core/auth") -const { adminOnly } = require("@budibase/backend-core/auth") -const Joi = require("joi") +import Router from "@koa/router" +import * as controller from "../../controllers/global/email" +import { EmailTemplatePurpose } from "../../../constants" +import { auth } from "@budibase/backend-core" +import Joi from "joi" -const router = new Router() +const router: Router = new Router() function buildEmailSendValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ email: Joi.string().email({ multiple: true, }), @@ -30,8 +29,8 @@ function buildEmailSendValidation() { router.post( "/api/global/email/send", buildEmailSendValidation(), - adminOnly, + auth.adminOnly, controller.sendEmail ) -module.exports = router +export = router diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 03908e052b..17b0d59e70 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -1,7 +1,7 @@ import Router from "@koa/router" import * as controller from "../../controllers/global/license" -const router = new Router() +const router: Router = new Router() router .post("/api/global/license/activate", controller.activate) diff --git a/packages/worker/src/api/routes/global/roles.js b/packages/worker/src/api/routes/global/roles.js deleted file mode 100644 index da7d5405ad..0000000000 --- a/packages/worker/src/api/routes/global/roles.js +++ /dev/null @@ -1,12 +0,0 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/global/roles") -const { builderOrAdmin } = require("@budibase/backend-core/auth") - -const router = new Router() - -router - .get("/api/global/roles", builderOrAdmin, controller.fetch) - .get("/api/global/roles/:appId", builderOrAdmin, controller.find) - .delete("/api/global/roles/:appId", builderOrAdmin, controller.removeAppRole) - -module.exports = router diff --git a/packages/worker/src/api/routes/global/roles.ts b/packages/worker/src/api/routes/global/roles.ts new file mode 100644 index 0000000000..249d1b7076 --- /dev/null +++ b/packages/worker/src/api/routes/global/roles.ts @@ -0,0 +1,16 @@ +import Router from "@koa/router" +import * as controller from "../../controllers/global/roles" +import { auth } from "@budibase/backend-core" + +const router: Router = new Router() + +router + .get("/api/global/roles", auth.builderOrAdmin, controller.fetch) + .get("/api/global/roles/:appId", auth.builderOrAdmin, controller.find) + .delete( + "/api/global/roles/:appId", + auth.builderOrAdmin, + controller.removeAppRole + ) + +export = router diff --git a/packages/worker/src/api/routes/global/self.ts b/packages/worker/src/api/routes/global/self.ts index 4b52225783..bb7828c09d 100644 --- a/packages/worker/src/api/routes/global/self.ts +++ b/packages/worker/src/api/routes/global/self.ts @@ -3,7 +3,7 @@ import * as controller from "../../controllers/global/self" import { auth } from "@budibase/backend-core" import { users } from "../validation" -const router = new Router() +const router: Router = new Router() router .post("/api/global/self/api_key", auth.builderOnly, controller.generateAPIKey) @@ -15,4 +15,4 @@ router controller.updateSelf ) -export default router as any +export = router diff --git a/packages/worker/src/api/routes/global/templates.ts b/packages/worker/src/api/routes/global/templates.ts index 40600ce9aa..a45e244a0b 100644 --- a/packages/worker/src/api/routes/global/templates.ts +++ b/packages/worker/src/api/routes/global/templates.ts @@ -5,7 +5,7 @@ import { auth as authCore } from "@budibase/backend-core" import Joi from "joi" const { adminOnly, joiValidator } = authCore -const router = new Router() +const router: Router = new Router() function buildTemplateSaveValidation() { // prettier-ignore @@ -34,4 +34,4 @@ router .get("/api/global/template/:id", controller.find) .delete("/api/global/template/:id/:rev", adminOnly, controller.destroy) -export default router +export = router diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.ts similarity index 61% rename from packages/worker/src/api/routes/global/users.js rename to packages/worker/src/api/routes/global/users.ts index 2d9b1d9ac9..cbaba67ba3 100644 --- a/packages/worker/src/api/routes/global/users.js +++ b/packages/worker/src/api/routes/global/users.ts @@ -1,17 +1,15 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/global/users") -const { joiValidator } = require("@budibase/backend-core/auth") -const { adminOnly } = require("@budibase/backend-core/auth") -const Joi = require("joi") -const cloudRestricted = require("../../../middleware/cloudRestricted") -const { users } = require("../validation") -const selfController = require("../../controllers/global/self") -const { builderOrAdmin } = require("@budibase/backend-core/auth") +import Router from "@koa/router" +import * as controller from "../../controllers/global/users" +import { auth } from "@budibase/backend-core" +import Joi from "joi" +import cloudRestricted from "../../../middleware/cloudRestricted" +import { users } from "../validation" +import * as selfController from "../../controllers/global/self" -const router = new Router() +const router: Router = new Router() function buildAdminInitValidation() { - return joiValidator.body( + return auth.joiValidator.body( Joi.object({ email: Joi.string().required(), password: Joi.string(), @@ -24,7 +22,7 @@ function buildAdminInitValidation() { function buildInviteValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ email: Joi.string().required(), userInfo: Joi.object().optional(), }).required()) @@ -32,7 +30,7 @@ function buildInviteValidation() { function buildInviteMultipleValidation() { // prettier-ignore - return joiValidator.body(Joi.array().required().items( + return auth.joiValidator.body(Joi.array().required().items( Joi.object({ email: Joi.string(), userInfo: Joi.object().optional(), @@ -42,7 +40,7 @@ function buildInviteMultipleValidation() { function buildInviteAcceptValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ inviteCode: Joi.string().required(), password: Joi.string().required(), }).required().unknown(true)) @@ -51,31 +49,35 @@ function buildInviteAcceptValidation() { router .post( "/api/global/users", - adminOnly, + auth.adminOnly, users.buildUserSaveValidation(), controller.save ) .post( "/api/global/users/bulk", - adminOnly, + auth.adminOnly, users.buildUserBulkUserValidation(), controller.bulkUpdate ) - .get("/api/global/users", builderOrAdmin, controller.fetch) - .post("/api/global/users/search", builderOrAdmin, controller.search) - .delete("/api/global/users/:id", adminOnly, controller.destroy) - .get("/api/global/users/count/:appId", builderOrAdmin, controller.countByApp) + .get("/api/global/users", auth.builderOrAdmin, controller.fetch) + .post("/api/global/users/search", auth.builderOrAdmin, controller.search) + .delete("/api/global/users/:id", auth.adminOnly, controller.destroy) + .get( + "/api/global/users/count/:appId", + auth.builderOrAdmin, + controller.countByApp + ) .get("/api/global/roles/:appId") .post( "/api/global/users/invite", - adminOnly, + auth.adminOnly, buildInviteValidation(), controller.invite ) .post( "/api/global/users/multi/invite", - adminOnly, + auth.adminOnly, buildInviteMultipleValidation(), controller.inviteMultiple ) @@ -94,7 +96,7 @@ router ) .get("/api/global/users/tenant/:id", controller.tenantUserLookup) // global endpoint but needs to come at end (blocks other endpoints otherwise) - .get("/api/global/users/:id", builderOrAdmin, controller.find) + .get("/api/global/users/:id", auth.builderOrAdmin, controller.find) // DEPRECATED - use new versions with self API .get("/api/global/users/self", selfController.getSelf) .post( @@ -103,4 +105,4 @@ router selfController.updateSelf ) -module.exports = router +export = router diff --git a/packages/worker/src/api/routes/global/workspaces.js b/packages/worker/src/api/routes/global/workspaces.ts similarity index 61% rename from packages/worker/src/api/routes/global/workspaces.js rename to packages/worker/src/api/routes/global/workspaces.ts index c0e172cd8d..82d4af9230 100644 --- a/packages/worker/src/api/routes/global/workspaces.js +++ b/packages/worker/src/api/routes/global/workspaces.ts @@ -1,14 +1,13 @@ -const Router = require("@koa/router") -const controller = require("../../controllers/global/workspaces") -const { joiValidator } = require("@budibase/backend-core/auth") -const { adminOnly } = require("@budibase/backend-core/auth") -const Joi = require("joi") +import Router from "@koa/router" +import * as controller from "../../controllers/global/workspaces" +import { auth } from "@budibase/backend-core" +import Joi from "joi" -const router = new Router() +const router: Router = new Router() function buildWorkspaceSaveValidation() { // prettier-ignore - return joiValidator.body(Joi.object({ + return auth.joiValidator.body(Joi.object({ _id: Joi.string().optional(), _rev: Joi.string().optional(), name: Joi.string().required(), @@ -27,12 +26,12 @@ function buildWorkspaceSaveValidation() { router .post( "/api/global/workspaces", - adminOnly, + auth.adminOnly, buildWorkspaceSaveValidation(), controller.save ) - .delete("/api/global/workspaces/:id", adminOnly, controller.destroy) + .delete("/api/global/workspaces/:id", auth.adminOnly, controller.destroy) .get("/api/global/workspaces", controller.fetch) .get("/api/global/workspaces/:id", controller.find) -module.exports = router +export = router diff --git a/packages/worker/src/api/routes/index.ts b/packages/worker/src/api/routes/index.ts index 0c107aae26..f0d4911771 100644 --- a/packages/worker/src/api/routes/index.ts +++ b/packages/worker/src/api/routes/index.ts @@ -1,3 +1,4 @@ +import Router from "@koa/router" import { api } from "@budibase/pro" import userRoutes from "./global/users" import configRoutes from "./global/configs" @@ -16,7 +17,7 @@ import accountRoutes from "./system/accounts" import restoreRoutes from "./system/restore" let userGroupRoutes = api.groups -export const routes = [ +export const routes: Router[] = [ configRoutes, userRoutes, workspaceRoutes, diff --git a/packages/worker/src/api/routes/system/accounts.ts b/packages/worker/src/api/routes/system/accounts.ts index 61a46ae437..a5996f3934 100644 --- a/packages/worker/src/api/routes/system/accounts.ts +++ b/packages/worker/src/api/routes/system/accounts.ts @@ -2,7 +2,7 @@ import Router from "@koa/router" import * as controller from "../../controllers/system/accounts" import { middleware } from "@budibase/backend-core" -const router = new Router() +const router: Router = new Router() router .put( diff --git a/packages/worker/src/api/routes/system/environment.ts b/packages/worker/src/api/routes/system/environment.ts index 360ec7ed84..841ec33c82 100644 --- a/packages/worker/src/api/routes/system/environment.ts +++ b/packages/worker/src/api/routes/system/environment.ts @@ -1,8 +1,8 @@ import Router from "@koa/router" import * as controller from "../../controllers/system/environment" -const router = new Router() +const router: Router = new Router() router.get("/api/system/environment", controller.fetch) -export default router +export = router diff --git a/packages/worker/src/api/routes/system/migrations.ts b/packages/worker/src/api/routes/system/migrations.ts index 5dcf90c4de..958de6bb60 100644 --- a/packages/worker/src/api/routes/system/migrations.ts +++ b/packages/worker/src/api/routes/system/migrations.ts @@ -2,7 +2,7 @@ import Router from "@koa/router" import * as migrationsController from "../../controllers/system/migrations" import { auth } from "@budibase/backend-core" -const router = new Router() +const router: Router = new Router() router .post( diff --git a/packages/worker/src/api/routes/system/restore.ts b/packages/worker/src/api/routes/system/restore.ts index ee4bee091d..e348715405 100644 --- a/packages/worker/src/api/routes/system/restore.ts +++ b/packages/worker/src/api/routes/system/restore.ts @@ -1,7 +1,7 @@ import * as controller from "../../controllers/system/restore" import Router from "@koa/router" -const router = new Router() +const router: Router = new Router() router.post("/api/system/restored", controller.systemRestored) diff --git a/packages/worker/src/api/routes/system/status.ts b/packages/worker/src/api/routes/system/status.ts index a5b393b421..7ae6d57699 100644 --- a/packages/worker/src/api/routes/system/status.ts +++ b/packages/worker/src/api/routes/system/status.ts @@ -1,8 +1,8 @@ import Router from "@koa/router" import * as controller from "../../controllers/system/status" -const router = new Router() +const router: Router = new Router() router.get("/api/system/status", controller.fetch) -export default router +export = router diff --git a/packages/worker/src/api/routes/system/tenants.ts b/packages/worker/src/api/routes/system/tenants.ts index 7feb73a234..7967de34b3 100644 --- a/packages/worker/src/api/routes/system/tenants.ts +++ b/packages/worker/src/api/routes/system/tenants.ts @@ -2,7 +2,7 @@ import Router from "@koa/router" import * as controller from "../../controllers/system/tenants" import { middleware } from "@budibase/backend-core" -const router = new Router() +const router: Router = new Router() router.delete( "/api/system/tenants/:tenantId", @@ -10,4 +10,4 @@ router.delete( controller.delete ) -export default router +export = router diff --git a/packages/worker/src/api/routes/validation/users.ts b/packages/worker/src/api/routes/validation/users.ts index 0cb14c047e..35f293ce87 100644 --- a/packages/worker/src/api/routes/validation/users.ts +++ b/packages/worker/src/api/routes/validation/users.ts @@ -1,4 +1,4 @@ -const { joiValidator } = require("@budibase/backend-core/auth") +import { auth } from "@budibase/backend-core" import Joi from "joi" let schema: any = { @@ -25,7 +25,7 @@ export const buildUserSaveValidation = (isSelf = false) => { _rev: Joi.string(), } } - return joiValidator.body(Joi.object(schema).required().unknown(true)) + return auth.joiValidator.body(Joi.object(schema).required().unknown(true)) } export const buildUserBulkUserValidation = (isSelf = false) => { @@ -46,5 +46,5 @@ export const buildUserBulkUserValidation = (isSelf = false) => { }), } - return joiValidator.body(Joi.object(bulkSchema).required().unknown(true)) + return auth.joiValidator.body(Joi.object(bulkSchema).required().unknown(true)) } diff --git a/packages/worker/src/constants/templates/index.js b/packages/worker/src/constants/templates/index.ts similarity index 60% rename from packages/worker/src/constants/templates/index.js rename to packages/worker/src/constants/templates/index.ts index 0631df7011..b0b6029c36 100644 --- a/packages/worker/src/constants/templates/index.js +++ b/packages/worker/src/constants/templates/index.ts @@ -1,15 +1,15 @@ -const { readStaticFile } = require("../../utilities/fileSystem") -const { +import { readStaticFile } from "../../utilities/fileSystem" +import { EmailTemplatePurpose, TemplateType, TemplatePurpose, GLOBAL_OWNER, -} = require("../index") -const { join } = require("path") -const { getTemplateParams } = require("@budibase/backend-core/db") -const { getGlobalDB } = require("@budibase/backend-core/tenancy") +} from "../index" +import { join } from "path" +import { db as dbCore, tenancy } from "@budibase/backend-core" +import { Template } from "@budibase/types" -exports.EmailTemplates = { +export const EmailTemplates = { [EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile( join(__dirname, "passwordRecovery.hbs") ), @@ -23,7 +23,7 @@ exports.EmailTemplates = { [EmailTemplatePurpose.CUSTOM]: readStaticFile(join(__dirname, "custom.hbs")), } -exports.addBaseTemplates = (templates, type = null) => { +export function addBaseTemplates(templates: Template[], type?: string) { let purposeList switch (type) { case TemplateType.EMAIL: @@ -38,9 +38,9 @@ exports.addBaseTemplates = (templates, type = null) => { if (templates.find(template => template.purpose === purpose)) { continue } - if (exports.EmailTemplates[purpose]) { + if (EmailTemplates[purpose]) { templates.push({ - contents: exports.EmailTemplates[purpose], + contents: EmailTemplates[purpose], purpose, type, }) @@ -49,10 +49,14 @@ exports.addBaseTemplates = (templates, type = null) => { return templates } -exports.getTemplates = async ({ ownerId, type, id } = {}) => { - const db = getGlobalDB() +export async function getTemplates({ + ownerId, + type, + id, +}: { ownerId?: string; type?: string; id?: string } = {}) { + const db = tenancy.getGlobalDB() const response = await db.allDocs( - getTemplateParams(ownerId || GLOBAL_OWNER, id, { + dbCore.getTemplateParams(ownerId || GLOBAL_OWNER, id, { include_docs: true, }) ) @@ -64,10 +68,10 @@ exports.getTemplates = async ({ ownerId, type, id } = {}) => { if (type) { templates = templates.filter(template => template.type === type) } - return exports.addBaseTemplates(templates, type) + return addBaseTemplates(templates, type) } -exports.getTemplateByPurpose = async (type, purpose) => { - const templates = await exports.getTemplates({ type }) - return templates.find(template => template.purpose === purpose) +export async function getTemplateByPurpose(type: string, purpose: string) { + const templates = await getTemplates({ type }) + return templates.find((template: Template) => template.purpose === purpose) } diff --git a/packages/worker/src/index.ts b/packages/worker/src/index.ts index a718beabb8..b1ebb72e20 100644 --- a/packages/worker/src/index.ts +++ b/packages/worker/src/index.ts @@ -1,5 +1,5 @@ // need to load environment first -const env = require("./environment") +import env from "./environment" // enable APM if configured if (process.env.ELASTIC_APM_ENABLED) { @@ -14,19 +14,17 @@ import { Event } from "@sentry/types/dist/event" import Application from "koa" import { bootstrap } from "global-agent" import * as db from "./db" +import { auth, logging, events, pinoSettings } from "@budibase/backend-core" db.init() -const Koa = require("koa") -const destroyable = require("server-destroy") -const koaBody = require("koa-body") +import Koa from "koa" +import koaBody from "koa-body" +import http from "http" +import * as api from "./api" +import * as redis from "./utilities/redis" +import Sentry from "@sentry/node" const koaSession = require("koa-session") -const { passport } = require("@budibase/backend-core/auth") -const { logAlert } = require("@budibase/backend-core/logging") const logger = require("koa-pino-logger") -const http = require("http") -const api = require("./api") -const redis = require("./utilities/redis") -const Sentry = require("@sentry/node") -import { events, pinoSettings } from "@budibase/backend-core" +const destroyable = require("server-destroy") // this will setup http and https proxies form env variables bootstrap() @@ -41,8 +39,8 @@ app.use(koaSession(app)) app.use(logger(pinoSettings())) // authentication -app.use(passport.initialize()) -app.use(passport.session()) +app.use(auth.passport.initialize()) +app.use(auth.passport.session()) // api routes app.use(api.routes()) @@ -81,17 +79,18 @@ server.on("close", async () => { const shutdown = () => { server.close() + // @ts-ignore server.destroy() } -export = server.listen(parseInt(env.PORT || 4002), async () => { +export = server.listen(parseInt(env.PORT || "4002"), async () => { console.log(`Worker running on ${JSON.stringify(server.address())}`) await redis.init() }) process.on("uncaughtException", err => { errCode = -1 - logAlert("Uncaught exception.", err) + logging.logAlert("Uncaught exception.", err) shutdown() }) diff --git a/packages/worker/src/middleware/cloudRestricted.js b/packages/worker/src/middleware/cloudRestricted.ts similarity index 53% rename from packages/worker/src/middleware/cloudRestricted.js rename to packages/worker/src/middleware/cloudRestricted.ts index 5b9d64b92f..d2ca0c7964 100644 --- a/packages/worker/src/middleware/cloudRestricted.js +++ b/packages/worker/src/middleware/cloudRestricted.ts @@ -1,13 +1,14 @@ -const env = require("../environment") -const { Header } = require("@budibase/backend-core/constants") +import env from "../environment" +import { constants } from "@budibase/backend-core" +import { BBContext } from "@budibase/types" /** * This is a restricted endpoint in the cloud. * Ensure that the correct API key has been supplied. */ -module.exports = async (ctx, next) => { +export = async (ctx: BBContext, next: any) => { if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { - const apiKey = ctx.request.headers[Header.API_KEY] + const apiKey = ctx.request.headers[constants.Header.API_KEY] if (apiKey !== env.INTERNAL_API_KEY) { ctx.throw(403, "Unauthorized") } diff --git a/packages/worker/src/utilities/appService.js b/packages/worker/src/utilities/appService.js deleted file mode 100644 index ae895b831c..0000000000 --- a/packages/worker/src/utilities/appService.js +++ /dev/null @@ -1,33 +0,0 @@ -const fetch = require("node-fetch") -const { Header } = require("@budibase/backend-core/constants") -const { getTenantId, isTenantIdSet } = require("@budibase/backend-core/tenancy") -const { checkSlashesInUrl } = require("../utilities") -const env = require("../environment") - -async function makeAppRequest(url, method, body) { - if (env.isTest()) { - return - } - const request = { headers: {} } - request.headers[Header.API_KEY] = env.INTERNAL_API_KEY - if (isTenantIdSet()) { - request.headers[Header.TENANT_ID] = getTenantId() - } - if (body) { - request.headers["Content-Type"] = "application/json" - request.body = JSON.stringify(body) - } - request.method = method - return fetch(checkSlashesInUrl(env.APPS_URL + url), request) -} - -exports.syncUserInApps = async userId => { - const response = await makeAppRequest( - `/api/users/metadata/sync/${userId}`, - "POST", - {} - ) - if (response && response.status !== 200) { - throw "Unable to sync user." - } -} diff --git a/packages/worker/src/utilities/appService.ts b/packages/worker/src/utilities/appService.ts new file mode 100644 index 0000000000..a0c4314f65 --- /dev/null +++ b/packages/worker/src/utilities/appService.ts @@ -0,0 +1,32 @@ +import fetch from "node-fetch" +import { constants, tenancy } from "@budibase/backend-core" +import { checkSlashesInUrl } from "../utilities" +import env from "../environment" + +async function makeAppRequest(url: string, method: string, body: any) { + if (env.isTest()) { + return + } + const request: any = { headers: {} } + request.headers[constants.Header.API_KEY] = env.INTERNAL_API_KEY + if (tenancy.isTenantIdSet()) { + request.headers[constants.Header.TENANT_ID] = tenancy.getTenantId() + } + if (body) { + request.headers["Content-Type"] = "application/json" + request.body = JSON.stringify(body) + } + request.method = method + return fetch(checkSlashesInUrl(env.APPS_URL + url), request) +} + +export async function syncUserInApps(userId: string) { + const response = await makeAppRequest( + `/api/users/metadata/sync/${userId}`, + "POST", + {} + ) + if (response && response.status !== 200) { + throw "Unable to sync user." + } +} diff --git a/packages/worker/src/utilities/fileSystem.js b/packages/worker/src/utilities/fileSystem.js deleted file mode 100644 index 8f0bc8d3ed..0000000000 --- a/packages/worker/src/utilities/fileSystem.js +++ /dev/null @@ -1,5 +0,0 @@ -const { readFileSync } = require("fs") - -exports.readStaticFile = path => { - return readFileSync(path, "utf-8") -} diff --git a/packages/worker/src/utilities/fileSystem.ts b/packages/worker/src/utilities/fileSystem.ts new file mode 100644 index 0000000000..603a797407 --- /dev/null +++ b/packages/worker/src/utilities/fileSystem.ts @@ -0,0 +1,5 @@ +import { readFileSync } from "fs" + +export function readStaticFile(path: string) { + return readFileSync(path, "utf-8") +} diff --git a/packages/worker/src/utilities/index.js b/packages/worker/src/utilities/index.ts similarity index 85% rename from packages/worker/src/utilities/index.js rename to packages/worker/src/utilities/index.ts index b402a82cf3..e1e065bd4e 100644 --- a/packages/worker/src/utilities/index.js +++ b/packages/worker/src/utilities/index.ts @@ -4,6 +4,6 @@ * @param {string} url The URL to test and remove any extra double slashes. * @return {string} The updated url. */ -exports.checkSlashesInUrl = url => { +export function checkSlashesInUrl(url: string) { return url.replace(/(https?:\/\/)|(\/)+/g, "$1$2") } diff --git a/packages/worker/src/utilities/users.js b/packages/worker/src/utilities/users.js deleted file mode 100644 index 93057ca34f..0000000000 --- a/packages/worker/src/utilities/users.js +++ /dev/null @@ -1,17 +0,0 @@ -const { getGlobalDB } = require("@budibase/backend-core/tenancy") -const { getGlobalUserParams } = require("@budibase/backend-core/db") - -exports.checkAnyUserExists = async () => { - try { - const db = getGlobalDB() - const users = await db.allDocs( - getGlobalUserParams(null, { - include_docs: true, - limit: 1, - }) - ) - return users && users.rows.length >= 1 - } catch (err) { - throw new Error("Unable to retrieve user list") - } -} diff --git a/packages/worker/src/utilities/users.ts b/packages/worker/src/utilities/users.ts new file mode 100644 index 0000000000..149d4d985c --- /dev/null +++ b/packages/worker/src/utilities/users.ts @@ -0,0 +1,16 @@ +import { tenancy, db as dbCore } from "@budibase/backend-core" + +export async function checkAnyUserExists() { + try { + const db = tenancy.getGlobalDB() + const users = await db.allDocs( + dbCore.getGlobalUserParams(null, { + include_docs: true, + limit: 1, + }) + ) + return users && users.rows.length >= 1 + } catch (err) { + throw new Error("Unable to retrieve user list") + } +}