user / rbac events + tests

This commit is contained in:
Rory Powell 2022-04-08 01:28:22 +01:00
parent ac8573b67e
commit e98e659346
41 changed files with 861 additions and 340 deletions

View File

@ -1,8 +1,8 @@
class BudibaseError extends Error {
constructor(message, type, code) {
constructor(message, code, type) {
super(message)
this.type = type
this.code = code
this.type = type
}
}

View File

@ -0,0 +1,11 @@
const { BudibaseError } = require("./base")
class GenericError extends BudibaseError {
constructor(message, code, type) {
super(message, code, type ? type : "generic")
}
}
module.exports = {
GenericError,
}

View File

@ -0,0 +1,12 @@
const { GenericError } = require("./generic")
class HTTPError extends GenericError {
constructor(message, httpStatus, code, type) {
super(message, code ? code : "http", type)
this.status = httpStatus
}
}
module.exports = {
HTTPError,
}

View File

@ -1,12 +1,11 @@
const http = require("./http")
const licensing = require("./licensing")
const codes = {
...licensing.codes,
}
const types = {
...licensing.types,
}
const types = [licensing.type]
const context = {
...licensing.context,
@ -36,6 +35,9 @@ const getPublicError = err => {
module.exports = {
codes,
types,
UsageLimitError: licensing.UsageLimitError,
errors: {
UsageLimitError: licensing.UsageLimitError,
HTTPError: http.HTTPError,
},
getPublicError,
}

View File

@ -1,8 +1,6 @@
const { BudibaseError } = require("./base")
const { HTTPError } = require("./http")
const types = {
LICENSE_ERROR: "license_error",
}
const type = "license_error"
const codes = {
USAGE_LIMIT_EXCEEDED: "usage_limit_exceeded",
@ -16,16 +14,15 @@ const context = {
},
}
class UsageLimitError extends BudibaseError {
class UsageLimitError extends HTTPError {
constructor(message, limitName) {
super(message, types.LICENSE_ERROR, codes.USAGE_LIMIT_EXCEEDED)
super(message, 400, codes.USAGE_LIMIT_EXCEEDED, type)
this.limitName = limitName
this.status = 400
}
}
module.exports = {
types,
type,
codes,
context,
UsageLimitError,

View File

@ -59,8 +59,10 @@ exports.Events = {
// ROLE
ROLE_CREATED: "role:created",
ROLE_UPDATED: "role:updated",
ROLE_DELETED: "role:deleted",
ROLE_ASSIGNED: "role:assigned",
ROLE_UNASSIGNED: "role:unassigned",
// APP / CLIENT
CLIENT_SERVED: "client:served",

View File

@ -4,7 +4,7 @@ const analytics = require("../analytics")
const logEvent = messsage => {
const tenantId = getTenantId()
const userId = getTenantId() // TODO
console.log(`[tenant=${tenantId}] [user=${userId}] ${messsage}`)
console.log(`[audit] [tenant=${tenantId}] [user=${userId}] ${messsage}`)
}
exports.processEvent = (event, properties) => {

View File

@ -8,6 +8,7 @@ const license = require("./license")
const layout = require("./layout")
const org = require("./org")
const query = require("./query")
const role = require("./role")
const row = require("./screen")
const table = require("./table")
const serve = require("./serve")
@ -25,6 +26,7 @@ module.exports = {
layout,
org,
query,
role,
row,
table,
serve,

View File

@ -1,26 +1,31 @@
const events = require("../events")
const { Events } = require("../constants")
// TODO
exports.updgraded = () => {
const properties = {}
events.processEvent(Events.LICENSE_UPGRADED, properties)
}
// TODO
exports.downgraded = () => {
const properties = {}
events.processEvent(Events.LICENSE_DOWNGRADED, properties)
}
// TODO
exports.updated = () => {
const properties = {}
events.processEvent(Events.LICENSE_UPDATED, properties)
}
// TODO
exports.activated = () => {
const properties = {}
events.processEvent(Events.LICENSE_ACTIVATED, properties)
}
// TODO
exports.quotaExceeded = (quotaName, value) => {
const properties = {
name: quotaName,

View File

@ -1,23 +1,24 @@
const events = require("../events")
const { Events } = require("../constants")
exports.created = () => {
/* eslint-disable */
exports.created = (datasource, query) => {
const properties = {}
events.processEvent(Events.QUERY_CREATED, properties)
}
exports.updated = () => {
exports.updated = (datasource, query) => {
const properties = {}
events.processEvent(Events.QUERY_UPDATED, properties)
}
exports.deleted = () => {
exports.deleted = (datasource, query) => {
const properties = {}
events.processEvent(Events.QUERY_DELETED, properties)
}
// TODO
exports.import = () => {
exports.import = (datasource, importSource, count) => {
const properties = {}
events.processEvent(Events.QUERY_IMPORT, properties)
}
@ -28,8 +29,7 @@ exports.import = () => {
// events.processEvent(Events.QUERY_RUN, properties)
// }
// TODO
exports.previewed = () => {
exports.previewed = datasource => {
const properties = {}
events.processEvent(Events.QUERY_PREVIEWED, properties)
}

View File

@ -1,19 +1,29 @@
const events = require("../events")
const { Events } = require("../constants")
exports.created = () => {
/* eslint-disable */
exports.created = role => {
const properties = {}
events.processEvent(Events.ROLE_CREATED, properties)
}
// TODO
exports.deleted = () => {
exports.updated = role => {
const properties = {}
events.processEvent(Events.ROLE_UPDATED, properties)
}
exports.deleted = role => {
const properties = {}
events.processEvent(Events.ROLE_DELETED, properties)
}
// TODO
exports.assigned = () => {
exports.assigned = (user, role) => {
const properties = {}
events.processEvent(Events.ROLE_ASSIGNED, properties)
}
exports.unassigned = (user, role) => {
const properties = {}
events.processEvent(Events.ROLE_UNASSIGNED, properties)
}

View File

@ -1,85 +1,87 @@
const events = require("../events")
const { Events } = require("../constants")
// TODO
exports.created = () => {
/* eslint-disable */
exports.created = user => {
const properties = {}
events.processEvent(Events.USER_CREATED, properties)
}
// TODO
exports.updated = () => {
exports.updated = user => {
const properties = {}
events.processEvent(Events.USER_UPDATED, properties)
}
exports.deleted = () => {
exports.deleted = user => {
const properties = {}
events.processEvent(Events.USER_DELETED, properties)
}
// TODO
exports.passwordForceReset = () => {
exports.passwordForceReset = user => {
const properties = {}
events.processEvent(Events.USER_PASSWORD_FORCE_RESET, properties)
}
// PERMISSIONS
// TODO
exports.permissionAdminAssigned = () => {
exports.permissionAdminAssigned = user => {
const properties = {}
events.processEvent(Events.USER_PERMISSION_ADMIN_ASSIGNED, properties)
}
// TODO
exports.permissionAdminRemoved = () => {
exports.permissionAdminRemoved = user => {
const properties = {}
events.processEvent(Events.USER_PERMISSION_ADMIN_REMOVED, properties)
}
// TODO
exports.permissionBuilderAssigned = () => {
exports.permissionBuilderAssigned = user => {
const properties = {}
events.processEvent(Events.USER_PERMISSION_BUILDER_ASSIGNED, properties)
}
// TODO
exports.permissionBuilderRemoved = () => {
exports.permissionBuilderRemoved = user => {
const properties = {}
events.processEvent(Events.USER_PERMISSION_BUILDER_REMOVED, properties)
}
// INVITE
exports.invited = () => {
// TODO
exports.invited = user => {
const properties = {}
events.processEvent(Events.USER_INVITED, properties)
}
exports.inviteAccepted = () => {
// TODO
exports.inviteAccepted = user => {
const properties = {}
events.processEvent(Events.USER_INVITED_ACCEPTED, properties)
}
// SELF
exports.selfUpdated = () => {
// TODO
exports.selfUpdated = user => {
const properties = {}
events.processEvent(Events.USER_SELF_UPDATED, properties)
}
exports.selfPasswordUpdated = () => {
// TODO
exports.selfPasswordUpdated = user => {
const properties = {}
events.processEvent(Events.USER_SELF_PASSWORD_UPDATED, properties)
}
exports.passwordResetRequested = () => {
// TODO
exports.passwordResetRequested = user => {
const properties = {}
events.processEvent(Events.USER_PASSWORD_RESET_REQUESTED, properties)
}
exports.passwordReset = () => {
// TODO
exports.passwordReset = user => {
const properties = {}
events.processEvent(Events.USER_PASSWORD_RESET, properties)
}

View File

@ -1,4 +1,5 @@
const db = require("./db")
const errors = require("./errors")
module.exports = {
init(opts = {}) {
@ -11,15 +12,19 @@ module.exports = {
redis: require("../redis"),
objectStore: require("../objectStore"),
utils: require("../utils"),
users: require("./users"),
cache: require("../cache"),
auth: require("../auth"),
constants: require("../constants"),
migrations: require("../migrations"),
errors: require("./errors"),
...errors.errors,
env: require("./environment"),
accounts: require("./cloud/accounts"),
tenancy: require("./tenancy"),
featureFlags: require("./featureFlags"),
events: require("./events"),
analytics: require("./analytics"),
sessions: require("./security/sessions"),
deprovisioning: require("./context/deprovision"),
}

View File

@ -2,7 +2,7 @@ const jwt = require("jsonwebtoken")
const { UserStatus } = require("../../constants")
const { compare } = require("../../hashing")
const env = require("../../environment")
const { getGlobalUserByEmail } = require("../../utils")
const users = require("../../users")
const { authError } = require("./utils")
const { newid } = require("../../hashing")
const { createASession } = require("../../security/sessions")
@ -28,7 +28,7 @@ exports.authenticate = async function (ctx, email, password, done) {
if (!email) return authError(done, "Email Required")
if (!password) return authError(done, "Password Required")
const dbUser = await getGlobalUserByEmail(email)
const dbUser = await users.getGlobalUserByEmail(email)
if (dbUser == null) {
return authError(done, "User not found")
}

View File

@ -4,7 +4,7 @@ const { generateGlobalUserID } = require("../../db/utils")
const { authError } = require("./utils")
const { newid } = require("../../hashing")
const { createASession } = require("../../security/sessions")
const { getGlobalUserByEmail } = require("../../utils")
const users = require("../../users")
const { getGlobalDB, getTenantId } = require("../../tenancy")
const fetch = require("node-fetch")
@ -52,7 +52,7 @@ exports.authenticateThirdParty = async function (
// fallback to loading by email
if (!dbUser) {
dbUser = await getGlobalUserByEmail(thirdPartyUser.email)
dbUser = await users.getGlobalUserByEmail(thirdPartyUser.email)
}
// exit early if there is still no user and auto creation is disabled
@ -81,7 +81,7 @@ exports.authenticateThirdParty = async function (
// create or sync the user
let response
try {
response = await saveUserFn(dbUser, getTenantId(), false, false)
response = await saveUserFn(dbUser, false, false)
} catch (err) {
return authError(done, err)
}

View File

@ -62,6 +62,29 @@ jest.mock("../../../events", () => {
import: jest.fn(),
previewed: jest.fn(),
},
role: {
created: jest.fn(),
updated: jest.fn(),
deleted: jest.fn(),
assigned: jest.fn(),
unassigned: jest.fn(),
},
user: {
created: jest.fn(),
updated: jest.fn(),
deleted: jest.fn(),
passwordForceReset: jest.fn(),
permissionAdminAssigned: jest.fn(),
permissionAdminRemoved: jest.fn(),
permissionBuilderAssigned: jest.fn(),
permissionBuilderRemoved: jest.fn(),
invited: jest.fn(),
inviteAccepted: jest.fn(),
selfUpdated: jest.fn(),
selfPasswordUpdated: jest.fn(),
passwordResetRequested: jest.fn(),
passwordReset: jest.fn(),
},
}
})

View File

@ -0,0 +1,19 @@
const { ViewNames } = require("./db/utils")
const { queryGlobalView } = require("./db/views")
/**
* Given an email address this will use a view to search through
* all the users to find one with this email address.
* @param {string} email the email to lookup the user by.
* @return {Promise<object|null>}
*/
exports.getGlobalUserByEmail = async email => {
if (email == null) {
throw "Must supply an email address to view"
}
return queryGlobalView(ViewNames.USER_BY_EMAIL, {
key: email.toLowerCase(),
include_docs: true,
})
}

View File

@ -1,24 +1,10 @@
const {
DocumentTypes,
SEPARATOR,
ViewNames,
generateGlobalUserID,
} = require("./db/utils")
const { DocumentTypes, SEPARATOR, ViewNames } = require("./db/utils")
const jwt = require("jsonwebtoken")
const { options } = require("./middleware/passport/jwt")
const { queryGlobalView } = require("./db/views")
const { Headers, UserStatus, Cookies, MAX_VALID_DATE } = require("./constants")
const {
getGlobalDB,
updateTenantId,
getTenantUser,
tryAddTenant,
} = require("./tenancy")
const environment = require("./environment")
const accounts = require("./cloud/accounts")
const { hash } = require("./hashing")
const userCache = require("./cache/user")
const { Headers, Cookies, MAX_VALID_DATE } = require("./constants")
const env = require("./environment")
const userCache = require("./cache/user")
const { getUserSessions, invalidateSessions } = require("./security/sessions")
const events = require("./events")
@ -106,8 +92,8 @@ exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => {
overwrite: true,
}
if (environment.COOKIE_DOMAIN) {
config.domain = environment.COOKIE_DOMAIN
if (env.COOKIE_DOMAIN) {
config.domain = env.COOKIE_DOMAIN
}
ctx.cookies.set(name, value, config)
@ -130,23 +116,6 @@ exports.isClient = ctx => {
return ctx.headers[Headers.TYPE] === "client"
}
/**
* Given an email address this will use a view to search through
* all the users to find one with this email address.
* @param {string} email the email to lookup the user by.
* @return {Promise<object|null>}
*/
exports.getGlobalUserByEmail = async email => {
if (email == null) {
throw "Must supply an email address to view"
}
return queryGlobalView(ViewNames.USER_BY_EMAIL, {
key: email.toLowerCase(),
include_docs: true,
})
}
exports.getBuildersCount = async () => {
const builders = await queryGlobalView(ViewNames.USER_BY_BUILDERS, {
include_docs: false,
@ -154,96 +123,6 @@ exports.getBuildersCount = async () => {
return builders.length
}
exports.saveUser = async (
user,
tenantId,
hashPassword = true,
requirePassword = true
) => {
if (!tenantId) {
throw "No tenancy specified."
}
// need to set the context for this request, as specified
updateTenantId(tenantId)
// specify the tenancy incase we're making a new admin user (public)
const db = getGlobalDB(tenantId)
let { email, password, _id } = user
// make sure another user isn't using the same email
let dbUser
if (email) {
// check budibase users inside the tenant
dbUser = await exports.getGlobalUserByEmail(email)
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
throw `Email address ${email} already in use.`
}
// check budibase users in other tenants
if (env.MULTI_TENANCY) {
const tenantUser = await getTenantUser(email)
if (tenantUser != null && tenantUser.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
// check root account users in account portal
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const account = await accounts.getAccount(email)
if (account && account.verified && account.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
} else {
dbUser = await db.get(_id)
}
// get the password, make sure one is defined
let hashedPassword
if (password) {
hashedPassword = hashPassword ? await hash(password) : password
} else if (dbUser) {
hashedPassword = dbUser.password
} else if (requirePassword) {
throw "Password must be specified."
}
_id = _id || generateGlobalUserID()
user = {
createdAt: Date.now(),
...dbUser,
...user,
_id,
password: hashedPassword,
tenantId,
}
// make sure the roles object is always present
if (!user.roles) {
user.roles = {}
}
// add the active status to a user if its not provided
if (user.status == null) {
user.status = UserStatus.ACTIVE
}
try {
const response = await db.put({
password: hashedPassword,
...user,
})
await tryAddTenant(tenantId, _id, email)
await userCache.invalidateUser(response.id)
return {
_id: response.id,
_rev: response.rev,
email,
}
} catch (err) {
if (err.status === 409) {
throw "User exists already"
} else {
throw err
}
}
}
/**
* Logs a user out from budibase. Re-used across account portal and builder.
*/

View File

@ -84,7 +84,7 @@ export class RestImporter {
const count = successQueries.length
const importSource = this.source.getImportSource()
const datasource = await db.get(datasourceId)
events.query.import({ datasource, importSource, count })
events.query.import(datasource, importSource, count)
for (let query of successQueries) {
events.query.created(query)
}

View File

@ -109,8 +109,7 @@ describe("Rest Importer", () => {
expect(importResult.errorQueries.length).toBe(0)
expect(importResult.queries.length).toBe(assertions[key].count)
expect(events.query.import).toBeCalledTimes(1)
const eventData = { datasource, importSource: assertions[key].source, count: assertions[key].count}
expect(events.query.import).toBeCalledWith(eventData)
expect(events.query.import).toBeCalledWith(datasource, assertions[key].source, assertions[key].count)
jest.clearAllMocks()
}

View File

@ -216,9 +216,12 @@ const removeDynamicVariables = async (queryId: any) => {
export async function destroy(ctx: any) {
const db = getAppDB()
await removeDynamicVariables(ctx.params.queryId)
const queryId = ctx.params.queryId
await removeDynamicVariables(queryId)
const query = await db.get(queryId)
const datasource = await db.get(query.datasourceId)
await db.remove(ctx.params.queryId, ctx.params.revId)
ctx.message = `Query deleted.`
ctx.status = 200
events.query.deleted()
events.query.deleted(datasource, query)
}

View File

@ -10,6 +10,7 @@ const {
InternalTables,
} = require("../../db/utils")
const { getAppDB } = require("@budibase/backend-core/context")
const { events } = require("@budibase/backend-core")
const UpdateRolesOptions = {
CREATED: "created",
@ -50,8 +51,10 @@ exports.find = async function (ctx) {
exports.save = async function (ctx) {
const db = getAppDB()
let { _id, name, inherits, permissionId } = ctx.request.body
let isCreate = false
if (!_id) {
_id = generateRoleID()
isCreate = true
} else if (isBuiltin(_id)) {
ctx.throw(400, "Cannot update builtin roles.")
}
@ -62,6 +65,11 @@ exports.save = async function (ctx) {
role._rev = ctx.request.body._rev
}
const result = await db.put(role)
if (isCreate) {
events.role.created(role)
} else {
events.role.updated(role)
}
await updateRolesOnUserTable(db, _id, UpdateRolesOptions.CREATED)
role._rev = result.rev
ctx.body = role
@ -71,6 +79,7 @@ exports.save = async function (ctx) {
exports.destroy = async function (ctx) {
const db = getAppDB()
const roleId = ctx.params.roleId
const role = await db.get(roleId)
if (isBuiltin(roleId)) {
ctx.throw(400, "Cannot delete builtin role.")
}
@ -88,6 +97,7 @@ exports.destroy = async function (ctx) {
}
await db.remove(roleId, ctx.params.rev)
events.role.deleted(role)
await updateRolesOnUserTable(
db,
ctx.params.roleId,

View File

@ -189,6 +189,7 @@ describe("/queries", () => {
expect(res.body).toEqual([])
expect(events.query.deleted).toBeCalledTimes(1)
expect(events.query.deleted).toBeCalledWith(datasource, query)
})
it("should apply authorization to endpoint", async () => {

View File

@ -4,6 +4,7 @@ const {
} = require("@budibase/backend-core/permissions")
const setup = require("./utilities")
const { basicRole } = setup.structures
const { events } = require("@budibase/backend-core")
describe("/roles", () => {
let request = setup.getRequest()
@ -15,20 +16,48 @@ describe("/roles", () => {
await config.init()
})
const createRole = async (role) => {
if (!role) {
role = basicRole()
}
return request
.post(`/api/roles`)
.send(role)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
describe("create", () => {
it("returns a success message when role is successfully created", async () => {
const res = await request
.post(`/api/roles`)
.send(basicRole())
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
const res = await createRole()
expect(res.res.statusMessage).toEqual(
"Role 'NewRole' created successfully."
)
expect(res.body._id).toBeDefined()
expect(res.body._rev).toBeDefined()
expect(events.role.updated).not.toBeCalled()
expect(events.role.created).toBeCalledTimes(1)
expect(events.role.created).toBeCalledWith(res.body)
})
})
describe("update", () => {
it("updates a role", async () => {
let res = await createRole()
jest.clearAllMocks()
res = await createRole(res.body)
expect(res.res.statusMessage).toEqual(
"Role 'NewRole' created successfully."
)
expect(res.body._id).toBeDefined()
expect(res.body._rev).toBeDefined()
expect(events.role.created).not.toBeCalled()
expect(events.role.updated).toBeCalledTimes(1)
expect(events.role.updated).toBeCalledWith(res.body)
})
})
@ -80,8 +109,10 @@ describe("/roles", () => {
it("should delete custom roles", async () => {
const customRole = await config.createRole({
name: "user",
permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY
permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY,
inherits: BUILTIN_ROLE_IDS.BASIC,
})
delete customRole._rev_tree
await request
.delete(`/api/roles/${customRole._id}/${customRole._rev}`)
.set(config.defaultHeaders())
@ -90,6 +121,8 @@ describe("/roles", () => {
.get(`/api/roles/${customRole._id}`)
.set(config.defaultHeaders())
.expect(404)
expect(events.role.deleted).toBeCalledTimes(1)
expect(events.role.deleted).toBeCalledWith(customRole)
})
})
})

View File

@ -4,14 +4,7 @@ const { google } = require("@budibase/backend-core/middleware")
const { oidc } = require("@budibase/backend-core/middleware")
const { Configs, EmailTemplatePurpose } = require("../../../constants")
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
const {
setCookie,
getCookie,
clearCookie,
getGlobalUserByEmail,
hash,
platformLogout,
} = core.utils
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
const { Cookies, Headers } = core.constants
const { passport } = core.auth
const { checkResetPasswordCode } = require("../../../utilities/redis")
@ -21,8 +14,8 @@ const {
isMultiTenant,
} = require("@budibase/backend-core/tenancy")
const env = require("../../../environment")
import { users } from "@budibase/pro"
const { events } = require("@budibase/backend-core")
const { events, users: usersCore } = require("@budibase/backend-core")
import { users } from "../../../sdk"
const ssoCallbackUrl = async (config: any, type: any) => {
// incase there is a callback URL from before
@ -112,7 +105,7 @@ export const reset = async (ctx: any) => {
)
}
try {
const user = await getGlobalUserByEmail(email)
const user = await usersCore.getGlobalUserByEmail(email)
// only if user exists, don't error though if they don't
if (user) {
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {

View File

@ -7,7 +7,7 @@ const {
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
const { user: userCache } = require("@budibase/backend-core/cache")
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
const { allUsers } = require("../../utilities")
const { users } = require("../../../sdk")
exports.fetch = async ctx => {
const tenantId = ctx.user.tenantId
@ -49,10 +49,10 @@ exports.find = async ctx => {
exports.removeAppRole = async ctx => {
const { appId } = ctx.params
const db = getGlobalDB()
const users = await allUsers(ctx)
const allUsers = await users.allUsers(ctx)
const bulk = []
const cacheInvalidations = []
for (let user of users) {
for (let user of allUsers) {
if (user.roles[appId]) {
cacheInvalidations.push(userCache.invalidateUser(user._id))
delete user.roles[appId]

View File

@ -13,7 +13,7 @@ const {
} = require("@budibase/backend-core/utils")
const { encrypt } = require("@budibase/backend-core/encryption")
const { newid } = require("@budibase/backend-core/utils")
const { getUser } = require("../../utilities")
const { users } = require("../../../sdk")
const { Cookies } = require("@budibase/backend-core/constants")
function newApiKey() {
@ -103,7 +103,7 @@ exports.getSelf = async ctx => {
checkCurrentApp(ctx)
// get the main body of the user
ctx.body = await getUser(userId)
ctx.body = await users.getUser(userId)
addSessionAttributesToUser(ctx)
}

View File

@ -1,32 +1,18 @@
const {
getGlobalUserParams,
StaticDatabases,
} = require("@budibase/backend-core/db")
const { getGlobalUserByEmail } = require("@budibase/backend-core/utils")
import { EmailTemplatePurpose } from "../../../constants"
import { checkInviteCode } from "../../../utilities/redis"
import { sendEmail } from "../../../utilities/email"
const { user: userCache } = require("@budibase/backend-core/cache")
const { invalidateSessions } = require("@budibase/backend-core/sessions")
const accounts = require("@budibase/backend-core/accounts")
import { users } from "../../../sdk"
const {
getGlobalDB,
getTenantId,
getTenantUser,
doesTenantExist,
} = require("@budibase/backend-core/tenancy")
const { removeUserFromInfoDB } = require("@budibase/backend-core/deprovision")
import env from "../../../environment"
import { syncUserInApps } from "../../../utilities/appService"
import { quotas, users } from "@budibase/pro"
const { errors } = require("@budibase/backend-core")
import { allUsers, getUser } from "../../utilities"
errors,
users: usersCore,
tenancy,
db: dbUtils,
} = require("@budibase/backend-core")
export const save = async (ctx: any) => {
try {
const user: any = await users.save(ctx.request.body, getTenantId())
// let server know to sync user
await syncUserInApps(user._id)
const user = await users.save(ctx.request.body)
ctx.body = user
} catch (err: any) {
ctx.throw(err.status || 400, err)
@ -45,36 +31,19 @@ export const adminUser = async (ctx: any) => {
// account portal sends no password for SSO users
const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
if (await doesTenantExist(tenantId)) {
if (await tenancy.doesTenantExist(tenantId)) {
ctx.throw(403, "Organisation already exists.")
}
const db = getGlobalDB(tenantId)
const db = tenancy.getGlobalDB(tenantId)
const response = await db.allDocs(
getGlobalUserParams(null, {
dbUtils.getGlobalUserParams(null, {
include_docs: true,
})
)
// write usage quotas for cloud
if (!env.SELF_HOSTED) {
// could be a scenario where it exists, make sure its clean
try {
const usageQuota = await db.get(StaticDatabases.GLOBAL.docs.usageQuota)
if (usageQuota) {
await db.remove(usageQuota._id, usageQuota._rev)
}
} catch (err) {
// don't worry about errors
}
await db.put(quotas.generateNewQuotaUsage())
}
if (response.rows.some((row: any) => row.doc.admin)) {
ctx.throw(
403,
"You cannot initialise once an global user has been created."
)
ctx.throw(403, "You cannot initialise once a global user has been created.")
}
const user = {
@ -91,44 +60,25 @@ export const adminUser = async (ctx: any) => {
tenantId,
}
try {
ctx.body = await users.save(user, tenantId, hashPassword, requirePassword)
ctx.body = await tenancy.doInTenant(tenantId, async () => {
return users.save(user, hashPassword, requirePassword)
})
} catch (err: any) {
ctx.throw(err.status || 400, err)
}
}
export const destroy = async (ctx: any) => {
const db = getGlobalDB()
const dbUser = await db.get(ctx.params.id)
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
// root account holder can't be deleted from inside budibase
const email = dbUser.email
const account = await accounts.getAccount(email)
if (account) {
if (email === ctx.user.email) {
ctx.throw(400, 'Please visit "Account" to delete this user')
} else {
ctx.throw(400, "Account holder cannot be deleted")
}
}
}
await removeUserFromInfoDB(dbUser)
await db.remove(dbUser._id, dbUser._rev)
await quotas.removeUser(dbUser)
await userCache.invalidateUser(dbUser._id)
await invalidateSessions(dbUser._id)
// let server know to sync user
await syncUserInApps(dbUser._id)
const id = ctx.params.id
await users.destroy(id, ctx.user)
ctx.body = {
message: `User ${ctx.params.id} deleted.`,
message: `User ${id} deleted.`,
}
}
// called internally by app server user fetch
export const fetch = async (ctx: any) => {
const all = await allUsers()
const all = await users.allUsers()
// user hashed password shouldn't ever be returned
for (let user of all) {
if (user) {
@ -140,12 +90,12 @@ export const fetch = async (ctx: any) => {
// called internally by app server user find
export const find = async (ctx: any) => {
ctx.body = await getUser(ctx.params.id)
ctx.body = await users.getUser(ctx.params.id)
}
export const tenantUserLookup = async (ctx: any) => {
const id = ctx.params.id
const user = await getTenantUser(id)
const user = await tenancy.getTenantUser(id)
if (user) {
ctx.body = user
} else {
@ -155,14 +105,14 @@ export const tenantUserLookup = async (ctx: any) => {
export const invite = async (ctx: any) => {
let { email, userInfo } = ctx.request.body
const existing = await getGlobalUserByEmail(email)
const existing = await usersCore.getGlobalUserByEmail(email)
if (existing) {
ctx.throw(400, "Email address already in use.")
}
if (!userInfo) {
userInfo = {}
}
userInfo.tenantId = getTenantId()
userInfo.tenantId = tenancy.getTenantId()
const opts: any = {
subject: "{{ company }} platform invitation",
info: userInfo,
@ -178,16 +128,15 @@ export const inviteAccept = async (ctx: any) => {
try {
// info is an extension of the user object that was stored by global
const { email, info }: any = await checkInviteCode(inviteCode)
ctx.body = await users.save(
{
ctx.body = await tenancy.doInTenant(info.tenantId, () => {
return users.save({
firstName,
lastName,
password,
email,
...info,
},
info.tenantId
)
})
})
} catch (err: any) {
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
// explicitly re-throw limit exceeded errors

View File

@ -1,7 +1,7 @@
const Router = require("@koa/router")
const controller = require("../../controllers/global/self")
const builderOnly = require("../../../middleware/builderOnly")
const { buildUserSaveValidation } = require("../../utilities/validation")
const { users } = require("../validation")
const router = Router()
@ -11,7 +11,7 @@ router
.get("/api/global/self", controller.getSelf)
.post(
"/api/global/self",
buildUserSaveValidation(true),
users.buildUserSaveValidation(true),
controller.updateSelf
)

View File

@ -4,7 +4,7 @@ const joiValidator = require("../../../middleware/joi-validator")
const adminOnly = require("../../../middleware/adminOnly")
const Joi = require("joi")
const cloudRestricted = require("../../../middleware/cloudRestricted")
const { buildUserSaveValidation } = require("../../utilities/validation")
const { users } = require("../validation")
const selfController = require("../../controllers/global/self")
const router = Router()
@ -41,7 +41,7 @@ router
.post(
"/api/global/users",
adminOnly,
buildUserSaveValidation(),
users.buildUserSaveValidation(),
controller.save
)
.get("/api/global/users", adminOnly, controller.fetch)
@ -72,7 +72,7 @@ router
.get("/api/global/users/self", selfController.getSelf)
.post(
"/api/global/users/self",
buildUserSaveValidation(true),
users.buildUserSaveValidation(true),
selfController.updateSelf
)

View File

@ -1,6 +1,7 @@
jest.mock("nodemailer")
const { config, request, mocks } = require("../../../tests")
const { config, request, mocks, structures } = require("../../../tests")
const sendMailMock = mocks.email.mock()
const { events } = require("@budibase/backend-core")
describe("/api/global/users", () => {
let code
@ -48,4 +49,258 @@ describe("/api/global/users", () => {
expect(user).toBeDefined()
expect(user._id).toEqual(res.body._id)
})
const createUser = async (user) => {
const existing = await config.getUser(user.email)
if (existing) {
await deleteUser(existing._id)
}
return saveUser(user)
}
const updateUser = async (user) => {
const existing = await config.getUser(user.email)
user._id = existing._id
return saveUser(user)
}
const saveUser = async (user) => {
const res = await request
.post(`/api/global/users`)
.send(user)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
// .expect(200)
return res.body
}
const deleteUser = async (email) => {
const user = await config.getUser(email)
if (user) {
await request
.delete(`/api/global/users/${user._id}`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
}
describe("create", () => {
it("should be able to create a basic user", async () => {
jest.clearAllMocks()
const user = structures.users.user({ email: "basic@test.com" })
await createUser(user)
expect(events.user.created).toBeCalledTimes(1)
expect(events.user.updated).not.toBeCalled()
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
expect(events.user.permissionAdminAssigned).not.toBeCalled()
})
it("should be able to create an admin user", async () => {
jest.clearAllMocks()
const user = structures.users.adminUser({ email: "admin@test.com" })
await createUser(user)
expect(events.user.created).toBeCalledTimes(1)
expect(events.user.updated).not.toBeCalled()
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
})
it("should be able to create a builder user", async () => {
jest.clearAllMocks()
const user = structures.users.builderUser({ email: "builder@test.com" })
await createUser(user)
expect(events.user.created).toBeCalledTimes(1)
expect(events.user.updated).not.toBeCalled()
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
expect(events.user.permissionAdminAssigned).not.toBeCalled()
})
it("should be able to assign app roles", async () => {
jest.clearAllMocks()
const user = structures.users.user({ email: "assign-roles@test.com" })
user.roles = {
"app_123": "role1",
"app_456": "role2",
}
await createUser(user)
expect(events.user.created).toBeCalledTimes(1)
expect(events.user.updated).not.toBeCalled()
expect(events.role.assigned).toBeCalledTimes(2)
expect(events.role.assigned).toBeCalledWith("role1")
expect(events.role.assigned).toBeCalledWith("role2")
})
})
describe("update", () => {
it("should be able to update a basic user", async () => {
let user = structures.users.user({ email: "basic-update@test.com" })
await createUser(user)
jest.clearAllMocks()
await updateUser(user)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
expect(events.user.permissionAdminAssigned).not.toBeCalled()
})
it("should be able to update a basic user to an admin user", async () => {
let user = structures.users.user({ email: "basic-update-admin@test.com" })
await createUser(user)
jest.clearAllMocks()
await updateUser(structures.users.adminUser(user))
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.user.permissionBuilderAssigned).not.toBeCalled()
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
})
it("should be able to update a basic user to a builder user", async () => {
let user = structures.users.user({ email: "basic-update-builder@test.com" })
await createUser(user)
jest.clearAllMocks()
await updateUser(structures.users.builderUser(user))
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
expect(events.user.permissionAdminAssigned).not.toBeCalled()
})
it("should be able to update an admin user to a basic user", async () => {
let user = structures.users.adminUser({ email: "admin-update-basic@test.com" })
await createUser(user)
jest.clearAllMocks()
user.admin.global = false
await updateUser(user)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
expect(events.user.permissionBuilderRemoved).not.toBeCalled()
})
it("should be able to update an builder user to a basic user", async () => {
let user = structures.users.builderUser({ email: "builder-update-basic@test.com" })
await createUser(user)
jest.clearAllMocks()
user.builder.global = false
await updateUser(user)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
expect(events.user.permissionAdminRemoved).not.toBeCalled()
})
it("should be able to assign app roles", async () => {
const user = structures.users.user({ email: "assign-roles-update@test.com" })
await createUser(user)
jest.clearAllMocks()
user.roles = {
"app_123": "role1",
"app_456": "role2",
}
await updateUser(user)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.role.assigned).toBeCalledTimes(2)
expect(events.role.assigned).toBeCalledWith("role1")
expect(events.role.assigned).toBeCalledWith("role2")
})
it("should be able to unassign app roles", async () => {
const user = structures.users.user({ email: "unassign-roles@test.com" })
user.roles = {
"app_123": "role1",
"app_456": "role2",
}
await createUser(user)
jest.clearAllMocks()
user.roles = {}
await updateUser(user)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.role.unassigned).toBeCalledTimes(2)
expect(events.role.unassigned).toBeCalledWith("role1")
expect(events.role.unassigned).toBeCalledWith("role2")
})
it("should be able to update existing app roles", async () => {
const user = structures.users.user({ email: "update-roles@test.com" })
user.roles = {
"app_123": "role1",
"app_456": "role2",
}
await createUser(user)
jest.clearAllMocks()
user.roles = {
"app_123": "role1",
"app_456": "role2-edit",
}
await updateUser(user)
expect(events.user.created).not.toBeCalled()
expect(events.user.updated).toBeCalledTimes(1)
expect(events.role.unassigned).toBeCalledTimes(1)
expect(events.role.unassigned).toBeCalledWith("role2")
expect(events.role.assigned).toBeCalledTimes(1)
expect(events.role.assigned).toBeCalledWith("role2-edit")
})
})
describe("destroy", () => {
it("should be able to destroy a basic user", async () => {
let user = structures.users.user({ email: "destroy@test.com" })
await createUser(user)
jest.clearAllMocks()
await deleteUser(user.email)
expect(events.user.deleted).toBeCalledTimes(1)
expect(events.user.permissionBuilderRemoved).not.toBeCalled()
expect(events.user.permissionAdminRemoved).not.toBeCalled()
})
it("should be able to destroy an admin user", async () => {
let user = structures.users.adminUser({ email: "destroy-admin@test.com" })
await createUser(user)
jest.clearAllMocks()
await deleteUser(user.email)
expect(events.user.deleted).toBeCalledTimes(1)
expect(events.user.permissionBuilderRemoved).not.toBeCalled()
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
})
it("should be able to destroy a builder user", async () => {
let user = structures.users.builderUser({ email: "destroy-admin@test.com" })
await createUser(user)
jest.clearAllMocks()
await deleteUser(user.email)
expect(events.user.deleted).toBeCalledTimes(1)
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
expect(events.user.permissionAdminRemoved).not.toBeCalled()
})
})
})

View File

@ -0,0 +1 @@
export * as users from "./users"

View File

@ -1,8 +1,8 @@
const joiValidator = require("../../middleware/joi-validator")
const Joi = require("joi")
import joiValidator from "../../../middleware/joi-validator"
import Joi from "joi"
exports.buildUserSaveValidation = (isSelf = false) => {
let schema = {
export const buildUserSaveValidation = (isSelf = false) => {
let schema: any = {
email: Joi.string().allow(null, ""),
password: Joi.string().allow(null, ""),
forceResetPassword: Joi.boolean().optional(),

View File

@ -1,33 +0,0 @@
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
const { getGlobalUserParams } = require("@budibase/backend-core/db")
/**
* Retrieves all users from the current tenancy.
*/
exports.allUsers = async () => {
const db = getGlobalDB()
const response = await db.allDocs(
getGlobalUserParams(null, {
include_docs: true,
})
)
return response.rows.map(row => row.doc)
}
/**
* Gets a user by ID from the global database, based on the current tenancy.
*/
exports.getUser = async userId => {
const db = getGlobalDB()
let user
try {
user = await db.get(userId)
} catch (err) {
// no user found, just return nothing
user = {}
}
if (user) {
delete user.password
}
return user
}

View File

@ -0,0 +1 @@
export * as users from "./users"

View File

@ -0,0 +1,136 @@
const { events } = require("@budibase/backend-core")
export const handleDeleteEvents = (user: any) => {
events.user.deleted(user)
if (isBuilder(user)) {
events.user.permissionBuilderRemoved(user)
}
if (isAdmin(user)) {
events.user.permissionAdminRemoved(user)
}
}
const assignAppRoleEvents = (roles: any, existingRoles: any) => {
for (const [appId, role] of Object.entries(roles)) {
// app role in existing is not same as new
if (!existingRoles || existingRoles[appId] !== role) {
events.role.assigned(role)
}
}
}
const unassignAppRoleEvents = (roles: any, existingRoles: any) => {
if (!existingRoles) {
return
}
for (const [appId, role] of Object.entries(existingRoles)) {
// app role in new is not same as existing
if (!roles || roles[appId] !== role) {
events.role.unassigned(role)
}
}
}
const handleAppRoleEvents = (user: any, existingUser: any) => {
const roles = user.roles
const existingRoles = existingUser?.roles
assignAppRoleEvents(roles, existingRoles)
unassignAppRoleEvents(roles, existingRoles)
}
export const handleSaveEvents = (user: any, existingUser: any) => {
if (existingUser) {
events.user.updated(user)
if (isRemovingBuilder(user, existingUser)) {
events.user.permissionBuilderRemoved(user)
}
if (isRemovingAdmin(user, existingUser)) {
events.user.permissionAdminRemoved(user)
}
} else {
events.user.created(user)
}
if (isAddingBuilder(user, existingUser)) {
events.user.permissionBuilderAssigned(user)
}
if (isAddingAdmin(user, existingUser)) {
events.user.permissionAdminAssigned(user)
}
handleAppRoleEvents(user, existingUser)
}
const isBuilder = (user: any) => user.builder && user.builder.global
const isAdmin = (user: any) => user.admin && user.admin.global
export const isAddingBuilder = (user: any, existingUser: any) => {
return isAddingPermission(user, existingUser, isBuilder)
}
export const isRemovingBuilder = (user: any, existingUser: any) => {
return isRemovingPermission(user, existingUser, isBuilder)
}
const isAddingAdmin = (user: any, existingUser: any) => {
return isAddingPermission(user, existingUser, isAdmin)
}
const isRemovingAdmin = (user: any, existingUser: any) => {
return isRemovingPermission(user, existingUser, isAdmin)
}
/**
* Check if a permission is being added to a new or existing user.
*/
const isAddingPermission = (
user: any,
existingUser: any,
hasPermission: any
) => {
// new user doesn't have the permission
if (!hasPermission(user)) {
return false
}
// existing user has the permission
if (existingUser && hasPermission(existingUser)) {
return false
}
// permission is being added
return true
}
/**
* Check if a permission is being removed from an existing user.
*/
const isRemovingPermission = (
user: any,
existingUser: any,
hasPermission: any
) => {
// new user has the permission
if (hasPermission(user)) {
return false
}
// no existing user or existing user doesn't have the permission
if (!existingUser) {
return false
}
// existing user doesn't have the permission
if (!hasPermission(existingUser)) {
return false
}
// permission is being removed
return true
}

View File

@ -0,0 +1 @@
export * from "./users"

View File

@ -0,0 +1,180 @@
import env from "../../environment"
import { quotas } from "@budibase/pro"
import * as apps from "../../utilities/appService"
const { events } = require("@budibase/backend-core")
import * as eventHelpers from "./events"
const {
tenancy,
accounts,
utils,
db: dbUtils,
constants,
cache,
users: usersCore,
deprovisioning,
sessions,
HTTPError,
} = require("@budibase/backend-core")
/**
* Retrieves all users from the current tenancy.
*/
export const allUsers = async () => {
const db = tenancy.getGlobalDB()
const response = await db.allDocs(
dbUtils.getGlobalUserParams(null, {
include_docs: true,
})
)
return response.rows.map((row: any) => row.doc)
}
/**
* Gets a user by ID from the global database, based on the current tenancy.
*/
export const getUser = async (userId: string) => {
const db = tenancy.getGlobalDB()
let user
try {
user = await db.get(userId)
} catch (err: any) {
// no user found, just return nothing
if (err.status === 404) {
return {}
}
throw err
}
if (user) {
delete user.password
}
return user
}
export const save = async (
user: any,
hashPassword = true,
requirePassword = true
) => {
const tenantId = tenancy.getTenantId()
// specify the tenancy incase we're making a new admin user (public)
const db = tenancy.getGlobalDB(tenantId)
let { email, password, _id } = user
// make sure another user isn't using the same email
let dbUser
if (email) {
// check budibase users inside the tenant
dbUser = await usersCore.getGlobalUserByEmail(email)
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
throw `Email address ${email} already in use.`
}
// check budibase users in other tenants
if (env.MULTI_TENANCY) {
const tenantUser = await tenancy.getTenantUser(email)
if (tenantUser != null && tenantUser.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
// check root account users in account portal
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
const account = await accounts.getAccount(email)
if (account && account.verified && account.tenantId !== tenantId) {
throw `Email address ${email} already in use.`
}
}
} else if (_id) {
dbUser = await db.get(_id)
}
// get the password, make sure one is defined
let hashedPassword
if (password) {
hashedPassword = hashPassword ? await utils.hash(password) : password
} else if (dbUser) {
hashedPassword = dbUser.password
} else if (requirePassword) {
throw "Password must be specified."
}
if (!_id) {
_id = dbUtils.generateGlobalUserID(email)
}
user = {
createdAt: Date.now(),
...dbUser,
...user,
_id,
password: hashedPassword,
tenantId,
}
// make sure the roles object is always present
if (!user.roles) {
user.roles = {}
}
// add the active status to a user if its not provided
if (user.status == null) {
user.status = constants.UserStatus.ACTIVE
}
try {
// save the user to db
let response
const putUserFn = () => {
return db.put(user)
}
if (await eventHelpers.isAddingBuilder(user, dbUser)) {
response = await quotas.addDeveloper(putUserFn)
} else {
response = await putUserFn()
}
eventHelpers.handleSaveEvents(user, dbUser)
await tenancy.tryAddTenant(tenantId, _id, email)
await cache.user.invalidateUser(response.id)
// let server know to sync user
await apps.syncUserInApps(user._id)
return {
_id: response.id,
_rev: response.rev,
email,
}
} catch (err: any) {
if (err.status === 409) {
throw "User exists already"
} else {
throw err
}
}
}
export const destroy = async (id: string, currentUser: any) => {
const db = tenancy.getGlobalDB()
const dbUser = await db.get(id)
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
// root account holder can't be deleted from inside budibase
const email = dbUser.email
const account = await accounts.getAccount(email)
if (account) {
if (email === currentUser.email) {
throw new HTTPError('Please visit "Account" to delete this user', 400)
} else {
throw new HTTPError("Account holder cannot be deleted", 400)
}
}
}
await deprovisioning.removeUserFromInfoDB(dbUser)
await db.remove(dbUser._id, dbUser._rev)
eventHelpers.handleDeleteEvents(dbUser)
await quotas.removeUser(dbUser)
await cache.user.invalidateUser(dbUser._id)
await sessions.invalidateSessions(dbUser._id)
// let server know to sync user
await apps.syncUserInApps(dbUser._id)
}

View File

@ -6,7 +6,7 @@ const supertest = require("supertest")
const { jwt } = require("@budibase/backend-core/auth")
const { Cookies, Headers } = require("@budibase/backend-core/constants")
const { Configs } = require("../constants")
const { getGlobalUserByEmail } = require("@budibase/backend-core/utils")
const { users } = require("@budibase/backend-core")
const { createASession } = require("@budibase/backend-core/sessions")
const { TENANT_ID, CSRF_TOKEN } = require("./structures")
const structures = require("./structures")
@ -112,20 +112,17 @@ class TestConfiguration {
async getUser(email) {
return doInTenant(TENANT_ID, () => {
return getGlobalUserByEmail(email)
return users.getGlobalUserByEmail(email)
})
}
async createUser(email = "test@test.com", password = "test") {
const user = await this.getUser(email)
async createUser(email, password) {
const user = await this.getUser(structures.users.email)
if (user) {
return user
}
await this._req(
{
email,
password,
},
structures.users.user({ email, password }),
null,
controllers.users.save
)
@ -133,11 +130,7 @@ class TestConfiguration {
async saveAdminUser() {
await this._req(
{
email: "testuser@test.com",
password: "test@test.com",
tenantId: TENANT_ID,
},
structures.users.user({ tenantId: TENANT_ID }),
null,
controllers.users.adminUser
)

View File

@ -1,10 +1,12 @@
const configs = require("./configs")
const users = require("./users")
const TENANT_ID = "default"
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
module.exports = {
configs,
users,
TENANT_ID,
CSRF_TOKEN,
}

View File

@ -0,0 +1,28 @@
export const email = "test@test.com"
export const user = (userProps: any) => {
return {
email: "test@test.com",
password: "test",
roles: {},
...userProps,
}
}
export const adminUser = (userProps: any) => {
return {
...user(userProps),
admin: {
global: true,
},
}
}
export const builderUser = (userProps: any) => {
return {
...user(userProps),
builder: {
global: true,
},
}
}