Merge commit to dev
This commit is contained in:
parent
0ee77aa9b0
commit
b4c88bd545
|
@ -16,6 +16,7 @@ export interface BulkUserRequest {
|
|||
userIds: string[]
|
||||
}
|
||||
create?: {
|
||||
roles?: any[]
|
||||
users: User[]
|
||||
groups: any[]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { checkInviteCode } from "../../../utilities/redis"
|
||||
import {
|
||||
checkInviteCode,
|
||||
getInviteCodes,
|
||||
updateInviteCode,
|
||||
} from "../../../utilities/redis"
|
||||
import sdk from "../../../sdk"
|
||||
import env from "../../../environment"
|
||||
import {
|
||||
|
@ -6,7 +10,6 @@ import {
|
|||
BulkUserResponse,
|
||||
CloudAccount,
|
||||
CreateAdminUserRequest,
|
||||
InviteUserRequest,
|
||||
InviteUsersRequest,
|
||||
SearchUsersRequest,
|
||||
User,
|
||||
|
@ -19,6 +22,7 @@ import {
|
|||
tenancy,
|
||||
} from "@budibase/backend-core"
|
||||
import { checkAnyUserExists } from "../../../utilities/users"
|
||||
import { isEmailConfigured } from "src/utilities/email"
|
||||
|
||||
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) => {
|
||||
const request = ctx.request.body as InviteUserRequest
|
||||
const response = await sdk.users.invite([request])
|
||||
const request = ctx.request.body as InviteUsersRequest
|
||||
const response = await sdk.users.invite(request)
|
||||
|
||||
// explicitly throw for single user invite
|
||||
if (response.unsuccessful.length) {
|
||||
|
@ -202,6 +251,8 @@ export const invite = async (ctx: any) => {
|
|||
|
||||
ctx.body = {
|
||||
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) => {
|
||||
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
||||
try {
|
||||
// info is an extension of the user object that was stored by global
|
||||
const { email, info }: any = await checkInviteCode(inviteCode)
|
||||
ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
|
||||
const saved = await sdk.users.save({
|
||||
let request = {
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
email,
|
||||
roles: info.apps,
|
||||
}
|
||||
|
||||
delete info.apps
|
||||
|
||||
request = {
|
||||
...request,
|
||||
...info,
|
||||
})
|
||||
}
|
||||
|
||||
const saved = await sdk.users.save(request)
|
||||
const db = tenancy.getGlobalDB()
|
||||
const user = await db.get(saved._id)
|
||||
await events.user.inviteAccepted(user)
|
||||
|
|
|
@ -203,7 +203,7 @@ export const save = async (
|
|||
const tenantId = tenancy.getTenantId()
|
||||
const db = tenancy.getGlobalDB()
|
||||
|
||||
let { email, _id, userGroups = [] } = user
|
||||
let { email, _id, userGroups = [], roles } = user
|
||||
|
||||
if (!email && !_id) {
|
||||
throw new Error("_id or email is required")
|
||||
|
@ -245,6 +245,10 @@ export const save = async (
|
|||
builtUser.roles = dbUser.roles
|
||||
}
|
||||
|
||||
if (!dbUser && roles?.length) {
|
||||
builtUser.roles = { ...roles }
|
||||
}
|
||||
|
||||
// make sure we set the _id field for a new user
|
||||
// Also if this is a new user, associate groups with them
|
||||
let groupPromises = []
|
||||
|
|
|
@ -29,6 +29,20 @@ async function writeACode(db: string, value: any) {
|
|||
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) {
|
||||
const client = await getClient(db)
|
||||
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."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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