Merge commit to dev
This commit is contained in:
parent
0ee77aa9b0
commit
b4c88bd545
|
@ -16,6 +16,7 @@ export interface BulkUserRequest {
|
||||||
userIds: string[]
|
userIds: string[]
|
||||||
}
|
}
|
||||||
create?: {
|
create?: {
|
||||||
|
roles?: any[]
|
||||||
users: User[]
|
users: User[]
|
||||||
groups: any[]
|
groups: any[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { checkInviteCode } from "../../../utilities/redis"
|
import {
|
||||||
|
checkInviteCode,
|
||||||
|
getInviteCodes,
|
||||||
|
updateInviteCode,
|
||||||
|
} from "../../../utilities/redis"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import {
|
import {
|
||||||
|
@ -6,7 +10,6 @@ import {
|
||||||
BulkUserResponse,
|
BulkUserResponse,
|
||||||
CloudAccount,
|
CloudAccount,
|
||||||
CreateAdminUserRequest,
|
CreateAdminUserRequest,
|
||||||
InviteUserRequest,
|
|
||||||
InviteUsersRequest,
|
InviteUsersRequest,
|
||||||
SearchUsersRequest,
|
SearchUsersRequest,
|
||||||
User,
|
User,
|
||||||
|
@ -19,6 +22,7 @@ import {
|
||||||
tenancy,
|
tenancy,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { checkAnyUserExists } from "../../../utilities/users"
|
import { checkAnyUserExists } from "../../../utilities/users"
|
||||||
|
import { isEmailConfigured } from "src/utilities/email"
|
||||||
|
|
||||||
const MAX_USERS_UPLOAD_LIMIT = 1000
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
||||||
|
|
||||||
|
@ -186,9 +190,54 @@ export const tenantUserLookup = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Encapsulate the app user onboarding flows here.
|
||||||
|
*/
|
||||||
|
export const onboardUsers = async (ctx: any) => {
|
||||||
|
const request = ctx.request.body as InviteUsersRequest | BulkUserRequest
|
||||||
|
const isBulkCreate = "create" in request
|
||||||
|
|
||||||
|
const emailConfigured = await isEmailConfigured()
|
||||||
|
|
||||||
|
let onboardingResponse
|
||||||
|
|
||||||
|
if (isBulkCreate) {
|
||||||
|
// @ts-ignore
|
||||||
|
const { users, groups, roles } = request.create
|
||||||
|
const assignUsers = users.map((user: User) => (user.roles = roles))
|
||||||
|
onboardingResponse = await sdk.users.bulkCreate(assignUsers, groups)
|
||||||
|
ctx.body = onboardingResponse
|
||||||
|
} else if (emailConfigured) {
|
||||||
|
onboardingResponse = await invite(ctx)
|
||||||
|
} else if (!emailConfigured) {
|
||||||
|
const inviteRequest = ctx.request.body as InviteUsersRequest
|
||||||
|
const users: User[] = inviteRequest.map(invite => {
|
||||||
|
let password = Math.random().toString(36).substring(2, 22)
|
||||||
|
|
||||||
|
return {
|
||||||
|
email: invite.email,
|
||||||
|
password,
|
||||||
|
forceResetPassword: true,
|
||||||
|
roles: invite.userInfo.apps,
|
||||||
|
admin: { global: false },
|
||||||
|
builder: { global: false },
|
||||||
|
tenantId: tenancy.getTenantId(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let bulkCreateReponse = await sdk.users.bulkCreate(users, [])
|
||||||
|
onboardingResponse = {
|
||||||
|
...bulkCreateReponse,
|
||||||
|
created: true,
|
||||||
|
}
|
||||||
|
ctx.body = onboardingResponse
|
||||||
|
} else {
|
||||||
|
ctx.throw(400, "User onboarding failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const invite = async (ctx: any) => {
|
export const invite = async (ctx: any) => {
|
||||||
const request = ctx.request.body as InviteUserRequest
|
const request = ctx.request.body as InviteUsersRequest
|
||||||
const response = await sdk.users.invite([request])
|
const response = await sdk.users.invite(request)
|
||||||
|
|
||||||
// explicitly throw for single user invite
|
// explicitly throw for single user invite
|
||||||
if (response.unsuccessful.length) {
|
if (response.unsuccessful.length) {
|
||||||
|
@ -202,6 +251,8 @@ export const invite = async (ctx: any) => {
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Invitation has been sent.",
|
message: "Invitation has been sent.",
|
||||||
|
successful: response.successful,
|
||||||
|
unsuccessful: response.unsuccessful,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,19 +274,75 @@ export const checkInvite = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getUserInvites = async (ctx: any) => {
|
||||||
|
let invites
|
||||||
|
try {
|
||||||
|
// Restricted to the currently authenticated tenant
|
||||||
|
invites = await getInviteCodes([ctx.user.tenantId])
|
||||||
|
} catch (e) {
|
||||||
|
ctx.throw(400, "There was a problem fetching invites")
|
||||||
|
}
|
||||||
|
ctx.body = invites
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateInvite = async (ctx: any) => {
|
||||||
|
const { code } = ctx.params
|
||||||
|
let updateBody = { ...ctx.request.body }
|
||||||
|
|
||||||
|
delete updateBody.email
|
||||||
|
|
||||||
|
let invite
|
||||||
|
try {
|
||||||
|
invite = await checkInviteCode(code, false)
|
||||||
|
if (!invite) {
|
||||||
|
throw new Error("The invite could not be retrieved")
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ctx.throw(400, "There was a problem with the invite")
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated = {
|
||||||
|
...invite,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) {
|
||||||
|
updated.info.apps = []
|
||||||
|
} else {
|
||||||
|
updated.info = {
|
||||||
|
...invite.info,
|
||||||
|
apps: {
|
||||||
|
...invite.info.apps,
|
||||||
|
...updateBody.apps,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateInviteCode(code, updated)
|
||||||
|
ctx.body = { ...invite }
|
||||||
|
}
|
||||||
|
|
||||||
export const inviteAccept = async (ctx: any) => {
|
export const inviteAccept = async (ctx: any) => {
|
||||||
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
||||||
try {
|
try {
|
||||||
// info is an extension of the user object that was stored by global
|
// info is an extension of the user object that was stored by global
|
||||||
const { email, info }: any = await checkInviteCode(inviteCode)
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
||||||
ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
|
ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
|
||||||
const saved = await sdk.users.save({
|
let request = {
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
|
roles: info.apps,
|
||||||
|
}
|
||||||
|
|
||||||
|
delete info.apps
|
||||||
|
|
||||||
|
request = {
|
||||||
|
...request,
|
||||||
...info,
|
...info,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const saved = await sdk.users.save(request)
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
const user = await db.get(saved._id)
|
const user = await db.get(saved._id)
|
||||||
await events.user.inviteAccepted(user)
|
await events.user.inviteAccepted(user)
|
||||||
|
|
|
@ -203,7 +203,7 @@ export const save = async (
|
||||||
const tenantId = tenancy.getTenantId()
|
const tenantId = tenancy.getTenantId()
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
|
|
||||||
let { email, _id, userGroups = [] } = user
|
let { email, _id, userGroups = [], roles } = user
|
||||||
|
|
||||||
if (!email && !_id) {
|
if (!email && !_id) {
|
||||||
throw new Error("_id or email is required")
|
throw new Error("_id or email is required")
|
||||||
|
@ -245,6 +245,10 @@ export const save = async (
|
||||||
builtUser.roles = dbUser.roles
|
builtUser.roles = dbUser.roles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dbUser && roles?.length) {
|
||||||
|
builtUser.roles = { ...roles }
|
||||||
|
}
|
||||||
|
|
||||||
// make sure we set the _id field for a new user
|
// make sure we set the _id field for a new user
|
||||||
// Also if this is a new user, associate groups with them
|
// Also if this is a new user, associate groups with them
|
||||||
let groupPromises = []
|
let groupPromises = []
|
||||||
|
|
|
@ -29,6 +29,20 @@ async function writeACode(db: string, value: any) {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateACode(db: string, code: string, value: any) {
|
||||||
|
const client = await getClient(db)
|
||||||
|
await client.store(code, value, getExpirySecondsForDB(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an invite code and invite body, allow the update an existing/valid invite in redis
|
||||||
|
* @param {string} inviteCode The invite code for an invite in redis
|
||||||
|
* @param {object} value The body of the updated user invitation
|
||||||
|
*/
|
||||||
|
export async function updateInviteCode(inviteCode: string, value: string) {
|
||||||
|
await updateACode(redis.utils.Databases.INVITATIONS, inviteCode, value)
|
||||||
|
}
|
||||||
|
|
||||||
async function getACode(db: string, code: string, deleteCode = true) {
|
async function getACode(db: string, code: string, deleteCode = true) {
|
||||||
const client = await getClient(db)
|
const client = await getClient(db)
|
||||||
const value = await client.get(code)
|
const value = await client.get(code)
|
||||||
|
@ -111,3 +125,29 @@ export async function checkInviteCode(
|
||||||
throw "Invitation is not valid or has expired, please request a new one."
|
throw "Invitation is not valid or has expired, please request a new one."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get all currently available user invitations.
|
||||||
|
@return {Object[]} A
|
||||||
|
**/
|
||||||
|
export async function getInviteCodes(
|
||||||
|
tenantIds?: string[] //should default to the current tenant of the user session.
|
||||||
|
) {
|
||||||
|
const client = await getClient(redis.utils.Databases.INVITATIONS)
|
||||||
|
const invites: any[] = await client.scan()
|
||||||
|
|
||||||
|
const results = invites.map(invite => {
|
||||||
|
return {
|
||||||
|
...invite.value,
|
||||||
|
code: invite.key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return results.reduce((acc, invite) => {
|
||||||
|
if (tenantIds?.length && tenantIds.includes(invite.info.tenantId)) {
|
||||||
|
acc.push(invite)
|
||||||
|
} else {
|
||||||
|
acc.push(invite)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue