1
0
Fork 0
mirror of synced 2024-06-14 00:14:39 +12:00
budibase/packages/server/src/api/controllers/user.js

235 lines
6.1 KiB
JavaScript

const {
generateUserMetadataID,
getUserMetadataParams,
generateUserFlagID,
} = require("../../db/utils")
const { InternalTables } = require("../../db/utils")
const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global")
const { getFullUser } = require("../../utilities/users")
const { isEqual } = require("lodash")
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
const {
getDevelopmentAppID,
getProdAppIDs,
dbExists,
} = require("@budibase/backend-core/db")
const { UserStatus } = require("@budibase/backend-core/constants")
const { getAppDB, doInAppContext } = require("@budibase/backend-core/context")
async function rawMetadata() {
const db = getAppDB()
return (
await db.allDocs(
getUserMetadataParams(null, {
include_docs: true,
})
)
).rows.map(row => row.doc)
}
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 () => {
// sync user metadata
const db = getAppDB()
const [users, metadata] = await Promise.all([getGlobalUsers(), rawMetadata()])
const toWrite = []
for (let user of users) {
const combined = await combineMetadataAndUser(user, metadata)
if (combined) {
toWrite.push(combined)
}
}
await db.bulkDocs(toWrite)
}
exports.syncUser = async function (ctx) {
let deleting = false,
user
const userId = ctx.params.id
try {
user = await getRawGlobalUser(userId)
} catch (err) {
if (err && err.status === 404) {
user = {}
deleting = true
} else {
throw err
}
}
const roles = deleting ? {} : user.roles
// remove props which aren't useful to metadata
delete user.password
delete user.forceResetPassword
delete user.roles
// run through all production appIDs in the users roles
let prodAppIds
// if they are a builder then get all production app IDs
if ((user.builder && user.builder.global) || deleting) {
prodAppIds = await getProdAppIDs()
} else {
prodAppIds = Object.entries(roles)
.filter(entry => entry[1] !== BUILTIN_ROLE_IDS.PUBLIC)
.map(([appId]) => appId)
}
for (let prodAppId of prodAppIds) {
const roleId = roles[prodAppId]
const devAppId = getDevelopmentAppID(prodAppId)
for (let appId of [prodAppId, devAppId]) {
if (!(await dbExists(appId))) {
continue
}
await doInAppContext(appId, async () => {
const db = getAppDB()
const metadataId = generateUserMetadataID(userId)
let metadata
try {
metadata = await db.get(metadataId)
} catch (err) {
if (deleting) {
return
}
metadata = {
tableId: InternalTables.USER_METADATA,
}
}
// assign the roleId for the metadata doc
if (roleId) {
metadata.roleId = roleId
}
let combined = !deleting
? combineMetadataAndUser(user, metadata)
: {
...metadata,
status: UserStatus.INACTIVE,
metadata: BUILTIN_ROLE_IDS.PUBLIC,
}
// if its null then there was no updates required
if (combined) {
await db.put(combined)
}
})
}
}
ctx.body = {
message: "User synced.",
}
}
exports.fetchMetadata = async function (ctx) {
const database = getAppDB()
const global = await getGlobalUsers()
const metadata = await rawMetadata(database)
const users = []
for (let user of global) {
// find the metadata that matches up to the global ID
const info = metadata.find(meta => meta._id.includes(user._id))
// remove these props, not for the correct DB
users.push({
...user,
...info,
tableId: InternalTables.USER_METADATA,
// make sure the ID is always a local ID, not a global one
_id: generateUserMetadataID(user._id),
})
}
ctx.body = users
}
exports.updateSelfMetadata = async function (ctx) {
// overwrite the ID with current users
ctx.request.body._id = ctx.user._id
// make sure no stale rev
delete ctx.request.body._rev
// make sure no csrf token
delete ctx.request.body.csrfToken
await exports.updateMetadata(ctx)
}
exports.updateMetadata = async function (ctx) {
const db = getAppDB()
const user = ctx.request.body
// this isn't applicable to the user
delete user.roles
const metadata = {
tableId: InternalTables.USER_METADATA,
...user,
}
ctx.body = await db.put(metadata)
}
exports.destroyMetadata = async function (ctx) {
const db = getAppDB()
try {
const dbUser = await db.get(ctx.params.id)
await db.remove(dbUser._id, dbUser._rev)
} catch (err) {
// error just means the global user has no config in this app
}
ctx.body = {
message: `User metadata ${ctx.params.id} deleted.`,
}
}
exports.findMetadata = async function (ctx) {
ctx.body = await getFullUser(ctx, ctx.params.id)
}
exports.setFlag = async function (ctx) {
const userId = ctx.user._id
const { flag, value } = ctx.request.body
if (!flag) {
ctx.throw(400, "Must supply a 'flag' field in request body.")
}
const flagDocId = generateUserFlagID(userId)
const db = getAppDB()
let doc
try {
doc = await db.get(flagDocId)
} catch (err) {
doc = { _id: flagDocId }
}
doc[flag] = value || true
await db.put(doc)
ctx.body = { message: "Flag set successfully" }
}
exports.getFlags = async function (ctx) {
const userId = ctx.user._id
const docId = generateUserFlagID(userId)
const db = getAppDB()
let doc
try {
doc = await db.get(docId)
} catch (err) {
doc = { _id: docId }
}
ctx.body = doc
}