2022-03-16 21:18:09 +13:00
|
|
|
import { EmailTemplatePurpose } from "../../../constants"
|
|
|
|
import { checkInviteCode } from "../../../utilities/redis"
|
|
|
|
import { sendEmail } from "../../../utilities/email"
|
2022-04-08 12:28:22 +12:00
|
|
|
import { users } from "../../../sdk"
|
2022-05-26 21:13:26 +12:00
|
|
|
import env from "../../../environment"
|
|
|
|
import { User, CloudAccount } from "@budibase/types"
|
|
|
|
import { events, accounts, tenancy } from "@budibase/backend-core"
|
2022-04-08 12:28:22 +12:00
|
|
|
|
2021-08-05 20:59:08 +12:00
|
|
|
const {
|
2022-04-08 12:28:22 +12:00
|
|
|
errors,
|
|
|
|
users: usersCore,
|
|
|
|
db: dbUtils,
|
|
|
|
} = require("@budibase/backend-core")
|
2021-06-02 02:58:40 +12:00
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
export const save = async (ctx: any) => {
|
2021-08-05 20:59:08 +12:00
|
|
|
try {
|
2022-04-08 12:28:22 +12:00
|
|
|
const user = await users.save(ctx.request.body)
|
2021-11-05 03:53:03 +13:00
|
|
|
ctx.body = user
|
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-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) => {
|
2021-08-05 20:59:08 +12:00
|
|
|
const { email, password, tenantId } = ctx.request.body
|
2021-09-10 04:08:27 +12:00
|
|
|
|
|
|
|
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
2021-09-22 05:20:26 +12:00
|
|
|
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-04-08 12:28:22 +12:00
|
|
|
if (await tenancy.doesTenantExist(tenantId)) {
|
2021-08-05 20:59:08 +12:00
|
|
|
ctx.throw(403, "Organisation already exists.")
|
|
|
|
}
|
|
|
|
|
2022-04-28 03:32:00 +12:00
|
|
|
const response = await tenancy.doWithGlobalDB(tenantId, async (db: any) => {
|
|
|
|
return db.allDocs(
|
|
|
|
dbUtils.getGlobalUserParams(null, {
|
2022-04-20 06:42:52 +12:00
|
|
|
include_docs: true,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
2021-09-24 10:25:25 +12:00
|
|
|
|
2022-03-16 21:18:09 +13:00
|
|
|
if (response.rows.some((row: any) => row.doc.admin)) {
|
2022-04-08 12:28:22 +12:00
|
|
|
ctx.throw(403, "You cannot initialise once a global user has been created.")
|
2021-05-06 09:06:31 +12:00
|
|
|
}
|
|
|
|
|
2022-05-25 07:01:13 +12:00
|
|
|
const user: User = {
|
2021-05-06 08:56:43 +12:00
|
|
|
email: email,
|
|
|
|
password: password,
|
2021-04-21 04:17:44 +12:00
|
|
|
roles: {},
|
|
|
|
builder: {
|
2021-05-06 08:56:43 +12:00
|
|
|
global: true,
|
|
|
|
},
|
|
|
|
admin: {
|
2021-04-21 04:17:44 +12:00
|
|
|
global: true,
|
|
|
|
},
|
2021-08-05 20:59:08 +12:00
|
|
|
tenantId,
|
|
|
|
}
|
|
|
|
try {
|
2022-04-08 12:28:22 +12:00
|
|
|
ctx.body = await tenancy.doInTenant(tenantId, async () => {
|
|
|
|
return users.save(user, hashPassword, requirePassword)
|
|
|
|
})
|
2022-05-26 21:13:26 +12:00
|
|
|
let account: CloudAccount | undefined
|
|
|
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
|
|
|
account = await accounts.getAccountByTenantId(tenantId)
|
|
|
|
}
|
|
|
|
await events.identification.identifyTenant(tenantId, account)
|
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)
|
2021-07-16 04:57:02 +12:00
|
|
|
}
|
2021-04-21 04:17:44 +12:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
await users.destroy(id, ctx.user)
|
2021-04-19 22:34:07 +12:00
|
|
|
ctx.body = {
|
2022-04-08 12:28:22 +12:00
|
|
|
message: `User ${id} deleted.`,
|
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-04-08 12:28:22 +12:00
|
|
|
const all = await 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-04-08 12:28:22 +12:00
|
|
|
ctx.body = await 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) => {
|
2021-08-05 20:59:08 +12:00
|
|
|
let { email, userInfo } = ctx.request.body
|
2022-04-08 12:28:22 +12:00
|
|
|
const existing = await usersCore.getGlobalUserByEmail(email)
|
2021-05-06 02:17:15 +12:00
|
|
|
if (existing) {
|
|
|
|
ctx.throw(400, "Email address already in use.")
|
|
|
|
}
|
2021-08-05 20:59:08 +12:00
|
|
|
if (!userInfo) {
|
|
|
|
userInfo = {}
|
|
|
|
}
|
2022-04-08 12:28:22 +12:00
|
|
|
userInfo.tenantId = tenancy.getTenantId()
|
2022-03-16 21:18:09 +13:00
|
|
|
const opts: any = {
|
2021-05-12 02:24:17 +12:00
|
|
|
subject: "{{ company }} platform invitation",
|
2021-05-25 05:45:43 +12:00
|
|
|
info: userInfo,
|
2022-03-16 21:18:09 +13:00
|
|
|
}
|
|
|
|
await sendEmail(email, EmailTemplatePurpose.INVITATION, opts)
|
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
|
|
|
}
|
2022-05-24 09:14:44 +12:00
|
|
|
await events.user.invited(userInfo)
|
2021-05-06 02:10:28 +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-05-25 07:01:13 +12:00
|
|
|
const saved = await 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
|
|
|
}
|
|
|
|
}
|