diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 4d338cb221..bab7f61df1 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -1,8 +1,11 @@ version: "3" +# optional ports are specified throughout for more advanced use cases. + services: app-service: restart: always + #build: ./build/server image: budibase/budibase-apps ports: - "${APP_PORT}:4002" @@ -20,6 +23,7 @@ services: worker-service: restart: always + #build: ./build/worker image: budibase/budibase-worker ports: - "${WORKER_PORT}:4003" @@ -62,7 +66,7 @@ services: - ./envoy.yaml:/etc/envoy/envoy.yaml ports: - "${MAIN_PORT}:10000" - - "9901:9901" + #- "9901:9901" depends_on: - minio-service - worker-service @@ -77,8 +81,8 @@ services: - COUCHDB_USER=${COUCH_DB_USER} ports: - "${COUCH_DB_PORT}:5984" - - "4369:4369" - - "9100:9100" + #- "4369:4369" + #- "9100:9100" volumes: - couchdb_data:/couchdb diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 3c816cb1ca..11f5c81b99 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -20,6 +20,11 @@ static_resources: route: cluster: app-service prefix_rewrite: "/" + + # special case for presenting our static self hosting page + - match: { path: "/" } + route: + cluster: app-service # special case for when API requests are made, can just forward, not to minio - match: { prefix: "/api/" } diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index af6e0b2ee2..5c3674d1b9 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -45,7 +45,7 @@ exports.authenticate = async ctx => { expiresIn: "1 day", }) - setCookie(ctx, appId, token) + setCookie(ctx, token, appId) delete dbUser.password ctx.body = { diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index 2096b3039e..770cbaf088 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -49,6 +49,17 @@ exports.serveBuilder = async function(ctx) { await send(ctx, ctx.file, { root: ctx.devPath || builderPath }) } +exports.serveSelfHostPage = async function(ctx) { + const logo = fs.readFileSync(resolve(__dirname, "selfhost/logo.svg"), "utf8") + const hostingHbs = fs.readFileSync( + resolve(__dirname, "selfhost/index.hbs"), + "utf8" + ) + ctx.body = await processString(hostingHbs, { + logo, + }) +} + exports.uploadFile = async function(ctx) { let files files = diff --git a/packages/server/src/api/controllers/static/selfhost/index.hbs b/packages/server/src/api/controllers/static/selfhost/index.hbs new file mode 100644 index 0000000000..cbf1b8f3e5 --- /dev/null +++ b/packages/server/src/api/controllers/static/selfhost/index.hbs @@ -0,0 +1,173 @@ + + + + + Budibase self hosting️ + + + +
+ +

Get started with Budibase Self Hosting

+

Use the address in your Builder

+
+
+

📚Documentation

+

+ Find out more about your self hosted platform. +

+ + Documentation + +
+
+

💻Next steps

+

+ Find out how to make use of your self hosted Budibase platform. +

+ + Next steps + +
+
+
+ + + + + + +

A Hosting Key will also be required, this can be found in your hosting properties, info found here.

