2022-05-24 02:03:52 +12:00
|
|
|
import { checkInviteCode } from "../../../utilities/redis"
|
2022-09-24 09:21:51 +12:00
|
|
|
import sdk from "../../../sdk"
|
2022-05-26 21:13:26 +12:00
|
|
|
import env from "../../../environment"
|
2022-09-01 18:58:05 +12:00
|
|
|
import {
|
2022-09-22 05:05:45 +12:00
|
|
|
BulkUserRequest,
|
|
|
|
BulkUserResponse,
|
2022-09-01 18:58:05 +12:00
|
|
|
CloudAccount,
|
2022-11-12 04:43:41 +13:00
|
|
|
CreateAdminUserRequest,
|
2022-09-01 18:58:05 +12:00
|
|
|
InviteUserRequest,
|
|
|
|
InviteUsersRequest,
|
2022-10-04 02:02:58 +13:00
|
|
|
SearchUsersRequest,
|
2022-09-01 18:58:05 +12:00
|
|
|
User,
|
|
|
|
} from "@budibase/types"
|
2022-05-29 10:03:31 +12:00
|
|
|
import {
|
2022-05-29 11:25:40 +12:00
|
|
|
accounts,
|
2022-05-29 10:03:31 +12:00
|
|
|
cache,
|
2022-08-03 01:58:18 +12:00
|
|
|
errors,
|
|
|
|
events,
|
|
|
|
tenancy,
|
2022-05-29 10:03:31 +12:00
|
|
|
} from "@budibase/backend-core"
|
2022-06-30 23:01:15 +12:00
|
|
|
import { checkAnyUserExists } from "../../../utilities/users"
|
2022-08-03 01:58:18 +12:00
|
|
|
|
2022-07-26 23:17:01 +12:00
|
|
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
export const save = async (ctx: any) => {
|
2021-08-05 20:59:08 +12:00
|
|
|
try {
|
2022-09-24 09:21:51 +12:00
|
|
|
ctx.body = await sdk.users.save(ctx.request.body)
|
2022-03-16 21:18:09 +13:00
|
|
|
} catch (err: any) {
|
2021-08-05 20:59:08 +12:00
|
|
|
ctx.throw(err.status || 400, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-22 05:05:45 +12:00
|
|
|
const bulkDelete = async (userIds: string[], currentUserId: string) => {
|
|
|
|
if (userIds?.indexOf(currentUserId) !== -1) {
|
|
|
|
throw new Error("Unable to delete self.")
|
|
|
|
}
|
2022-09-24 09:21:51 +12:00
|
|
|
return await sdk.users.bulkDelete(userIds)
|
2022-09-22 05:05:45 +12:00
|
|
|
}
|
2022-07-26 23:17:01 +12:00
|
|
|
|
2022-09-22 05:05:45 +12:00
|
|
|
const bulkCreate = async (users: User[], groupIds: string[]) => {
|
|
|
|
if (!env.SELF_HOSTED && users.length > MAX_USERS_UPLOAD_LIMIT) {
|
|
|
|
throw new Error(
|
2022-07-26 23:17:01 +12:00
|
|
|
"Max limit for upload is 1000 users. Please reduce file size and try again."
|
|
|
|
)
|
|
|
|
}
|
2022-09-24 09:21:51 +12:00
|
|
|
return await sdk.users.bulkCreate(users, groupIds)
|
2022-09-22 05:05:45 +12:00
|
|
|
}
|
2022-07-26 23:17:01 +12:00
|
|
|
|
2022-09-22 05:05:45 +12:00
|
|
|
export const bulkUpdate = async (ctx: any) => {
|
|
|
|
const currentUserId = ctx.user._id
|
|
|
|
const input = ctx.request.body as BulkUserRequest
|
|
|
|
let created, deleted
|
2022-07-18 23:33:56 +12:00
|
|
|
try {
|
2022-09-22 05:05:45 +12:00
|
|
|
if (input.create) {
|
|
|
|
created = await bulkCreate(input.create.users, input.create.groups)
|
|
|
|
}
|
|
|
|
if (input.delete) {
|
|
|
|
deleted = await bulkDelete(input.delete.userIds, currentUserId)
|
|
|
|
}
|
2022-07-12 02:29:39 +12:00
|
|
|
} catch (err: any) {
|
2022-09-24 09:21:51 +12:00
|
|
|
ctx.throw(err.status || 400, err?.message || err)
|
2022-07-12 02:29:39 +12:00
|
|
|
}
|
2022-09-22 05:05:45 +12:00
|
|
|
ctx.body = { created, deleted } as BulkUserResponse
|
2022-07-12 02:29:39 +12:00
|
|
|
}
|
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
const parseBooleanParam = (param: any) => {
|
2021-11-16 08:34:08 +13:00
|
|
|
return !(param && param === "false")
|
2021-09-22 05:20:26 +12:00
|
|
|
}
|
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
export const adminUser = async (ctx: any) => {
|
2022-11-12 04:43:41 +13:00
|
|
|
const { email, password, tenantId } = ctx.request
|
|
|
|
.body as CreateAdminUserRequest
|
2022-05-29 10:03:31 +12:00
|
|
|
await tenancy.doInTenant(tenantId, async () => {
|
2022-05-27 03:48:26 +12:00
|
|
|
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
|
|
|
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
|
|
|
|
// account portal sends no password for SSO users
|
|
|
|
const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
|
2021-09-10 04:08:27 +12:00
|
|
|
|
2022-05-29 10:03:31 +12:00
|
|
|
if (await tenancy.doesTenantExist(tenantId)) {
|
2022-05-27 03:48:26 +12:00
|
|
|
ctx.throw(403, "Organisation already exists.")
|
|
|
|
}
|
2021-08-05 20:59:08 +12:00
|
|
|
|
2022-06-30 23:01:15 +12:00
|
|
|
const userExists = await checkAnyUserExists()
|
|
|
|
if (userExists) {
|
2022-05-27 03:48:26 +12:00
|
|
|
ctx.throw(
|
|
|
|
403,
|
|
|
|
"You cannot initialise once an global user has been created."
|
|
|
|
)
|
|
|
|
}
|
2021-05-06 09:06:31 +12:00
|
|
|
|
2022-05-29 11:25:40 +12:00
|
|
|
const user: User = {
|
2022-05-27 03:48:26 +12:00
|
|
|
email: email,
|
|
|
|
password: password,
|
|
|
|
createdAt: Date.now(),
|
|
|
|
roles: {},
|
|
|
|
builder: {
|
|
|
|
global: true,
|
|
|
|
},
|
|
|
|
admin: {
|
|
|
|
global: true,
|
|
|
|
},
|
2022-05-24 02:03:52 +12:00
|
|
|
tenantId,
|
2022-05-27 03:48:26 +12:00
|
|
|
}
|
|
|
|
try {
|
2022-07-07 03:42:38 +12:00
|
|
|
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
|
|
|
// stuck in a cycle
|
|
|
|
await cache.bustCache(cache.CacheKeys.CHECKLIST)
|
2022-09-24 09:21:51 +12:00
|
|
|
const finalUser = await sdk.users.save(user, {
|
2022-05-27 03:48:26 +12:00
|
|
|
hashPassword,
|
2022-05-29 11:25:40 +12:00
|
|
|
requirePassword,
|
2022-04-20 06:42:52 +12:00
|
|
|
})
|
2021-09-24 10:25:25 +12:00
|
|
|
|
2022-05-29 11:25:40 +12:00
|
|
|
// events
|
|
|
|
let account: CloudAccount | undefined
|
|
|
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
|
|
|
account = await accounts.getAccountByTenantId(tenantId)
|
|
|
|
}
|
|
|
|
await events.identification.identifyTenantGroup(tenantId, account)
|
2021-05-06 09:06:31 +12:00
|
|
|
|
2022-05-27 03:48:26 +12:00
|
|
|
ctx.body = finalUser
|
|
|
|
} catch (err: any) {
|
|
|
|
ctx.throw(err.status || 400, err)
|
|
|
|
}
|
|
|
|
})
|
2021-04-21 04:17:44 +12:00
|
|
|
}
|
|
|
|
|
2022-07-30 00:10:00 +12:00
|
|
|
export const countByApp = async (ctx: any) => {
|
|
|
|
const appId = ctx.params.appId
|
|
|
|
try {
|
2022-09-24 09:21:51 +12:00
|
|
|
ctx.body = await sdk.users.countUsersByApp(appId)
|
2022-07-30 00:10:00 +12:00
|
|
|
} catch (err: any) {
|
|
|
|
ctx.throw(err.status || 400, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
export const destroy = async (ctx: any) => {
|
2022-04-08 12:28:22 +12:00
|
|
|
const id = ctx.params.id
|
2022-08-03 01:58:18 +12:00
|
|
|
if (id === ctx.user._id) {
|
|
|
|
ctx.throw(400, "Unable to delete self.")
|
|
|
|
}
|
2022-07-20 01:20:57 +12:00
|
|
|
|
2022-09-24 09:21:51 +12:00
|
|
|
await sdk.users.destroy(id, ctx.user)
|
2022-07-20 01:20:57 +12:00
|
|
|
|
2021-04-19 22:34:07 +12:00
|
|
|
ctx.body = {
|
2022-04-08 12:28:22 +12:00
|
|
|
message: `User ${id} deleted.`,
|
2021-10-04 23:30:59 +13:00
|
|
|
}
|
2021-04-19 22:34:07 +12:00
|
|
|
}
|
2021-10-04 23:30:59 +13:00
|
|
|
|
2022-07-01 03:01:14 +12:00
|
|
|
export const search = async (ctx: any) => {
|
2022-10-04 02:02:58 +13:00
|
|
|
const body = ctx.request.body as SearchUsersRequest
|
|
|
|
const paginated = await sdk.users.paginatedUsers(body)
|
2021-04-19 22:34:07 +12:00
|
|
|
// user hashed password shouldn't ever be returned
|
2022-06-30 06:11:00 +12:00
|
|
|
for (let user of paginated.data) {
|
2021-04-19 22:34:07 +12:00
|
|
|
if (user) {
|
|
|
|
delete user.password
|
|
|
|
}
|
|
|
|
}
|
2022-06-30 06:11:00 +12:00
|
|
|
ctx.body = paginated
|
2021-04-19 22:34:07 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
// called internally by app server user fetch
|
2022-03-16 21:18:09 +13:00
|
|
|
export const fetch = async (ctx: any) => {
|
2022-09-24 09:21:51 +12:00
|
|
|
const all = await sdk.users.allUsers()
|
2021-04-19 22:34:07 +12:00
|
|
|
// user hashed password shouldn't ever be returned
|
2022-03-22 06:13:16 +13:00
|
|
|
for (let user of all) {
|
2021-04-19 22:34:07 +12:00
|
|
|
if (user) {
|
|
|
|
delete user.password
|
|
|
|
}
|
|
|
|
}
|
2022-03-26 05:08:12 +13:00
|
|
|
ctx.body = all
|
2021-04-19 22:34:07 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
// called internally by app server user find
|
2022-03-16 21:18:09 +13:00
|
|
|
export const find = async (ctx: any) => {
|
2022-09-24 09:21:51 +12:00
|
|
|
ctx.body = await sdk.users.getUser(ctx.params.id)
|
2021-04-19 22:34:07 +12:00
|
|
|
}
|
2021-05-06 02:10:28 +12:00
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
export const tenantUserLookup = async (ctx: any) => {
|
2021-09-29 03:49:03 +13:00
|
|
|
const id = ctx.params.id
|
2022-04-08 12:28:22 +12:00
|
|
|
const user = await tenancy.getTenantUser(id)
|
2021-09-29 03:49:03 +13:00
|
|
|
if (user) {
|
|
|
|
ctx.body = user
|
|
|
|
} else {
|
2021-09-18 00:41:22 +12:00
|
|
|
ctx.throw(400, "No tenant user found.")
|
2021-08-05 20:59:08 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
export const invite = async (ctx: any) => {
|
2022-09-01 18:58:05 +12:00
|
|
|
const request = ctx.request.body as InviteUserRequest
|
2022-09-24 09:21:51 +12:00
|
|
|
const response = await sdk.users.invite([request])
|
2022-09-01 18:58:05 +12:00
|
|
|
|
|
|
|
// explicitly throw for single user invite
|
|
|
|
if (response.unsuccessful.length) {
|
|
|
|
const reason = response.unsuccessful[0].reason
|
|
|
|
if (reason === "Unavailable") {
|
|
|
|
ctx.throw(400, reason)
|
|
|
|
} else {
|
|
|
|
ctx.throw(500, reason)
|
|
|
|
}
|
2022-03-16 21:18:09 +13:00
|
|
|
}
|
2022-09-01 18:58:05 +12:00
|
|
|
|
2021-05-06 02:17:15 +12:00
|
|
|
ctx.body = {
|
2021-05-06 02:19:44 +12:00
|
|
|
message: "Invitation has been sent.",
|
2021-05-06 02:17:15 +12:00
|
|
|
}
|
2021-05-06 02:10:28 +12:00
|
|
|
}
|
|
|
|
|
2022-07-05 20:21:59 +12:00
|
|
|
export const inviteMultiple = async (ctx: any) => {
|
2022-09-01 18:58:05 +12:00
|
|
|
const request = ctx.request.body as InviteUsersRequest
|
2022-09-24 09:21:51 +12:00
|
|
|
ctx.body = await sdk.users.invite(request)
|
2022-07-05 20:21:59 +12:00
|
|
|
}
|
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
export const inviteAccept = async (ctx: any) => {
|
2021-05-20 02:09:57 +12:00
|
|
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
2021-05-06 02:17:15 +12:00
|
|
|
try {
|
2021-08-05 20:59:08 +12:00
|
|
|
// info is an extension of the user object that was stored by global
|
2022-03-16 21:18:09 +13:00
|
|
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
2022-04-12 23:34:36 +12:00
|
|
|
ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
|
2022-09-24 09:21:51 +12:00
|
|
|
const saved = await sdk.users.save({
|
2021-08-05 20:59:08 +12:00
|
|
|
firstName,
|
|
|
|
lastName,
|
|
|
|
password,
|
|
|
|
email,
|
|
|
|
...info,
|
2022-04-08 12:28:22 +12:00
|
|
|
})
|
2022-05-26 21:13:26 +12:00
|
|
|
const db = tenancy.getGlobalDB()
|
2022-05-25 07:01:13 +12:00
|
|
|
const user = await db.get(saved._id)
|
2022-05-24 09:14:44 +12:00
|
|
|
await events.user.inviteAccepted(user)
|
2022-05-25 07:01:13 +12:00
|
|
|
return saved
|
2022-04-08 12:28:22 +12:00
|
|
|
})
|
2022-03-16 21:18:09 +13:00
|
|
|
} catch (err: any) {
|
2022-03-05 02:42:50 +13:00
|
|
|
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
|
|
|
// explicitly re-throw limit exceeded errors
|
|
|
|
ctx.throw(400, err)
|
|
|
|
}
|
2021-05-06 02:17:15 +12:00
|
|
|
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
2021-05-06 02:10:28 +12:00
|
|
|
}
|
|
|
|
}
|