From 98576f586ee4e0a1172fa52e5e15068d48becff9 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Sun, 11 Apr 2021 11:35:55 +0100 Subject: [PATCH 1/2] login page --- hosting/generated-envoy.dev.yaml | 125 ------------------ packages/auth/src/index.js | 9 +- packages/auth/src/middleware/authenticated.js | 53 ++++++++ packages/auth/src/middleware/index.js | 2 + packages/auth/src/utils.js | 24 ++++ packages/builder/src/builderStore/api.js | 8 +- packages/builder/src/builderStore/index.js | 4 +- .../src/components/login/LoginForm.svelte | 31 +++++ .../builder/src/components/login/index.js | 1 + .../builder/src/pages/builder/_layout.svelte | 75 +++++++---- packages/builder/src/stores/backend/auth.js | 22 +++ packages/builder/src/stores/backend/index.js | 1 + packages/server/src/api/controllers/auth.js | 8 +- packages/server/src/api/index.js | 2 +- packages/server/src/api/routes/auth.js | 2 +- packages/server/src/middleware/authorized.js | 13 +- packages/worker/src/api/controllers/auth.js | 8 +- packages/worker/src/api/routes/admin/index.js | 9 +- packages/worker/src/api/routes/auth.js | 12 +- .../worker/src/middleware/authenticated.js | 30 ----- packages/worker/src/middleware/authorized.js | 7 - 21 files changed, 231 insertions(+), 215 deletions(-) delete mode 100644 hosting/generated-envoy.dev.yaml create mode 100644 packages/auth/src/middleware/authenticated.js create mode 100644 packages/builder/src/components/login/LoginForm.svelte create mode 100644 packages/builder/src/components/login/index.js create mode 100644 packages/builder/src/stores/backend/auth.js delete mode 100644 packages/worker/src/middleware/authenticated.js delete mode 100644 packages/worker/src/middleware/authorized.js diff --git a/hosting/generated-envoy.dev.yaml b/hosting/generated-envoy.dev.yaml deleted file mode 100644 index 72cad34104..0000000000 --- a/hosting/generated-envoy.dev.yaml +++ /dev/null @@ -1,125 +0,0 @@ -static_resources: - listeners: - - name: main_listener - address: - socket_address: { address: 0.0.0.0, port_value: 10000 } - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: ingress - codec_type: auto - route_config: - name: local_route - virtual_hosts: - - name: local_services - domains: ["*"] - routes: - - match: { prefix: "/db/" } - route: - cluster: couchdb-service - prefix_rewrite: "/" - - - match: { prefix: "/cache/" } - route: - cluster: redis-service - prefix_rewrite: "/" - - - match: { prefix: "/api/" } - route: - cluster: server-dev - - - match: { prefix: "/app_" } - route: - cluster: server-dev - - - match: { prefix: "/builder/" } - route: - cluster: builder-dev - - - match: { prefix: "/builder" } - route: - cluster: builder-dev - prefix_rewrite: "/builder/" - - # minio is on the default route because this works - # best, minio + AWS SDK doesn't handle path proxy - - match: { prefix: "/" } - route: - cluster: minio-service - - http_filters: - - name: envoy.filters.http.router - - clusters: - - name: minio-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: minio-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: minio-service - port_value: 9000 - - - name: couchdb-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: couchdb-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: couchdb-service - port_value: 5984 - - - name: redis-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: redis-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: redis-service - port_value: 6379 - - - name: server-dev - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: server-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: host.docker.internal - port_value: 4001 - - - name: builder-dev - connect_timeout: 15s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: builder-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: host.docker.internal - port_value: 3000 - diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index b65690b064..b3b60b2cc9 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -4,11 +4,15 @@ const JwtStrategy = require("passport-jwt").Strategy // const GoogleStrategy = require("passport-google-oauth").Strategy const CouchDB = require("./db") const { StaticDatabases } = require("./db/utils") -const { jwt, local, google } = require("./middleware") +const { jwt, local, google, authenticated } = require("./middleware") const { Cookies, UserStatus } = require("./constants") const { hash, compare } = require("./hashing") const { getAppId, setCookie } = require("./utils") -const { generateUserID, getUserParams, getEmailFromUserID } = require("./db/utils") +const { + generateUserID, + getUserParams, + getEmailFromUserID, +} = require("./db/utils") // Strategies passport.use(new LocalStrategy(local.options, local.authenticate)) @@ -41,4 +45,5 @@ module.exports = { compare, getAppId, setCookie, + authenticated, } diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js new file mode 100644 index 0000000000..0c77fbbd17 --- /dev/null +++ b/packages/auth/src/middleware/authenticated.js @@ -0,0 +1,53 @@ +const CouchDB = require("../db") +const { Cookies } = require("../constants") +const { getAppId, setCookie, getCookie } = require("../utils") +const { StaticDatabases } = require("../db/utils") + +async function setCurrentAppContext(ctx) { + let role = "PUBLIC" + + // Current app cookie + let appId = getAppId(ctx) + if (!appId) { + ctx.user = { + role, + } + return + } + + const currentAppCookie = getCookie(ctx, Cookies.CurrentApp, { decrypt: true }) + const appIdChanged = appId && currentAppCookie.appId !== appId + if (appIdChanged) { + try { + // get roles for user from global DB + const db = new CouchDB(StaticDatabases.USER) + const user = await db.get(ctx.user) + role = user.roles[appId] + } catch (err) { + // no user exists + } + } else if (currentAppCookie.appId) { + appId = currentAppCookie.appId + } + setCookie(ctx, { appId, role }, Cookies.CurrentApp, { encrypt: true }) + return appId +} + +module.exports = async (ctx, next) => { + try { + // check the actual user is authenticated first + const authCookie = getCookie(ctx, Cookies.Auth, { decrypt: true }) + + if (authCookie) { + ctx.isAuthenticated = true + ctx.user = authCookie._id + } + + ctx.appId = await setCurrentAppContext(ctx) + + await next() + } catch (err) { + console.log(err) + ctx.throw(err.status || 403, err.text) + } +} diff --git a/packages/auth/src/middleware/index.js b/packages/auth/src/middleware/index.js index 9d822e5937..519233eda4 100644 --- a/packages/auth/src/middleware/index.js +++ b/packages/auth/src/middleware/index.js @@ -1,9 +1,11 @@ const jwt = require("./passport/jwt") const local = require("./passport/local") const google = require("./passport/google") +const authenticated = require("./authenticated") module.exports = { google, jwt, local, + authenticated, } diff --git a/packages/auth/src/utils.js b/packages/auth/src/utils.js index 7eb39a3005..8aa8eb97e0 100644 --- a/packages/auth/src/utils.js +++ b/packages/auth/src/utils.js @@ -1,4 +1,6 @@ const { DocumentTypes, SEPARATOR } = require("./db/utils") +const jwt = require("jsonwebtoken") +const { options } = require("./middleware/passport/jwt") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -39,6 +41,23 @@ exports.getAppId = ctx => { return appId } +/** + * Get a cookie from context, and decrypt if necessary. + * @param {object} ctx The request which is to be manipulated. + * @param {string} name The name of the cookie to get. + * @param {object} options options . + */ +exports.getCookie = (ctx, value, options = {}) => { + const cookie = ctx.cookies.get(value) + + if (!cookie) return + + if (!options.decrypt) return cookie + + const payload = jwt.verify(cookie, process.env.JWT_SECRET) + return payload +} + /** * Store a cookie for the request, has a hardcoded expiry. * @param {object} ctx The request which is to be manipulated. @@ -52,6 +71,11 @@ exports.setCookie = (ctx, value, name = "builder") => { if (!value) { ctx.cookies.set(name) } else { + if (options.encrypt) { + value = jwt.sign(value, process.env.JWT_SECRET, { + expiresIn: "1 day", + }) + } ctx.cookies.set(name, value, { expires, path: "/", diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index 8b5206da93..1cb21a9941 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -20,9 +20,9 @@ export const get = apiCall("GET") export const patch = apiCall("PATCH") export const del = apiCall("DELETE") export const put = apiCall("PUT") -export const getBuilderCookie = async () => { - await post("/api/builder/login", {}) -} +// export const getBuilderCookie = async () => { +// await post("/api/builder/login", {}) +// } export default { post: apiCall("POST"), @@ -30,5 +30,5 @@ export default { patch: apiCall("PATCH"), delete: apiCall("DELETE"), put: apiCall("PUT"), - getBuilderCookie, + // getBuilderCookie, } diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index 48f466169b..25e3c5979b 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -6,7 +6,7 @@ import { derived, writable } from "svelte/store" import analytics from "analytics" import { FrontendTypes, LAYOUT_NAMES } from "../constants" import { findComponent } from "./storeUtils" -import { getBuilderCookie } from "./api" +// import { getBuilderCookie } from "./api" export const store = getFrontendStore() export const automationStore = getAutomationStore() @@ -59,7 +59,7 @@ export const selectedAccessRole = writable("BASIC") export const initialise = async () => { try { // TODO this needs to be replaced by a real login - await getBuilderCookie() + // await getBuilderCookie() await analytics.activate() analytics.captureEvent("Builder Started") } catch (err) { diff --git a/packages/builder/src/components/login/LoginForm.svelte b/packages/builder/src/components/login/LoginForm.svelte new file mode 100644 index 0000000000..216891e35f --- /dev/null +++ b/packages/builder/src/components/login/LoginForm.svelte @@ -0,0 +1,31 @@ + + +
+ + + + + + + + + + + diff --git a/packages/builder/src/components/login/index.js b/packages/builder/src/components/login/index.js new file mode 100644 index 0000000000..9c9e708614 --- /dev/null +++ b/packages/builder/src/components/login/index.js @@ -0,0 +1 @@ +export { LoginForm } from "./LoginForm.svelte" \ No newline at end of file diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 22e3a81c7c..2b98536ab0 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -7,44 +7,55 @@ CommunityIcon, BugIcon, } from "components/common/Icons" + import LoginForm from "components/login/LoginForm.svelte" import BuilderSettingsButton from "components/start/BuilderSettingsButton.svelte" import Logo from "/assets/budibase-logo.svg" + import { auth } from "stores/backend" let modal -
-
- -