diff --git a/hosting/scripts/build-target-paths.sh b/hosting/scripts/build-target-paths.sh index fce768e2ee..c974d9a304 100644 --- a/hosting/scripts/build-target-paths.sh +++ b/hosting/scripts/build-target-paths.sh @@ -9,7 +9,11 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then chown -R couchdb:couchdb $DATA_DIR/couch/ apt update apt-get install -y openssh-server - sed -i "s/#Port 22/Port 2222/" /etc/ssh/sshd_config + echo "root:Docker!" | chpasswd + mkdir -p /tmp + chmod +x /tmp/ssh_setup.sh \ + && (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null) + cp /etc/sshd_config /etc/ssh/sshd_config /etc/init.d/ssh restart sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index 476a6e5e94..f34290f627 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -29,23 +29,8 @@ ENV TARGETBUILD $TARGETBUILD COPY --from=build /app /app COPY --from=build /worker /worker -ENV \ - APP_PORT=4001 \ - ARCHITECTURE=amd \ - BUDIBASE_ENVIRONMENT=PRODUCTION \ - CLUSTER_PORT=80 \ - # CUSTOM_DOMAIN=budi001.custom.com \ - DATA_DIR=/data \ - DEPLOYMENT_ENVIRONMENT=docker \ - MINIO_URL=http://localhost:9000 \ - POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU \ - REDIS_URL=localhost:6379 \ - SELF_HOSTED=1 \ - TARGETBUILD=$TARGETBUILD \ - WORKER_PORT=4002 \ - WORKER_URL=http://localhost:4002 \ - APPS_URL=http://localhost:4001 - +# ENV CUSTOM_DOMAIN=budi001.custom.com \ +# See runner.sh for Env Vars # These secret env variables are generated by the runner at startup # their values can be overriden by the user, they will be written # to the .env file in the /data directory for use later on @@ -117,6 +102,8 @@ RUN chmod +x ./build-target-paths.sh # Script below sets the path for storing data based on $DATA_DIR # For Azure App Service install SSH & point data locations to /home +ADD hosting/single/ssh/sshd_config /etc/ +ADD hosting/single/ssh/ssh_setup.sh /tmp RUN /build-target-paths.sh # cleanup cache @@ -124,6 +111,8 @@ RUN yarn cache clean -f EXPOSE 80 EXPOSE 443 +# Expose port 2222 for SSH on Azure App Service build +EXPOSE 2222 VOLUME /data # setup letsencrypt certificate diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index 77015d75ee..cf82e6701b 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -1,6 +1,21 @@ #!/bin/bash declare -a ENV_VARS=("COUCHDB_USER" "COUCHDB_PASSWORD" "DATA_DIR" "MINIO_ACCESS_KEY" "MINIO_SECRET_KEY" "INTERNAL_API_KEY" "JWT_SECRET" "REDIS_PASSWORD") - +declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONMENT" "CLUSTER_PORT" "DEPLOYMENT_ENVIRONMENT" "MINIO_URL" "NODE_ENV" "POSTHOG_TOKEN" "REDIS_URL" "SELF_HOSTED" "WORKER_PORT" "WORKER_URL") +# Check the env vars set in Dockerfile have come through, AAS seems to drop them +[[ -z "${APP_PORT}" ]] && export APP_PORT=4001 +[[ -z "${ARCHITECTURE}" ]] && export ARCHITECTURE=amd +[[ -z "${BUDIBASE_ENVIRONMENT}" ]] && export BUDIBASE_ENVIRONMENT=PRODUCTION +[[ -z "${CLUSTER_PORT}" ]] && export CLUSTER_PORT=80 +[[ -z "${DEPLOYMENT_ENVIRONMENT}" ]] && export DEPLOYMENT_ENVIRONMENT=docker +[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000 +[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production +[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU +[[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379 +[[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1 +[[ -z "${WORKER_PORT}" ]] && export WORKER_PORT=4002 +[[ -z "${WORKER_URL}" ]] && export WORKER_URL=http://localhost:4002 +[[ -z "${APPS_URL}" ]] && export APPS_URL=http://localhost:4001 +# export CUSTOM_DOMAIN=budi001.custom.com # Azure App Service customisations if [[ "${TARGETBUILD}" = "aas" ]]; then DATA_DIR=/home @@ -10,9 +25,10 @@ else fi if [ -f "${DATA_DIR}/.env" ]; then - export $(cat ${DATA_DIR}/.env | xargs) + # Read in the .env file and export the variables + for LINE in $(cat ${DATA_DIR}/.env); do export $LINE; done fi -# first randomise any unset environment variables +# randomise any unset environment variables for ENV_VAR in "${ENV_VARS[@]}" do temp=$(eval "echo \$$ENV_VAR") @@ -30,11 +46,18 @@ if [ ! -f "${DATA_DIR}/.env" ]; then temp=$(eval "echo \$$ENV_VAR") echo "$ENV_VAR=$temp" >> ${DATA_DIR}/.env done + for ENV_VAR in "${DOCKER_VARS[@]}" + do + temp=$(eval "echo \$$ENV_VAR") + echo "$ENV_VAR=$temp" >> ${DATA_DIR}/.env + done echo "COUCH_DB_URL=${COUCH_DB_URL}" >> ${DATA_DIR}/.env fi -export COUCH_DB_URL=http://$COUCHDB_USER:$COUCHDB_PASSWORD@localhost:5984 - +# Read in the .env file and export the variables +for LINE in $(cat ${DATA_DIR}/.env); do export $LINE; done +ln -s ${DATA_DIR}/.env /app/.env +ln -s ${DATA_DIR}/.env /worker/.env # make these directories in runner, incase of mount mkdir -p ${DATA_DIR}/couch/{dbs,views} mkdir -p ${DATA_DIR}/minio diff --git a/hosting/single/ssh/ssh_setup.sh b/hosting/single/ssh/ssh_setup.sh new file mode 100644 index 0000000000..0af0b6d7ad --- /dev/null +++ b/hosting/single/ssh/ssh_setup.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +ssh-keygen -A + +#prepare run dir +if [ ! -d "/var/run/sshd" ]; then + mkdir -p /var/run/sshd +fi \ No newline at end of file diff --git a/hosting/single/ssh/sshd_config b/hosting/single/ssh/sshd_config new file mode 100644 index 0000000000..7eb5df953a --- /dev/null +++ b/hosting/single/ssh/sshd_config @@ -0,0 +1,12 @@ +Port 2222 +ListenAddress 0.0.0.0 +LoginGraceTime 180 +X11Forwarding yes +Ciphers aes128-cbc,3des-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr +MACs hmac-sha1,hmac-sha1-96 +StrictModes yes +SyslogFacility DAEMON +PasswordAuthentication yes +PermitEmptyPasswords no +PermitRootLogin yes +Subsystem sftp internal-sftp diff --git a/lerna.json b/lerna.json index ea81293bd9..1c19e55de0 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.3.15-alpha.9", + "version": "1.3.18", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 24df48b8f7..a8360f4dbc 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -20,7 +20,7 @@ "test:watch": "jest --watchAll" }, "dependencies": { - "@budibase/types": "1.3.15-alpha.9", + "@budibase/types": "^1.3.18", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-sdk": "2.1030.0", diff --git a/packages/backend-core/src/db/constants.ts b/packages/backend-core/src/db/constants.ts index fd464ba5fb..2c2c29cee2 100644 --- a/packages/backend-core/src/db/constants.ts +++ b/packages/backend-core/src/db/constants.ts @@ -19,6 +19,7 @@ export enum ViewName { ROUTING = "screen_routes", AUTOMATION_LOGS = "automation_logs", ACCOUNT_BY_EMAIL = "account_by_email", + PLATFORM_USERS_LOWERCASE = "platform_users_lowercase", } export const DeprecatedViews = { diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index 4926a60150..c93c7b5662 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -2,7 +2,8 @@ import { newid } from "../hashing" import { DEFAULT_TENANT_ID, Configs } from "../constants" import env from "../environment" import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants" -import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy" +import { getTenantId, getGlobalDB } from "../context" +import { getGlobalDBName } from "../tenancy/utils" import fetch from "node-fetch" import { doWithDB, allDbs } from "./index" import { getCouchInfo } from "./pouch" diff --git a/packages/backend-core/src/db/views.js b/packages/backend-core/src/db/views.js deleted file mode 100644 index b2562bdc71..0000000000 --- a/packages/backend-core/src/db/views.js +++ /dev/null @@ -1,203 +0,0 @@ -const { - DocumentType, - ViewName, - DeprecatedViews, - SEPARATOR, -} = require("./utils") -const { getGlobalDB } = require("../tenancy") -const { StaticDatabases } = require("./constants") -const { doWithDB } = require("./") - -const DESIGN_DB = "_design/database" - -function DesignDoc() { - return { - _id: DESIGN_DB, - // view collation information, read before writing any complex views: - // https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification - views: {}, - } -} - -async function removeDeprecated(db, viewName) { - if (!DeprecatedViews[viewName]) { - return - } - try { - const designDoc = await db.get(DESIGN_DB) - for (let deprecatedNames of DeprecatedViews[viewName]) { - delete designDoc.views[deprecatedNames] - } - await db.put(designDoc) - } catch (err) { - // doesn't exist, ignore - } -} - -exports.createNewUserEmailView = async () => { - const db = getGlobalDB() - let designDoc - try { - designDoc = await db.get(DESIGN_DB) - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - // if using variables in a map function need to inject them before use - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}")) { - emit(doc.email.toLowerCase(), doc._id) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.USER_BY_EMAIL]: view, - } - await db.put(designDoc) -} - -exports.createAccountEmailView = async () => { - await doWithDB(StaticDatabases.PLATFORM_INFO.name, async db => { - let designDoc - try { - designDoc = await db.get(DESIGN_DB) - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - // if using variables in a map function need to inject them before use - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.ACCOUNT_METADATA}${SEPARATOR}")) { - emit(doc.email.toLowerCase(), doc._id) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.ACCOUNT_BY_EMAIL]: view, - } - await db.put(designDoc) - }) -} - -exports.createUserAppView = async () => { - const db = getGlobalDB() - let designDoc - try { - designDoc = await db.get("_design/database") - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - // if using variables in a map function need to inject them before use - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) { - for (let prodAppId of Object.keys(doc.roles)) { - let emitted = prodAppId + "${SEPARATOR}" + doc._id - emit(emitted, null) - } - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.USER_BY_APP]: view, - } - await db.put(designDoc) -} - -exports.createApiKeyView = async () => { - const db = getGlobalDB() - let designDoc - try { - designDoc = await db.get("_design/database") - } catch (err) { - designDoc = DesignDoc() - } - const view = { - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.DEV_INFO}") && doc.apiKey) { - emit(doc.apiKey, doc.userId) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.BY_API_KEY]: view, - } - await db.put(designDoc) -} - -exports.createUserBuildersView = async () => { - const db = getGlobalDB() - let designDoc - try { - designDoc = await db.get("_design/database") - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - map: `function(doc) { - if (doc.builder && doc.builder.global === true) { - emit(doc._id, doc._id) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.USER_BY_BUILDERS]: view, - } - await db.put(designDoc) -} - -exports.queryView = async (viewName, params, db, CreateFuncByName) => { - try { - let response = (await db.query(`database/${viewName}`, params)).rows - response = response.map(resp => - params.include_docs ? resp.doc : resp.value - ) - if (params.arrayResponse) { - return response - } else { - return response.length <= 1 ? response[0] : response - } - } catch (err) { - if (err != null && err.name === "not_found") { - const createFunc = CreateFuncByName[viewName] - await removeDeprecated(db, viewName) - await createFunc() - return exports.queryView(viewName, params, db, CreateFuncByName) - } else { - throw err - } - } -} - -exports.queryPlatformView = async (viewName, params) => { - const CreateFuncByName = { - [ViewName.ACCOUNT_BY_EMAIL]: exports.createAccountEmailView, - } - - return doWithDB(StaticDatabases.PLATFORM_INFO.name, async db => { - return exports.queryView(viewName, params, db, CreateFuncByName) - }) -} - -exports.queryGlobalView = async (viewName, params, db = null) => { - const CreateFuncByName = { - [ViewName.USER_BY_EMAIL]: exports.createNewUserEmailView, - [ViewName.BY_API_KEY]: exports.createApiKeyView, - [ViewName.USER_BY_BUILDERS]: exports.createUserBuildersView, - [ViewName.USER_BY_APP]: exports.createUserAppView, - } - // can pass DB in if working with something specific - if (!db) { - db = getGlobalDB() - } - return exports.queryView(viewName, params, db, CreateFuncByName) -} diff --git a/packages/backend-core/src/db/views.ts b/packages/backend-core/src/db/views.ts new file mode 100644 index 0000000000..de313e8190 --- /dev/null +++ b/packages/backend-core/src/db/views.ts @@ -0,0 +1,260 @@ +import { DocumentType, ViewName, DeprecatedViews, SEPARATOR } from "./utils" +import { getGlobalDB } from "../context" +import { StaticDatabases } from "./constants" +import { doWithDB } from "./" + +const DESIGN_DB = "_design/database" + +function DesignDoc() { + return { + _id: DESIGN_DB, + // view collation information, read before writing any complex views: + // https://docs.couchdb.org/en/master/ddocs/views/collation.html#collation-specification + views: {}, + } +} + +interface DesignDocument { + views: any +} + +async function removeDeprecated(db: PouchDB.Database, viewName: ViewName) { + // @ts-ignore + if (!DeprecatedViews[viewName]) { + return + } + try { + const designDoc = await db.get(DESIGN_DB) + // @ts-ignore + for (let deprecatedNames of DeprecatedViews[viewName]) { + delete designDoc.views[deprecatedNames] + } + await db.put(designDoc) + } catch (err) { + // doesn't exist, ignore + } +} + +export const createNewUserEmailView = async () => { + const db = getGlobalDB() + let designDoc + try { + designDoc = await db.get(DESIGN_DB) + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + // if using variables in a map function need to inject them before use + map: `function(doc) { + if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}")) { + emit(doc.email.toLowerCase(), doc._id) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewName.USER_BY_EMAIL]: view, + } + await db.put(designDoc) +} + +export const createAccountEmailView = async () => { + await doWithDB( + StaticDatabases.PLATFORM_INFO.name, + async (db: PouchDB.Database) => { + let designDoc + try { + designDoc = await db.get(DESIGN_DB) + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + // if using variables in a map function need to inject them before use + map: `function(doc) { + if (doc._id.startsWith("${DocumentType.ACCOUNT_METADATA}${SEPARATOR}")) { + emit(doc.email.toLowerCase(), doc._id) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewName.ACCOUNT_BY_EMAIL]: view, + } + await db.put(designDoc) + } + ) +} + +export const createUserAppView = async () => { + const db = getGlobalDB() as PouchDB.Database + let designDoc + try { + designDoc = await db.get("_design/database") + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + // if using variables in a map function need to inject them before use + map: `function(doc) { + if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) { + for (let prodAppId of Object.keys(doc.roles)) { + let emitted = prodAppId + "${SEPARATOR}" + doc._id + emit(emitted, null) + } + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewName.USER_BY_APP]: view, + } + await db.put(designDoc) +} + +export const createApiKeyView = async () => { + const db = getGlobalDB() + let designDoc + try { + designDoc = await db.get("_design/database") + } catch (err) { + designDoc = DesignDoc() + } + const view = { + map: `function(doc) { + if (doc._id.startsWith("${DocumentType.DEV_INFO}") && doc.apiKey) { + emit(doc.apiKey, doc.userId) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewName.BY_API_KEY]: view, + } + await db.put(designDoc) +} + +export const createUserBuildersView = async () => { + const db = getGlobalDB() + let designDoc + try { + designDoc = await db.get("_design/database") + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + map: `function(doc) { + if (doc.builder && doc.builder.global === true) { + emit(doc._id, doc._id) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewName.USER_BY_BUILDERS]: view, + } + await db.put(designDoc) +} + +export const createPlatformUserView = async () => { + await doWithDB( + StaticDatabases.PLATFORM_INFO.name, + async (db: PouchDB.Database) => { + let designDoc + try { + designDoc = await db.get(DESIGN_DB) + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + // if using variables in a map function need to inject them before use + map: `function(doc) { + if (doc.tenantId) { + emit(doc._id.toLowerCase(), doc._id) + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewName.PLATFORM_USERS_LOWERCASE]: view, + } + await db.put(designDoc) + } + ) +} + +export interface QueryViewOptions { + arrayResponse?: boolean +} + +export const queryView = async ( + viewName: ViewName, + params: PouchDB.Query.Options, + db: PouchDB.Database, + CreateFuncByName: any, + opts?: QueryViewOptions +): Promise => { + try { + let response = await db.query(`database/${viewName}`, params) + const rows = response.rows + const docs = rows.map((resp: any) => + params.include_docs ? resp.doc : resp.value + ) + + if (opts?.arrayResponse) { + return docs + } else { + return docs.length <= 1 ? docs[0] : docs + } + } catch (err: any) { + if (err != null && err.name === "not_found") { + const createFunc = CreateFuncByName[viewName] + await removeDeprecated(db, viewName) + await createFunc() + return queryView(viewName, params, db, CreateFuncByName, opts) + } else { + throw err + } + } +} + +export const queryPlatformView = async ( + viewName: ViewName, + params: PouchDB.Query.Options, + opts?: QueryViewOptions +): Promise => { + const CreateFuncByName = { + [ViewName.ACCOUNT_BY_EMAIL]: createAccountEmailView, + [ViewName.PLATFORM_USERS_LOWERCASE]: createPlatformUserView, + } + + return doWithDB( + StaticDatabases.PLATFORM_INFO.name, + async (db: PouchDB.Database) => { + return queryView(viewName, params, db, CreateFuncByName, opts) + } + ) +} + +export const queryGlobalView = async ( + viewName: ViewName, + params: PouchDB.Query.Options, + db?: PouchDB.Database, + opts?: QueryViewOptions +): Promise => { + const CreateFuncByName = { + [ViewName.USER_BY_EMAIL]: createNewUserEmailView, + [ViewName.BY_API_KEY]: createApiKeyView, + [ViewName.USER_BY_BUILDERS]: createUserBuildersView, + [ViewName.USER_BY_APP]: createUserAppView, + } + // can pass DB in if working with something specific + if (!db) { + db = getGlobalDB() as PouchDB.Database + } + return queryView(viewName, params, db, CreateFuncByName, opts) +} diff --git a/packages/backend-core/src/tenancy/index.ts b/packages/backend-core/src/tenancy/index.ts index e0006abab2..d1ccbbf001 100644 --- a/packages/backend-core/src/tenancy/index.ts +++ b/packages/backend-core/src/tenancy/index.ts @@ -1,9 +1,11 @@ import * as context from "../context" import * as tenancy from "./tenancy" +import * as utils from "./utils" const pkg = { ...context, ...tenancy, + ...utils, } export = pkg diff --git a/packages/backend-core/src/tenancy/tenancy.ts b/packages/backend-core/src/tenancy/tenancy.ts index 041f694d34..1c71935eb0 100644 --- a/packages/backend-core/src/tenancy/tenancy.ts +++ b/packages/backend-core/src/tenancy/tenancy.ts @@ -1,6 +1,7 @@ import { doWithDB } from "../db" -import { StaticDatabases } from "../db/constants" -import { baseGlobalDBName } from "./utils" +import { queryPlatformView } from "../db/views" +import { StaticDatabases, ViewName } from "../db/constants" +import { getGlobalDBName } from "./utils" import { getTenantId, DEFAULT_TENANT_ID, @@ -8,6 +9,7 @@ import { getTenantIDFromAppID, } from "../context" import env from "../environment" +import { PlatformUser, PlatformUserByEmail } from "@budibase/types" const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name @@ -87,15 +89,6 @@ export const tryAddTenant = async ( }) } -export const getGlobalDBName = (tenantId?: string) => { - // tenant ID can be set externally, for example user API where - // new tenants are being created, this may be the case - if (!tenantId) { - tenantId = getTenantId() - } - return baseGlobalDBName(tenantId) -} - export const doWithGlobalDB = (tenantId: string, cb: any) => { return doWithDB(getGlobalDBName(tenantId), cb) } @@ -116,14 +109,16 @@ export const lookupTenantId = async (userId: string) => { } // lookup, could be email or userId, either will return a doc -export const getTenantUser = async (identifier: string) => { - return doWithDB(PLATFORM_INFO_DB, async (db: any) => { - try { - return await db.get(identifier) - } catch (err) { - return null - } - }) +export const getTenantUser = async ( + identifier: string +): Promise => { + // use the view here and allow to find anyone regardless of casing + // Use lowercase to ensure email login is case insensitive + const response = queryPlatformView(ViewName.PLATFORM_USERS_LOWERCASE, { + keys: [identifier.toLowerCase()], + include_docs: true, + }) as Promise + return response } export const isUserInAppTenant = (appId: string, user: any) => { diff --git a/packages/backend-core/src/tenancy/utils.js b/packages/backend-core/src/tenancy/utils.js deleted file mode 100644 index 70a965ddb7..0000000000 --- a/packages/backend-core/src/tenancy/utils.js +++ /dev/null @@ -1,12 +0,0 @@ -const { DEFAULT_TENANT_ID } = require("../constants") -const { StaticDatabases, SEPARATOR } = require("../db/constants") - -exports.baseGlobalDBName = tenantId => { - let dbName - if (!tenantId || tenantId === DEFAULT_TENANT_ID) { - dbName = StaticDatabases.GLOBAL.name - } else { - dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}` - } - return dbName -} diff --git a/packages/backend-core/src/tenancy/utils.ts b/packages/backend-core/src/tenancy/utils.ts new file mode 100644 index 0000000000..f99f1e30af --- /dev/null +++ b/packages/backend-core/src/tenancy/utils.ts @@ -0,0 +1,22 @@ +import { DEFAULT_TENANT_ID } from "../constants" +import { StaticDatabases, SEPARATOR } from "../db/constants" +import { getTenantId } from "../context" + +export const getGlobalDBName = (tenantId?: string) => { + // tenant ID can be set externally, for example user API where + // new tenants are being created, this may be the case + if (!tenantId) { + tenantId = getTenantId() + } + return baseGlobalDBName(tenantId) +} + +export const baseGlobalDBName = (tenantId: string | undefined | null) => { + let dbName + if (!tenantId || tenantId === DEFAULT_TENANT_ID) { + dbName = StaticDatabases.GLOBAL.name + } else { + dbName = `${tenantId}${SEPARATOR}${StaticDatabases.GLOBAL.name}` + } + return dbName +} diff --git a/packages/backend-core/yarn.lock b/packages/backend-core/yarn.lock index 22c17a9444..2e62aea734 100644 --- a/packages/backend-core/yarn.lock +++ b/packages/backend-core/yarn.lock @@ -1377,6 +1377,11 @@ bcrypt@5.0.1: "@mapbox/node-pre-gyp" "^1.0.0" node-addon-api "^3.1.0" +bcryptjs@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" + integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" diff --git a/packages/bbui/package.json b/packages/bbui/package.json index c05ea9b038..b554bdfc21 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": "1.3.15-alpha.9", + "version": "1.3.18", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.2.1", - "@budibase/string-templates": "1.3.15-alpha.9", + "@budibase/string-templates": "^1.3.18", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", diff --git a/packages/builder/package.json b/packages/builder/package.json index 18e0c56eb1..c28c79054b 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "license": "GPL-3.0", "private": true, "scripts": { @@ -69,10 +69,10 @@ } }, "dependencies": { - "@budibase/bbui": "1.3.15-alpha.9", - "@budibase/client": "1.3.15-alpha.9", - "@budibase/frontend-core": "1.3.15-alpha.9", - "@budibase/string-templates": "1.3.15-alpha.9", + "@budibase/bbui": "^1.3.18", + "@budibase/client": "^1.3.18", + "@budibase/frontend-core": "^1.3.18", + "@budibase/string-templates": "^1.3.18", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte b/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte index 0b6a48ff9d..9c47178b0e 100644 --- a/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/QueryParamSelector.svelte @@ -50,7 +50,6 @@ type="string" {bindings} fillWidth={true} - allowJS={false} /> {/each} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json index acd28c6a41..671637f381 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/componentStructure.json @@ -23,7 +23,8 @@ "dataprovider", "repeater", "table", - "dynamicfilter" + "dynamicfilter", + "daterangepicker" ] }, { @@ -60,7 +61,6 @@ "booleanfield", "longformfield", "attachmentfield", - "daterangepicker", "jsonfield", "relationshipfield", "datetimefield", @@ -80,5 +80,4 @@ "donut" ] } -] - +] \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index 32c377e842..5e7506a03e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { @@ -26,9 +26,9 @@ "outputPath": "build" }, "dependencies": { - "@budibase/backend-core": "1.3.15-alpha.9", - "@budibase/string-templates": "1.3.15-alpha.9", - "@budibase/types": "1.3.15-alpha.9", + "@budibase/backend-core": "1.3.18", + "@budibase/string-templates": "1.3.18", + "@budibase/types": "1.3.18", "axios": "0.21.2", "chalk": "4.1.0", "cli-progress": "3.11.2", diff --git a/packages/client/package.json b/packages/client/package.json index 0ed9b93428..a3c6cd0fef 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "1.3.15-alpha.9", - "@budibase/frontend-core": "1.3.15-alpha.9", - "@budibase/string-templates": "1.3.15-alpha.9", + "@budibase/bbui": "^1.3.18", + "@budibase/frontend-core": "^1.3.18", + "@budibase/string-templates": "^1.3.18", "@spectrum-css/button": "^3.0.3", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index 7c915469b3..aab5cb7bd9 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -1,12 +1,12 @@ { "name": "@budibase/frontend-core", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "description": "Budibase frontend core libraries used in builder and client", "author": "Budibase", "license": "MPL-2.0", "svelte": "src/index.js", "dependencies": { - "@budibase/bbui": "1.3.15-alpha.9", + "@budibase/bbui": "^1.3.18", "lodash": "^4.17.21", "svelte": "^3.46.2" } diff --git a/packages/server/package.json b/packages/server/package.json index f951743347..57c51bc626 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -77,11 +77,11 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "10.0.3", - "@budibase/backend-core": "1.3.15-alpha.9", - "@budibase/client": "1.3.15-alpha.9", - "@budibase/pro": "1.3.15-alpha.9", - "@budibase/string-templates": "1.3.15-alpha.9", - "@budibase/types": "1.3.15-alpha.9", + "@budibase/backend-core": "^1.3.18", + "@budibase/client": "^1.3.18", + "@budibase/pro": "1.3.18", + "@budibase/string-templates": "^1.3.18", + "@budibase/types": "^1.3.18", "@bull-board/api": "3.7.0", "@bull-board/koa": "3.9.4", "@elastic/elasticsearch": "7.10.0", diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index dd5778b9d9..dc62453f77 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -371,7 +371,10 @@ module External { const toColumn = `${linkedTable.name}.${relationship.to}` // this is important when working with multiple relationships // between the same tables, don't want to overlap/multiply the relations - if (!relationship.through && row[fromColumn] !== row[toColumn]) { + if ( + !relationship.through && + row[fromColumn]?.toString() !== row[toColumn]?.toString() + ) { continue } let linked = basicProcessing(row, linkedTable) diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js index efec280b26..ef8e674a86 100644 --- a/packages/server/src/environment.js +++ b/packages/server/src/environment.js @@ -37,8 +37,8 @@ function parseIntSafe(number) { let inThread = false module.exports = { - // important - PORT: process.env.PORT || process.env.APP_PORT, + // important - prefer app port to generic port + PORT: process.env.APP_PORT || process.env.PORT, JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL, MINIO_URL: process.env.MINIO_URL, diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js index 1001cdcdb1..96d8c85e0b 100644 --- a/packages/server/src/utilities/fileSystem/index.js +++ b/packages/server/src/utilities/fileSystem/index.js @@ -49,7 +49,15 @@ const DATASOURCE_PATH = join(budibaseTempDir(), "datasource") exports.init = () => { const tempDir = budibaseTempDir() if (!fs.existsSync(tempDir)) { - fs.mkdirSync(tempDir) + // some test cases fire this quickly enough that + // synchronous cases can end up here at the same time + try { + fs.mkdirSync(tempDir) + } catch (err) { + if (!err || err.code !== "EEXIST") { + throw err + } + } } const clientLibPath = join(budibaseTempDir(), "budibase-client.js") if (env.isTest() && !fs.existsSync(clientLibPath)) { diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 62cc591d47..2df10f1a2c 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1094,17 +1094,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.3.15-alpha.9": - version "1.3.15-alpha.9" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.15-alpha.9.tgz#06c582d1aac93ba689a9738ea35939ccc5bac430" - integrity sha512-uShQQVPE2EsfPY6BNZnl/yKQoR8VwJyiIfCNE/EYl5aIVvhrqva+vA7JeYAs4sf/vf8Y5kkZaJGzfvzEws4dHg== +"@budibase/backend-core@1.3.18": + version "1.3.18" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.18.tgz#5ef26e3283f46b321103766b0359ca72035c8942" + integrity sha512-utux5ydlnejKavU2H2HlCJkIyymVmGj+rVJp+SR1JrtPW2wkzHzpYn2k9eXFOYnAwqTF8jpCj4FVOEeDTfQcbw== dependencies: - "@budibase/types" "1.3.15-alpha.9" + "@budibase/types" "^1.3.18" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-sdk "2.1030.0" bcrypt "5.0.1" - bcryptjs "2.4.3" dotenv "16.0.1" emitter-listener "1.1.2" ioredis "4.28.0" @@ -1180,13 +1179,13 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" -"@budibase/pro@1.3.15-alpha.9": - version "1.3.15-alpha.9" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.15-alpha.9.tgz#cb0ab3041df4c412eed9a8c7950f20ca0e3b62c5" - integrity sha512-1eE7O/bYeHxSoG8V61B2OZN3bGD44RAinAFJLHvBK7ncHZSTOOH4fe5HLvFiWPm0gV5ONDHpqr9W5Vs6ZBXukQ== +"@budibase/pro@1.3.18": + version "1.3.18" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.18.tgz#35a59e86c00971dd77a0b196e381a5ffd3be767c" + integrity sha512-aFGUr/ut4Ki56qLSdFaDLoEIPmwMqT5dGmY1xBT3Bwvhoq7hXevk1bcPFGj0rWBJnYG9zg/jmik9V4mE/lUOag== dependencies: - "@budibase/backend-core" "1.3.15-alpha.9" - "@budibase/types" "1.3.15-alpha.9" + "@budibase/backend-core" "1.3.18" + "@budibase/types" "1.3.18" "@koa/router" "8.0.8" joi "17.6.0" node-fetch "^2.6.1" @@ -1209,10 +1208,10 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" -"@budibase/types@1.3.15-alpha.9": - version "1.3.15-alpha.9" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.15-alpha.9.tgz#443cab0ca757a37af7a45f99caac35643fe273e5" - integrity sha512-r1Z2PLPF3nR/bGzVyZ0/6RcOYb4Csmk4J/b1KJ57wzTeDdsRwgVgGO6QS+XYdwZctsKDNOm9FvpIQj3tNM8Rjw== +"@budibase/types@1.3.18", "@budibase/types@^1.3.18": + version "1.3.18" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.18.tgz#cd65213f476a2f37065796d0ccba18c8e8e07504" + integrity sha512-bMtS2RbbI3Ztl5aAwEVlsK+2PQ0vRSZm8n6YDRjC9Arc94B4GDl/sqowRndKYqVtUeA/gVW0HH8x+qzhuMX6Dw== "@bull-board/api@3.7.0": version "3.7.0" @@ -2325,6 +2324,11 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + "@spectrum-css/actionbutton@^1.0.1": version "1.1.14" resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.1.14.tgz#4e12eb7f482fb5944c3d97547591964baebeb1d4" @@ -2676,11 +2680,6 @@ resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== -"@types/component-emitter@^1.2.10": - version "1.2.11" - resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.11.tgz#50d47d42b347253817a39709fef03ce66a108506" - integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== - "@types/connect@*": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -2927,9 +2926,9 @@ integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== "@types/node@>=10.0.0": - version "18.7.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.6.tgz#31743bc5772b6ac223845e18c3fc26f042713c83" - integrity sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A== + version "18.7.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154" + integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== "@types/node@>=8.0.0 <15": version "14.18.21" @@ -4800,7 +4799,7 @@ commoner@^0.10.1: q "^1.1.2" recast "^0.11.17" -component-emitter@^1.2.0, component-emitter@^1.2.1, component-emitter@~1.3.0: +component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -12849,26 +12848,25 @@ socket.io-adapter@~2.4.0: resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6" integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== -socket.io-parser@~4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.5.tgz#cb404382c32324cc962f27f3a44058cf6e0552df" - integrity sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig== +socket.io-parser@~4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5" + integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g== dependencies: - "@types/component-emitter" "^1.2.10" - component-emitter "~1.3.0" + "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" socket.io@^4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.1.tgz#aa7e73f8a6ce20ee3c54b2446d321bbb6b1a9029" - integrity sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ== + version "4.5.2" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.2.tgz#1eb25fd380ab3d63470aa8279f8e48d922d443ac" + integrity sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ== dependencies: accepts "~1.3.4" base64id "~2.0.0" debug "~4.3.2" engine.io "~6.2.0" socket.io-adapter "~2.4.0" - socket.io-parser "~4.0.4" + socket.io-parser "~4.2.0" sonic-boom@^1.0.2: version "1.4.1" diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 40958b3434..dc251d741a 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/types/package.json b/packages/types/package.json index b42417b229..bc59e79328 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/types", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "description": "Budibase types", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/types/src/documents/platform/users.ts b/packages/types/src/documents/platform/users.ts index 1b65ea42f2..46cc44b31d 100644 --- a/packages/types/src/documents/platform/users.ts +++ b/packages/types/src/documents/platform/users.ts @@ -7,3 +7,12 @@ export interface PlatformUserByEmail extends Document { tenantId: string userId: string } + +/** + * doc id is userId + */ +export interface PlatformUserById extends Document { + tenantId: string +} + +export type PlatformUser = PlatformUserByEmail | PlatformUserById diff --git a/packages/types/src/sdk/migrations.ts b/packages/types/src/sdk/migrations.ts index 23a4d6d097..5ad5ccb87c 100644 --- a/packages/types/src/sdk/migrations.ts +++ b/packages/types/src/sdk/migrations.ts @@ -47,6 +47,7 @@ export enum MigrationName { EVENT_GLOBAL_BACKFILL = "event_global_backfill", EVENT_INSTALLATION_BACKFILL = "event_installation_backfill", GLOBAL_INFO_SYNC_USERS = "global_info_sync_users", + PLATFORM_USERS_EMAIL_CASING = "platform_users_email_casing", } export interface MigrationDefinition { diff --git a/packages/worker/package.json b/packages/worker/package.json index 7495d39eeb..c4ce118996 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "1.3.15-alpha.9", + "version": "1.3.18", "description": "Budibase background service", "main": "src/index.ts", "repository": { @@ -23,6 +23,7 @@ "dev:stack:init": "node ./scripts/dev/manage.js init", "dev:builder": "npm run dev:stack:init && nodemon", "test": "jest --runInBand", + "test:watch": "jest --watch", "env:multi:enable": "node scripts/multiTenancy.js enable", "env:multi:disable": "node scripts/multiTenancy.js disable", "env:selfhost:enable": "node scripts/selfhost.js enable", @@ -35,10 +36,10 @@ "author": "Budibase", "license": "GPL-3.0", "dependencies": { - "@budibase/backend-core": "1.3.15-alpha.9", - "@budibase/pro": "1.3.15-alpha.9", - "@budibase/string-templates": "1.3.15-alpha.9", - "@budibase/types": "1.3.15-alpha.9", + "@budibase/backend-core": "^1.3.18", + "@budibase/pro": "1.3.18", + "@budibase/string-templates": "^1.3.18", + "@budibase/types": "^1.3.18", "@koa/router": "8.0.8", "@sentry/node": "6.17.7", "@techpass/passport-openidconnect": "0.3.2", diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index 7762c2e7e2..fd9ef7ff9f 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -242,6 +242,26 @@ describe("/api/global/users", () => { expect(response.body.message).toBe(`Unavailable`) expect(events.user.created).toBeCalledTimes(0) }) + + it("should not be able to create a user with the same email and different casing", async () => { + const user = structures.users.user() + await api.users.saveUser(user) + + user.email = user.email.toUpperCase() + await api.users.saveUser(user, 400) + + expect(events.user.created).toBeCalledTimes(1) + }) + + it("should not be able to bulk create a user with the same email and different casing", async () => { + const user = structures.users.user() + await api.users.saveUser(user) + + user.email = user.email.toUpperCase() + await api.users.bulkCreateUsers([user]) + + expect(events.user.created).toBeCalledTimes(1) + }) }) describe("update", () => { diff --git a/packages/worker/src/environment.ts b/packages/worker/src/environment.ts index fd6749a0f7..1e6d0da637 100644 --- a/packages/worker/src/environment.ts +++ b/packages/worker/src/environment.ts @@ -43,7 +43,8 @@ const env = { PLATFORM_URL: process.env.PLATFORM_URL, APPS_URL: process.env.APPS_URL, // ports - PORT: process.env.PORT || process.env.WORKER_PORT, + // prefer worker port to generic port + PORT: process.env.WORKER_PORT || process.env.PORT, CLUSTER_PORT: process.env.CLUSTER_PORT, // flags NODE_ENV: process.env.NODE_ENV, diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index a8944b936a..83b0510333 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -278,39 +278,61 @@ export const addTenant = async ( } const getExistingTenantUsers = async (emails: string[]): Promise => { - return dbUtils.queryGlobalView(ViewName.USER_BY_EMAIL, { - keys: emails, + const lcEmails = emails.map(email => email.toLowerCase()) + const params = { + keys: lcEmails, include_docs: true, + } + + const opts = { arrayResponse: true, - }) + } + + return dbUtils.queryGlobalView( + ViewName.USER_BY_EMAIL, + params, + undefined, + opts + ) as Promise } const getExistingPlatformUsers = async ( emails: string[] ): Promise => { - return dbUtils.doWithDB( - StaticDatabases.PLATFORM_INFO.name, - async (infoDb: any) => { - const response: AllDocsResponse = - await infoDb.allDocs({ - keys: emails, - include_docs: true, - }) - return response.rows - .filter(row => row.doc && (row.error !== "not_found") !== null) - .map((row: any) => row.doc) - } - ) + const lcEmails = emails.map(email => email.toLowerCase()) + const params = { + keys: lcEmails, + include_docs: true, + } + + const opts = { + arrayResponse: true, + } + return dbUtils.queryPlatformView( + ViewName.PLATFORM_USERS_LOWERCASE, + params, + opts + ) as Promise } const getExistingAccounts = async ( emails: string[] ): Promise => { - return dbUtils.queryPlatformView(ViewName.ACCOUNT_BY_EMAIL, { - keys: emails, + const lcEmails = emails.map(email => email.toLowerCase()) + const params = { + keys: lcEmails, include_docs: true, + } + + const opts = { arrayResponse: true, - }) + } + + return dbUtils.queryPlatformView( + ViewName.ACCOUNT_BY_EMAIL, + params, + opts + ) as Promise } /** @@ -332,7 +354,7 @@ const searchExistingEmails = async (emails: string[]) => { const existingAccounts = await getExistingAccounts(emails) matchedEmails.push(...existingAccounts.map(account => account.email)) - return [...new Set(matchedEmails)] + return [...new Set(matchedEmails.map(email => email.toLowerCase()))] } export const bulkCreate = async ( @@ -351,8 +373,10 @@ export const bulkCreate = async ( for (const newUser of newUsersRequested) { if ( - newUsers.find((x: any) => x.email === newUser.email) || - existingEmails.includes(newUser.email) + newUsers.find( + (x: User) => x.email.toLowerCase() === newUser.email.toLowerCase() + ) || + existingEmails.includes(newUser.email.toLowerCase()) ) { unsuccessful.push({ email: newUser.email, diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index 828b02f1b8..b6dd949d64 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -291,17 +291,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.3.15-alpha.9": - version "1.3.15-alpha.9" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.15-alpha.9.tgz#06c582d1aac93ba689a9738ea35939ccc5bac430" - integrity sha512-uShQQVPE2EsfPY6BNZnl/yKQoR8VwJyiIfCNE/EYl5aIVvhrqva+vA7JeYAs4sf/vf8Y5kkZaJGzfvzEws4dHg== +"@budibase/backend-core@1.3.18": + version "1.3.18" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.18.tgz#5ef26e3283f46b321103766b0359ca72035c8942" + integrity sha512-utux5ydlnejKavU2H2HlCJkIyymVmGj+rVJp+SR1JrtPW2wkzHzpYn2k9eXFOYnAwqTF8jpCj4FVOEeDTfQcbw== dependencies: - "@budibase/types" "1.3.15-alpha.9" + "@budibase/types" "^1.3.18" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-sdk "2.1030.0" bcrypt "5.0.1" - bcryptjs "2.4.3" dotenv "16.0.1" emitter-listener "1.1.2" ioredis "4.28.0" @@ -327,21 +326,21 @@ uuid "8.3.2" zlib "1.0.5" -"@budibase/pro@1.3.15-alpha.9": - version "1.3.15-alpha.9" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.15-alpha.9.tgz#cb0ab3041df4c412eed9a8c7950f20ca0e3b62c5" - integrity sha512-1eE7O/bYeHxSoG8V61B2OZN3bGD44RAinAFJLHvBK7ncHZSTOOH4fe5HLvFiWPm0gV5ONDHpqr9W5Vs6ZBXukQ== +"@budibase/pro@1.3.18": + version "1.3.18" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.18.tgz#35a59e86c00971dd77a0b196e381a5ffd3be767c" + integrity sha512-aFGUr/ut4Ki56qLSdFaDLoEIPmwMqT5dGmY1xBT3Bwvhoq7hXevk1bcPFGj0rWBJnYG9zg/jmik9V4mE/lUOag== dependencies: - "@budibase/backend-core" "1.3.15-alpha.9" - "@budibase/types" "1.3.15-alpha.9" + "@budibase/backend-core" "1.3.18" + "@budibase/types" "1.3.18" "@koa/router" "8.0.8" joi "17.6.0" node-fetch "^2.6.1" -"@budibase/types@1.3.15-alpha.9": - version "1.3.15-alpha.9" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.15-alpha.9.tgz#443cab0ca757a37af7a45f99caac35643fe273e5" - integrity sha512-r1Z2PLPF3nR/bGzVyZ0/6RcOYb4Csmk4J/b1KJ57wzTeDdsRwgVgGO6QS+XYdwZctsKDNOm9FvpIQj3tNM8Rjw== +"@budibase/types@1.3.18", "@budibase/types@^1.3.18": + version "1.3.18" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.18.tgz#cd65213f476a2f37065796d0ccba18c8e8e07504" + integrity sha512-bMtS2RbbI3Ztl5aAwEVlsK+2PQ0vRSZm8n6YDRjC9Arc94B4GDl/sqowRndKYqVtUeA/gVW0HH8x+qzhuMX6Dw== "@cspotcode/source-map-consumer@0.8.0": version "0.8.0"