Fixing test cases and bugs that they raised.
This commit is contained in:
parent
24012c2fba
commit
313302cae2
|
@ -82,13 +82,21 @@ exports.getGlobalDB = tenantId => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given a koa context this tries to find the correct tenant Global DB.
|
||||
* Given a koa context this tries to extra what tenant is being accessed.
|
||||
*/
|
||||
exports.getGlobalDBFromCtx = ctx => {
|
||||
exports.getTenantIdFromCtx = ctx => {
|
||||
const user = ctx.user || {}
|
||||
const params = ctx.request.params || {}
|
||||
const query = ctx.request.query || {}
|
||||
return exports.getGlobalDB(user.tenantId || params.tenantId || query.tenantId)
|
||||
return user.tenantId || params.tenantId || query.tenantId
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a koa context this tries to find the correct tenant Global DB.
|
||||
*/
|
||||
exports.getGlobalDBFromCtx = ctx => {
|
||||
const tenantId = exports.getTenantIdFromCtx(ctx)
|
||||
return exports.getGlobalDB(tenantId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@ const appController = require("../../../controllers/application")
|
|||
const CouchDB = require("../../../../db")
|
||||
const { AppStatus } = require("../../../../db/utils")
|
||||
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
|
||||
const { TENANT_ID } = require("../../../../tests/utilities/structures")
|
||||
|
||||
function Request(appId, params) {
|
||||
this.appId = appId
|
||||
|
@ -16,8 +17,8 @@ exports.getAllTableRows = async config => {
|
|||
return req.body
|
||||
}
|
||||
|
||||
exports.clearAllApps = async () => {
|
||||
const req = { query: { status: AppStatus.DEV } }
|
||||
exports.clearAllApps = async (tenantId = TENANT_ID) => {
|
||||
const req = { query: { status: AppStatus.DEV }, user: { tenantId } }
|
||||
await appController.fetch(req)
|
||||
const apps = req.body
|
||||
if (!apps || apps.length <= 0) {
|
||||
|
|
|
@ -10,20 +10,23 @@ const {
|
|||
basicScreen,
|
||||
basicLayout,
|
||||
basicWebhook,
|
||||
TENANT_ID,
|
||||
} = require("./structures")
|
||||
const controllers = require("./controllers")
|
||||
const supertest = require("supertest")
|
||||
const { cleanup } = require("../../utilities/fileSystem")
|
||||
const { Cookies } = require("@budibase/auth").constants
|
||||
const { jwt } = require("@budibase/auth").auth
|
||||
const auth = require("@budibase/auth")
|
||||
const { getGlobalDB } = require("@budibase/auth/db")
|
||||
const { createASession } = require("@budibase/auth/sessions")
|
||||
const { user: userCache } = require("@budibase/auth/cache")
|
||||
const CouchDB = require("../../db")
|
||||
auth.init(CouchDB)
|
||||
|
||||
const GLOBAL_USER_ID = "us_uuid1"
|
||||
const EMAIL = "babs@babs.com"
|
||||
const PASSWORD = "babs_password"
|
||||
const TENANT_ID = "default"
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(openServer = true) {
|
||||
|
@ -52,7 +55,7 @@ class TestConfiguration {
|
|||
request.cookies = { set: () => {}, get: () => {} }
|
||||
request.config = { jwtSecret: env.JWT_SECRET }
|
||||
request.appId = this.appId
|
||||
request.user = { appId: this.appId }
|
||||
request.user = { appId: this.appId, tenantId: TENANT_ID }
|
||||
request.query = {}
|
||||
request.request = {
|
||||
body: config,
|
||||
|
@ -78,7 +81,7 @@ class TestConfiguration {
|
|||
roles: roles || {},
|
||||
tenantId: TENANT_ID,
|
||||
}
|
||||
await createASession(id, "sessionid")
|
||||
await createASession(id, { sessionId: "sessionid", tenantId: TENANT_ID })
|
||||
if (builder) {
|
||||
user.builder = { global: true }
|
||||
}
|
||||
|
@ -108,6 +111,7 @@ class TestConfiguration {
|
|||
const auth = {
|
||||
userId: GLOBAL_USER_ID,
|
||||
sessionId: "sessionid",
|
||||
tenantId: TENANT_ID,
|
||||
}
|
||||
const app = {
|
||||
roleId: BUILTIN_ROLE_IDS.ADMIN,
|
||||
|
@ -334,11 +338,12 @@ class TestConfiguration {
|
|||
if (!email || !password) {
|
||||
await this.createUser()
|
||||
}
|
||||
await createASession(userId, "sessionid")
|
||||
await createASession(userId, { sessionId: "sessionid", tenantId: TENANT_ID })
|
||||
// have to fake this
|
||||
const auth = {
|
||||
userId,
|
||||
sessionId: "sessionid",
|
||||
tenantId: TENANT_ID,
|
||||
}
|
||||
const app = {
|
||||
roleId: roleId,
|
||||
|
|
|
@ -4,6 +4,8 @@ const { createHomeScreen } = require("../../constants/screens")
|
|||
const { EMPTY_LAYOUT } = require("../../constants/layouts")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
|
||||
exports.TENANT_ID = "default"
|
||||
|
||||
exports.basicTable = () => {
|
||||
return {
|
||||
name: "TestTable",
|
||||
|
|
|
@ -3,3 +3,4 @@ const env = require("../src/environment")
|
|||
env._set("NODE_ENV", "jest")
|
||||
env._set("JWT_SECRET", "test-jwtsecret")
|
||||
env._set("LOG_LEVEL", "silent")
|
||||
env._set("MULTI_TENANCY", true)
|
||||
|
|
|
@ -74,6 +74,7 @@ exports.reset = async ctx => {
|
|||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
// don't throw any kind of error to the user, this might give away something
|
||||
}
|
||||
ctx.body = {
|
||||
|
@ -88,7 +89,7 @@ exports.resetUpdate = async ctx => {
|
|||
const { resetCode, password } = ctx.request.body
|
||||
try {
|
||||
const userId = await checkResetPasswordCode(resetCode)
|
||||
const db = new getGlobalDB(ctx.params.tenantId)
|
||||
const db = getGlobalDB(ctx.params.tenantId)
|
||||
const user = await db.get(userId)
|
||||
user.password = await hash(password)
|
||||
await db.put(user)
|
||||
|
|
|
@ -4,7 +4,7 @@ const {
|
|||
TemplateBindings,
|
||||
GLOBAL_OWNER,
|
||||
} = require("../../../constants")
|
||||
const { getTemplates } = require("../../../constants/templates")
|
||||
const { getTemplatesCtx } = require("../../../constants/templates")
|
||||
|
||||
exports.save = async ctx => {
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
|
@ -45,23 +45,23 @@ exports.definitions = async ctx => {
|
|||
}
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
ctx.body = await getTemplates(ctx)
|
||||
ctx.body = await getTemplatesCtx(ctx)
|
||||
}
|
||||
|
||||
exports.fetchByType = async ctx => {
|
||||
ctx.body = await getTemplates(ctx, {
|
||||
ctx.body = await getTemplatesCtx(ctx, {
|
||||
type: ctx.params.type,
|
||||
})
|
||||
}
|
||||
|
||||
exports.fetchByOwner = async ctx => {
|
||||
ctx.body = await getTemplates(ctx, {
|
||||
ctx.body = await getTemplatesCtx(ctx, {
|
||||
ownerId: ctx.params.ownerId,
|
||||
})
|
||||
}
|
||||
|
||||
exports.find = async ctx => {
|
||||
ctx.body = await getTemplates(ctx, {
|
||||
ctx.body = await getTemplatesCtx(ctx, {
|
||||
id: ctx.params.id,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -265,12 +265,16 @@ exports.find = async ctx => {
|
|||
}
|
||||
|
||||
exports.invite = async ctx => {
|
||||
const { email, userInfo } = ctx.request.body
|
||||
let { email, userInfo } = ctx.request.body
|
||||
const tenantId = ctx.user.tenantId
|
||||
const existing = await getGlobalUserByEmail(email, tenantId)
|
||||
if (existing) {
|
||||
ctx.throw(400, "Email address already in use.")
|
||||
}
|
||||
if (!userInfo) {
|
||||
userInfo = {}
|
||||
}
|
||||
userInfo.tenantId = tenantId
|
||||
await sendEmail(tenantId, email, EmailTemplatePurpose.INVITATION, {
|
||||
subject: "{{ company }} platform invitation",
|
||||
info: userInfo,
|
||||
|
@ -293,6 +297,9 @@ exports.inviteAccept = async ctx => {
|
|||
email,
|
||||
...info,
|
||||
}
|
||||
ctx.user = {
|
||||
tenantId: info.tenantId,
|
||||
}
|
||||
// this will flesh out the body response
|
||||
await exports.save(ctx)
|
||||
} catch (err) {
|
||||
|
|
|
@ -36,8 +36,8 @@ describe("/api/global/auth", () => {
|
|||
expect(sendMailMock).toHaveBeenCalled()
|
||||
const emailCall = sendMailMock.mock.calls[0][0]
|
||||
// after this URL there should be a code
|
||||
const parts = emailCall.html.split(`http://localhost:10000/builder/auth/${TENANT_ID}/reset?code=`)
|
||||
code = parts[1].split("\"")[0]
|
||||
const parts = emailCall.html.split(`http://localhost:10000/builder/auth/reset?code=`)
|
||||
code = parts[1].split("\"")[0].split("&")[0]
|
||||
expect(code).toBeDefined()
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const setup = require("./utilities")
|
||||
const { EmailTemplatePurpose } = require("../../../constants")
|
||||
const { TENANT_ID } = require("./utilities/structures")
|
||||
|
||||
// mock the email system
|
||||
const sendMailMock = jest.fn()
|
||||
|
@ -29,6 +30,7 @@ describe("/api/global/email", () => {
|
|||
.send({
|
||||
email: "test@test.com",
|
||||
purpose: EmailTemplatePurpose.INVITATION,
|
||||
tenantId: TENANT_ID,
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const setup = require("./utilities")
|
||||
const { TENANT_ID } = require("./utilities/structures")
|
||||
|
||||
jest.mock("nodemailer")
|
||||
const sendMailMock = setup.emailMock()
|
||||
|
@ -31,7 +32,7 @@ describe("/api/global/users", () => {
|
|||
const emailCall = sendMailMock.mock.calls[0][0]
|
||||
// after this URL there should be a code
|
||||
const parts = emailCall.html.split("http://localhost:10000/builder/invite?code=")
|
||||
code = parts[1].split("\"")[0]
|
||||
code = parts[1].split("\"")[0].split("&")[0]
|
||||
expect(code).toBeDefined()
|
||||
})
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@ const { Configs, LOGO_URL } = require("../../../../constants")
|
|||
const { getGlobalUserByEmail } = require("@budibase/auth").utils
|
||||
const { createASession } = require("@budibase/auth/sessions")
|
||||
const { newid } = require("../../../../../../auth/src/hashing")
|
||||
|
||||
const TENANT_ID = "default"
|
||||
const { TENANT_ID } = require("./structures")
|
||||
const auth = require("@budibase/auth")
|
||||
const CouchDB = require("../../../../db")
|
||||
auth.init(CouchDB)
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(openServer = true) {
|
||||
|
@ -30,7 +32,7 @@ class TestConfiguration {
|
|||
request.cookies = { set: () => {}, get: () => {} }
|
||||
request.config = { jwtSecret: env.JWT_SECRET }
|
||||
request.appId = this.appId
|
||||
request.user = { appId: this.appId }
|
||||
request.user = { appId: this.appId, tenantId: TENANT_ID }
|
||||
request.query = {}
|
||||
request.request = {
|
||||
body: config,
|
||||
|
@ -60,7 +62,7 @@ class TestConfiguration {
|
|||
null,
|
||||
controllers.users.save
|
||||
)
|
||||
await createASession("us_uuid1", "sessionid")
|
||||
await createASession("us_uuid1", { sessionId: "sessionid", tenantId: TENANT_ID })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,6 +235,7 @@ class TestConfiguration {
|
|||
{
|
||||
email: "testuser@test.com",
|
||||
password: "test@test.com",
|
||||
tenantId: TENANT_ID,
|
||||
},
|
||||
null,
|
||||
controllers.users.adminUser
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
exports.TENANT_ID = "default"
|
|
@ -6,7 +6,7 @@ const {
|
|||
GLOBAL_OWNER,
|
||||
} = require("../index")
|
||||
const { join } = require("path")
|
||||
const { getTemplateParams, getGlobalDBFromCtx } = require("@budibase/auth/db")
|
||||
const { getTemplateParams, getTenantIdFromCtx, getGlobalDB } = require("@budibase/auth/db")
|
||||
|
||||
exports.EmailTemplates = {
|
||||
[EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile(
|
||||
|
@ -48,8 +48,13 @@ exports.addBaseTemplates = (templates, type = null) => {
|
|||
return templates
|
||||
}
|
||||
|
||||
exports.getTemplates = async (ctx, { ownerId, type, id } = {}) => {
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
exports.getTemplatesCtx = async (ctx, opts = {}) => {
|
||||
const tenantId = getTenantIdFromCtx(ctx)
|
||||
return exports.getTemplates(tenantId, opts)
|
||||
}
|
||||
|
||||
exports.getTemplates = async (tenantId, { ownerId, type, id} = {}) => {
|
||||
const db = getGlobalDB(tenantId)
|
||||
const response = await db.allDocs(
|
||||
getTemplateParams(ownerId || GLOBAL_OWNER, id, {
|
||||
include_docs: true,
|
||||
|
@ -66,7 +71,10 @@ exports.getTemplates = async (ctx, { ownerId, type, id } = {}) => {
|
|||
return exports.addBaseTemplates(templates, type)
|
||||
}
|
||||
|
||||
exports.getTemplateByPurpose = async (ctx, type, purpose) => {
|
||||
const templates = await exports.getTemplates(ctx, { type })
|
||||
exports.getTemplateByPurpose = async ({ tenantId, ctx }, type, purpose) => {
|
||||
if (!tenantId && ctx) {
|
||||
tenantId = getTenantIdFromCtx(ctx)
|
||||
}
|
||||
const templates = await exports.getTemplates(tenantId, { type })
|
||||
return templates.find(template => template.purpose === purpose)
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ async function getLinkCode(purpose, email, user, info = null) {
|
|||
|
||||
/**
|
||||
* Builds an email using handlebars and the templates found in the system (default or otherwise).
|
||||
* @param {string} tenantId the ID of the tenant which is sending the email.
|
||||
* @param {string} purpose the purpose of the email being built, e.g. invitation, password reset.
|
||||
* @param {string} email the address which it is being sent to for contextual purposes.
|
||||
* @param {object} context the context which is being used for building the email (hbs context).
|
||||
|
@ -67,14 +68,14 @@ async function getLinkCode(purpose, email, user, info = null) {
|
|||
* @param {string|null} contents if using a custom template can supply contents for context.
|
||||
* @return {Promise<string>} returns the built email HTML if all provided parameters were valid.
|
||||
*/
|
||||
async function buildEmail(purpose, email, context, { user, contents } = {}) {
|
||||
async function buildEmail(tenantId, purpose, email, context, { user, contents } = {}) {
|
||||
// this isn't a full email
|
||||
if (FULL_EMAIL_PURPOSES.indexOf(purpose) === -1) {
|
||||
throw `Unable to build an email of type ${purpose}`
|
||||
}
|
||||
let [base, body] = await Promise.all([
|
||||
getTemplateByPurpose(TYPE, EmailTemplatePurpose.BASE),
|
||||
getTemplateByPurpose(TYPE, purpose),
|
||||
getTemplateByPurpose({ tenantId }, TYPE, EmailTemplatePurpose.BASE),
|
||||
getTemplateByPurpose({ tenantId }, TYPE, purpose),
|
||||
])
|
||||
if (!base || !body) {
|
||||
throw "Unable to build email, missing base components"
|
||||
|
@ -147,7 +148,7 @@ exports.sendEmail = async (
|
|||
purpose,
|
||||
{ workspaceId, user, from, contents, subject, info } = {}
|
||||
) => {
|
||||
const db = new getGlobalDB(tenantId)
|
||||
const db = getGlobalDB(tenantId)
|
||||
let config = (await getSmtpConfiguration(db, workspaceId)) || {}
|
||||
if (Object.keys(config).length === 0 && !TEST_MODE) {
|
||||
throw "Unable to find SMTP configuration."
|
||||
|
@ -159,7 +160,7 @@ exports.sendEmail = async (
|
|||
const message = {
|
||||
from: from || config.from,
|
||||
to: email,
|
||||
html: await buildEmail(purpose, email, context, { user, contents }),
|
||||
html: await buildEmail(tenantId, purpose, email, context, { user, contents }),
|
||||
}
|
||||
if (subject || config.subject) {
|
||||
message.subject = await processString(subject || config.subject, context)
|
||||
|
|
|
@ -11,8 +11,16 @@ const env = require("../environment")
|
|||
const LOCAL_URL = `http://localhost:${env.CLUSTER_PORT || 10000}`
|
||||
const BASE_COMPANY = "Budibase"
|
||||
|
||||
function addTenantToUrl(url, tenantId) {
|
||||
if (env.MULTI_TENANCY) {
|
||||
const char = url.indexOf("?") === -1 ? "?" : "&"
|
||||
url += `${char}tenantId=${tenantId}`
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => {
|
||||
const db = new getGlobalDB(tenantId)
|
||||
const db = getGlobalDB(tenantId)
|
||||
// TODO: use more granular settings in the future if required
|
||||
let settings = (await getScopedConfig(db, { type: Configs.SETTINGS })) || {}
|
||||
if (!settings || !settings.platformUrl) {
|
||||
|
@ -26,7 +34,7 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => {
|
|||
[InternalTemplateBindings.COMPANY]: settings.company || BASE_COMPANY,
|
||||
[InternalTemplateBindings.DOCS_URL]:
|
||||
settings.docsUrl || "https://docs.budibase.com/",
|
||||
[InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl(`${URL}/login`),
|
||||
[InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl(addTenantToUrl(`${URL}/login`, tenantId)),
|
||||
[InternalTemplateBindings.CURRENT_DATE]: new Date().toISOString(),
|
||||
[InternalTemplateBindings.CURRENT_YEAR]: new Date().getFullYear(),
|
||||
}
|
||||
|
@ -35,13 +43,13 @@ exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => {
|
|||
case EmailTemplatePurpose.PASSWORD_RECOVERY:
|
||||
context[InternalTemplateBindings.RESET_CODE] = code
|
||||
context[InternalTemplateBindings.RESET_URL] = checkSlashesInUrl(
|
||||
`${URL}/builder/auth/reset?code=${code}`
|
||||
addTenantToUrl(`${URL}/builder/auth/reset?code=${code}`, tenantId)
|
||||
)
|
||||
break
|
||||
case EmailTemplatePurpose.INVITATION:
|
||||
context[InternalTemplateBindings.INVITE_CODE] = code
|
||||
context[InternalTemplateBindings.INVITE_URL] = checkSlashesInUrl(
|
||||
`${URL}/builder/invite?code=${code}`
|
||||
addTenantToUrl(`${URL}/builder/invite?code=${code}&tenantId=${tenantId}`, tenantId)
|
||||
)
|
||||
break
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue