Fixing test cases and bugs that they raised.

This commit is contained in:
mike12345567 2021-07-22 22:36:16 +01:00
parent 24012c2fba
commit 313302cae2
16 changed files with 86 additions and 37 deletions

View File

@ -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)
}
/**

View File

@ -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) {

View File

@ -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,

View File

@ -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",

View File

@ -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)

View File

@ -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)

View File

@ -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,
})
}

View File

@ -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) {

View File

@ -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()
})

View File

@ -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/)

View File

@ -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()
})

View File

@ -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

View File

@ -0,0 +1 @@
exports.TENANT_ID = "default"

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}