Updating API keys and changing over system to allow use of builder endpoints when running in cloud.
This commit is contained in:
parent
c49637db47
commit
fca242b9ee
|
@ -1,56 +1,32 @@
|
|||
const fs = require("fs")
|
||||
const { join } = require("../../utilities/centralPath")
|
||||
const readline = require("readline")
|
||||
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
||||
const env = require("../../environment")
|
||||
const ENV_FILE_PATH = "/.env"
|
||||
const builderDB = require("../../db/builder")
|
||||
|
||||
exports.fetch = async function(ctx) {
|
||||
ctx.status = 200
|
||||
ctx.body = {
|
||||
budibase: env.BUDIBASE_API_KEY,
|
||||
userId: env.USERID_API_KEY,
|
||||
try {
|
||||
const mainDoc = await builderDB.getBuilderMainDoc()
|
||||
ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {}
|
||||
} catch (err) {
|
||||
/* istanbul ignore next */
|
||||
ctx.throw(400, err)
|
||||
}
|
||||
}
|
||||
|
||||
exports.update = async function(ctx) {
|
||||
const key = `${ctx.params.key.toUpperCase()}_API_KEY`
|
||||
const key = ctx.params.key
|
||||
const value = ctx.request.body.value
|
||||
|
||||
// set environment variables
|
||||
env._set(key, value)
|
||||
|
||||
// Write to file
|
||||
await updateValues([key, value])
|
||||
|
||||
ctx.status = 200
|
||||
ctx.message = `Updated ${ctx.params.key} API key succesfully.`
|
||||
ctx.body = { [ctx.params.key]: ctx.request.body.value }
|
||||
}
|
||||
|
||||
async function updateValues([key, value]) {
|
||||
let newContent = ""
|
||||
let keyExists = false
|
||||
let envPath = join(budibaseAppsDir(), ENV_FILE_PATH)
|
||||
const readInterface = readline.createInterface({
|
||||
input: fs.createReadStream(envPath),
|
||||
output: process.stdout,
|
||||
console: false,
|
||||
})
|
||||
readInterface.on("line", function(line) {
|
||||
// Mutate lines and change API Key
|
||||
if (line.startsWith(key)) {
|
||||
line = `${key}=${value}`
|
||||
keyExists = true
|
||||
try {
|
||||
const mainDoc = await builderDB.getBuilderMainDoc()
|
||||
if (mainDoc.apiKeys == null) {
|
||||
mainDoc.apiKeys = {}
|
||||
}
|
||||
newContent = `${newContent}\n${line}`
|
||||
})
|
||||
readInterface.on("close", function() {
|
||||
// Write file here
|
||||
if (!keyExists) {
|
||||
// Add API Key if it doesn't exist in the file at all
|
||||
newContent = `${newContent}\n${key}=${value}`
|
||||
mainDoc.apiKeys[key] = value
|
||||
const resp = await builderDB.setBuilderMainDoc(mainDoc)
|
||||
ctx.body = {
|
||||
_id: resp.id,
|
||||
_rev: resp.rev,
|
||||
}
|
||||
fs.writeFileSync(envPath, newContent)
|
||||
})
|
||||
} catch (err) {
|
||||
/* istanbul ignore next */
|
||||
ctx.throw(400, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const CouchDB = require("../../db")
|
||||
const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants")
|
||||
const {
|
||||
getHostingInfo,
|
||||
getDeployedApps,
|
||||
HostingTypes,
|
||||
getAppUrl,
|
||||
} = require("../../utilities/builder/hosting")
|
||||
const { StaticDatabases } = require("../../db/utils")
|
||||
|
||||
exports.fetchInfo = async ctx => {
|
||||
ctx.body = {
|
||||
|
@ -14,17 +14,17 @@ exports.fetchInfo = async ctx => {
|
|||
}
|
||||
|
||||
exports.save = async ctx => {
|
||||
const db = new CouchDB(BUILDER_CONFIG_DB)
|
||||
const db = new CouchDB(StaticDatabases.BUILDER_HOSTING.name)
|
||||
const { type } = ctx.request.body
|
||||
if (type === HostingTypes.CLOUD && ctx.request.body._rev) {
|
||||
ctx.body = await db.remove({
|
||||
...ctx.request.body,
|
||||
_id: HOSTING_DOC,
|
||||
_id: StaticDatabases.BUILDER_HOSTING.baseDoc,
|
||||
})
|
||||
} else {
|
||||
ctx.body = await db.put({
|
||||
...ctx.request.body,
|
||||
_id: HOSTING_DOC,
|
||||
_id: StaticDatabases.BUILDER_HOSTING.baseDoc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
const setup = require("./utilities")
|
||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
describe("/api/keys", () => {
|
||||
let request = setup.getRequest()
|
||||
|
@ -16,12 +13,14 @@ describe("/api/keys", () => {
|
|||
|
||||
describe("fetch", () => {
|
||||
it("should allow fetching", async () => {
|
||||
const res = await request
|
||||
.get(`/api/keys`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body).toBeDefined()
|
||||
await setup.switchToCloudForFunction(async () => {
|
||||
const res = await request
|
||||
.get(`/api/keys`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
it("should check authorization for builder", async () => {
|
||||
|
@ -35,17 +34,18 @@ describe("/api/keys", () => {
|
|||
|
||||
describe("update", () => {
|
||||
it("should allow updating a value", async () => {
|
||||
fs.writeFileSync(path.join(budibaseAppsDir(), ".env"), "TEST_API_KEY=thing")
|
||||
const res = await request
|
||||
.put(`/api/keys/TEST`)
|
||||
.send({
|
||||
value: "test"
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body["TEST"]).toEqual("test")
|
||||
expect(process.env.TEST_API_KEY).toEqual("test")
|
||||
await setup.switchToCloudForFunction(async () => {
|
||||
const res = await request
|
||||
.put(`/api/keys/TEST`)
|
||||
.send({
|
||||
value: "test"
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body._id).toBeDefined()
|
||||
expect(res.body._rev).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
it("should check authorization for builder", async () => {
|
||||
|
|
|
@ -80,8 +80,6 @@ exports.AutoFieldSubTypes = {
|
|||
AUTO_ID: "autoID",
|
||||
}
|
||||
|
||||
exports.BUILDER_CONFIG_DB = "builder-config-db"
|
||||
exports.HOSTING_DOC = "hosting-doc"
|
||||
exports.OBJ_STORE_DIRECTORY = "/app-assets/assets"
|
||||
exports.BaseQueryVerbs = {
|
||||
CREATE: "create",
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
const CouchDB = require("./index")
|
||||
const { StaticDatabases } = require("./utils")
|
||||
const env = require("../environment")
|
||||
|
||||
const SELF_HOST_ERR = "Unable to access builder DB/doc - not self hosted."
|
||||
const BUILDER_DB = StaticDatabases.BUILDER
|
||||
|
||||
/**
|
||||
* This is the builder database, right now this is a single, static database
|
||||
* that is present across the whole system and determines some core functionality
|
||||
* for the builder (e.g. storage of API keys). This has been limited to self hosting
|
||||
* as it doesn't make as much sense against the currently design Cloud system.
|
||||
*/
|
||||
|
||||
exports.getBuilderMainDoc = async () => {
|
||||
if (!env.SELF_HOSTED) {
|
||||
throw SELF_HOST_ERR
|
||||
}
|
||||
const db = new CouchDB(BUILDER_DB.name)
|
||||
try {
|
||||
return await db.get(BUILDER_DB.baseDoc)
|
||||
} catch (err) {
|
||||
// doesn't exist yet, nothing to get
|
||||
return {
|
||||
_id: BUILDER_DB.baseDoc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.setBuilderMainDoc = async doc => {
|
||||
if (!env.SELF_HOSTED) {
|
||||
throw SELF_HOST_ERR
|
||||
}
|
||||
// make sure to override the ID
|
||||
doc._id = BUILDER_DB.baseDoc
|
||||
const db = new CouchDB(BUILDER_DB.name)
|
||||
return db.put(doc)
|
||||
}
|
|
@ -3,6 +3,18 @@ const newid = require("./newid")
|
|||
const UNICODE_MAX = "\ufff0"
|
||||
const SEPARATOR = "_"
|
||||
|
||||
const StaticDatabases = {
|
||||
BUILDER: {
|
||||
name: "builder-db",
|
||||
baseDoc: "builder-doc",
|
||||
},
|
||||
// TODO: needs removed
|
||||
BUILDER_HOSTING: {
|
||||
name: "builder-config-db",
|
||||
baseDoc: "hosting-doc",
|
||||
},
|
||||
}
|
||||
|
||||
const DocumentTypes = {
|
||||
TABLE: "ta",
|
||||
ROW: "ro",
|
||||
|
@ -25,6 +37,7 @@ const ViewNames = {
|
|||
USERS: "ta_users",
|
||||
}
|
||||
|
||||
exports.StaticDatabases = StaticDatabases
|
||||
exports.ViewNames = ViewNames
|
||||
exports.DocumentTypes = DocumentTypes
|
||||
exports.SEPARATOR = SEPARATOR
|
||||
|
|
|
@ -29,15 +29,15 @@ module.exports = {
|
|||
CLOUD: process.env.CLOUD,
|
||||
SELF_HOSTED: process.env.SELF_HOSTED,
|
||||
WORKER_URL: process.env.WORKER_URL,
|
||||
HOSTING_KEY: process.env.HOSTING_KEY,
|
||||
DYNAMO_ENDPOINT: process.env.DYNAMO_ENDPOINT,
|
||||
AWS_REGION: process.env.AWS_REGION,
|
||||
DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL,
|
||||
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
|
||||
// TODO: remove all below - single stack conversion
|
||||
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
|
||||
BUDIBASE_API_KEY: process.env.BUDIBASE_API_KEY,
|
||||
USERID_API_KEY: process.env.USERID_API_KEY,
|
||||
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
|
||||
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
|
||||
LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES,
|
||||
DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL,
|
||||
HOSTING_KEY: process.env.HOSTING_KEY,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
module.exports[key] = value
|
||||
|
|
|
@ -13,18 +13,11 @@ const { AuthTypes } = require("../constants")
|
|||
|
||||
const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER]
|
||||
|
||||
const LOCAL_PASS = new RegExp(["webhooks/trigger"].join("|"))
|
||||
|
||||
function hasResource(ctx) {
|
||||
return ctx.resourceId != null
|
||||
}
|
||||
|
||||
module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
||||
// webhooks can pass locally
|
||||
if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
if (env.CLOUD && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) {
|
||||
// api key header passed by external webhook
|
||||
if (await isAPIKeyValid(ctx.headers["x-api-key"])) {
|
||||
|
@ -41,20 +34,23 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
return ctx.throw(403, "API key invalid")
|
||||
}
|
||||
|
||||
// don't expose builder endpoints in the cloud
|
||||
if (env.CLOUD && permType === PermissionTypes.BUILDER) return
|
||||
|
||||
if (!ctx.user) {
|
||||
return ctx.throw(403, "No user info found")
|
||||
}
|
||||
|
||||
const role = ctx.user.role
|
||||
const isBuilder = role._id === BUILTIN_ROLE_IDS.BUILDER
|
||||
const isAdmin = ADMIN_ROLES.includes(role._id)
|
||||
const isAuthed = ctx.auth.authenticated
|
||||
|
||||
if (permType === PermissionTypes.BUILDER && isBuilder) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const { basePermissions, permissions } = await getUserPermissions(
|
||||
ctx.appId,
|
||||
role._id
|
||||
)
|
||||
const isAdmin = ADMIN_ROLES.includes(role._id)
|
||||
const isAuthed = ctx.auth.authenticated
|
||||
|
||||
// this may need to change in the future, right now only admins
|
||||
// can have access to builder features, this is hard coded into
|
||||
|
|
|
@ -81,9 +81,10 @@ class TestConfiguration {
|
|||
roleId: BUILTIN_ROLE_IDS.BUILDER,
|
||||
}
|
||||
const builderToken = jwt.sign(builderUser, env.JWT_SECRET)
|
||||
const type = env.CLOUD ? "cloud" : "local"
|
||||
const headers = {
|
||||
Accept: "application/json",
|
||||
Cookie: [`budibase:builder:local=${builderToken}`],
|
||||
Cookie: [`budibase:builder:${type}=${builderToken}`],
|
||||
}
|
||||
if (this.appId) {
|
||||
headers["x-budibase-app-id"] = this.appId
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const CouchDB = require("../../db")
|
||||
const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants")
|
||||
const { StaticDatabases } = require("../../db/utils")
|
||||
const fetch = require("node-fetch")
|
||||
const env = require("../../environment")
|
||||
|
||||
|
@ -23,16 +23,16 @@ exports.HostingTypes = {
|
|||
}
|
||||
|
||||
exports.getHostingInfo = async () => {
|
||||
const db = new CouchDB(BUILDER_CONFIG_DB)
|
||||
const db = new CouchDB(StaticDatabases.BUILDER_HOSTING.name)
|
||||
let doc
|
||||
try {
|
||||
doc = await db.get(HOSTING_DOC)
|
||||
doc = await db.get(StaticDatabases.BUILDER_HOSTING.baseDoc)
|
||||
} catch (err) {
|
||||
// don't write this doc, want to be able to update these default props
|
||||
// for our servers with a new release without needing to worry about state of
|
||||
// PouchDB in peoples installations
|
||||
doc = {
|
||||
_id: HOSTING_DOC,
|
||||
_id: StaticDatabases.BUILDER_HOSTING.baseDoc,
|
||||
type: exports.HostingTypes.CLOUD,
|
||||
hostingUrl: PROD_HOSTING_URL,
|
||||
selfHostKey: "",
|
||||
|
|
Loading…
Reference in New Issue