From 80c7df9daeebe78e1000568de9fc6fe85f57fe2d Mon Sep 17 00:00:00 2001 From: michael shanks Date: Tue, 25 Jun 2019 22:48:22 +0100 Subject: [PATCH] add plugins to master app --- packages/core | 2 +- packages/server/app.js | 6 ++ .../server/appPackages/master/plugins.json | 1 + packages/server/config.js | 5 +- packages/server/config.sample.js | 11 ++- packages/server/initialise/createMasterDb.js | 8 ++- .../initialise/initialiseRuntimePackages.js | 69 +++++++++++++++++++ packages/server/middleware/routers.js | 6 +- packages/server/package.json | 3 +- packages/server/tests/authenticate.js | 31 ++++++++- packages/server/tests/testApp.js | 31 ++++++++- packages/server/utilities/budibaseApi.js | 44 +++++++++--- packages/server/utilities/createAppPackage.js | 31 +++++++++ .../server/utilities/masterAppInternal.js | 31 +++++++-- packages/server/yarn.lock | 5 ++ 15 files changed, 255 insertions(+), 29 deletions(-) create mode 100644 packages/server/appPackages/master/plugins.json create mode 100644 packages/server/initialise/initialiseRuntimePackages.js create mode 100644 packages/server/utilities/createAppPackage.js diff --git a/packages/core b/packages/core index b372ad740..feb2f3412 160000 --- a/packages/core +++ b/packages/core @@ -1 +1 @@ -Subproject commit b372ad7403fa12f92670ef38e7576bc5795006e9 +Subproject commit feb2f3412a0e3748d6509a3693b1f38c448bd743 diff --git a/packages/server/app.js b/packages/server/app.js index db411977d..6afae2930 100644 --- a/packages/server/app.js +++ b/packages/server/app.js @@ -3,11 +3,17 @@ const app = new Koa(); const getMasterAppInternal = require("./utilities/masterAppInternal"); const router = require("./middleware/routers"); const bodyParser = require('koa-bodyparser'); +const initialiseRuntimeApps = require("./initialise/initialiseRuntimePackages"); module.exports = async (config) => { app.keys = config.keys; app.context.master = await getMasterAppInternal(config); + app.context.getAppPackage = await initialiseRuntimeApps( + config, + app.context.master, + config.latestAppsPath + ) app.use(bodyParser()); app.use(router(config, app).routes()); return app.listen(); diff --git a/packages/server/appPackages/master/plugins.json b/packages/server/appPackages/master/plugins.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/packages/server/appPackages/master/plugins.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/server/config.js b/packages/server/config.js index 497be7fea..693539def 100644 --- a/packages/server/config.js +++ b/packages/server/config.js @@ -6,5 +6,8 @@ module.exports = () => ({ rootPath: "./.data" }, keys: ["secret1", "secret2"], - port: 4001 + port: 4001, + latestAppsPath: "./appPackages", + masterPlugins: {}, + customizeMaster: appDefinition => appDefinition }) \ No newline at end of file diff --git a/packages/server/config.sample.js b/packages/server/config.sample.js index 4772adaa7..7e585186e 100644 --- a/packages/server/config.sample.js +++ b/packages/server/config.sample.js @@ -15,6 +15,15 @@ module.exports = () => ({ keys: ["secret1", "secret1"], // port for http server to listen on - port: 4001 + port: 4001, + + // path to where your appDefinition etc is stored (dev time) + latestAppsPath: "./appPackages", + + // register plugins for master + masterPlugins: {}, + + // make modifications to master's appdefinition - e.g. add plugins + customizeMaster: appDefinition => appDefinition }) \ No newline at end of file diff --git a/packages/server/initialise/createMasterDb.js b/packages/server/initialise/createMasterDb.js index 0d33f8cc3..67a34ddca 100644 --- a/packages/server/initialise/createMasterDb.js +++ b/packages/server/initialise/createMasterDb.js @@ -4,6 +4,7 @@ const getDatabaseManager = require("../utilities/databaseManager"); const {getApisForUser, getApisWithFullAccess} = require("../utilities/budibaseApi"); const masterDbAppDefinition = require("../appPackages/master/appDefinition.json"); const masterDbAccessLevels = require("../appPackages/master/access_levels.json"); +const { masterAppPackage } = require("../utilities/createAppPackage"); module.exports = async (datastoreModule, rootConfig, username, password) => { try { @@ -18,19 +19,20 @@ module.exports = async (datastoreModule, rootConfig, username, password) => { const templateApi = getTemplateApi({datastore}); await initialiseData(datastore, { - heirarchy:templateApi.constructHeirarchy(masterDbAppDefinition.hierarchy), + hierarchy:templateApi.constructHierarchy(masterDbAppDefinition.hierarchy), actions:masterDbAppDefinition.actions, triggers:masterDbAppDefinition.triggers }); - const bbMaster = await getApisWithFullAccess(datastore); + const bbMaster = await getApisWithFullAccess( + datastore, masterAppPackage()); await bbMaster.authApi.saveAccessLevels(masterDbAccessLevels); const user = bbMaster.authApi.getNewUser(); user.name = username; user.accessLevels= ["owner"]; await bbMaster.authApi.createUser(user, password); - return await getApisForUser(datastore, username, password); + return await getApisForUser(datastore, masterAppPackage(), username, password); } catch(e) { throw e; } diff --git a/packages/server/initialise/initialiseRuntimePackages.js b/packages/server/initialise/initialiseRuntimePackages.js new file mode 100644 index 000000000..eec37b7cf --- /dev/null +++ b/packages/server/initialise/initialiseRuntimePackages.js @@ -0,0 +1,69 @@ +const util = require("util"); +const fs = require("fs"); +const { join } = require("path"); +const { ncp } = require('ncp'); +const { masterAppPackage } = require("../utilities/createAppPackage"); +const rimraf = util.promisify(require("rimraf")); +const mkdir = util.promisify(fs.mkdir); + +const exists = root => async (path) => { + try { + await util.promisify(fs.access)( + join(root,path) + ); + } catch (e) { + return false; + } + return true; +}; + +const copyfolder = (source, destination) => + new Promise((resolve, reject) => { + ncp(source, destination, function (err) { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + +module.exports = async (config, bbMaster, latestAppsFolder) => { + + const appsRuntimeFolder = "./runtime_apps"; + // create runtime folder + // copy master into /master/latest + if(await exists(appsRuntimeFolder)) { + try { + await rimraf(appsRuntimeFolder); + } catch(err) { + console.log(err); + } + } + + await mkdir(appsRuntimeFolder); + + + /* + const allApps = await bbMaster + .indexApi + .listItems("/all_applications"); + + for(let app of allApps) { + app. + } + */ + + + const apps = { + "_master": masterAppPackage(config) + } + + return ((appName, versionId) => { + if(appName === "_master") { + return apps[appName]; + } + + return apps[appName][versionId]; + }); +} \ No newline at end of file diff --git a/packages/server/middleware/routers.js b/packages/server/middleware/routers.js index bd2fb1fa4..61d77f188 100644 --- a/packages/server/middleware/routers.js +++ b/packages/server/middleware/routers.js @@ -166,14 +166,14 @@ module.exports = (config, app) => { ); ctx.response.status = StatusCodes.OK; }) - .post("/:appname/api/appHeirarchy", async (ctx) => { - ctx.body = await ctx.instance.templateApi.saveApplicationHeirarchy( + .post("/:appname/api/apphierarchy", async (ctx) => { + ctx.body = await ctx.instance.templateApi.saveApplicationHierarchy( ctx.body ); ctx.response.status = StatusCodes.OK; }) .post("/:appname/api/actionsAndTriggers", async (ctx) => { - ctx.body = await ctx.instance.templateApi.saveApplicationHeirarchy( + ctx.body = await ctx.instance.templateApi.saveApplicationHierarchy( ctx.body ); ctx.response.status = StatusCodes.OK; diff --git a/packages/server/package.json b/packages/server/package.json index abafcc61a..d3049454b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,6 +18,7 @@ "koa-bodyparser": "^4.2.1", "koa-router": "^7.4.0", "koa-session": "^5.12.0", + "ncp": "^2.0.0", "rimraf": "^2.6.3", "yargs": "^13.2.4" }, @@ -27,7 +28,7 @@ "server-destroy": "^1.0.1", "supertest": "^4.0.2" }, - "jest" : { + "jest": { "testEnvironment": "node" } } diff --git a/packages/server/tests/authenticate.js b/packages/server/tests/authenticate.js index f7a86e958..350937308 100644 --- a/packages/server/tests/authenticate.js +++ b/packages/server/tests/authenticate.js @@ -1,4 +1,7 @@ const statusCodes = require("../utilities/statusCodes"); +const util = require("util"); +const fs = require("fs"); +const readFile = util.promisify(fs.readFile); module.exports = (app) => { @@ -41,7 +44,7 @@ module.exports = (app) => { }); const testUserName = "test_user"; - const testPassword = "test_user_password"; + let testPassword = "test_user_password"; it("should be able to create new user with authenticated cookie", async () => { await app.post("/_master/api/createUser", { @@ -120,4 +123,30 @@ module.exports = (app) => { }) .expect(statusCodes.OK); }); + + it("should be able to reset password with temporary access", async () => { + // need to sort out behaviour sources for this... + await app.post("/_master/api/createTemporaryAccess", { + username: testUserName + }) + .expect(statusCodes.OK); + + testPassword = "test_user_new_password"; + + const tempCode = await readFile(`tempaccess${testUserName}`, "utf8"); + + await app.post("/_master/api/setPasswordFromTemporaryCode", { + username: testUserName, + tempCode, + newPassword:testPassword + }) + .expect(statusCodes.OK); + + await app.post("/_master/api/authenticate", { + username: testUserName, + password: testPassword + }) + .expect(statusCodes.OK); + + }); }; diff --git a/packages/server/tests/testApp.js b/packages/server/tests/testApp.js index fab85278d..5a4b5febf 100644 --- a/packages/server/tests/testApp.js +++ b/packages/server/tests/testApp.js @@ -10,13 +10,42 @@ const mkdir = promisify(fs.mkdir); const masterOwnerName = "test_master"; const masterOwnerPassword = "test_master_pass"; +const masterPlugins = { + main: { + outputToFile : ({filename, content}) => { + fs.writeFile(`./tests/${filename}`, content, {encoding:"utf8"}); + } + } +} + +const customizeMaster = appDefinition => { + + appDefinition.actions.outputToFile = { + name: 'outputToFile', + behaviourSource: 'main', + behaviourName: 'outputToFile', + initialOptions: {} + }; + + appDefinition.triggers.push({ + actionName: 'outputToFile', + eventName: 'authApi:createTemporaryAccess:onComplete', + optionsCreator: 'return ({filename:"tempaccess" + context.userName, content:context.result})', + condition: '' + }); + + return appDefinition; +} + const config = { datastore: "local", datastoreConfig: { rootPath: "./tests/.data" }, keys: ["secret1", "secret2"], - port: 4002 + port: 4002, + masterPlugins, + customizeMaster } diff --git a/packages/server/utilities/budibaseApi.js b/packages/server/utilities/budibaseApi.js index ea06cbbe8..3a9a3f5e3 100644 --- a/packages/server/utilities/budibaseApi.js +++ b/packages/server/utilities/budibaseApi.js @@ -1,11 +1,25 @@ const crypto = require("../nodeCrypto"); -const {getAppApis} = require("budibase-core"); +const {getAppApis, getTemplateApi} = require("budibase-core"); + +const constructHierarchy = (datastore, appDefinition) => { + appDefinition.hierarchy = getTemplateApi({datastore}) + .constructHierarchy(appDefinition.hierarchy); + return appDefinition; +} + +module.exports.getApisWithFullAccess = async (datastore, appPackage) => { + + const appDefinition = constructHierarchy( + datastore, + appPackage.appDefinition); -module.exports.getApisWithFullAccess = async (datastore) => { const bb = await getAppApis( datastore, - null, null, null, - crypto + appPackage.behaviourSources, + null, //cleanupTransations + null, // getEpochTime + crypto, + appDefinition ); bb.withFullAccess(); @@ -13,11 +27,16 @@ module.exports.getApisWithFullAccess = async (datastore) => { return bb; }; -module.exports.getApisForUser = async (datastore, username, password) => { +module.exports.getApisForUser = async (datastore, appPackage, username, password) => { const bb = await getAppApis( datastore, - null, null, null, - crypto + appPackage.behaviourSources, + null, //cleanupTransations + null, // getEpochTime + crypto, + constructHierarchy( + datastore, + appPackage.appDefinition) ); await bb.authenticateAs(username, password); @@ -25,14 +44,19 @@ module.exports.getApisForUser = async (datastore, username, password) => { return bb; } -module.exports.getApisForSession = async (datastore, session) => { +module.exports.getApisForSession = async (datastore, appPackage, session) => { const user = JSON.parse(session.user_json); const bb = await getAppApis( datastore, - null, null, null, - crypto + appPackage.behaviourSources, + null, //cleanupTransations + null, // getEpochTime + crypto, + constructHierarchy( + datastore, + appPackage.appDefinition) ); bb.asUser(user); diff --git a/packages/server/utilities/createAppPackage.js b/packages/server/utilities/createAppPackage.js new file mode 100644 index 000000000..ff29b2dc8 --- /dev/null +++ b/packages/server/utilities/createAppPackage.js @@ -0,0 +1,31 @@ +const { join } = require("path"); + +const createAppPackage = (appPath) => ({ + appDefinition: require(join(appPath, "appDefinition.json")), + behaviourSources: require(join(appPath, "plugins.json")), + appPath +}); + +module.exports.masterAppPackage = (config) => { + const standardPackage = createAppPackage("../appPackages/master"); + + const customizeMaster = config && config.customizeMaster + ? config.customizeMaster + : a => a; + + const appDefinition = customizeMaster( + standardPackage.appDefinition); + + return ({ + appDefinition, + behaviourSources: config && config.masterPlugins + ? config.masterPlugins + : standardPackage.behaviourSources, + appPath: standardPackage.appPath + }); +} + +module.exports.applictionVersionPackage = (appname, versionId) => + createAppPackage(`../runtimePackages/${appname}/${versionId}`); + + diff --git a/packages/server/utilities/masterAppInternal.js b/packages/server/utilities/masterAppInternal.js index a9e1a7281..78ff15d1d 100644 --- a/packages/server/utilities/masterAppInternal.js +++ b/packages/server/utilities/masterAppInternal.js @@ -1,9 +1,10 @@ const {getApisWithFullAccess, getApisForSession} = require("./budibaseApi"); const getDatastore = require("./datastore"); const getDatabaseManager = require("./databaseManager"); -const {$} = require("budibase-core").common; -const {keyBy} = require("lodash/fp"); +const {$, splitKey} = require("budibase-core").common; +const { keyBy, last } = require("lodash/fp"); const {unauthorized} = require("./exceptions"); +const { masterAppPackage, applictionVersionPackage } = require("../utilities/createAppPackage"); const isMaster = appname => appname === "_master"; @@ -19,7 +20,8 @@ module.exports = async (config) => { const masterDatastore = datastoreModule.getDatastore( databaseManager.masterDatastoreConfig); - const bb = await getApisWithFullAccess(masterDatastore); + const bb = await getApisWithFullAccess( + masterDatastore, masterAppPackage(config)); let applications; const loadApplications = async () => @@ -94,8 +96,15 @@ module.exports = async (config) => { const instance = await bb.recordApi.load( userInMaster.instance.key); + const versionId = $(instance.version.key, [ + splitKey, + last + ]); + const bbInstance = await getApisWithFullAccess( - datastoreModule.getDatastore(instance.datastoreconfig)); + datastoreModule.getDatastore(instance.datastoreconfig), + applictionVersionPackage(appname, versionId) + ); const authUser = await bbInstance.authApi.authenticate(username, password); @@ -117,7 +126,11 @@ module.exports = async (config) => { const customId = bb.recordApi.customId("mastersession", sessionId); try { const session = await bb.recordApi.load(`/sessions/${customId}`); - return await getApisForSession(masterDatastore, session); + return await getApisForSession( + masterDatastore, + masterAppPackage(config), + session); + } catch(_) { return null; } @@ -128,7 +141,10 @@ module.exports = async (config) => { try { const session = await bb.recordApi.load(`/applications/${app.id}/sessions/${customId}`); const instanceDatastore = getInstanceDatastore(session.instanceDatastoreConfig) - return await getApisForSession(instanceDatastore, session); + return await getApisForSession( + instanceDatastore, + applictionVersionPackage(appname, session.instanceVersion), + session); } catch(_) { return null; } @@ -203,7 +219,8 @@ module.exports = async (config) => { authenticate, getInstanceApiForSession, getFullAccessInstanceApiForUsername, - removeSessionsForUser + removeSessionsForUser, + bbMaster:bb }); } \ No newline at end of file diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index d6a678408..42d207dc6 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -2667,6 +2667,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +ncp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= + needle@^2.2.1: version "2.4.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"