@budibase/server
Version:
Budibase Web Server
162 lines (153 loc) • 4.25 kB
text/typescript
import { context, roles as rolesCore } from "@budibase/backend-core"
import {
ContextUser,
ContextUserMetadata,
Database,
isSSOUser,
UserBindings,
UserMetadata,
} from "@budibase/types"
import isEqual from "lodash/isEqual"
import {
generateUserMetadataID,
getGlobalIDFromUserMetadataID,
getUserMetadataParams,
InternalTables,
} from "../../db/utils"
import { getGlobalUsers } from "../../utilities/global"
export function combineMetadataAndUser(
user: ContextUser,
metadata: UserMetadata | UserMetadata[]
): ContextUserMetadata | null {
const metadataId = generateUserMetadataID(user._id!)
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// skip users with no access
if (
user.roleId == null ||
user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC
) {
// If it exists and it should not, we must remove it
if (found?._id) {
return { ...found, _deleted: true }
}
return null
}
delete user._rev
const newDoc = {
...user,
_id: metadataId,
tableId: InternalTables.USER_METADATA,
}
// copy rev over for the purposes of equality check
if (found) {
newDoc._rev = found._rev
newDoc.createdAt = found.createdAt
newDoc.updatedAt = found.updatedAt
}
// clear fields that shouldn't be in metadata
delete newDoc.password
delete newDoc.forceResetPassword
delete newDoc.roles
if (found == null || !isEqual(newDoc, found)) {
return {
...found,
...newDoc,
createdAt: found?.createdAt ?? (new Date().toISOString() as any),
updatedAt: new Date().toISOString(),
}
}
return null
}
export async function rawUserMetadata(db?: Database): Promise<UserMetadata[]> {
if (!db) {
db = context.getWorkspaceDB()
}
return (
await db.allDocs<UserMetadata>(
getUserMetadataParams(null, {
include_docs: true,
})
)
).rows.map(row => row.doc!)
}
export async function fetchMetadata(): Promise<ContextUserMetadata[]> {
const global = await getGlobalUsers()
const metadata = await rawUserMetadata()
const users: ContextUserMetadata[] = []
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!),
})
}
return users
}
export async function syncGlobalUsers() {
// sync user metadata
const dbs = [context.getDevWorkspaceDB(), context.getProdWorkspaceDB()]
for (let db of dbs) {
if (!(await db.exists())) {
continue
}
const [users, metadata] = await Promise.all([
getGlobalUsers(),
rawUserMetadata(db),
])
const toWrite = []
for (let user of users) {
const combined = combineMetadataAndUser(user, metadata)
if (combined) {
toWrite.push(combined)
}
}
let foundEmails: string[] = []
for (let data of metadata) {
if (!data._id) {
continue
}
const alreadyExisting =
data.email && foundEmails.indexOf(data.email) !== -1
const globalId = getGlobalIDFromUserMetadataID(data._id)
if (!users.find(user => user._id === globalId) || alreadyExisting) {
toWrite.push({ ...data, _deleted: true })
}
if (data.email) {
foundEmails.push(data.email)
}
}
await db.bulkDocs(toWrite)
}
}
export function getUserContextBindings(user: ContextUser): UserBindings {
if (!user) {
return {}
}
const bindings: UserBindings = {
_id: user._id,
_rev: user._rev,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
status: user.status,
roleId: user.roleId,
globalId: user.globalId,
userId: user.userId,
}
if (isSSOUser(user) && user.oauth2) {
bindings.oauth2 = {
accessToken: user.oauth2.accessToken,
refreshToken: user.oauth2.refreshToken,
}
bindings.provider = user.provider
bindings.providerType = user.providerType
}
return bindings
}