budibase/packages/server/src/middleware/usageQuota.js

77 lines
2.2 KiB
JavaScript
Raw Normal View History

const CouchDB = require("../db")
const environment = require("../environment")
const { apiKeyTable } = require("../db/dynamoClient")
// a normalised month in milliseconds
const QUOTA_RESET = 2592000000
// currently only counting new writes and deletes
const METHOD_MAP = {
POST: 1,
DELETE: -1,
}
const DOMAIN_MAP = {
models: "model",
records: "record",
}
function buildUpdateParams(key, property, usage) {
return {
primary: key,
condition: "#quota.#prop + :usage < #limits.model AND #quotaReset < :now",
expression: "ADD #quota.#prop :usage",
names: {
"#quota": "usageQuota",
"#prop": property,
"#limits": "limits",
"#quotaReset": "quotaReset",
},
values: {
":usage": usage,
":now": Date.now(),
},
}
}
module.exports = async (ctx, next) => {
const db = new CouchDB(ctx.user.instanceId)
const usage = METHOD_MAP[ctx.req.method]
const domainParts = ctx.req.url.split("/")
const property = DOMAIN_MAP[domainParts[domainParts.length - 1]]
if (usage == null || property == null) {
return next()
}
// post request could be a save of a pre-existing entry
if (ctx.request.body && ctx.request.body._id) {
try {
ctx.preExisting = await db.get(ctx.request.body._id)
return next()
} catch (err) {
ctx.throw(404, `${ctx.request.body._id} does not exist`)
return
}
}
// don't try validate in builder
if (!environment.CLOUD) {
return next()
}
try {
await apiKeyTable.update(buildUpdateParams(ctx.apiKey, property, usage))
} catch (err) {
if (err.code !== "ConditionalCheckFailedException") {
// get the API key so we can check it
let apiKey = await apiKeyTable.get({ primary: ctx.apiKey })
// we have infact breached the reset period
if (apiKey && apiKey.quotaReset >= Date.now()) {
// update the quota reset period and reset the values for all properties
apiKey.quotaReset = Date.now() + QUOTA_RESET
for (let prop of Object.keys(apiKey.usageQuota)) {
apiKey.usageQuota[prop] = 0
}
await apiKeyTable.put({ item: apiKey })
}
ctx.throw(403, `Resource limits have been reached`)
}
}
}