refactor groups into pro and some other pr comments
This commit is contained in:
parent
3914501084
commit
0c831f369d
|
@ -28,9 +28,9 @@ class UsageLimitError extends HTTPError {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FeatureDisabledError extends HTTPError {
|
class FeatureDisabledError extends HTTPError {
|
||||||
constructor(message, limitName) {
|
constructor(message, featureName) {
|
||||||
super(message, 400, codes.FEATURE_DISABLED, type)
|
super(message, 400, codes.FEATURE_DISABLED, type)
|
||||||
this.limitName = limitName
|
this.featureName = featureName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
GroupUpdatedEvent,
|
GroupUpdatedEvent,
|
||||||
GroupUsersAddedEvent,
|
GroupUsersAddedEvent,
|
||||||
GroupUsersDeletedEvent,
|
GroupUsersDeletedEvent,
|
||||||
GroupsAddedOnboarding,
|
GroupAddedOnboardingEvent,
|
||||||
UserGroupRoles,
|
UserGroupRoles,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
@ -34,24 +34,22 @@ export async function deleted(group: UserGroup) {
|
||||||
|
|
||||||
export async function usersAdded(emails: string[], group: UserGroup) {
|
export async function usersAdded(emails: string[], group: UserGroup) {
|
||||||
const properties: GroupUsersAddedEvent = {
|
const properties: GroupUsersAddedEvent = {
|
||||||
emails,
|
|
||||||
count: emails.length,
|
count: emails.length,
|
||||||
groupId: group._id as string,
|
groupId: group._id as string,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.USER_GROUP_USER_ADDED, properties)
|
await publishEvent(Event.USER_GROUP_USERS_ADDED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function usersDeleted(emails: string[], group: UserGroup) {
|
export async function usersDeleted(emails: string[], group: UserGroup) {
|
||||||
const properties: GroupUsersDeletedEvent = {
|
const properties: GroupUsersDeletedEvent = {
|
||||||
emails,
|
|
||||||
count: emails.length,
|
count: emails.length,
|
||||||
groupId: group._id as string,
|
groupId: group._id as string,
|
||||||
}
|
}
|
||||||
await publishEvent(Event.USER_GROUP_USER_REMOVED, properties)
|
await publishEvent(Event.USER_GROUP_USERS_REMOVED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createdOnboarding(groupId: string) {
|
export async function createdOnboarding(groupId: string) {
|
||||||
const properties: GroupsAddedOnboarding = {
|
const properties: GroupAddedOnboardingEvent = {
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
onboarding: true,
|
onboarding: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,21 @@ describe("/users", () => {
|
||||||
|
|
||||||
describe("fetch", () => {
|
describe("fetch", () => {
|
||||||
|
|
||||||
|
it("returns a list of users from an instance db", async () => {
|
||||||
|
await config.createUser("uuidx")
|
||||||
|
await config.createUser("uuidy")
|
||||||
|
const res = await request
|
||||||
|
.get(`/api/users/metadata`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.length).toBe(3)
|
||||||
|
expect(res.body.find(u => u._id === `ro_ta_users_us_uuidx`)).toBeDefined()
|
||||||
|
expect(res.body.find(u => u._id === `ro_ta_users_us_uuidy`)).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
it("should apply authorization to endpoint", async () => {
|
||||||
await config.createUser()
|
await config.createUser()
|
||||||
await checkPermissionsEndpoint({
|
await checkPermissionsEndpoint({
|
||||||
|
|
|
@ -14,8 +14,9 @@ const env = require("../environment")
|
||||||
const { getAppId } = require("@budibase/backend-core/context")
|
const { getAppId } = require("@budibase/backend-core/context")
|
||||||
const { groups } = require("@budibase/pro")
|
const { groups } = require("@budibase/pro")
|
||||||
|
|
||||||
exports.updateAppRole = async (user, { appId } = {}) => {
|
exports.updateAppRole = (user, { appId } = {}) => {
|
||||||
appId = appId || getAppId()
|
appId = appId || getAppId()
|
||||||
|
|
||||||
if (!user || !user.roles) {
|
if (!user || !user.roles) {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
@ -31,21 +32,33 @@ exports.updateAppRole = async (user, { appId } = {}) => {
|
||||||
// if a role wasn't found then either set as admin (builder) or public (everyone else)
|
// if a role wasn't found then either set as admin (builder) or public (everyone else)
|
||||||
if (!user.roleId && user.builder && user.builder.global) {
|
if (!user.roleId && user.builder && user.builder.global) {
|
||||||
user.roleId = BUILTIN_ROLE_IDS.ADMIN
|
user.roleId = BUILTIN_ROLE_IDS.ADMIN
|
||||||
} else if (!user.roleId && !user?.userGroups?.length) {
|
} else if (!user.roleId) {
|
||||||
user.roleId = BUILTIN_ROLE_IDS.PUBLIC
|
user.roleId = BUILTIN_ROLE_IDS.PUBLIC
|
||||||
} else if (!user.roleId && user?.userGroups?.length) {
|
|
||||||
let roleId = await groups.getGroupRoleId(user, appId)
|
|
||||||
user.roleId = roleId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete user.roles
|
delete user.roles
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
function processUser(user, { appId } = {}) {
|
async function checkGroupRoles(user, { appId } = {}) {
|
||||||
|
if (!user.roleId) {
|
||||||
|
let roleId = await groups.getGroupRoleId(user, appId)
|
||||||
|
user.roleId = roleId
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processUser(user, { appId } = {}) {
|
||||||
if (user) {
|
if (user) {
|
||||||
delete user.password
|
delete user.password
|
||||||
}
|
}
|
||||||
return exports.updateAppRole(user, { appId })
|
user = await exports.updateAppRole(user, { appId })
|
||||||
|
if (user?.userGroups?.length) {
|
||||||
|
user = await checkGroupRoles(user, { appId })
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getCachedSelf = async (ctx, appId) => {
|
exports.getCachedSelf = async (ctx, appId) => {
|
||||||
|
@ -93,6 +106,8 @@ exports.getGlobalUsers = async (users = null) => {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
return globalUsers
|
return globalUsers
|
||||||
}
|
}
|
||||||
|
console.log("maybe??")
|
||||||
|
|
||||||
return globalUsers.map(user => exports.updateAppRole(user))
|
return globalUsers.map(user => exports.updateAppRole(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ export interface User extends Document {
|
||||||
password?: string
|
password?: string
|
||||||
status?: string
|
status?: string
|
||||||
|
|
||||||
createdAt?: number
|
createdAt?: number // override the default createdAt behaviour - users sdk historically set this to Date.now()
|
||||||
userGroups?: string[] // override the default createdAt behaviour - users sdk historically set this to Date.now()
|
userGroups?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserRoles {
|
export interface UserRoles {
|
||||||
|
|
|
@ -5,7 +5,7 @@ export interface UserGroup extends Document {
|
||||||
icon: string
|
icon: string
|
||||||
color: string
|
color: string
|
||||||
users: User[]
|
users: User[]
|
||||||
apps: any
|
apps: any[]
|
||||||
roles: UserGroupRoles
|
roles: UserGroupRoles
|
||||||
createdAt?: number
|
createdAt?: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,10 +155,10 @@ export enum Event {
|
||||||
USER_GROUP_CREATED = "user_group:created",
|
USER_GROUP_CREATED = "user_group:created",
|
||||||
USER_GROUP_UPDATED = "user_group:updated",
|
USER_GROUP_UPDATED = "user_group:updated",
|
||||||
USER_GROUP_DELETED = "user_group:deleted",
|
USER_GROUP_DELETED = "user_group:deleted",
|
||||||
USER_GROUP_USER_ADDED = "user_group_user:added",
|
USER_GROUP_USERS_ADDED = "user_group:user_added",
|
||||||
USER_GROUP_USER_REMOVED = "user_group_user:deleted",
|
USER_GROUP_USERS_REMOVED = "user_group_:users_deleted",
|
||||||
USER_GROUP_PERMISSIONS_EDITED = "user_group_permissions:edited",
|
USER_GROUP_PERMISSIONS_EDITED = "user_group_:permissions_edited",
|
||||||
USER_GROUP_ONBOARDING = "user_group_onboarding:added",
|
USER_GROUP_ONBOARDING = "user_group_:onboarding_added",
|
||||||
}
|
}
|
||||||
|
|
||||||
// properties added at the final stage of the event pipeline
|
// properties added at the final stage of the event pipeline
|
||||||
|
|
|
@ -13,18 +13,16 @@ export interface GroupDeletedEvent extends BaseEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupUsersAddedEvent extends BaseEvent {
|
export interface GroupUsersAddedEvent extends BaseEvent {
|
||||||
emails: string[]
|
|
||||||
count: number
|
count: number
|
||||||
groupId: string
|
groupId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupUsersDeletedEvent extends BaseEvent {
|
export interface GroupUsersDeletedEvent extends BaseEvent {
|
||||||
emails: string[]
|
|
||||||
count: number
|
count: number
|
||||||
groupId: string
|
groupId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupsAddedOnboarding extends BaseEvent {
|
export interface GroupAddedOnboardingEvent extends BaseEvent {
|
||||||
groupId: string
|
groupId: string
|
||||||
onboarding: boolean
|
onboarding: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
cache,
|
cache,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { checkAnyUserExists } from "../../../utilities/users"
|
import { checkAnyUserExists } from "../../../utilities/users"
|
||||||
|
import { groups as groupUtils } from "@budibase/pro"
|
||||||
const MAX_USERS_UPLOAD_LIMIT = 1000
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
||||||
|
|
||||||
export const save = async (ctx: any) => {
|
export const save = async (ctx: any) => {
|
||||||
|
@ -46,34 +46,8 @@ export const bulkCreate = async (ctx: any) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response = await users.bulkCreate(newUsersRequested, groups)
|
let response = await users.bulkCreate(newUsersRequested, groups)
|
||||||
|
await groupUtils.bulkSaveGroupUsers(groupsToSave, response)
|
||||||
|
|
||||||
if (groupsToSave.length) {
|
|
||||||
let groupsPromises: any = []
|
|
||||||
groupsToSave.forEach(async (userGroup: UserGroup) => {
|
|
||||||
userGroup.users = [...userGroup.users, ...response]
|
|
||||||
groupsPromises.push(db.put(userGroup))
|
|
||||||
})
|
|
||||||
|
|
||||||
const groupResults = await Promise.all(groupsPromises)
|
|
||||||
await db.bulkDocs(groupResults)
|
|
||||||
|
|
||||||
let eventFns = []
|
|
||||||
for (const group of groupResults) {
|
|
||||||
eventFns.push(() => {
|
|
||||||
events.group.usersAdded(
|
|
||||||
response.map(u => u.email),
|
|
||||||
group
|
|
||||||
)
|
|
||||||
})
|
|
||||||
eventFns.push(() => {
|
|
||||||
events.group.createdOnboarding(group._id as string)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const fn of eventFns) {
|
|
||||||
await fn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.body = response
|
ctx.body = response
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ctx.throw(err.status || 400, err)
|
ctx.throw(err.status || 400, err)
|
||||||
|
@ -142,27 +116,9 @@ export const adminUser = async (ctx: any) => {
|
||||||
|
|
||||||
export const destroy = async (ctx: any) => {
|
export const destroy = async (ctx: any) => {
|
||||||
const id = ctx.params.id
|
const id = ctx.params.id
|
||||||
const db = tenancy.getGlobalDB()
|
|
||||||
let user: User = await db.get(id)
|
|
||||||
let groups = user.userGroups
|
|
||||||
|
|
||||||
await users.destroy(id, ctx.user)
|
await users.destroy(id, ctx.user)
|
||||||
|
|
||||||
// Remove asssosicated groups
|
|
||||||
if (groups) {
|
|
||||||
let groupsPromises = []
|
|
||||||
for (const groupId of groups) {
|
|
||||||
let group = await db.get(groupId)
|
|
||||||
let updatedUsersGroup = group.users.filter(
|
|
||||||
(groupUser: any) => groupUser.email !== user.email
|
|
||||||
)
|
|
||||||
group.users = updatedUsersGroup
|
|
||||||
groupsPromises.push(db.put(group))
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.bulkDocs(groupsPromises)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: `User ${id} deleted.`,
|
message: `User ${id} deleted.`,
|
||||||
}
|
}
|
||||||
|
@ -170,30 +126,9 @@ export const destroy = async (ctx: any) => {
|
||||||
|
|
||||||
export const bulkDelete = async (ctx: any) => {
|
export const bulkDelete = async (ctx: any) => {
|
||||||
const { userIds } = ctx.request.body
|
const { userIds } = ctx.request.body
|
||||||
const db = tenancy.getGlobalDB()
|
|
||||||
try {
|
try {
|
||||||
let { groupsToModify, usersResponse } = await users.bulkDelete(userIds)
|
let { groupsToModify, usersResponse } = await users.bulkDelete(userIds)
|
||||||
|
await groupUtils.bulkDeleteGroupUsers(groupsToModify)
|
||||||
// if there are groups to delete, do it here
|
|
||||||
if (Object.keys(groupsToModify).length) {
|
|
||||||
let groups = (
|
|
||||||
await db.allDocs({
|
|
||||||
include_docs: true,
|
|
||||||
keys: Object.keys(groupsToModify),
|
|
||||||
})
|
|
||||||
).rows.map((group: any) => group.doc)
|
|
||||||
|
|
||||||
let groupsPromises = []
|
|
||||||
for (const group of groups) {
|
|
||||||
let updatedUsersGroup = group.users.filter(
|
|
||||||
(groupUser: any) => !groupsToModify[group._id].includes(groupUser._id)
|
|
||||||
)
|
|
||||||
group.users = updatedUsersGroup
|
|
||||||
groupsPromises.push(db.put(group))
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.bulkDocs(groupsPromises)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: `${usersResponse.length} user(s) deleted`,
|
message: `${usersResponse.length} user(s) deleted`,
|
||||||
|
@ -314,7 +249,6 @@ export const inviteAccept = async (ctx: any) => {
|
||||||
return saved
|
return saved
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log(err)
|
|
||||||
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||||
// explicitly re-throw limit exceeded errors
|
// explicitly re-throw limit exceeded errors
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export * from "./users"
|
export * from "./users"
|
||||||
export * from "./events"
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { quotas } from "@budibase/pro"
|
||||||
import * as apps from "../../utilities/appService"
|
import * as apps from "../../utilities/appService"
|
||||||
import * as eventHelpers from "./events"
|
import * as eventHelpers from "./events"
|
||||||
import {
|
import {
|
||||||
events,
|
|
||||||
tenancy,
|
tenancy,
|
||||||
utils,
|
utils,
|
||||||
db as dbUtils,
|
db as dbUtils,
|
||||||
|
@ -16,8 +15,8 @@ import {
|
||||||
accounts,
|
accounts,
|
||||||
migrations,
|
migrations,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { MigrationType, UserGroup } from "@budibase/types"
|
import { MigrationType, User } from "@budibase/types"
|
||||||
import { build } from "joi"
|
import { groups as groupUtils } from "@budibase/pro/"
|
||||||
|
|
||||||
const PAGE_LIMIT = 8
|
const PAGE_LIMIT = 8
|
||||||
|
|
||||||
|
@ -107,10 +106,11 @@ export const buildUser = async (
|
||||||
) => {
|
) => {
|
||||||
let { password, _id } = user
|
let { password, _id } = user
|
||||||
|
|
||||||
// get the password, make sure one is defined
|
|
||||||
let hashedPassword
|
let hashedPassword
|
||||||
if (password) {
|
if (password) {
|
||||||
hashedPassword = opts.hashPassword ? await utils.hash(password) : password
|
hashedPassword = opts.hashPassword ? await utils.hash(password) : password
|
||||||
|
} else if (dbUser) {
|
||||||
|
hashedPassword = dbUser.password
|
||||||
} else if (opts.requirePassword) {
|
} else if (opts.requirePassword) {
|
||||||
throw "Password must be specified."
|
throw "Password must be specified."
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,6 @@ export const buildUser = async (
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
tenantId,
|
tenantId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the roles object is always present
|
// make sure the roles object is always present
|
||||||
if (!user.roles) {
|
if (!user.roles) {
|
||||||
user.roles = {}
|
user.roles = {}
|
||||||
|
@ -202,6 +201,7 @@ export const save = async (
|
||||||
const putUserFn = () => {
|
const putUserFn = () => {
|
||||||
return db.put(builtUser)
|
return db.put(builtUser)
|
||||||
}
|
}
|
||||||
|
console.log(builtUser)
|
||||||
if (eventHelpers.isAddingBuilder(builtUser, dbUser)) {
|
if (eventHelpers.isAddingBuilder(builtUser, dbUser)) {
|
||||||
response = await quotas.addDeveloper(putUserFn)
|
response = await quotas.addDeveloper(putUserFn)
|
||||||
} else {
|
} else {
|
||||||
|
@ -244,7 +244,10 @@ export const addTenant = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkCreate = async (newUsersRequested: any[], groups: any) => {
|
export const bulkCreate = async (
|
||||||
|
newUsersRequested: User[],
|
||||||
|
groups: string[]
|
||||||
|
) => {
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
const tenantId = tenancy.getTenantId()
|
const tenantId = tenancy.getTenantId()
|
||||||
|
|
||||||
|
@ -291,16 +294,18 @@ export const bulkCreate = async (newUsersRequested: any[], groups: any) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const usersToBulkSave = await Promise.all(usersToSave)
|
const usersToBulkSave = await Promise.all(usersToSave)
|
||||||
await quotas.addDevelopers(() => db.bulkDocs(usersToBulkSave), builderCount)
|
const response = await quotas.addDevelopers(
|
||||||
|
() => db.bulkDocs(usersToBulkSave),
|
||||||
|
builderCount
|
||||||
|
)
|
||||||
|
|
||||||
// Post processing of bulk added users, i.e events and cache operations
|
// Post processing of bulk added users, i.e events and cache operations
|
||||||
for (const user of usersToBulkSave) {
|
for (const user of usersToBulkSave) {
|
||||||
delete user.password
|
//await eventHelpers.handleSaveEvents(user, null)
|
||||||
await eventHelpers.handleSaveEvents(user, null)
|
//await apps.syncUserInApps(user._id)
|
||||||
await apps.syncUserInApps(user._id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return usersToBulkSave
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkDelete = async (userIds: any) => {
|
export const bulkDelete = async (userIds: any) => {
|
||||||
|
@ -356,6 +361,7 @@ export const bulkDelete = async (userIds: any) => {
|
||||||
export const destroy = async (id: string, currentUser: any) => {
|
export const destroy = async (id: string, currentUser: any) => {
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
const dbUser = await db.get(id)
|
const dbUser = await db.get(id)
|
||||||
|
let groups = dbUser.userGroups
|
||||||
|
|
||||||
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||||
// root account holder can't be deleted from inside budibase
|
// root account holder can't be deleted from inside budibase
|
||||||
|
@ -371,7 +377,13 @@ export const destroy = async (id: string, currentUser: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await deprovisioning.removeUserFromInfoDB(dbUser)
|
await deprovisioning.removeUserFromInfoDB(dbUser)
|
||||||
|
|
||||||
await db.remove(dbUser._id, dbUser._rev)
|
await db.remove(dbUser._id, dbUser._rev)
|
||||||
|
|
||||||
|
if (groups) {
|
||||||
|
await groupUtils.deleteGroupUsers(groups, dbUser)
|
||||||
|
}
|
||||||
|
|
||||||
await eventHelpers.handleDeleteEvents(dbUser)
|
await eventHelpers.handleDeleteEvents(dbUser)
|
||||||
await quotas.removeUser(dbUser)
|
await quotas.removeUser(dbUser)
|
||||||
await cache.user.invalidateUser(dbUser._id)
|
await cache.user.invalidateUser(dbUser._id)
|
||||||
|
|
Loading…
Reference in New Issue