diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 37657ce009..0cd7bc92bf 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -105,6 +105,8 @@ services: restart: always image: redis command: redis-server --requirepass ${REDIS_PASSWORD} + ports: + - "${REDIS_PORT}:6379" volumes: - redis_data:/data diff --git a/lerna.json b/lerna.json index fa5307c017..01ddc677aa 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.1", + "version": "0.9.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index d8003df421..29b9ef380c 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.1", + "version": "0.9.3", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/auth/src/objectStore/index.js b/packages/auth/src/objectStore/index.js index c6d1c3e2ce..a157332ae5 100644 --- a/packages/auth/src/objectStore/index.js +++ b/packages/auth/src/objectStore/index.js @@ -34,11 +34,15 @@ function sanitizeKey(input) { return sanitize(sanitizeBucket(input)).replace(/\\/g, "/") } +exports.sanitizeKey = sanitizeKey + // simply handles the dev app to app conversion function sanitizeBucket(input) { return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX) } +exports.sanitizeBucket = sanitizeBucket + function publicPolicy(bucketName) { return { Version: "2012-10-17", diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 78e3ea7acd..7d9e9ad637 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -3,41 +3,86 @@ const env = require("../environment") const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") +const RETRY_PERIOD_MS = 2000 +const STARTUP_TIMEOUT_MS = 5000 const CLUSTERED = false // for testing just generate the client once -let CONNECTED = false +let CLOSED = false let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null +// if in test always connected +let CONNECTED = !!env.isTest() + +function connectionError(timeout, err) { + // manually shut down, ignore errors + if (CLOSED) { + return + } + // always clear this on error + clearTimeout(timeout) + CONNECTED = false + console.error("Redis connection failed - " + err) + setTimeout(() => { + init() + }, RETRY_PERIOD_MS) +} /** * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise * will return the ioredis client which will be ready to use. - * @return {Promise} The ioredis client. */ function init() { - return new Promise((resolve, reject) => { - // testing uses a single in memory client - if (env.isTest() || (CLIENT && CONNECTED)) { - return resolve(CLIENT) + let timeout + CLOSED = false + // testing uses a single in memory client + if (env.isTest() || (CLIENT && CONNECTED)) { + return + } + // start the timer - only allowed 5 seconds to connect + timeout = setTimeout(() => { + if (!CONNECTED) { + connectionError(timeout) } - const { opts, host, port } = getRedisOptions(CLUSTERED) - if (CLUSTERED) { - CLIENT = new Redis.Cluster([{ host, port }], opts) - } else { - CLIENT = new Redis(opts) + }, STARTUP_TIMEOUT_MS) + + // disconnect any lingering client + if (CLIENT) { + CLIENT.disconnect() + } + const { opts, host, port } = getRedisOptions(CLUSTERED) + if (CLUSTERED) { + CLIENT = new Redis.Cluster([{ host, port }], opts) + } else { + CLIENT = new Redis(opts) + } + // attach handlers + CLIENT.on("end", err => { + connectionError(timeout, err) + }) + CLIENT.on("error", err => { + connectionError(timeout, err) + }) + CLIENT.on("connect", () => { + clearTimeout(timeout) + CONNECTED = true + }) +} + +function waitForConnection() { + return new Promise(resolve => { + if (CLIENT == null) { + init() + } else if (CONNECTED) { + resolve() + return } - CLIENT.on("end", err => { - reject(err) - CONNECTED = false - }) - CLIENT.on("error", err => { - reject(err) - CONNECTED = false - }) - CLIENT.on("connect", () => { - resolve(CLIENT) - CONNECTED = true - }) + // check if the connection is ready + const interval = setInterval(() => { + if (CONNECTED) { + clearInterval(interval) + resolve() + } + }, 500) }) } @@ -85,31 +130,32 @@ class RedisWrapper { } async init() { - this._client = await init() + CLOSED = false + init() + await waitForConnection() return this } async finish() { - this._client.disconnect() + CLOSED = true + CLIENT.disconnect() } async scan() { - const db = this._db, - client = this._client + const db = this._db let stream if (CLUSTERED) { - let node = client.nodes("master") + let node = CLIENT.nodes("master") stream = node[0].scanStream({ match: db + "-*", count: 100 }) } else { - stream = client.scanStream({ match: db + "-*", count: 100 }) + stream = CLIENT.scanStream({ match: db + "-*", count: 100 }) } return promisifyStream(stream) } async get(key) { - const db = this._db, - client = this._client - let response = await client.get(addDbPrefix(db, key)) + const db = this._db + let response = await CLIENT.get(addDbPrefix(db, key)) // overwrite the prefixed key if (response != null && response.key) { response.key = key @@ -123,22 +169,20 @@ class RedisWrapper { } async store(key, value, expirySeconds = null) { - const db = this._db, - client = this._client + const db = this._db if (typeof value === "object") { value = JSON.stringify(value) } const prefixedKey = addDbPrefix(db, key) - await client.set(prefixedKey, value) + await CLIENT.set(prefixedKey, value) if (expirySeconds) { - await client.expire(prefixedKey, expirySeconds) + await CLIENT.expire(prefixedKey, expirySeconds) } } async delete(key) { - const db = this._db, - client = this._client - await client.del(addDbPrefix(db, key)) + const db = this._db + await CLIENT.del(addDbPrefix(db, key)) } async clear() { diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 1d05abcc19..e30c0821af 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.1", + "version": "0.9.3", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 4f759a60ea..80d38937ac 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -11,12 +11,23 @@ Cypress.Commands.add("login", () => { if (cookie) return cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.contains("Create Test User").click() + + // cy.get("button").then(btn => { + // const adminUserButton = "Create super admin user" + // console.log(btn.first().first()) + // if (!btn.first().contains(adminUserButton)) { + // // create admin user + // cy.get("input").first().type("test@test.com") + // cy.get('input[type="password"]').first().type("test") + // cy.get('input[type="password"]').eq(1).type("test") + // cy.contains(adminUserButton).click() + // } + + // login cy.get("input").first().type("test@test.com") - cy.get('input[type="password"]').type("test") - cy.contains("Login").click() + // }) }) }) diff --git a/packages/builder/package.json b/packages/builder/package.json index acc8c71a10..62891c187d 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.1", + "version": "0.9.3", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.1", - "@budibase/client": "^0.9.1", + "@budibase/bbui": "^0.9.3", + "@budibase/client": "^0.9.3", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.1", + "@budibase/string-templates": "^0.9.3", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte index 3eaf048872..c9c459b1f3 100644 --- a/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte +++ b/packages/builder/src/pages/builder/auth/_components/GoogleButton.svelte @@ -10,13 +10,13 @@ {#if show} - - -
- google icon -

Sign in with Google

-
-
+ window.open("/api/admin/auth/google", "_blank")} + > +
+ google icon +

Sign in with Google

+
{/if} diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte index b60f15d976..9504f73b68 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte @@ -5,6 +5,8 @@ Select, ModalContent, notifications, + Toggle, + Label, } from "@budibase/bbui" import { createValidationStore, emailValidator } from "helpers/validation" import { users } from "stores/portal" @@ -13,12 +15,12 @@ const options = ["Email onboarding", "Basic onboarding"] let selected = options[0] + let builder, admin const [email, error, touched] = createValidationStore("", emailValidator) async function createUserFlow() { - const res = await users.invite($email) - console.log(res) + const res = await users.invite({ email: $email, builder, admin }) if (res.status) { notifications.error(res.message) } else { @@ -56,4 +58,23 @@ placeholder="john@doe.com" label="Email" /> +
+
+ + +
+
+ + +
+
+ + diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte index 74a696191a..8ecb274585 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte @@ -1,13 +1,22 @@