diff --git a/packages/backend-core/src/migrations/definitions.ts b/packages/backend-core/src/migrations/definitions.ts index 745c8718c9..34ec0f0cad 100644 --- a/packages/backend-core/src/migrations/definitions.ts +++ b/packages/backend-core/src/migrations/definitions.ts @@ -37,4 +37,8 @@ export const DEFINITIONS: MigrationDefinition[] = [ type: MigrationType.INSTALLATION, name: MigrationName.EVENT_INSTALLATION_BACKFILL, }, + { + type: MigrationType.GLOBAL, + name: MigrationName.GLOBAL_INFO_SYNC_USERS, + }, ] diff --git a/packages/types/src/sdk/migrations.ts b/packages/types/src/sdk/migrations.ts index bb32d2e045..23a4d6d097 100644 --- a/packages/types/src/sdk/migrations.ts +++ b/packages/types/src/sdk/migrations.ts @@ -46,6 +46,7 @@ export enum MigrationName { EVENT_APP_BACKFILL = "event_app_backfill", EVENT_GLOBAL_BACKFILL = "event_global_backfill", EVENT_INSTALLATION_BACKFILL = "event_installation_backfill", + GLOBAL_INFO_SYNC_USERS = "global_info_sync_users", } export interface MigrationDefinition { diff --git a/packages/worker/src/api/controllers/system/migrations.ts b/packages/worker/src/api/controllers/system/migrations.ts new file mode 100644 index 0000000000..57a5f6261c --- /dev/null +++ b/packages/worker/src/api/controllers/system/migrations.ts @@ -0,0 +1,13 @@ +const { migrate, MIGRATIONS } = require("../../../migrations") + +export const runMigrations = async (ctx: any) => { + const options = ctx.request.body + // don't await as can take a while, just return + migrate(options) + ctx.status = 200 +} + +export const fetchDefinitions = async (ctx: any) => { + ctx.body = MIGRATIONS + ctx.status = 200 +} diff --git a/packages/worker/src/api/index.js b/packages/worker/src/api/index.js index 281d9d097c..ca56e0c5d2 100644 --- a/packages/worker/src/api/index.js +++ b/packages/worker/src/api/index.js @@ -106,7 +106,10 @@ router if (ctx.publicEndpoint) { return next() } - if ((!ctx.isAuthenticated || !ctx.user.budibaseAccess) && !ctx.internal) { + if ( + (!ctx.isAuthenticated || (ctx.user && !ctx.user.budibaseAccess)) && + !ctx.internal + ) { ctx.throw(403, "Unauthorized - no public worker access") } return next() diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js index 89c67bdf88..e112d4def3 100644 --- a/packages/worker/src/api/routes/index.js +++ b/packages/worker/src/api/routes/index.js @@ -12,6 +12,7 @@ const tenantsRoutes = require("./system/tenants") const statusRoutes = require("./system/status") const selfRoutes = require("./global/self") const licenseRoutes = require("./global/license") +const migrationRoutes = require("./system/migrations") let userGroupRoutes = api.groups exports.routes = [ @@ -29,4 +30,5 @@ exports.routes = [ selfRoutes, licenseRoutes, userGroupRoutes, + migrationRoutes, ] diff --git a/packages/worker/src/api/routes/system/migrations.ts b/packages/worker/src/api/routes/system/migrations.ts new file mode 100644 index 0000000000..5dcf90c4de --- /dev/null +++ b/packages/worker/src/api/routes/system/migrations.ts @@ -0,0 +1,19 @@ +import Router from "@koa/router" +import * as migrationsController from "../../controllers/system/migrations" +import { auth } from "@budibase/backend-core" + +const router = new Router() + +router + .post( + "/api/system/migrations/run", + auth.internalApi, + migrationsController.runMigrations + ) + .get( + "/api/system/migrations/definitions", + auth.internalApi, + migrationsController.fetchDefinitions + ) + +export = router diff --git a/packages/worker/src/migrations/functions/globalInfoSyncUsers.ts b/packages/worker/src/migrations/functions/globalInfoSyncUsers.ts new file mode 100644 index 0000000000..cae6c6af51 --- /dev/null +++ b/packages/worker/src/migrations/functions/globalInfoSyncUsers.ts @@ -0,0 +1,20 @@ +import { User } from "@budibase/types" +import * as sdk from "../../sdk" + +/** + * Date: + * Aug 2022 + * + * Description: + * Re-sync the global-db users to the global-info db users + */ +export const run = async (globalDb: any) => { + const users = (await sdk.users.allUsers()) as User[] + const promises = [] + for (let user of users) { + promises.push( + sdk.users.addTenant(user.tenantId, user._id as string, user.email) + ) + } + await Promise.all(promises) +} diff --git a/packages/worker/src/migrations/index.ts b/packages/worker/src/migrations/index.ts new file mode 100644 index 0000000000..6900596216 --- /dev/null +++ b/packages/worker/src/migrations/index.ts @@ -0,0 +1,74 @@ +import { migrations, redis } from "@budibase/backend-core" +import { Migration, MigrationOptions, MigrationName } from "@budibase/types" +import env from "../environment" + +// migration functions +import * as syncUserInfo from "./functions/globalInfoSyncUsers" + +/** + * Populate the migration function and additional configuration from + * the static migration definitions. + */ +export const buildMigrations = () => { + const definitions = migrations.DEFINITIONS + const workerMigrations: Migration[] = [] + + for (const definition of definitions) { + switch (definition.name) { + case MigrationName.GLOBAL_INFO_SYNC_USERS: { + // only needed in cloud + if (!env.SELF_HOSTED) { + workerMigrations.push({ + ...definition, + fn: syncUserInfo.run, + }) + } + break + } + } + } + + return workerMigrations +} + +export const MIGRATIONS = buildMigrations() + +export const migrate = async (options?: MigrationOptions) => { + if (env.SELF_HOSTED) { + await migrateWithLock(options) + } else { + await migrations.runMigrations(MIGRATIONS, options) + } +} + +const migrateWithLock = async (options?: MigrationOptions) => { + // get a new lock client + const redlock = await redis.clients.getMigrationsRedlock() + // lock for 15 minutes + const ttl = 1000 * 60 * 15 + + let migrationLock + + // acquire lock + try { + migrationLock = await redlock.lock("migrations", ttl) + } catch (e: any) { + if (e.name === "LockError") { + return + } else { + throw e + } + } + + // run migrations + try { + await migrations.runMigrations(MIGRATIONS, options) + } finally { + // release lock + try { + await migrationLock.unlock() + } catch (e) { + console.error("unable to release migration lock") + } + } +} diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index de312b7a25..e6b3f0a21d 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -106,7 +106,6 @@ const buildUser = async ( opts: SaveUserOpts = { hashPassword: true, requirePassword: true, - bulkCreate: false, }, tenantId: string, dbUser?: any @@ -185,15 +184,7 @@ export const save = async ( dbUser = await db.get(_id) } - let builtUser = await buildUser( - user, - { - hashPassword: true, - requirePassword: user.requirePassword, - }, - tenantId, - dbUser - ) + let builtUser = await buildUser(user, opts, tenantId, dbUser) // make sure we set the _id field for a new user if (!_id) { @@ -298,7 +289,6 @@ export const bulkCreate = async ( { hashPassword: true, requirePassword: user.requirePassword, - bulkCreate: false, }, tenantId )