From 8bf2e7278e06010344efe09783a844af86ae2b3d Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Wed, 3 Nov 2021 23:15:38 +0000 Subject: [PATCH] Adding sync user endpoint to server which can be used by the worker. --- packages/auth/src/db/utils.js | 11 +++ packages/server/src/api/controllers/user.js | 87 +++++++++++++++------ packages/server/src/api/routes/user.js | 5 ++ packages/server/src/utilities/global.js | 8 +- packages/server/src/utilities/index.js | 6 ++ 5 files changed, 90 insertions(+), 27 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index fa162603e6..03bd773922 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -152,6 +152,17 @@ exports.getDeployedAppID = appId => { return appId } +/** + * Convert a deployed app ID to a development app ID. + */ +exports.getDevelopmentAppID = appId => { + if (!appId.startsWith(exports.APP_DEV_PREFIX)) { + const id = appId.split(exports.APP_PREFIX)[1] + return `${exports.APP_DEV_PREFIX}${id}` + } + return appId +} + exports.getCouchUrl = () => { if (!env.COUCH_DB_URL) return diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 5ff1026a4d..0362d1298b 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -4,12 +4,14 @@ const { getUserMetadataParams, } = require("../../db/utils") const { InternalTables } = require("../../db/utils") -const { getGlobalUsers } = require("../../utilities/global") +const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global") const { getFullUser } = require("../../utilities/users") const { isEqual } = require("lodash") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") +const { getDevelopmentAppID } = require("@budibase/auth/db") +const { doesDatabaseExist } = require("../../utilities") -exports.rawMetadata = async db => { +async function rawMetadata(db) { return ( await db.allDocs( getUserMetadataParams(null, { @@ -19,45 +21,80 @@ exports.rawMetadata = async db => { ).rows.map(row => row.doc) } +async function combineMetadataAndUser(user, metadata) { + // skip users with no access + if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) { + return null + } + delete user._rev + const metadataId = generateUserMetadataID(user._id) + const newDoc = { + ...user, + _id: metadataId, + tableId: InternalTables.USER_METADATA, + } + const found = Array.isArray(metadata) + ? metadata.find(doc => doc._id === metadataId) + : metadata + // copy rev over for the purposes of equality check + if (found) { + newDoc._rev = found._rev + } + if (found == null || !isEqual(newDoc, found)) { + return { + ...found, + ...newDoc, + } + } + return null +} + exports.syncGlobalUsers = async appId => { // sync user metadata const db = new CouchDB(appId) const [users, metadata] = await Promise.all([ getGlobalUsers(appId), - exports.rawMetadata(db), + rawMetadata(db), ]) const toWrite = [] for (let user of users) { - // skip users with no access - if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) { - continue - } - delete user._rev - const metadataId = generateUserMetadataID(user._id) - const newDoc = { - ...user, - _id: metadataId, - tableId: InternalTables.USER_METADATA, - } - const found = metadata.find(doc => doc._id === metadataId) - // copy rev over for the purposes of equality check - if (found) { - newDoc._rev = found._rev - } - if (found == null || !isEqual(newDoc, found)) { - toWrite.push({ - ...found, - ...newDoc, - }) + const combined = await combineMetadataAndUser(user, metadata) + if (combined) { + toWrite.push(combined) } } await db.bulkDocs(toWrite) } +exports.syncUser = async function (ctx) { + const user = await getRawGlobalUser(ctx.params.id) + const roles = user.roles + delete user.roles + for (let [prodAppId, roleId] of Object.entries(roles)) { + if (roleId === BUILTIN_ROLE_IDS.PUBLIC) { + continue + } + const devAppId = getDevelopmentAppID(prodAppId) + for (let appId of [prodAppId, devAppId]) { + if (!(await doesDatabaseExist(appId))) { + continue + } + const db = new CouchDB(appId) + const userId = generateUserMetadataID(user._id) + const metadata = await db.get(userId) + const combined = combineMetadataAndUser(user, metadata) + await db.put(combined) + } + } + ctx.body = { + message: "User synced.", + } +} + exports.fetchMetadata = async function (ctx) { const database = new CouchDB(ctx.appId) const global = await getGlobalUsers(ctx.appId) - const metadata = await exports.rawMetadata(database) + const metadata = await rawMetadata(database) const users = [] for (let user of global) { // find the metadata that matches up to the global ID diff --git a/packages/server/src/api/routes/user.js b/packages/server/src/api/routes/user.js index d171870215..43c08a7f33 100644 --- a/packages/server/src/api/routes/user.js +++ b/packages/server/src/api/routes/user.js @@ -34,5 +34,10 @@ router authorized(PermissionTypes.USER, PermissionLevels.WRITE), controller.destroyMetadata ) + .post( + "/api/users/sync/:id", + authorized(PermissionTypes.USER, PermissionLevels.WRITE), + controller.syncUser + ) module.exports = router diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js index 4b9bbcba8c..eb64aa7f77 100644 --- a/packages/server/src/utilities/global.js +++ b/packages/server/src/utilities/global.js @@ -46,9 +46,13 @@ exports.getCachedSelf = async (ctx, appId) => { return processUser(appId, user) } -exports.getGlobalUser = async (appId, userId) => { +exports.getRawGlobalUser = async userId => { const db = getGlobalDB() - let user = await db.get(getGlobalIDFromUserMetadataID(userId)) + return db.get(getGlobalIDFromUserMetadataID(userId)) +} + +exports.getGlobalUser = async (appId, userId) => { + let user = await exports.getRawGlobalUser(userId) return processUser(appId, user) } diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index e568ba063c..d67c8f1da0 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -134,3 +134,9 @@ exports.stringToReadStream = string => { }, }) } + +exports.doesDatabaseExist = async dbName => { + const db = new CouchDB(dbName, { skip_setup: true }) + const info = await db.info() + return info && !info.error +}