+
+
+ + + \ No newline at end of file diff --git a/packages/server/src/api/controllers/static/selfhost/logo.svg b/packages/server/src/api/controllers/static/selfhost/logo.svg new file mode 100644 index 0000000000..37bfb4ff83 --- /dev/null +++ b/packages/server/src/api/controllers/static/selfhost/logo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index ea0ed4b875..5b9b51363b 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -65,6 +65,8 @@ for (let route of mainRoutes) { router.use(staticRoutes.routes()) router.use(staticRoutes.allowedMethods()) -router.redirect("/", "/_builder") +if (!env.SELF_HOSTED && !env.CLOUD) { + router.redirect("/", "/_builder") +} module.exports = router diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index a519a63781..2b0c9b36fc 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -23,6 +23,10 @@ if (env.NODE_ENV !== "production") { router.get("/_builder/:file*", controller.serveBuilder) } +if (env.SELF_HOSTED) { + router.get("/", controller.serveSelfHostPage) +} + router .post( "/api/attachments/process", diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index 27b3f52725..e4e678abad 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -2,7 +2,13 @@ const jwt = require("jsonwebtoken") const STATUS_CODES = require("../utilities/statusCodes") const { getRole, BUILTIN_ROLES } = require("../utilities/security/roles") const { AuthTypes } = require("../constants") -const { getAppId, getCookieName, setCookie, isClient } = require("../utilities") +const { + getAppId, + getCookieName, + clearCookie, + setCookie, + isClient, +} = require("../utilities") module.exports = async (ctx, next) => { if (ctx.path === "/_builder") { @@ -15,16 +21,18 @@ module.exports = async (ctx, next) => { let appId = getAppId(ctx) const cookieAppId = ctx.cookies.get(getCookieName("currentapp")) if (appId && cookieAppId !== appId) { - setCookie(ctx, "currentapp", appId) + setCookie(ctx, appId, "currentapp") } else if (cookieAppId) { appId = cookieAppId } - - let token = ctx.cookies.get(getCookieName(appId)) - let authType = AuthTypes.APP - if (!token && !isClient(ctx)) { - authType = AuthTypes.BUILDER + let token, authType + if (!isClient(ctx)) { token = ctx.cookies.get(getCookieName()) + authType = AuthTypes.BUILDER + } + if (!token && appId) { + token = ctx.cookies.get(getCookieName(appId)) + authType = AuthTypes.APP } if (!token) { @@ -49,9 +57,13 @@ module.exports = async (ctx, next) => { role: await getRole(appId, jwtPayload.roleId), } } catch (err) { - // TODO - this can happen if the JWT secret is changed and can never login - // TODO: wipe cookies if they exist - ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text) + if (authType === AuthTypes.BUILDER) { + clearCookie(ctx) + ctx.status = 200 + return + } else { + ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text) + } } await next() diff --git a/packages/server/src/utilities/builder/setBuilderToken.js b/packages/server/src/utilities/builder/setBuilderToken.js index 93863cee63..42730fd2ea 100644 --- a/packages/server/src/utilities/builder/setBuilderToken.js +++ b/packages/server/src/utilities/builder/setBuilderToken.js @@ -3,7 +3,7 @@ const env = require("../../environment") const CouchDB = require("../../db") const jwt = require("jsonwebtoken") const { DocumentTypes, SEPARATOR } = require("../../db/utils") -const { setCookie } = require("../index") +const { setCookie, clearCookie } = require("../index") const APP_PREFIX = DocumentTypes.APP + SEPARATOR module.exports = async (ctx, appId, version) => { @@ -20,13 +20,13 @@ module.exports = async (ctx, appId, version) => { }) // set the builder token - setCookie(ctx, "builder", token) - setCookie(ctx, "currentapp", appId) + setCookie(ctx, token, "builder") + setCookie(ctx, appId, "currentapp") // need to clear all app tokens or else unable to use the app in the builder let allDbNames = await CouchDB.allDbs() allDbNames.map(dbName => { if (dbName.startsWith(APP_PREFIX)) { - setCookie(ctx, dbName, "") + clearCookie(ctx, dbName) } }) } diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index fd51bcdaf0..9da2937a4d 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -111,16 +111,28 @@ exports.getCookieName = (name = "builder") => { * @param {string} name The name of the cookie to set. * @param {string|object} value The value of cookie which will be set. */ -exports.setCookie = (ctx, name, value) => { +exports.setCookie = (ctx, value, name = "builder") => { const expires = new Date() expires.setDate(expires.getDate() + 1) - ctx.cookies.set(exports.getCookieName(name), value, { - expires, - path: "/", - httpOnly: false, - overwrite: true, - }) + const cookieName = exports.getCookieName(name) + if (!value) { + ctx.cookies.set(cookieName) + } else { + ctx.cookies.set(cookieName, value, { + expires, + path: "/", + httpOnly: false, + overwrite: true, + }) + } +} + +/** + * Utility function, simply calls setCookie with an empty string for value + */ +exports.clearCookie = (ctx, name) => { + exports.setCookie(ctx, "", name) } exports.isClient = ctx => {