Putting together redis lock system.
This commit is contained in:
parent
8f7a3f4d69
commit
f6fbeb4858
|
@ -9,6 +9,7 @@ const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD
|
|||
exports.Databases = {
|
||||
PW_RESETS: "pwReset",
|
||||
INVITATIONS: "invitation",
|
||||
DEV_LOCKS: "devLocks",
|
||||
}
|
||||
|
||||
exports.getRedisOptions = (clustered = false) => {
|
||||
|
|
|
@ -37,6 +37,7 @@ router
|
|||
})
|
||||
)
|
||||
.use(currentApp)
|
||||
.use(development)
|
||||
|
||||
// error handling middleware
|
||||
router.use(async (ctx, next) => {
|
||||
|
|
|
@ -13,6 +13,7 @@ const automations = require("./automations/index")
|
|||
const Sentry = require("@sentry/node")
|
||||
const fileSystem = require("./utilities/fileSystem")
|
||||
const bullboard = require("./automations/bullboard")
|
||||
const redis = require("./utilities/redis")
|
||||
|
||||
const app = new Koa()
|
||||
|
||||
|
@ -84,6 +85,7 @@ module.exports = server.listen(env.PORT || 0, async () => {
|
|||
eventEmitter.emitPort(env.PORT)
|
||||
fileSystem.init()
|
||||
await automations.init()
|
||||
await redis.init()
|
||||
})
|
||||
|
||||
process.on("uncaughtException", err => {
|
||||
|
|
|
@ -17,6 +17,7 @@ const DocumentTypes = {
|
|||
AUTOMATION: "au",
|
||||
LINK: "li",
|
||||
APP: "app",
|
||||
APP_DEV: "app_dev",
|
||||
ROLE: "role",
|
||||
WEBHOOK: "wh",
|
||||
INSTANCE: "inst",
|
||||
|
@ -39,6 +40,8 @@ const SearchIndexes = {
|
|||
ROWS: "rows",
|
||||
}
|
||||
|
||||
exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||
exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR
|
||||
exports.StaticDatabases = StaticDatabases
|
||||
exports.ViewNames = ViewNames
|
||||
exports.InternalTables = InternalTables
|
||||
|
@ -138,9 +141,11 @@ exports.generateUserMetadataID = globalId => {
|
|||
* Breaks up the ID to get the global ID.
|
||||
*/
|
||||
exports.getGlobalIDFromUserMetadataID = id => {
|
||||
return id.split(
|
||||
`${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
|
||||
)[1]
|
||||
const prefix = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
|
||||
if (!id.includes(prefix)) {
|
||||
return id
|
||||
}
|
||||
return id.split(prefix)[1]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,10 +204,13 @@ exports.generateAppID = () => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving apps, this is a utility function for the getDocParams function.
|
||||
* Generates a development app ID from a real app ID.
|
||||
* @returns {string} the dev app ID which can be used for dev database.
|
||||
*/
|
||||
exports.getAppParams = (appId = null, otherProps = {}) => {
|
||||
return getDocParams(DocumentTypes.APP, appId, otherProps)
|
||||
exports.generateDevAppID = appId => {
|
||||
const prefix = `${DocumentTypes.APP}${SEPARATOR}`
|
||||
const uuid = appId.split(prefix)[1]
|
||||
return `${DocumentTypes.APP_DEV}${SEPARATOR}${uuid}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,8 @@ const {
|
|||
doesHaveResourcePermission,
|
||||
doesHaveBasePermission,
|
||||
} = require("../utilities/security/permissions")
|
||||
const { APP_DEV_PREFIX, getGlobalIDFromUserMetadataID } = require("../db/utils")
|
||||
const { doesUserHaveLock, updateLock } = require("../utilities/redis")
|
||||
|
||||
function hasResource(ctx) {
|
||||
return ctx.resourceId != null
|
||||
|
@ -13,6 +15,23 @@ const WEBHOOK_ENDPOINTS = new RegExp(
|
|||
["webhooks/trigger", "webhooks/schema"].join("|")
|
||||
)
|
||||
|
||||
async function checkDevAppLocks(ctx) {
|
||||
const appId = ctx.appId
|
||||
|
||||
// not a development app, don't need to do anything
|
||||
if (!appId.startsWith(APP_DEV_PREFIX)) {
|
||||
return
|
||||
}
|
||||
// get the user which is currently using the dev app
|
||||
const userId = getGlobalIDFromUserMetadataID(ctx.user._id)
|
||||
if (!await doesUserHaveLock(appId, userId)) {
|
||||
ctx.throw(403, "User does not hold app lock.")
|
||||
}
|
||||
|
||||
// they do have lock, update it
|
||||
await updateLock(appId, userId)
|
||||
}
|
||||
|
||||
module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
||||
// webhooks don't need authentication, each webhook unique
|
||||
if (WEBHOOK_ENDPOINTS.test(ctx.request.url)) {
|
||||
|
@ -23,8 +42,14 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
return ctx.throw(403, "No user info found")
|
||||
}
|
||||
|
||||
const isAuthed = ctx.isAuthenticated
|
||||
const builderCall = permType === PermissionTypes.BUILDER
|
||||
|
||||
// this makes sure that builder calls abide by dev locks
|
||||
if (builderCall) {
|
||||
await checkDevAppLocks(ctx)
|
||||
}
|
||||
|
||||
const isAuthed = ctx.isAuthenticated
|
||||
const { basePermissions, permissions } = await getUserPermissions(
|
||||
ctx.appId,
|
||||
ctx.roleId
|
||||
|
@ -35,7 +60,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
let isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
|
||||
if (isBuilder) {
|
||||
return next()
|
||||
} else if (permType === PermissionTypes.BUILDER && !isBuilder) {
|
||||
} else if (builderCall && !isBuilder) {
|
||||
return ctx.throw(403, "Not Authorized")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
const env = require("../environment")
|
||||
const { DocumentTypes, SEPARATOR } = require("../db/utils")
|
||||
const { APP_PREFIX } = require("../db/utils")
|
||||
const CouchDB = require("../db")
|
||||
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
|
||||
|
||||
const BB_CDN = "https://cdn.app.budi.live/assets"
|
||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||
|
||||
exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
const { Client, utils } = require("@budibase/auth").redis
|
||||
|
||||
const APP_DEV_LOCK_SECONDS = 600
|
||||
const DB_NAME = utils.Databases.DEV_LOCKS
|
||||
let devAppClient
|
||||
|
||||
// we init this as we want to keep the connection open all the time
|
||||
// reduces the performance hit
|
||||
exports.init = async () => {
|
||||
devAppClient = await (new Client(DB_NAME)).init()
|
||||
}
|
||||
|
||||
exports.doesUserHaveLock = async (devAppId, userId) => {
|
||||
const value = await devAppClient.get(devAppId)
|
||||
return value == null || value === userId
|
||||
}
|
||||
|
||||
exports.updateLock = async (devAppId, userId) => {
|
||||
await devAppClient.store(devAppId, userId, APP_DEV_LOCK_SECONDS)
|
||||
}
|
||||
|
||||
exports.clearLock = async (devAppId, userId) => {
|
||||
const value = await devAppClient.get(devAppId)
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
if (value !== userId) {
|
||||
throw "User does not hold lock, cannot clear it."
|
||||
}
|
||||
await devAppClient.delete(devAppClient)
|
||||
}
|
Loading…
Reference in New Issue