Breaking out pro components back into the worker user SDK, and attempting to separate the pro components as much as possible from the user SDK itself, so that it can be easily re-created in other services.
This commit is contained in:
parent
90371b9d69
commit
66fbdfe4e8
|
@ -2,34 +2,64 @@ import env from "../environment"
|
||||||
import * as eventHelpers from "./events"
|
import * as eventHelpers from "./events"
|
||||||
import * as accounts from "../accounts"
|
import * as accounts from "../accounts"
|
||||||
import * as cache from "../cache"
|
import * as cache from "../cache"
|
||||||
import { UserStatus, ViewName } from "../constants"
|
|
||||||
import { getIdentity, getTenantId, getGlobalDB } from "../context"
|
import { getIdentity, getTenantId, getGlobalDB } from "../context"
|
||||||
import * as dbUtils from "../db"
|
import * as dbUtils from "../db"
|
||||||
import { EmailUnavailableError, HTTPError } from "../errors"
|
import { EmailUnavailableError, HTTPError } from "../errors"
|
||||||
import * as platform from "../platform"
|
import * as platform from "../platform"
|
||||||
import * as sessions from "../security/sessions"
|
import * as sessions from "../security/sessions"
|
||||||
import * as utils from "../utils"
|
|
||||||
import * as usersCore from "./users"
|
import * as usersCore from "./users"
|
||||||
import {
|
import {
|
||||||
Account,
|
|
||||||
AllDocsResponse,
|
AllDocsResponse,
|
||||||
BulkUserCreated,
|
BulkUserCreated,
|
||||||
BulkUserDeleted,
|
BulkUserDeleted,
|
||||||
PlatformUser,
|
|
||||||
RowResponse,
|
RowResponse,
|
||||||
SaveUserOpts,
|
SaveUserOpts,
|
||||||
User,
|
User,
|
||||||
|
Account,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as pro from "@budibase/pro"
|
|
||||||
import * as accountSdk from "../accounts"
|
import * as accountSdk from "../accounts"
|
||||||
import {
|
import { validateUniqueUser, getAccountHolderFromUserIds } from "./utils"
|
||||||
isPreventPasswordActions,
|
|
||||||
validateUniqueUser,
|
|
||||||
getAccountHolderFromUserIds,
|
|
||||||
} from "./utils"
|
|
||||||
import { searchExistingEmails } from "./lookup"
|
import { searchExistingEmails } from "./lookup"
|
||||||
|
|
||||||
export async function allUsers() {
|
type QuotaUpdateFn = (change: number, cb?: () => Promise<any>) => Promise<any>
|
||||||
|
type GroupUpdateFn = (groupId: string, userIds: string[]) => Promise<any>
|
||||||
|
type QuotaFns = { addUsers: QuotaUpdateFn; removeUsers: QuotaUpdateFn }
|
||||||
|
type GroupFns = { addUsers: GroupUpdateFn }
|
||||||
|
type BuildUserFn = (
|
||||||
|
user: User,
|
||||||
|
opts: SaveUserOpts,
|
||||||
|
tenantId: string,
|
||||||
|
dbUser?: User,
|
||||||
|
account?: Account
|
||||||
|
) => Promise<any>
|
||||||
|
|
||||||
|
const bulkDeleteProcessing = async (dbUser: User) => {
|
||||||
|
const userId = dbUser._id as string
|
||||||
|
await platform.users.removeUser(dbUser)
|
||||||
|
await eventHelpers.handleDeleteEvents(dbUser)
|
||||||
|
await cache.user.invalidateUser(userId)
|
||||||
|
await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserDB {
|
||||||
|
quotas: QuotaFns
|
||||||
|
groups: GroupFns
|
||||||
|
ssoEnforcedFn: () => Promise<boolean>
|
||||||
|
buildUserFn: BuildUserFn
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
quotaFns: QuotaFns,
|
||||||
|
groupFns: GroupFns,
|
||||||
|
ssoEnforcedFn: () => Promise<boolean>,
|
||||||
|
buildUserFn: BuildUserFn
|
||||||
|
) {
|
||||||
|
this.quotas = quotaFns
|
||||||
|
this.groups = groupFns
|
||||||
|
this.ssoEnforcedFn = ssoEnforcedFn
|
||||||
|
this.buildUserFn = buildUserFn
|
||||||
|
}
|
||||||
|
|
||||||
|
async allUsers() {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
dbUtils.getGlobalUserParams(null, {
|
dbUtils.getGlobalUserParams(null, {
|
||||||
|
@ -39,14 +69,14 @@ export async function allUsers() {
|
||||||
return response.rows.map((row: any) => row.doc)
|
return response.rows.map((row: any) => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function countUsersByApp(appId: string) {
|
async countUsersByApp(appId: string) {
|
||||||
let response: any = await usersCore.searchGlobalUsersByApp(appId, {})
|
let response: any = await usersCore.searchGlobalUsersByApp(appId, {})
|
||||||
return {
|
return {
|
||||||
userCount: response.length,
|
userCount: response.length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsersByAppAccess(appId?: string) {
|
async getUsersByAppAccess(appId?: string) {
|
||||||
const opts: any = {
|
const opts: any = {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
|
@ -58,14 +88,14 @@ export async function getUsersByAppAccess(appId?: string) {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserByEmail(email: string) {
|
async getUserByEmail(email: string) {
|
||||||
return usersCore.getGlobalUserByEmail(email)
|
return usersCore.getGlobalUserByEmail(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a user by ID from the global database, based on the current tenancy.
|
* Gets a user by ID from the global database, based on the current tenancy.
|
||||||
*/
|
*/
|
||||||
export async function getUser(userId: string) {
|
async getUser(userId: string) {
|
||||||
const user = await usersCore.getById(userId)
|
const user = await usersCore.getById(userId)
|
||||||
if (user) {
|
if (user) {
|
||||||
delete user.password
|
delete user.password
|
||||||
|
@ -73,66 +103,7 @@ export async function getUser(userId: string) {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildUser(
|
async save(user: User, opts: SaveUserOpts = {}): Promise<User> {
|
||||||
user: User,
|
|
||||||
opts: SaveUserOpts = {
|
|
||||||
hashPassword: true,
|
|
||||||
requirePassword: true,
|
|
||||||
},
|
|
||||||
tenantId: string,
|
|
||||||
dbUser?: any,
|
|
||||||
account?: Account
|
|
||||||
): Promise<User> {
|
|
||||||
let { password, _id } = user
|
|
||||||
|
|
||||||
// don't require a password if the db user doesn't already have one
|
|
||||||
if (dbUser && !dbUser.password) {
|
|
||||||
opts.requirePassword = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let hashedPassword
|
|
||||||
if (password) {
|
|
||||||
if (await isPreventPasswordActions(user, account)) {
|
|
||||||
throw new HTTPError("Password change is disabled for this user", 400)
|
|
||||||
}
|
|
||||||
hashedPassword = opts.hashPassword ? await utils.hash(password) : password
|
|
||||||
} else if (dbUser) {
|
|
||||||
hashedPassword = dbUser.password
|
|
||||||
}
|
|
||||||
|
|
||||||
// passwords are never required if sso is enforced
|
|
||||||
const requirePasswords =
|
|
||||||
opts.requirePassword && !(await pro.features.isSSOEnforced())
|
|
||||||
if (!hashedPassword && requirePasswords) {
|
|
||||||
throw "Password must be specified."
|
|
||||||
}
|
|
||||||
|
|
||||||
_id = _id || dbUtils.generateGlobalUserID()
|
|
||||||
|
|
||||||
const fullUser = {
|
|
||||||
createdAt: Date.now(),
|
|
||||||
...dbUser,
|
|
||||||
...user,
|
|
||||||
_id,
|
|
||||||
password: hashedPassword,
|
|
||||||
tenantId,
|
|
||||||
}
|
|
||||||
// make sure the roles object is always present
|
|
||||||
if (!fullUser.roles) {
|
|
||||||
fullUser.roles = {}
|
|
||||||
}
|
|
||||||
// add the active status to a user if its not provided
|
|
||||||
if (fullUser.status == null) {
|
|
||||||
fullUser.status = UserStatus.ACTIVE
|
|
||||||
}
|
|
||||||
|
|
||||||
return fullUser
|
|
||||||
}
|
|
||||||
|
|
||||||
export const save = async (
|
|
||||||
user: User,
|
|
||||||
opts: SaveUserOpts = {}
|
|
||||||
): Promise<User> => {
|
|
||||||
// default booleans to true
|
// default booleans to true
|
||||||
if (opts.hashPassword == null) {
|
if (opts.hashPassword == null) {
|
||||||
opts.hashPassword = true
|
opts.hashPassword = true
|
||||||
|
@ -176,10 +147,10 @@ export const save = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = dbUser ? 0 : 1 // no change if there is existing user
|
const change = dbUser ? 0 : 1 // no change if there is existing user
|
||||||
return pro.quotas.addUsers(change, async () => {
|
return this.quotas.addUsers(change, async () => {
|
||||||
await validateUniqueUser(email, tenantId)
|
await validateUniqueUser(email, tenantId)
|
||||||
|
|
||||||
let builtUser = await buildUser(user, opts, tenantId, dbUser)
|
let builtUser = await this.buildUserFn(user, opts, tenantId, dbUser)
|
||||||
// don't allow a user to update its own roles/perms
|
// don't allow a user to update its own roles/perms
|
||||||
if (opts.currentUserId && opts.currentUserId === dbUser?._id) {
|
if (opts.currentUserId && opts.currentUserId === dbUser?._id) {
|
||||||
builtUser = usersCore.cleanseUserObject(builtUser, dbUser) as User
|
builtUser = usersCore.cleanseUserObject(builtUser, dbUser) as User
|
||||||
|
@ -197,7 +168,7 @@ export const save = async (
|
||||||
|
|
||||||
if (userGroups.length > 0) {
|
if (userGroups.length > 0) {
|
||||||
for (let groupId of userGroups) {
|
for (let groupId of userGroups) {
|
||||||
groupPromises.push(pro.groups.addUsers(groupId, [_id]))
|
groupPromises.push(this.groups.addUsers(groupId, [_id!]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,10 +196,10 @@ export const save = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkCreate = async (
|
async bulkCreate(
|
||||||
newUsersRequested: User[],
|
newUsersRequested: User[],
|
||||||
groups: string[]
|
groups: string[]
|
||||||
): Promise<BulkUserCreated> => {
|
): Promise<BulkUserCreated> {
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
|
|
||||||
let usersToSave: any[] = []
|
let usersToSave: any[] = []
|
||||||
|
@ -256,11 +227,11 @@ export const bulkCreate = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await accountSdk.getAccountByTenantId(tenantId)
|
const account = await accountSdk.getAccountByTenantId(tenantId)
|
||||||
return pro.quotas.addUsers(newUsers.length, async () => {
|
return this.quotas.addUsers(newUsers.length, async () => {
|
||||||
// create the promises array that will be called by bulkDocs
|
// create the promises array that will be called by bulkDocs
|
||||||
newUsers.forEach((user: any) => {
|
newUsers.forEach((user: any) => {
|
||||||
usersToSave.push(
|
usersToSave.push(
|
||||||
buildUser(
|
this.buildUserFn(
|
||||||
user,
|
user,
|
||||||
{
|
{
|
||||||
hashPassword: true,
|
hashPassword: true,
|
||||||
|
@ -296,7 +267,7 @@ export const bulkCreate = async (
|
||||||
const groupPromises = []
|
const groupPromises = []
|
||||||
const createdUserIds = saved.map(user => user._id)
|
const createdUserIds = saved.map(user => user._id)
|
||||||
for (let groupId of groups) {
|
for (let groupId of groups) {
|
||||||
groupPromises.push(pro.groups.addUsers(groupId, createdUserIds))
|
groupPromises.push(this.groups.addUsers(groupId, createdUserIds))
|
||||||
}
|
}
|
||||||
await Promise.all(groupPromises)
|
await Promise.all(groupPromises)
|
||||||
}
|
}
|
||||||
|
@ -308,9 +279,7 @@ export const bulkCreate = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkDelete = async (
|
async bulkDelete(userIds: string[]): Promise<BulkUserDeleted> {
|
||||||
userIds: string[]
|
|
||||||
): Promise<BulkUserDeleted> => {
|
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
|
|
||||||
const response: BulkUserDeleted = {
|
const response: BulkUserDeleted = {
|
||||||
|
@ -348,7 +317,7 @@ export const bulkDelete = async (
|
||||||
}))
|
}))
|
||||||
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
|
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
|
||||||
|
|
||||||
await pro.quotas.removeUsers(toDelete.length)
|
await this.quotas.removeUsers(toDelete.length)
|
||||||
for (let user of usersToDelete) {
|
for (let user of usersToDelete) {
|
||||||
await bulkDeleteProcessing(user)
|
await bulkDeleteProcessing(user)
|
||||||
}
|
}
|
||||||
|
@ -378,7 +347,7 @@ export const bulkDelete = async (
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
export const destroy = async (id: string) => {
|
async destroy(id: string) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const dbUser = (await db.get(id)) as User
|
const dbUser = (await db.get(id)) as User
|
||||||
const userId = dbUser._id as string
|
const userId = dbUser._id as string
|
||||||
|
@ -400,16 +369,9 @@ export const destroy = async (id: string) => {
|
||||||
|
|
||||||
await db.remove(userId, dbUser._rev)
|
await db.remove(userId, dbUser._rev)
|
||||||
|
|
||||||
await pro.quotas.removeUsers(1)
|
await this.quotas.removeUsers(1)
|
||||||
await eventHelpers.handleDeleteEvents(dbUser)
|
await eventHelpers.handleDeleteEvents(dbUser)
|
||||||
await cache.user.invalidateUser(userId)
|
await cache.user.invalidateUser(userId)
|
||||||
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const bulkDeleteProcessing = async (dbUser: User) => {
|
|
||||||
const userId = dbUser._id as string
|
|
||||||
await platform.users.removeUser(dbUser)
|
|
||||||
await eventHelpers.handleDeleteEvents(dbUser)
|
|
||||||
await cache.user.invalidateUser(userId)
|
|
||||||
await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from "./users"
|
export * from "./users"
|
||||||
export * from "./utils"
|
export * from "./utils"
|
||||||
export * from "./lookup"
|
export * from "./lookup"
|
||||||
export * as db from "./db"
|
export { UserDB } from "./db"
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { CloudAccount } from "@budibase/types"
|
||||||
Account,
|
|
||||||
CloudAccount,
|
|
||||||
isSSOAccount,
|
|
||||||
isSSOUser,
|
|
||||||
User,
|
|
||||||
} from "@budibase/types"
|
|
||||||
import * as pro from "@budibase/pro"
|
|
||||||
import * as accountSdk from "../accounts"
|
import * as accountSdk from "../accounts"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { getPlatformUser } from "./lookup"
|
import { getPlatformUser } from "./lookup"
|
||||||
|
@ -40,30 +33,6 @@ export async function validateUniqueUser(email: string, tenantId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isPreventPasswordActions(user: User, account?: Account) {
|
|
||||||
// when in maintenance mode we allow sso users with the admin role
|
|
||||||
// to perform any password action - this prevents lockout
|
|
||||||
if (env.ENABLE_SSO_MAINTENANCE_MODE && isAdmin(user)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSO is enforced for all users
|
|
||||||
if (await pro.features.isSSOEnforced()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check local sso
|
|
||||||
if (isSSOUser(user)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check account sso
|
|
||||||
if (!account) {
|
|
||||||
account = await accountSdk.getAccountByTenantId(getTenantId())
|
|
||||||
}
|
|
||||||
return !!(account && account.email === user.email && isSSOAccount(account))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For the given user id's, return the account holder if it is in the ids.
|
* For the given user id's, return the account holder if it is in the ids.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -40,13 +40,27 @@ describe("/api/global/users/:userId/app/builder", () => {
|
||||||
describe("PATCH /api/global/users/:userId/app/:appId/builder", () => {
|
describe("PATCH /api/global/users/:userId/app/:appId/builder", () => {
|
||||||
it("shouldn't allow granting access to an app to a non-app builder", async () => {
|
it("shouldn't allow granting access to an app to a non-app builder", async () => {
|
||||||
const user = await newUser()
|
const user = await newUser()
|
||||||
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID)
|
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID, 400)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to grant a user access to a particular app", async () => {
|
it("should be able to grant a user access to a particular app", async () => {
|
||||||
const user = await grantAppBuilder()
|
const user = await grantAppBuilder()
|
||||||
|
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID)
|
||||||
|
const updated = await getUser(user._id!)
|
||||||
|
expect(updated.builder?.appBuilder).toBe(true)
|
||||||
|
expect(updated.builder?.apps).toBe([MOCK_APP_ID])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("DELETE /api/global/users/:userId/app/:appId/builder", () => {})
|
describe("DELETE /api/global/users/:userId/app/:appId/builder", () => {
|
||||||
|
it("should allow revoking access", async () => {
|
||||||
|
const user = await grantAppBuilder()
|
||||||
|
await config.api.users.grantBuilderToApp(user._id!, MOCK_APP_ID)
|
||||||
|
let updated = await getUser(user._id!)
|
||||||
|
expect(updated.builder?.apps).toBe([MOCK_APP_ID])
|
||||||
|
await config.api.users.revokeBuilderToApp(user._id!, MOCK_APP_ID)
|
||||||
|
updated = await getUser(user._id!)
|
||||||
|
expect(updated.builder?.apps).toBe([])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
export * from "./users"
|
export * from "./users"
|
||||||
|
import { buildUser } from "./users"
|
||||||
import { users } from "@budibase/backend-core"
|
import { users } from "@budibase/backend-core"
|
||||||
export const db = users.db
|
import * as pro from "@budibase/pro"
|
||||||
|
// pass in the components which are specific to the worker/the parts of pro which backend-core cannot access
|
||||||
|
export const db = new users.UserDB(
|
||||||
|
pro.quotas,
|
||||||
|
pro.groups,
|
||||||
|
pro.features.isSSOEnforced,
|
||||||
|
buildUser
|
||||||
|
)
|
||||||
export { users as core } from "@budibase/backend-core"
|
export { users as core } from "@budibase/backend-core"
|
||||||
|
|
|
@ -1,11 +1,111 @@
|
||||||
import { events, tenancy, users as usersCore } from "@budibase/backend-core"
|
import {
|
||||||
import { InviteUsersRequest, InviteUsersResponse } from "@budibase/types"
|
events,
|
||||||
|
HTTPError,
|
||||||
|
tenancy,
|
||||||
|
users as usersCore,
|
||||||
|
UserStatus,
|
||||||
|
db as dbUtils,
|
||||||
|
utils,
|
||||||
|
accounts as accountSdk,
|
||||||
|
context,
|
||||||
|
env as coreEnv,
|
||||||
|
} from "@budibase/backend-core"
|
||||||
|
import {
|
||||||
|
Account,
|
||||||
|
InviteUsersRequest,
|
||||||
|
InviteUsersResponse,
|
||||||
|
isSSOAccount,
|
||||||
|
isSSOUser,
|
||||||
|
SaveUserOpts,
|
||||||
|
User,
|
||||||
|
} from "@budibase/types"
|
||||||
import { sendEmail } from "../../utilities/email"
|
import { sendEmail } from "../../utilities/email"
|
||||||
import { EmailTemplatePurpose } from "../../constants"
|
import { EmailTemplatePurpose } from "../../constants"
|
||||||
|
import * as pro from "@budibase/pro"
|
||||||
|
|
||||||
export const invite = async (
|
export async function isPreventPasswordActions(user: User, account?: Account) {
|
||||||
|
// when in maintenance mode we allow sso users with the admin role
|
||||||
|
// to perform any password action - this prevents lockout
|
||||||
|
if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE && usersCore.isAdmin(user)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSO is enforced for all users
|
||||||
|
if (await pro.features.isSSOEnforced()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check local sso
|
||||||
|
if (isSSOUser(user)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check account sso
|
||||||
|
if (!account) {
|
||||||
|
account = await accountSdk.getAccountByTenantId(context.getTenantId())
|
||||||
|
}
|
||||||
|
return !!(account && account.email === user.email && isSSOAccount(account))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function buildUser(
|
||||||
|
user: User,
|
||||||
|
opts: SaveUserOpts = {
|
||||||
|
hashPassword: true,
|
||||||
|
requirePassword: true,
|
||||||
|
},
|
||||||
|
tenantId: string,
|
||||||
|
dbUser?: any,
|
||||||
|
account?: Account
|
||||||
|
): Promise<User> {
|
||||||
|
let { password, _id } = user
|
||||||
|
|
||||||
|
// don't require a password if the db user doesn't already have one
|
||||||
|
if (dbUser && !dbUser.password) {
|
||||||
|
opts.requirePassword = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashedPassword
|
||||||
|
if (password) {
|
||||||
|
if (await isPreventPasswordActions(user, account)) {
|
||||||
|
throw new HTTPError("Password change is disabled for this user", 400)
|
||||||
|
}
|
||||||
|
hashedPassword = opts.hashPassword ? await utils.hash(password) : password
|
||||||
|
} else if (dbUser) {
|
||||||
|
hashedPassword = dbUser.password
|
||||||
|
}
|
||||||
|
|
||||||
|
// passwords are never required if sso is enforced
|
||||||
|
const requirePasswords =
|
||||||
|
opts.requirePassword && !(await pro.features.isSSOEnforced())
|
||||||
|
if (!hashedPassword && requirePasswords) {
|
||||||
|
throw "Password must be specified."
|
||||||
|
}
|
||||||
|
|
||||||
|
_id = _id || dbUtils.generateGlobalUserID()
|
||||||
|
|
||||||
|
const fullUser = {
|
||||||
|
createdAt: Date.now(),
|
||||||
|
...dbUser,
|
||||||
|
...user,
|
||||||
|
_id,
|
||||||
|
password: hashedPassword,
|
||||||
|
tenantId,
|
||||||
|
}
|
||||||
|
// make sure the roles object is always present
|
||||||
|
if (!fullUser.roles) {
|
||||||
|
fullUser.roles = {}
|
||||||
|
}
|
||||||
|
// add the active status to a user if its not provided
|
||||||
|
if (fullUser.status == null) {
|
||||||
|
fullUser.status = UserStatus.ACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullUser
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function invite(
|
||||||
users: InviteUsersRequest
|
users: InviteUsersRequest
|
||||||
): Promise<InviteUsersResponse> => {
|
): Promise<InviteUsersResponse> {
|
||||||
const response: InviteUsersResponse = {
|
const response: InviteUsersResponse = {
|
||||||
successful: [],
|
successful: [],
|
||||||
unsuccessful: [],
|
unsuccessful: [],
|
||||||
|
|
|
@ -149,15 +149,23 @@ export class UserAPI extends TestAPI {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
grantBuilderToApp = (userId: string, appId: string) => {
|
grantBuilderToApp = (
|
||||||
|
userId: string,
|
||||||
|
appId: string,
|
||||||
|
statusCode: number = 200
|
||||||
|
) => {
|
||||||
return this.request
|
return this.request
|
||||||
.patch(`/api/global/users/${userId}/app/${appId}/builder`)
|
.patch(`/api/global/users/${userId}/app/${appId}/builder`)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
revokeBuilderToApp = (userId: string, appId: string) => {
|
revokeBuilderToApp = (
|
||||||
|
userId: string,
|
||||||
|
appId: string,
|
||||||
|
statusCode: number = 200
|
||||||
|
) => {
|
||||||
return this.request
|
return this.request
|
||||||
.delete(`/api/global/users/${userId}/app/${appId}/builder`)
|
.delete(`/api/global/users/${userId}/app/${appId}/builder`)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
|
|
Loading…
Reference in New Issue