First version of multi-tenancy, work still to be done.
This commit is contained in:
parent
afd642c60d
commit
912659a8ad
|
@ -1,15 +1,18 @@
|
|||
const { getDB } = require("../db")
|
||||
const { StaticDatabases } = require("../db/utils")
|
||||
const { getGlobalDB } = require("../db/utils")
|
||||
const redis = require("../redis/authRedis")
|
||||
const { lookupTenantId } = require("../utils")
|
||||
|
||||
const EXPIRY_SECONDS = 3600
|
||||
|
||||
exports.getUser = async userId => {
|
||||
exports.getUser = async (userId, tenantId = null) => {
|
||||
if (!tenantId) {
|
||||
tenantId = await lookupTenantId({ userId })
|
||||
}
|
||||
const client = await redis.getUserClient()
|
||||
// try cache
|
||||
let user = await client.get(userId)
|
||||
if (!user) {
|
||||
user = await getDB(StaticDatabases.GLOBAL.name).get(userId)
|
||||
user = await getGlobalDB(tenantId).get(userId)
|
||||
client.store(userId, user, EXPIRY_SECONDS)
|
||||
}
|
||||
return user
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const { newid } = require("../hashing")
|
||||
const Replication = require("./Replication")
|
||||
const { getDB } = require("./index")
|
||||
|
||||
const UNICODE_MAX = "\ufff0"
|
||||
const SEPARATOR = "_"
|
||||
|
@ -18,6 +19,9 @@ exports.StaticDatabases = {
|
|||
// contains information about tenancy and so on
|
||||
PLATFORM_INFO: {
|
||||
name: "global-info",
|
||||
docs: {
|
||||
tenants: "tenants",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -64,6 +68,25 @@ function getDocParams(docType, docId = null, otherProps = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the global DB to connect to in a multi-tenancy system.
|
||||
*/
|
||||
exports.getGlobalDB = tenantId => {
|
||||
const globalName = exports.StaticDatabases.GLOBAL.name
|
||||
// fallback for system pre multi-tenancy
|
||||
if (!tenantId) {
|
||||
return globalName
|
||||
}
|
||||
return getDB(`${tenantId}${SEPARATOR}${globalName}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a koa context this tries to find the correct tenant Global DB.
|
||||
*/
|
||||
exports.getGlobalDBFromCtx = ctx => {
|
||||
return exports.getGlobalDB(ctx.user.tenantId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new workspace ID.
|
||||
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const { DocumentTypes, ViewNames, StaticDatabases } = require("./utils")
|
||||
const { getDB } = require("./index")
|
||||
const { DocumentTypes, ViewNames } = require("./utils")
|
||||
|
||||
function DesignDoc() {
|
||||
return {
|
||||
|
@ -10,8 +9,7 @@ function DesignDoc() {
|
|||
}
|
||||
}
|
||||
|
||||
exports.createUserEmailView = async () => {
|
||||
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||
exports.createUserEmailView = async db => {
|
||||
let designDoc
|
||||
try {
|
||||
designDoc = await db.get("_design/database")
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const passport = require("koa-passport")
|
||||
const LocalStrategy = require("passport-local").Strategy
|
||||
const JwtStrategy = require("passport-jwt").Strategy
|
||||
const { StaticDatabases } = require("./db/utils")
|
||||
const { getGlobalDB } = require("./db/utils")
|
||||
const { jwt, local, authenticated, google, auditLog } = require("./middleware")
|
||||
const { setDB, getDB } = require("./db")
|
||||
const { setDB } = require("./db")
|
||||
const userCache = require("./cache/user")
|
||||
|
||||
// Strategies
|
||||
|
@ -13,7 +13,7 @@ passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
|
|||
passport.serializeUser((user, done) => done(null, user))
|
||||
|
||||
passport.deserializeUser(async (user, done) => {
|
||||
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||
const db = getGlobalDB(user.tenantId)
|
||||
|
||||
try {
|
||||
const user = await db.get(user._id)
|
||||
|
|
|
@ -56,7 +56,7 @@ module.exports = (noAuthPatterns = [], opts) => {
|
|||
error = "No session found"
|
||||
} else {
|
||||
try {
|
||||
user = await getUser(userId)
|
||||
user = await getUser(userId, session.tenantId)
|
||||
delete user.password
|
||||
authenticated = true
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
const env = require("../../environment")
|
||||
const jwt = require("jsonwebtoken")
|
||||
const database = require("../../db")
|
||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||
const {
|
||||
StaticDatabases,
|
||||
generateGlobalUserID,
|
||||
getGlobalDB,
|
||||
ViewNames,
|
||||
} = require("../../db/utils")
|
||||
const { newid } = require("../../hashing")
|
||||
const { createASession } = require("../../security/sessions")
|
||||
const { lookupTenantId } = require("../../utils")
|
||||
|
||||
async function authenticate(token, tokenSecret, profile, done) {
|
||||
// Check the user exists in the instance DB by email
|
||||
const db = database.getDB(StaticDatabases.GLOBAL.name)
|
||||
const userId = generateGlobalUserID(profile.id)
|
||||
const tenantId = await lookupTenantId({ userId })
|
||||
const db = getGlobalDB(tenantId)
|
||||
|
||||
let dbUser
|
||||
|
||||
const userId = generateGlobalUserID(profile.id)
|
||||
|
||||
try {
|
||||
// use the google profile id
|
||||
dbUser = await db.get(userId)
|
||||
|
@ -62,7 +61,7 @@ async function authenticate(token, tokenSecret, profile, done) {
|
|||
|
||||
// authenticate
|
||||
const sessionId = newid()
|
||||
await createASession(dbUser._id, sessionId)
|
||||
await createASession(dbUser._id, { sessionId, tenantId: dbUser.tenantId })
|
||||
|
||||
dbUser.token = jwt.sign(
|
||||
{
|
||||
|
|
|
@ -34,12 +34,14 @@ exports.authenticate = async function (email, password, done) {
|
|||
// authenticate
|
||||
if (await compare(password, dbUser.password)) {
|
||||
const sessionId = newid()
|
||||
await createASession(dbUser._id, sessionId)
|
||||
const tenantId = dbUser.tenantId
|
||||
await createASession(dbUser._id, { sessionId, tenantId })
|
||||
|
||||
dbUser.token = jwt.sign(
|
||||
{
|
||||
userId: dbUser._id,
|
||||
sessionId,
|
||||
tenantId,
|
||||
},
|
||||
env.JWT_SECRET
|
||||
)
|
||||
|
|
|
@ -12,12 +12,13 @@ function makeSessionID(userId, sessionId) {
|
|||
return `${userId}/${sessionId}`
|
||||
}
|
||||
|
||||
exports.createASession = async (userId, sessionId) => {
|
||||
exports.createASession = async (userId, session) => {
|
||||
const client = await redis.getSessionClient()
|
||||
const session = {
|
||||
const sessionId = session.sessionId
|
||||
session = {
|
||||
createdAt: new Date().toISOString(),
|
||||
lastAccessedAt: new Date().toISOString(),
|
||||
sessionId,
|
||||
...session,
|
||||
userId,
|
||||
}
|
||||
await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS)
|
||||
|
|
|
@ -8,6 +8,7 @@ const jwt = require("jsonwebtoken")
|
|||
const { options } = require("./middleware/passport/jwt")
|
||||
const { createUserEmailView } = require("./db/views")
|
||||
const { getDB } = require("./db")
|
||||
const { getGlobalDB } = require("./db/utils")
|
||||
|
||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||
|
||||
|
@ -100,17 +101,31 @@ exports.isClient = ctx => {
|
|||
return ctx.headers["x-budibase-type"] === "client"
|
||||
}
|
||||
|
||||
exports.lookupTenantId = async ({ email, userId }) => {
|
||||
const toQuery = email || userId
|
||||
const db = getDB(StaticDatabases.PLATFORM_INFO.name)
|
||||
const doc = await db.get(toQuery)
|
||||
if (!doc || !doc.tenantId) {
|
||||
throw "Unable to find tenant"
|
||||
}
|
||||
return doc.tenantId
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {string|null} tenantId If tenant ID is known it can be specified
|
||||
* @return {Promise<object|null>}
|
||||
*/
|
||||
exports.getGlobalUserByEmail = async email => {
|
||||
exports.getGlobalUserByEmail = async (email, tenantId = null) => {
|
||||
if (email == null) {
|
||||
throw "Must supply an email address to view"
|
||||
}
|
||||
const db = getDB(StaticDatabases.GLOBAL.name)
|
||||
if (!tenantId) {
|
||||
tenantId = await exports.lookupTenantId({ email })
|
||||
}
|
||||
const db = getGlobalDB(tenantId)
|
||||
try {
|
||||
let users = (
|
||||
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
||||
|
@ -122,7 +137,7 @@ exports.getGlobalUserByEmail = async email => {
|
|||
return users.length <= 1 ? users[0] : users
|
||||
} catch (err) {
|
||||
if (err != null && err.name === "not_found") {
|
||||
await createUserEmailView()
|
||||
await createUserEmailView(db)
|
||||
return exports.getGlobalUserByEmail(email)
|
||||
} else {
|
||||
throw err
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
const CouchDB = require("../../db")
|
||||
const { StaticDatabases } = require("@budibase/auth/db")
|
||||
const { StaticDatabases, getGlobalDBFromCtx } = require("@budibase/auth/db")
|
||||
|
||||
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||
const KEYS_DOC = StaticDatabases.GLOBAL.docs.apiKeys
|
||||
|
||||
async function getBuilderMainDoc() {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
async function getBuilderMainDoc(ctx) {
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
try {
|
||||
return await db.get(KEYS_DOC)
|
||||
} catch (err) {
|
||||
|
@ -16,17 +15,17 @@ async function getBuilderMainDoc() {
|
|||
}
|
||||
}
|
||||
|
||||
async function setBuilderMainDoc(doc) {
|
||||
async function setBuilderMainDoc(ctx, doc) {
|
||||
// make sure to override the ID
|
||||
doc._id = KEYS_DOC
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
return db.put(doc)
|
||||
}
|
||||
|
||||
|
||||
exports.fetch = async function (ctx) {
|
||||
try {
|
||||
const mainDoc = await getBuilderMainDoc()
|
||||
const mainDoc = await getBuilderMainDoc(ctx)
|
||||
ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {}
|
||||
} catch (err) {
|
||||
/* istanbul ignore next */
|
||||
|
@ -39,12 +38,12 @@ exports.update = async function (ctx) {
|
|||
const value = ctx.request.body.value
|
||||
|
||||
try {
|
||||
const mainDoc = await getBuilderMainDoc()
|
||||
const mainDoc = await getBuilderMainDoc(ctx)
|
||||
if (mainDoc.apiKeys == null) {
|
||||
mainDoc.apiKeys = {}
|
||||
}
|
||||
mainDoc.apiKeys[key] = value
|
||||
const resp = await setBuilderMainDoc(mainDoc)
|
||||
const resp = await setBuilderMainDoc(ctx, mainDoc)
|
||||
ctx.body = {
|
||||
_id: resp.id,
|
||||
_rev: resp.rev,
|
||||
|
|
|
@ -295,7 +295,7 @@ exports.delete = async function (ctx) {
|
|||
await deleteApp(ctx.params.appId)
|
||||
}
|
||||
// make sure the app/role doesn't stick around after the app has been deleted
|
||||
await removeAppFromUserRoles(ctx.params.appId)
|
||||
await removeAppFromUserRoles(ctx, ctx.params.appId)
|
||||
|
||||
ctx.status = 200
|
||||
ctx.body = result
|
||||
|
|
|
@ -22,7 +22,7 @@ exports.fetchSelf = async ctx => {
|
|||
const userTable = await db.get(InternalTables.USER_METADATA)
|
||||
const metadata = await db.get(userId)
|
||||
// specifically needs to make sure is enriched
|
||||
ctx.body = await outputProcessing(appId, userTable, {
|
||||
ctx.body = await outputProcessing(ctx, userTable, {
|
||||
...user,
|
||||
...metadata,
|
||||
})
|
||||
|
|
|
@ -159,6 +159,7 @@ exports.create = async function (ctx) {
|
|||
|
||||
automation._id = generateAutomationID()
|
||||
|
||||
automation.tenantId = ctx.user.tenantId
|
||||
automation.type = "automation"
|
||||
automation = cleanAutomationInputs(automation)
|
||||
automation = await checkForWebhooks({
|
||||
|
|
|
@ -17,7 +17,7 @@ function removeGlobalProps(user) {
|
|||
|
||||
exports.fetchMetadata = async function (ctx) {
|
||||
const database = new CouchDB(ctx.appId)
|
||||
const global = await getGlobalUsers(ctx.appId)
|
||||
const global = await getGlobalUsers(ctx, ctx.appId)
|
||||
const metadata = (
|
||||
await database.allDocs(
|
||||
getUserMetadataParams(null, {
|
||||
|
|
|
@ -46,13 +46,13 @@ module.exports.definition = {
|
|||
},
|
||||
}
|
||||
|
||||
module.exports.run = async function ({ inputs }) {
|
||||
module.exports.run = async function ({ inputs, tenantId }) {
|
||||
let { to, from, subject, contents } = inputs
|
||||
if (!contents) {
|
||||
contents = "<h1>No content</h1>"
|
||||
}
|
||||
try {
|
||||
let response = await sendSmtpEmail(to, from, subject, contents)
|
||||
let response = await sendSmtpEmail(tenantId, to, from, subject, contents)
|
||||
return {
|
||||
success: true,
|
||||
response,
|
||||
|
|
|
@ -22,6 +22,7 @@ class Orchestrator {
|
|||
// step zero is never used as the template string is zero indexed for customer facing
|
||||
this._context = { steps: [{}], trigger: triggerOutput }
|
||||
this._automation = automation
|
||||
this._tenantId = automation.tenantId
|
||||
// create an emitter which has the chain count for this automation run in it, so it can block
|
||||
// excessive chaining if required
|
||||
this._emitter = new AutomationEmitter(this._chainCount + 1)
|
||||
|
@ -57,6 +58,7 @@ class Orchestrator {
|
|||
apiKey: automation.apiKey,
|
||||
emitter: this._emitter,
|
||||
context: this._context,
|
||||
tenantId: this._tenantId,
|
||||
})
|
||||
if (step.stepId === FILTER_STEP_ID && !outputs.success) {
|
||||
break
|
||||
|
|
|
@ -60,7 +60,7 @@ async function getLinksForRows(appId, rows) {
|
|||
)
|
||||
}
|
||||
|
||||
async function getFullLinkedDocs(appId, links) {
|
||||
async function getFullLinkedDocs(ctx, appId, links) {
|
||||
// create DBs
|
||||
const db = new CouchDB(appId)
|
||||
const linkedRowIds = links.map(link => link.id)
|
||||
|
@ -71,7 +71,7 @@ async function getFullLinkedDocs(appId, links) {
|
|||
let [users, other] = partition(linked, linkRow =>
|
||||
linkRow._id.startsWith(USER_METDATA_PREFIX)
|
||||
)
|
||||
const globalUsers = await getGlobalUsers(appId, users)
|
||||
const globalUsers = await getGlobalUsers(ctx, appId, users)
|
||||
users = users.map(user => {
|
||||
const globalUser = globalUsers.find(
|
||||
globalUser => globalUser && user._id.includes(globalUser._id)
|
||||
|
@ -166,12 +166,13 @@ exports.attachLinkIDs = async (appId, rows) => {
|
|||
/**
|
||||
* Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row.
|
||||
* This is required for formula fields, this may only be utilised internally (for now).
|
||||
* @param {string} appId The app in which the tables/rows/links exist.
|
||||
* @param {object} ctx The request which is looking for rows.
|
||||
* @param {object} table The table from which the rows originated.
|
||||
* @param {array<object>} rows The rows which are to be enriched.
|
||||
* @return {Promise<*>} returns the rows with all of the enriched relationships on it.
|
||||
*/
|
||||
exports.attachFullLinkedDocs = async (appId, table, rows) => {
|
||||
exports.attachFullLinkedDocs = async (ctx, table, rows) => {
|
||||
const appId = ctx.appId
|
||||
const linkedTableIds = getLinkedTableIDs(table)
|
||||
if (linkedTableIds.length === 0) {
|
||||
return rows
|
||||
|
@ -182,7 +183,7 @@ exports.attachFullLinkedDocs = async (appId, table, rows) => {
|
|||
const links = (await getLinksForRows(appId, rows)).filter(link =>
|
||||
rows.some(row => row._id === link.thisId)
|
||||
)
|
||||
let linked = await getFullLinkedDocs(appId, links)
|
||||
let linked = await getFullLinkedDocs(ctx, appId, links)
|
||||
const linkedTables = []
|
||||
for (let row of rows) {
|
||||
for (let link of links.filter(link => link.thisId === row._id)) {
|
||||
|
|
|
@ -16,14 +16,14 @@ const supertest = require("supertest")
|
|||
const { cleanup } = require("../../utilities/fileSystem")
|
||||
const { Cookies } = require("@budibase/auth").constants
|
||||
const { jwt } = require("@budibase/auth").auth
|
||||
const { StaticDatabases } = require("@budibase/auth/db")
|
||||
const { getGlobalDB } = require("@budibase/auth/db")
|
||||
const { createASession } = require("@budibase/auth/sessions")
|
||||
const { user: userCache } = require("@budibase/auth/cache")
|
||||
const CouchDB = require("../../db")
|
||||
|
||||
const GLOBAL_USER_ID = "us_uuid1"
|
||||
const EMAIL = "babs@babs.com"
|
||||
const PASSWORD = "babs_password"
|
||||
const TENANT_ID = "tenant1"
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(openServer = true) {
|
||||
|
@ -65,7 +65,7 @@ class TestConfiguration {
|
|||
}
|
||||
|
||||
async globalUser(id = GLOBAL_USER_ID, builder = true, roles) {
|
||||
const db = new CouchDB(StaticDatabases.GLOBAL.name)
|
||||
const db = getGlobalDB(TENANT_ID)
|
||||
let existing
|
||||
try {
|
||||
existing = await db.get(id)
|
||||
|
@ -76,6 +76,7 @@ class TestConfiguration {
|
|||
_id: id,
|
||||
...existing,
|
||||
roles: roles || {},
|
||||
tenantId: TENANT_ID,
|
||||
}
|
||||
await createASession(id, "sessionid")
|
||||
if (builder) {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
const CouchDB = require("../db")
|
||||
const {
|
||||
getMultiIDParams,
|
||||
getGlobalIDFromUserMetadataID,
|
||||
StaticDatabases,
|
||||
} = require("../db/utils")
|
||||
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
|
||||
const { getDeployedAppID } = require("@budibase/auth/db")
|
||||
const { getDeployedAppID, getGlobalDBFromCtx } = require("@budibase/auth/db")
|
||||
const { getGlobalUserParams } = require("@budibase/auth/db")
|
||||
const { user: userCache } = require("@budibase/auth/cache")
|
||||
|
||||
|
@ -34,18 +32,18 @@ function processUser(appId, user) {
|
|||
}
|
||||
|
||||
exports.getCachedSelf = async (ctx, appId) => {
|
||||
const user = await userCache.getUser(ctx.user._id)
|
||||
const user = await userCache.getUser(ctx.user._id, ctx.user.tenantId)
|
||||
return processUser(appId, user)
|
||||
}
|
||||
|
||||
exports.getGlobalUser = async (appId, userId) => {
|
||||
const db = CouchDB(StaticDatabases.GLOBAL.name)
|
||||
exports.getGlobalUser = async (ctx, appId, userId) => {
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
let user = await db.get(getGlobalIDFromUserMetadataID(userId))
|
||||
return processUser(appId, user)
|
||||
}
|
||||
|
||||
exports.getGlobalUsers = async (appId = null, users = null) => {
|
||||
const db = CouchDB(StaticDatabases.GLOBAL.name)
|
||||
exports.getGlobalUsers = async (ctx, appId = null, users = null) => {
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
let globalUsers
|
||||
if (users) {
|
||||
const globalIds = users.map(user => getGlobalIDFromUserMetadataID(user._id))
|
||||
|
|
|
@ -193,13 +193,14 @@ exports.inputProcessing = (user = {}, table, row) => {
|
|||
/**
|
||||
* This function enriches the input rows with anything they are supposed to contain, for example
|
||||
* link records or attachment links.
|
||||
* @param {string} appId the ID of the application for which rows are being enriched.
|
||||
* @param {object} ctx the request which is looking for enriched rows.
|
||||
* @param {object} table the table from which these rows came from originally, this is used to determine
|
||||
* the schema of the rows and then enrich.
|
||||
* @param {object[]} rows the rows which are to be enriched.
|
||||
* @returns {object[]} the enriched rows will be returned.
|
||||
*/
|
||||
exports.outputProcessing = async (appId, table, rows) => {
|
||||
exports.outputProcessing = async (ctx, table, rows) => {
|
||||
const appId = ctx.appId
|
||||
let wasArray = true
|
||||
if (!(rows instanceof Array)) {
|
||||
rows = [rows]
|
||||
|
|
|
@ -3,7 +3,7 @@ const { InternalTables } = require("../db/utils")
|
|||
const { getGlobalUser } = require("../utilities/global")
|
||||
|
||||
exports.getFullUser = async (ctx, userId) => {
|
||||
const global = await getGlobalUser(ctx.appId, userId)
|
||||
const global = await getGlobalUser(ctx, ctx.appId, userId)
|
||||
let metadata
|
||||
try {
|
||||
// this will throw an error if the db doesn't exist, or there is no appId
|
||||
|
|
|
@ -4,11 +4,11 @@ const { checkSlashesInUrl } = require("./index")
|
|||
const { getDeployedAppID } = require("@budibase/auth/db")
|
||||
const { updateAppRole, getGlobalUser } = require("./global")
|
||||
|
||||
function request(ctx, request, noApiKey) {
|
||||
function request(ctx, request) {
|
||||
if (!request.headers) {
|
||||
request.headers = {}
|
||||
}
|
||||
if (!noApiKey) {
|
||||
if (!ctx) {
|
||||
request.headers["x-budibase-api-key"] = env.INTERNAL_API_KEY
|
||||
}
|
||||
if (request.body && Object.keys(request.body).length > 0) {
|
||||
|
@ -28,12 +28,13 @@ function request(ctx, request, noApiKey) {
|
|||
|
||||
exports.request = request
|
||||
|
||||
exports.sendSmtpEmail = async (to, from, subject, contents) => {
|
||||
exports.sendSmtpEmail = async (tenantId, to, from, subject, contents) => {
|
||||
const response = await fetch(
|
||||
checkSlashesInUrl(env.WORKER_URL + `/api/admin/email/send`),
|
||||
request(null, {
|
||||
method: "POST",
|
||||
body: {
|
||||
tenantId,
|
||||
email: to,
|
||||
from,
|
||||
contents,
|
||||
|
@ -77,7 +78,7 @@ exports.getGlobalSelf = async (ctx, appId = null) => {
|
|||
const response = await fetch(
|
||||
checkSlashesInUrl(env.WORKER_URL + endpoint),
|
||||
// we don't want to use API key when getting self
|
||||
request(ctx, { method: "GET" }, true)
|
||||
request(ctx, { method: "GET" })
|
||||
)
|
||||
if (response.status !== 200) {
|
||||
ctx.throw(400, "Unable to get self globally.")
|
||||
|
@ -97,7 +98,7 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => {
|
|||
user = await exports.getGlobalSelf(ctx)
|
||||
endpoint = `/api/admin/users/self`
|
||||
} else {
|
||||
user = await getGlobalUser(appId, userId)
|
||||
user = await getGlobalUser(ctx, appId, userId)
|
||||
body._id = userId
|
||||
endpoint = `/api/admin/users`
|
||||
}
|
||||
|
@ -121,11 +122,11 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => {
|
|||
return response.json()
|
||||
}
|
||||
|
||||
exports.removeAppFromUserRoles = async appId => {
|
||||
exports.removeAppFromUserRoles = async (ctx, appId) => {
|
||||
const deployedAppId = getDeployedAppID(appId)
|
||||
const response = await fetch(
|
||||
checkSlashesInUrl(env.WORKER_URL + `/api/admin/roles/${deployedAppId}`),
|
||||
request(null, {
|
||||
request(ctx, {
|
||||
method: "DELETE",
|
||||
})
|
||||
)
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
const authPkg = require("@budibase/auth")
|
||||
const { google } = require("@budibase/auth/src/middleware")
|
||||
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
||||
const CouchDB = require("../../../db")
|
||||
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
||||
const { clearCookie, getGlobalUserByEmail, hash } = authPkg.utils
|
||||
const { Cookies } = authPkg.constants
|
||||
const { passport } = authPkg.auth
|
||||
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
||||
|
||||
const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name
|
||||
const { getGlobalDB } = authPkg.db
|
||||
|
||||
async function authInternal(ctx, user, err = null) {
|
||||
if (err) {
|
||||
|
@ -46,7 +44,8 @@ exports.authenticate = async (ctx, next) => {
|
|||
*/
|
||||
exports.reset = async ctx => {
|
||||
const { email } = ctx.request.body
|
||||
const configured = await isEmailConfigured()
|
||||
const tenantId = ctx.params.tenantId
|
||||
const configured = await isEmailConfigured(tenantId)
|
||||
if (!configured) {
|
||||
ctx.throw(
|
||||
400,
|
||||
|
@ -54,10 +53,10 @@ exports.reset = async ctx => {
|
|||
)
|
||||
}
|
||||
try {
|
||||
const user = await getGlobalUserByEmail(email)
|
||||
const user = await getGlobalUserByEmail(email, tenantId)
|
||||
// only if user exists, don't error though if they don't
|
||||
if (user) {
|
||||
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
||||
await sendEmail(tenantId, email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
||||
user,
|
||||
subject: "{{ company }} platform password reset",
|
||||
})
|
||||
|
@ -77,7 +76,7 @@ exports.resetUpdate = async ctx => {
|
|||
const { resetCode, password } = ctx.request.body
|
||||
try {
|
||||
const userId = await checkResetPasswordCode(resetCode)
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = new getGlobalDB(ctx.params.tenantId)
|
||||
const user = await db.get(userId)
|
||||
user.password = await hash(password)
|
||||
await db.put(user)
|
||||
|
@ -99,7 +98,7 @@ exports.logout = async ctx => {
|
|||
* On a successful login, you will be redirected to the googleAuth callback route.
|
||||
*/
|
||||
exports.googlePreAuth = async (ctx, next) => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDB(ctx.params.tenantId)
|
||||
const config = await authPkg.db.getScopedConfig(db, {
|
||||
type: Configs.GOOGLE,
|
||||
workspace: ctx.query.workspace,
|
||||
|
@ -112,7 +111,7 @@ exports.googlePreAuth = async (ctx, next) => {
|
|||
}
|
||||
|
||||
exports.googleAuth = async (ctx, next) => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDB(ctx.params.tenantId)
|
||||
|
||||
const config = await authPkg.db.getScopedConfig(db, {
|
||||
type: Configs.GOOGLE,
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
const CouchDB = require("../../../db")
|
||||
const {
|
||||
generateConfigID,
|
||||
StaticDatabases,
|
||||
getConfigParams,
|
||||
getGlobalUserParams,
|
||||
getScopedFullConfig,
|
||||
} = require("@budibase/auth").db
|
||||
getGlobalDBFromCtx,
|
||||
getAllApps,
|
||||
} = require("@budibase/auth/db")
|
||||
const { Configs } = require("../../../constants")
|
||||
const email = require("../../../utilities/email")
|
||||
const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore
|
||||
|
||||
const APP_PREFIX = "app_"
|
||||
|
||||
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||
|
||||
exports.save = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const { type, workspace, user, config } = ctx.request.body
|
||||
|
||||
// Config does not exist yet
|
||||
|
@ -51,7 +48,7 @@ exports.save = async function (ctx) {
|
|||
}
|
||||
|
||||
exports.fetch = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const response = await db.allDocs(
|
||||
getConfigParams(
|
||||
{ type: ctx.params.type },
|
||||
|
@ -68,7 +65,7 @@ exports.fetch = async function (ctx) {
|
|||
* The hierarchy is type -> workspace -> user.
|
||||
*/
|
||||
exports.find = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
|
||||
const { userId, workspaceId } = ctx.query
|
||||
if (workspaceId && userId) {
|
||||
|
@ -101,7 +98,7 @@ exports.find = async function (ctx) {
|
|||
}
|
||||
|
||||
exports.publicSettings = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
try {
|
||||
// Find the config with the most granular scope based on context
|
||||
const config = await getScopedFullConfig(db, {
|
||||
|
@ -139,7 +136,7 @@ exports.upload = async function (ctx) {
|
|||
|
||||
// add to configuration structure
|
||||
// TODO: right now this only does a global level
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
let cfgStructure = await getScopedFullConfig(db, { type })
|
||||
if (!cfgStructure) {
|
||||
cfgStructure = {
|
||||
|
@ -159,7 +156,7 @@ exports.upload = async function (ctx) {
|
|||
}
|
||||
|
||||
exports.destroy = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const { id, rev } = ctx.params
|
||||
|
||||
try {
|
||||
|
@ -171,14 +168,13 @@ exports.destroy = async function (ctx) {
|
|||
}
|
||||
|
||||
exports.configChecklist = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
|
||||
try {
|
||||
// TODO: Watch get started video
|
||||
|
||||
// Apps exist
|
||||
let allDbs = await CouchDB.allDbs()
|
||||
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
|
||||
const apps = (await getAllApps({ CouchDB }))
|
||||
|
||||
// They have set up SMTP
|
||||
const smtpConfig = await getScopedFullConfig(db, {
|
||||
|
@ -199,7 +195,7 @@ exports.configChecklist = async function (ctx) {
|
|||
const adminUser = users.rows.some(row => row.doc.admin)
|
||||
|
||||
ctx.body = {
|
||||
apps: appDbNames.length,
|
||||
apps: apps.length,
|
||||
smtp: !!smtpConfig,
|
||||
adminUser,
|
||||
oauth: !!oauthConfig,
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
const { sendEmail } = require("../../../utilities/email")
|
||||
const CouchDB = require("../../../db")
|
||||
const authPkg = require("@budibase/auth")
|
||||
|
||||
const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name
|
||||
const { getGlobalDBFromCtx } = require("@budibase/auth/db")
|
||||
|
||||
exports.sendEmail = async ctx => {
|
||||
const { workspaceId, email, userId, purpose, contents, from, subject } =
|
||||
let { tenantId, workspaceId, email, userId, purpose, contents, from, subject } =
|
||||
ctx.request.body
|
||||
let user
|
||||
if (userId) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
user = await db.get(userId)
|
||||
}
|
||||
const response = await sendEmail(email, purpose, {
|
||||
if (!tenantId && ctx.user.tenantId) {
|
||||
tenantId = ctx.user.tenantId
|
||||
}
|
||||
const response = await sendEmail(tenantId, email, purpose, {
|
||||
workspaceId,
|
||||
user,
|
||||
contents,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const { generateTemplateID, StaticDatabases } = require("@budibase/auth").db
|
||||
const CouchDB = require("../../../db")
|
||||
const { generateTemplateID, getGlobalDBFromCtx } = require("@budibase/auth/db")
|
||||
const {
|
||||
TemplateMetadata,
|
||||
TemplateBindings,
|
||||
|
@ -7,10 +6,8 @@ const {
|
|||
} = require("../../../constants")
|
||||
const { getTemplates } = require("../../../constants/templates")
|
||||
|
||||
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||
|
||||
exports.save = async ctx => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
let template = ctx.request.body
|
||||
if (!template.ownerId) {
|
||||
template.ownerId = GLOBAL_OWNER
|
||||
|
@ -42,29 +39,29 @@ exports.definitions = async ctx => {
|
|||
}
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
ctx.body = await getTemplates()
|
||||
ctx.body = await getTemplates(ctx)
|
||||
}
|
||||
|
||||
exports.fetchByType = async ctx => {
|
||||
ctx.body = await getTemplates({
|
||||
ctx.body = await getTemplates(ctx, {
|
||||
type: ctx.params.type,
|
||||
})
|
||||
}
|
||||
|
||||
exports.fetchByOwner = async ctx => {
|
||||
ctx.body = await getTemplates({
|
||||
ctx.body = await getTemplates(ctx, {
|
||||
ownerId: ctx.params.ownerId,
|
||||
})
|
||||
}
|
||||
|
||||
exports.find = async ctx => {
|
||||
ctx.body = await getTemplates({
|
||||
ctx.body = await getTemplates(ctx, {
|
||||
id: ctx.params.id,
|
||||
})
|
||||
}
|
||||
|
||||
exports.destroy = async ctx => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
await db.remove(ctx.params.id, ctx.params.rev)
|
||||
ctx.message = `Template ${ctx.params.id} deleted.`
|
||||
ctx.status = 200
|
||||
|
|
|
@ -1,17 +1,43 @@
|
|||
const CouchDB = require("../../../db")
|
||||
const { generateGlobalUserID, getGlobalUserParams, StaticDatabases } =
|
||||
require("@budibase/auth").db
|
||||
const { hash, getGlobalUserByEmail } = require("@budibase/auth").utils
|
||||
const {
|
||||
generateGlobalUserID,
|
||||
getGlobalUserParams,
|
||||
getGlobalDB,
|
||||
getGlobalDBFromCtx,
|
||||
StaticDatabases
|
||||
} = require("@budibase/auth/db")
|
||||
const { hash, getGlobalUserByEmail, newid } = require("@budibase/auth").utils
|
||||
const { UserStatus, EmailTemplatePurpose } = require("../../../constants")
|
||||
const { checkInviteCode } = require("../../../utilities/redis")
|
||||
const { sendEmail } = require("../../../utilities/email")
|
||||
const { user: userCache } = require("@budibase/auth/cache")
|
||||
const { invalidateSessions } = require("@budibase/auth/sessions")
|
||||
const CouchDB = require("../../../db")
|
||||
|
||||
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||
const tenantDocId = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
|
||||
async function allUsers() {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
async function noTenantsExist() {
|
||||
const db = new CouchDB(PLATFORM_INFO_DB)
|
||||
const tenants = await db.get(tenantDocId)
|
||||
return !tenants || !tenants.tenantIds || tenants.tenantIds.length === 0
|
||||
}
|
||||
|
||||
async function tryAddTenant(tenantId) {
|
||||
const db = new CouchDB(PLATFORM_INFO_DB)
|
||||
let tenants = await db.get(tenantDocId)
|
||||
if (!tenants || !Array.isArray(tenants.tenantIds)) {
|
||||
tenants = {
|
||||
tenantIds: [],
|
||||
}
|
||||
}
|
||||
if (tenants.tenantIds.indexOf(tenantId) === -1) {
|
||||
tenants.tenantIds.push(tenantId)
|
||||
await db.put(tenants)
|
||||
}
|
||||
}
|
||||
|
||||
async function allUsers(ctx) {
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const response = await db.allDocs(
|
||||
getGlobalUserParams(null, {
|
||||
include_docs: true,
|
||||
|
@ -20,16 +46,19 @@ async function allUsers() {
|
|||
return response.rows.map(row => row.doc)
|
||||
}
|
||||
|
||||
exports.save = async ctx => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const { email, password, _id } = ctx.request.body
|
||||
|
||||
async function saveUser(user, tenantId) {
|
||||
if (!tenantId) {
|
||||
throw "No tenancy specified."
|
||||
}
|
||||
const db = getGlobalDB(tenantId)
|
||||
await tryAddTenant(tenantId)
|
||||
const { email, password, _id } = user
|
||||
// make sure another user isn't using the same email
|
||||
let dbUser
|
||||
if (email) {
|
||||
dbUser = await getGlobalUserByEmail(email)
|
||||
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
|
||||
ctx.throw(400, "Email address already in use.")
|
||||
throw "Email address already in use."
|
||||
}
|
||||
} else {
|
||||
dbUser = await db.get(_id)
|
||||
|
@ -42,14 +71,15 @@ exports.save = async ctx => {
|
|||
} else if (dbUser) {
|
||||
hashedPassword = dbUser.password
|
||||
} else {
|
||||
ctx.throw(400, "Password must be specified.")
|
||||
throw "Password must be specified."
|
||||
}
|
||||
|
||||
let user = {
|
||||
user = {
|
||||
...dbUser,
|
||||
...ctx.request.body,
|
||||
...user,
|
||||
_id: _id || generateGlobalUserID(),
|
||||
password: hashedPassword,
|
||||
tenantId,
|
||||
}
|
||||
// make sure the roles object is always present
|
||||
if (!user.roles) {
|
||||
|
@ -65,34 +95,37 @@ exports.save = async ctx => {
|
|||
...user,
|
||||
})
|
||||
await userCache.invalidateUser(response.id)
|
||||
ctx.body = {
|
||||
return {
|
||||
_id: response.id,
|
||||
_rev: response.rev,
|
||||
email,
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.status === 409) {
|
||||
ctx.throw(400, "User exists already")
|
||||
throw "User exists already"
|
||||
} else {
|
||||
ctx.throw(err.status, err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.adminUser = async ctx => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const response = await db.allDocs(
|
||||
getGlobalUserParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
exports.save = async ctx => {
|
||||
// this always stores the user into the requesting users tenancy
|
||||
const tenantId = ctx.user.tenantId
|
||||
try {
|
||||
ctx.body = await saveUser(ctx.request.body, tenantId)
|
||||
} catch (err) {
|
||||
ctx.throw(err.status || 400, err)
|
||||
}
|
||||
}
|
||||
|
||||
if (response.rows.some(row => row.doc.admin)) {
|
||||
exports.adminUser = async ctx => {
|
||||
if (!await noTenantsExist()) {
|
||||
ctx.throw(403, "You cannot initialise once an admin user has been created.")
|
||||
}
|
||||
|
||||
const { email, password } = ctx.request.body
|
||||
ctx.request.body = {
|
||||
const user = {
|
||||
email: email,
|
||||
password: password,
|
||||
roles: {},
|
||||
|
@ -103,11 +136,15 @@ exports.adminUser = async ctx => {
|
|||
global: true,
|
||||
},
|
||||
}
|
||||
await exports.save(ctx)
|
||||
try {
|
||||
ctx.body = await saveUser(user, newid())
|
||||
} catch (err) {
|
||||
ctx.throw(err.status || 400, err)
|
||||
}
|
||||
}
|
||||
|
||||
exports.destroy = async ctx => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const dbUser = await db.get(ctx.params.id)
|
||||
await db.remove(dbUser._id, dbUser._rev)
|
||||
await userCache.invalidateUser(dbUser._id)
|
||||
|
@ -119,7 +156,7 @@ exports.destroy = async ctx => {
|
|||
|
||||
exports.removeAppRole = async ctx => {
|
||||
const { appId } = ctx.params
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const users = await allUsers()
|
||||
const bulk = []
|
||||
const cacheInvalidations = []
|
||||
|
@ -149,7 +186,7 @@ exports.getSelf = async ctx => {
|
|||
}
|
||||
|
||||
exports.updateSelf = async ctx => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const user = await db.get(ctx.user._id)
|
||||
if (ctx.request.body.password) {
|
||||
ctx.request.body.password = await hash(ctx.request.body.password)
|
||||
|
@ -170,7 +207,7 @@ exports.updateSelf = async ctx => {
|
|||
|
||||
// called internally by app server user fetch
|
||||
exports.fetch = async ctx => {
|
||||
const users = await allUsers()
|
||||
const users = await allUsers(ctx)
|
||||
// user hashed password shouldn't ever be returned
|
||||
for (let user of users) {
|
||||
if (user) {
|
||||
|
@ -182,7 +219,7 @@ exports.fetch = async ctx => {
|
|||
|
||||
// called internally by app server user find
|
||||
exports.find = async ctx => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
let user
|
||||
try {
|
||||
user = await db.get(ctx.params.id)
|
||||
|
@ -198,11 +235,12 @@ exports.find = async ctx => {
|
|||
|
||||
exports.invite = async ctx => {
|
||||
const { email, userInfo } = ctx.request.body
|
||||
const existing = await getGlobalUserByEmail(email)
|
||||
const tenantId = ctx.user.tenantId
|
||||
const existing = await getGlobalUserByEmail(email, tenantId)
|
||||
if (existing) {
|
||||
ctx.throw(400, "Email address already in use.")
|
||||
}
|
||||
await sendEmail(email, EmailTemplatePurpose.INVITATION, {
|
||||
await sendEmail(tenantId, email, EmailTemplatePurpose.INVITATION, {
|
||||
subject: "{{ company }} platform invitation",
|
||||
info: userInfo,
|
||||
})
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
const CouchDB = require("../../../db")
|
||||
const { getWorkspaceParams, generateWorkspaceID, StaticDatabases } =
|
||||
require("@budibase/auth").db
|
||||
|
||||
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||
const { getWorkspaceParams, generateWorkspaceID, getGlobalDBFromCtx } =
|
||||
require("@budibase/auth/db")
|
||||
|
||||
exports.save = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const workspaceDoc = ctx.request.body
|
||||
|
||||
// workspace does not exist yet
|
||||
|
@ -25,7 +22,7 @@ exports.save = async function (ctx) {
|
|||
}
|
||||
|
||||
exports.fetch = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const response = await db.allDocs(
|
||||
getWorkspaceParams(undefined, {
|
||||
include_docs: true,
|
||||
|
@ -35,7 +32,7 @@ exports.fetch = async function (ctx) {
|
|||
}
|
||||
|
||||
exports.find = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
try {
|
||||
ctx.body = await db.get(ctx.params.id)
|
||||
} catch (err) {
|
||||
|
@ -44,7 +41,7 @@ exports.find = async function (ctx) {
|
|||
}
|
||||
|
||||
exports.destroy = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const { id, rev } = ctx.params
|
||||
|
||||
try {
|
||||
|
|
|
@ -30,14 +30,14 @@ function buildResetUpdateValidation() {
|
|||
|
||||
router
|
||||
.post("/api/admin/auth", buildAuthValidation(), authController.authenticate)
|
||||
.post("/api/admin/auth/reset", buildResetValidation(), authController.reset)
|
||||
.post("/api/admin/auth/:tenantId/reset", buildResetValidation(), authController.reset)
|
||||
.post(
|
||||
"/api/admin/auth/reset/update",
|
||||
"/api/admin/auth/:tenantId/reset/update",
|
||||
buildResetUpdateValidation(),
|
||||
authController.resetUpdate
|
||||
)
|
||||
.post("/api/admin/auth/logout", authController.logout)
|
||||
.get("/api/admin/auth/google", authController.googlePreAuth)
|
||||
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||
.get("/api/admin/auth/:tenantId/google", authController.googlePreAuth)
|
||||
.get("/api/admin/auth/:tenantId/google/callback", authController.googleAuth)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -6,8 +6,7 @@ const {
|
|||
GLOBAL_OWNER,
|
||||
} = require("../index")
|
||||
const { join } = require("path")
|
||||
const CouchDB = require("../../db")
|
||||
const { getTemplateParams, StaticDatabases } = require("@budibase/auth").db
|
||||
const { getTemplateParams, getGlobalDBFromCtx } = require("@budibase/auth/db")
|
||||
|
||||
exports.EmailTemplates = {
|
||||
[EmailTemplatePurpose.PASSWORD_RECOVERY]: readStaticFile(
|
||||
|
@ -49,8 +48,8 @@ exports.addBaseTemplates = (templates, type = null) => {
|
|||
return templates
|
||||
}
|
||||
|
||||
exports.getTemplates = async ({ ownerId, type, id } = {}) => {
|
||||
const db = new CouchDB(StaticDatabases.GLOBAL.name)
|
||||
exports.getTemplates = async (ctx, { ownerId, type, id } = {}) => {
|
||||
const db = getGlobalDBFromCtx(ctx)
|
||||
const response = await db.allDocs(
|
||||
getTemplateParams(ownerId || GLOBAL_OWNER, id, {
|
||||
include_docs: true,
|
||||
|
@ -67,7 +66,7 @@ exports.getTemplates = async ({ ownerId, type, id } = {}) => {
|
|||
return exports.addBaseTemplates(templates, type)
|
||||
}
|
||||
|
||||
exports.getTemplateByPurpose = async (type, purpose) => {
|
||||
const templates = await exports.getTemplates({ type })
|
||||
exports.getTemplateByPurpose = async (ctx, type, purpose) => {
|
||||
const templates = await exports.getTemplates(ctx, { type })
|
||||
return templates.find(template => template.purpose === purpose)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const nodemailer = require("nodemailer")
|
||||
const CouchDB = require("../db")
|
||||
const { StaticDatabases, getScopedConfig } = require("@budibase/auth").db
|
||||
const { getGlobalDB, getScopedConfig } = require("@budibase/auth/db")
|
||||
const { EmailTemplatePurpose, TemplateTypes, Configs } = require("../constants")
|
||||
const { getTemplateByPurpose } = require("../constants/templates")
|
||||
const { getSettingsTemplateContext } = require("./templates")
|
||||
|
@ -8,7 +7,6 @@ const { processString } = require("@budibase/string-templates")
|
|||
const { getResetPasswordCode, getInviteCode } = require("../utilities/redis")
|
||||
|
||||
const TEST_MODE = false
|
||||
const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
||||
const TYPE = TemplateTypes.EMAIL
|
||||
|
||||
const FULL_EMAIL_PURPOSES = [
|
||||
|
@ -116,15 +114,14 @@ async function getSmtpConfiguration(db, workspaceId = null) {
|
|||
|
||||
/**
|
||||
* Checks if a SMTP config exists based on passed in parameters.
|
||||
* @param workspaceId
|
||||
* @return {Promise<boolean>} returns true if there is a configuration that can be used.
|
||||
*/
|
||||
exports.isEmailConfigured = async (workspaceId = null) => {
|
||||
exports.isEmailConfigured = async (tenantId, workspaceId = null) => {
|
||||
// when "testing" simply return true
|
||||
if (TEST_MODE) {
|
||||
return true
|
||||
}
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = getGlobalDB(tenantId)
|
||||
const config = await getSmtpConfiguration(db, workspaceId)
|
||||
return config != null
|
||||
}
|
||||
|
@ -132,6 +129,7 @@ exports.isEmailConfigured = async (workspaceId = null) => {
|
|||
/**
|
||||
* Given an email address and an email purpose this will retrieve the SMTP configuration and
|
||||
* send an email using it.
|
||||
* @param {string} tenantId The tenant which is sending them email.
|
||||
* @param {string} email The email address to send to.
|
||||
* @param {string} purpose The purpose of the email being sent (e.g. reset password).
|
||||
* @param {string|undefined} workspaceId If finer grain controls being used then this will lookup config for workspace.
|
||||
|
@ -144,11 +142,12 @@ exports.isEmailConfigured = async (workspaceId = null) => {
|
|||
* nodemailer response.
|
||||
*/
|
||||
exports.sendEmail = async (
|
||||
tenantId,
|
||||
email,
|
||||
purpose,
|
||||
{ workspaceId, user, from, contents, subject, info } = {}
|
||||
) => {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const db = new getGlobalDB(tenantId)
|
||||
let config = (await getSmtpConfiguration(db, workspaceId)) || {}
|
||||
if (Object.keys(config).length === 0 && !TEST_MODE) {
|
||||
throw "Unable to find SMTP configuration."
|
||||
|
@ -156,7 +155,7 @@ exports.sendEmail = async (
|
|||
const transport = createSMTPTransport(config)
|
||||
// if there is a link code needed this will retrieve it
|
||||
const code = await getLinkCode(purpose, email, user, info)
|
||||
const context = await getSettingsTemplateContext(purpose, code)
|
||||
const context = await getSettingsTemplateContext(tenantId, purpose, code)
|
||||
const message = {
|
||||
from: from || config.from,
|
||||
to: email,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const CouchDB = require("../db")
|
||||
const { getScopedConfig, StaticDatabases } = require("@budibase/auth").db
|
||||
const { getScopedConfig, getGlobalDB } = require("@budibase/auth/db")
|
||||
const {
|
||||
Configs,
|
||||
InternalTemplateBindings,
|
||||
|
@ -12,8 +11,8 @@ const env = require("../environment")
|
|||
const LOCAL_URL = `http://localhost:${env.CLUSTER_PORT || 10000}`
|
||||
const BASE_COMPANY = "Budibase"
|
||||
|
||||
exports.getSettingsTemplateContext = async (purpose, code = null) => {
|
||||
const db = new CouchDB(StaticDatabases.GLOBAL.name)
|
||||
exports.getSettingsTemplateContext = async (tenantId, purpose, code = null) => {
|
||||
const db = new getGlobalDB(tenantId)
|
||||
// TODO: use more granular settings in the future if required
|
||||
let settings = (await getScopedConfig(db, { type: Configs.SETTINGS })) || {}
|
||||
if (!settings || !settings.platformUrl) {
|
||||
|
|
Loading…
Reference in New Issue