Merge branch 'master' into develop

This commit is contained in:
Rory Powell 2022-09-15 10:19:56 +01:00
commit 0f98f2fcaa
37 changed files with 543 additions and 374 deletions

View File

@ -9,7 +9,11 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then
chown -R couchdb:couchdb $DATA_DIR/couch/ chown -R couchdb:couchdb $DATA_DIR/couch/
apt update apt update
apt-get install -y openssh-server 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 /etc/init.d/ssh restart
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini

View File

@ -29,23 +29,8 @@ ENV TARGETBUILD $TARGETBUILD
COPY --from=build /app /app COPY --from=build /app /app
COPY --from=build /worker /worker COPY --from=build /worker /worker
ENV \ # ENV CUSTOM_DOMAIN=budi001.custom.com \
APP_PORT=4001 \ # See runner.sh for Env Vars
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
# These secret env variables are generated by the runner at startup # These secret env variables are generated by the runner at startup
# their values can be overriden by the user, they will be written # their values can be overriden by the user, they will be written
# to the .env file in the /data directory for use later on # 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 # Script below sets the path for storing data based on $DATA_DIR
# For Azure App Service install SSH & point data locations to /home # 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 RUN /build-target-paths.sh
# cleanup cache # cleanup cache
@ -124,6 +111,8 @@ RUN yarn cache clean -f
EXPOSE 80 EXPOSE 80
EXPOSE 443 EXPOSE 443
# Expose port 2222 for SSH on Azure App Service build
EXPOSE 2222
VOLUME /data VOLUME /data
# setup letsencrypt certificate # setup letsencrypt certificate

View File

@ -1,6 +1,21 @@
#!/bin/bash #!/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 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 # Azure App Service customisations
if [[ "${TARGETBUILD}" = "aas" ]]; then if [[ "${TARGETBUILD}" = "aas" ]]; then
DATA_DIR=/home DATA_DIR=/home
@ -10,9 +25,10 @@ else
fi fi
if [ -f "${DATA_DIR}/.env" ]; then 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 fi
# first randomise any unset environment variables # randomise any unset environment variables
for ENV_VAR in "${ENV_VARS[@]}" for ENV_VAR in "${ENV_VARS[@]}"
do do
temp=$(eval "echo \$$ENV_VAR") temp=$(eval "echo \$$ENV_VAR")
@ -30,11 +46,18 @@ if [ ! -f "${DATA_DIR}/.env" ]; then
temp=$(eval "echo \$$ENV_VAR") temp=$(eval "echo \$$ENV_VAR")
echo "$ENV_VAR=$temp" >> ${DATA_DIR}/.env echo "$ENV_VAR=$temp" >> ${DATA_DIR}/.env
done 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 echo "COUCH_DB_URL=${COUCH_DB_URL}" >> ${DATA_DIR}/.env
fi 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 # make these directories in runner, incase of mount
mkdir -p ${DATA_DIR}/couch/{dbs,views} mkdir -p ${DATA_DIR}/couch/{dbs,views}
mkdir -p ${DATA_DIR}/minio mkdir -p ${DATA_DIR}/minio

View File

@ -0,0 +1,8 @@
#!/bin/sh
ssh-keygen -A
#prepare run dir
if [ ! -d "/var/run/sshd" ]; then
mkdir -p /var/run/sshd
fi

View File

@ -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

View File

@ -1,5 +1,5 @@
{ {
"version": "1.3.15-alpha.9", "version": "1.3.18",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -20,7 +20,7 @@
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
}, },
"dependencies": { "dependencies": {
"@budibase/types": "1.3.15-alpha.9", "@budibase/types": "^1.3.18",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0", "aws-sdk": "2.1030.0",

View File

@ -19,6 +19,7 @@ export enum ViewName {
ROUTING = "screen_routes", ROUTING = "screen_routes",
AUTOMATION_LOGS = "automation_logs", AUTOMATION_LOGS = "automation_logs",
ACCOUNT_BY_EMAIL = "account_by_email", ACCOUNT_BY_EMAIL = "account_by_email",
PLATFORM_USERS_LOWERCASE = "platform_users_lowercase",
} }
export const DeprecatedViews = { export const DeprecatedViews = {

View File

@ -2,7 +2,8 @@ import { newid } from "../hashing"
import { DEFAULT_TENANT_ID, Configs } from "../constants" import { DEFAULT_TENANT_ID, Configs } from "../constants"
import env from "../environment" import env from "../environment"
import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants" 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 fetch from "node-fetch"
import { doWithDB, allDbs } from "./index" import { doWithDB, allDbs } from "./index"
import { getCouchInfo } from "./pouch" import { getCouchInfo } from "./pouch"

View File

@ -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)
}

View File

@ -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<DesignDocument>(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<DesignDocument>(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<DesignDocument>("_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<DesignDocument>(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 <T>(
viewName: ViewName,
params: PouchDB.Query.Options<T, T>,
db: PouchDB.Database,
CreateFuncByName: any,
opts?: QueryViewOptions
): Promise<T[] | T | undefined> => {
try {
let response = await db.query<T, T>(`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 <T>(
viewName: ViewName,
params: PouchDB.Query.Options<T, T>,
opts?: QueryViewOptions
): Promise<T[] | T | undefined> => {
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 <T>(
viewName: ViewName,
params: PouchDB.Query.Options<T, T>,
db?: PouchDB.Database,
opts?: QueryViewOptions
): Promise<T[] | T | undefined> => {
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)
}

View File

@ -1,9 +1,11 @@
import * as context from "../context" import * as context from "../context"
import * as tenancy from "./tenancy" import * as tenancy from "./tenancy"
import * as utils from "./utils"
const pkg = { const pkg = {
...context, ...context,
...tenancy, ...tenancy,
...utils,
} }
export = pkg export = pkg

View File

@ -1,6 +1,7 @@
import { doWithDB } from "../db" import { doWithDB } from "../db"
import { StaticDatabases } from "../db/constants" import { queryPlatformView } from "../db/views"
import { baseGlobalDBName } from "./utils" import { StaticDatabases, ViewName } from "../db/constants"
import { getGlobalDBName } from "./utils"
import { import {
getTenantId, getTenantId,
DEFAULT_TENANT_ID, DEFAULT_TENANT_ID,
@ -8,6 +9,7 @@ import {
getTenantIDFromAppID, getTenantIDFromAppID,
} from "../context" } from "../context"
import env from "../environment" import env from "../environment"
import { PlatformUser, PlatformUserByEmail } from "@budibase/types"
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name 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) => { export const doWithGlobalDB = (tenantId: string, cb: any) => {
return doWithDB(getGlobalDBName(tenantId), cb) 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 // lookup, could be email or userId, either will return a doc
export const getTenantUser = async (identifier: string) => { export const getTenantUser = async (
return doWithDB(PLATFORM_INFO_DB, async (db: any) => { identifier: string
try { ): Promise<PlatformUser | null> => {
return await db.get(identifier) // use the view here and allow to find anyone regardless of casing
} catch (err) { // Use lowercase to ensure email login is case insensitive
return null const response = queryPlatformView(ViewName.PLATFORM_USERS_LOWERCASE, {
} keys: [identifier.toLowerCase()],
}) include_docs: true,
}) as Promise<PlatformUser>
return response
} }
export const isUserInAppTenant = (appId: string, user: any) => { export const isUserInAppTenant = (appId: string, user: any) => {

View File

@ -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
}

View File

@ -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
}

View File

@ -1377,6 +1377,11 @@ bcrypt@5.0.1:
"@mapbox/node-pre-gyp" "^1.0.0" "@mapbox/node-pre-gyp" "^1.0.0"
node-addon-api "^3.1.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: binary-extensions@^2.0.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1", "@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/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2", "@spectrum-css/avatar": "^3.0.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -69,10 +69,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.15-alpha.9", "@budibase/bbui": "^1.3.18",
"@budibase/client": "1.3.15-alpha.9", "@budibase/client": "^1.3.18",
"@budibase/frontend-core": "1.3.15-alpha.9", "@budibase/frontend-core": "^1.3.18",
"@budibase/string-templates": "1.3.15-alpha.9", "@budibase/string-templates": "^1.3.18",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -50,7 +50,6 @@
type="string" type="string"
{bindings} {bindings}
fillWidth={true} fillWidth={true}
allowJS={false}
/> />
{/each} {/each}
</div> </div>

View File

@ -23,7 +23,8 @@
"dataprovider", "dataprovider",
"repeater", "repeater",
"table", "table",
"dynamicfilter" "dynamicfilter",
"daterangepicker"
] ]
}, },
{ {
@ -60,7 +61,6 @@
"booleanfield", "booleanfield",
"longformfield", "longformfield",
"attachmentfield", "attachmentfield",
"daterangepicker",
"jsonfield", "jsonfield",
"relationshipfield", "relationshipfield",
"datetimefield", "datetimefield",
@ -81,4 +81,3 @@
] ]
} }
] ]

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {
@ -26,9 +26,9 @@
"outputPath": "build" "outputPath": "build"
}, },
"dependencies": { "dependencies": {
"@budibase/backend-core": "1.3.15-alpha.9", "@budibase/backend-core": "1.3.18",
"@budibase/string-templates": "1.3.15-alpha.9", "@budibase/string-templates": "1.3.18",
"@budibase/types": "1.3.15-alpha.9", "@budibase/types": "1.3.18",
"axios": "0.21.2", "axios": "0.21.2",
"chalk": "4.1.0", "chalk": "4.1.0",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.15-alpha.9", "@budibase/bbui": "^1.3.18",
"@budibase/frontend-core": "1.3.15-alpha.9", "@budibase/frontend-core": "^1.3.18",
"@budibase/string-templates": "1.3.15-alpha.9", "@budibase/string-templates": "^1.3.18",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View File

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "1.3.15-alpha.9", "@budibase/bbui": "^1.3.18",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -77,11 +77,11 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.0.3", "@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "1.3.15-alpha.9", "@budibase/backend-core": "^1.3.18",
"@budibase/client": "1.3.15-alpha.9", "@budibase/client": "^1.3.18",
"@budibase/pro": "1.3.15-alpha.9", "@budibase/pro": "1.3.18",
"@budibase/string-templates": "1.3.15-alpha.9", "@budibase/string-templates": "^1.3.18",
"@budibase/types": "1.3.15-alpha.9", "@budibase/types": "^1.3.18",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View File

@ -371,7 +371,10 @@ module External {
const toColumn = `${linkedTable.name}.${relationship.to}` const toColumn = `${linkedTable.name}.${relationship.to}`
// this is important when working with multiple relationships // this is important when working with multiple relationships
// between the same tables, don't want to overlap/multiply the relations // 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 continue
} }
let linked = basicProcessing(row, linkedTable) let linked = basicProcessing(row, linkedTable)

View File

@ -37,8 +37,8 @@ function parseIntSafe(number) {
let inThread = false let inThread = false
module.exports = { module.exports = {
// important // important - prefer app port to generic port
PORT: process.env.PORT || process.env.APP_PORT, PORT: process.env.APP_PORT || process.env.PORT,
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,
COUCH_DB_URL: process.env.COUCH_DB_URL, COUCH_DB_URL: process.env.COUCH_DB_URL,
MINIO_URL: process.env.MINIO_URL, MINIO_URL: process.env.MINIO_URL,

View File

@ -49,7 +49,15 @@ const DATASOURCE_PATH = join(budibaseTempDir(), "datasource")
exports.init = () => { exports.init = () => {
const tempDir = budibaseTempDir() const tempDir = budibaseTempDir()
if (!fs.existsSync(tempDir)) { 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") const clientLibPath = join(budibaseTempDir(), "budibase-client.js")
if (env.isTest() && !fs.existsSync(clientLibPath)) { if (env.isTest() && !fs.existsSync(clientLibPath)) {

View File

@ -1094,17 +1094,16 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.3.15-alpha.9": "@budibase/backend-core@1.3.18":
version "1.3.15-alpha.9" version "1.3.18"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.15-alpha.9.tgz#06c582d1aac93ba689a9738ea35939ccc5bac430" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.18.tgz#5ef26e3283f46b321103766b0359ca72035c8942"
integrity sha512-uShQQVPE2EsfPY6BNZnl/yKQoR8VwJyiIfCNE/EYl5aIVvhrqva+vA7JeYAs4sf/vf8Y5kkZaJGzfvzEws4dHg== integrity sha512-utux5ydlnejKavU2H2HlCJkIyymVmGj+rVJp+SR1JrtPW2wkzHzpYn2k9eXFOYnAwqTF8jpCj4FVOEeDTfQcbw==
dependencies: dependencies:
"@budibase/types" "1.3.15-alpha.9" "@budibase/types" "^1.3.18"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0" aws-sdk "2.1030.0"
bcrypt "5.0.1" bcrypt "5.0.1"
bcryptjs "2.4.3"
dotenv "16.0.1" dotenv "16.0.1"
emitter-listener "1.1.2" emitter-listener "1.1.2"
ioredis "4.28.0" ioredis "4.28.0"
@ -1180,13 +1179,13 @@
svelte-flatpickr "^3.2.3" svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0" svelte-portal "^1.0.0"
"@budibase/pro@1.3.15-alpha.9": "@budibase/pro@1.3.18":
version "1.3.15-alpha.9" version "1.3.18"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.15-alpha.9.tgz#cb0ab3041df4c412eed9a8c7950f20ca0e3b62c5" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.18.tgz#35a59e86c00971dd77a0b196e381a5ffd3be767c"
integrity sha512-1eE7O/bYeHxSoG8V61B2OZN3bGD44RAinAFJLHvBK7ncHZSTOOH4fe5HLvFiWPm0gV5ONDHpqr9W5Vs6ZBXukQ== integrity sha512-aFGUr/ut4Ki56qLSdFaDLoEIPmwMqT5dGmY1xBT3Bwvhoq7hXevk1bcPFGj0rWBJnYG9zg/jmik9V4mE/lUOag==
dependencies: dependencies:
"@budibase/backend-core" "1.3.15-alpha.9" "@budibase/backend-core" "1.3.18"
"@budibase/types" "1.3.15-alpha.9" "@budibase/types" "1.3.18"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
joi "17.6.0" joi "17.6.0"
node-fetch "^2.6.1" node-fetch "^2.6.1"
@ -1209,10 +1208,10 @@
svelte-apexcharts "^1.0.2" svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0" svelte-flatpickr "^3.1.0"
"@budibase/types@1.3.15-alpha.9": "@budibase/types@1.3.18", "@budibase/types@^1.3.18":
version "1.3.15-alpha.9" version "1.3.18"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.15-alpha.9.tgz#443cab0ca757a37af7a45f99caac35643fe273e5" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.18.tgz#cd65213f476a2f37065796d0ccba18c8e8e07504"
integrity sha512-r1Z2PLPF3nR/bGzVyZ0/6RcOYb4Csmk4J/b1KJ57wzTeDdsRwgVgGO6QS+XYdwZctsKDNOm9FvpIQj3tNM8Rjw== integrity sha512-bMtS2RbbI3Ztl5aAwEVlsK+2PQ0vRSZm8n6YDRjC9Arc94B4GDl/sqowRndKYqVtUeA/gVW0HH8x+qzhuMX6Dw==
"@bull-board/api@3.7.0": "@bull-board/api@3.7.0":
version "3.7.0" version "3.7.0"
@ -2325,6 +2324,11 @@
dependencies: dependencies:
"@sinonjs/commons" "^1.7.0" "@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": "@spectrum-css/actionbutton@^1.0.1":
version "1.1.14" version "1.1.14"
resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.1.14.tgz#4e12eb7f482fb5944c3d97547591964baebeb1d4" 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" resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== 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@*": "@types/connect@*":
version "3.4.35" version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@ -2927,9 +2926,9 @@
integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==
"@types/node@>=10.0.0": "@types/node@>=10.0.0":
version "18.7.6" version "18.7.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.6.tgz#31743bc5772b6ac223845e18c3fc26f042713c83" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154"
integrity sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A== integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==
"@types/node@>=8.0.0 <15": "@types/node@>=8.0.0 <15":
version "14.18.21" version "14.18.21"
@ -4800,7 +4799,7 @@ commoner@^0.10.1:
q "^1.1.2" q "^1.1.2"
recast "^0.11.17" 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" version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== 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" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6"
integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==
socket.io-parser@~4.0.4: socket.io-parser@~4.2.0:
version "4.0.5" version "4.2.1"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.5.tgz#cb404382c32324cc962f27f3a44058cf6e0552df" resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5"
integrity sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig== integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==
dependencies: dependencies:
"@types/component-emitter" "^1.2.10" "@socket.io/component-emitter" "~3.1.0"
component-emitter "~1.3.0"
debug "~4.3.1" debug "~4.3.1"
socket.io@^4.5.1: socket.io@^4.5.1:
version "4.5.1" version "4.5.2"
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.1.tgz#aa7e73f8a6ce20ee3c54b2446d321bbb6b1a9029" resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.2.tgz#1eb25fd380ab3d63470aa8279f8e48d922d443ac"
integrity sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ== integrity sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ==
dependencies: dependencies:
accepts "~1.3.4" accepts "~1.3.4"
base64id "~2.0.0" base64id "~2.0.0"
debug "~4.3.2" debug "~4.3.2"
engine.io "~6.2.0" engine.io "~6.2.0"
socket.io-adapter "~2.4.0" socket.io-adapter "~2.4.0"
socket.io-parser "~4.0.4" socket.io-parser "~4.2.0"
sonic-boom@^1.0.2: sonic-boom@^1.0.2:
version "1.4.1" version "1.4.1"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/types", "name": "@budibase/types",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"description": "Budibase types", "description": "Budibase types",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -7,3 +7,12 @@ export interface PlatformUserByEmail extends Document {
tenantId: string tenantId: string
userId: string userId: string
} }
/**
* doc id is userId
*/
export interface PlatformUserById extends Document {
tenantId: string
}
export type PlatformUser = PlatformUserByEmail | PlatformUserById

View File

@ -47,6 +47,7 @@ export enum MigrationName {
EVENT_GLOBAL_BACKFILL = "event_global_backfill", EVENT_GLOBAL_BACKFILL = "event_global_backfill",
EVENT_INSTALLATION_BACKFILL = "event_installation_backfill", EVENT_INSTALLATION_BACKFILL = "event_installation_backfill",
GLOBAL_INFO_SYNC_USERS = "global_info_sync_users", GLOBAL_INFO_SYNC_USERS = "global_info_sync_users",
PLATFORM_USERS_EMAIL_CASING = "platform_users_email_casing",
} }
export interface MigrationDefinition { export interface MigrationDefinition {

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.3.15-alpha.9", "version": "1.3.18",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -23,6 +23,7 @@
"dev:stack:init": "node ./scripts/dev/manage.js init", "dev:stack:init": "node ./scripts/dev/manage.js init",
"dev:builder": "npm run dev:stack:init && nodemon", "dev:builder": "npm run dev:stack:init && nodemon",
"test": "jest --runInBand", "test": "jest --runInBand",
"test:watch": "jest --watch",
"env:multi:enable": "node scripts/multiTenancy.js enable", "env:multi:enable": "node scripts/multiTenancy.js enable",
"env:multi:disable": "node scripts/multiTenancy.js disable", "env:multi:disable": "node scripts/multiTenancy.js disable",
"env:selfhost:enable": "node scripts/selfhost.js enable", "env:selfhost:enable": "node scripts/selfhost.js enable",
@ -35,10 +36,10 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "1.3.15-alpha.9", "@budibase/backend-core": "^1.3.18",
"@budibase/pro": "1.3.15-alpha.9", "@budibase/pro": "1.3.18",
"@budibase/string-templates": "1.3.15-alpha.9", "@budibase/string-templates": "^1.3.18",
"@budibase/types": "1.3.15-alpha.9", "@budibase/types": "^1.3.18",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",

View File

@ -242,6 +242,26 @@ describe("/api/global/users", () => {
expect(response.body.message).toBe(`Unavailable`) expect(response.body.message).toBe(`Unavailable`)
expect(events.user.created).toBeCalledTimes(0) 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", () => { describe("update", () => {

View File

@ -43,7 +43,8 @@ const env = {
PLATFORM_URL: process.env.PLATFORM_URL, PLATFORM_URL: process.env.PLATFORM_URL,
APPS_URL: process.env.APPS_URL, APPS_URL: process.env.APPS_URL,
// ports // 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, CLUSTER_PORT: process.env.CLUSTER_PORT,
// flags // flags
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,

View File

@ -278,39 +278,61 @@ export const addTenant = async (
} }
const getExistingTenantUsers = async (emails: string[]): Promise<User[]> => { const getExistingTenantUsers = async (emails: string[]): Promise<User[]> => {
return dbUtils.queryGlobalView(ViewName.USER_BY_EMAIL, { const lcEmails = emails.map(email => email.toLowerCase())
keys: emails, const params = {
keys: lcEmails,
include_docs: true, include_docs: true,
}
const opts = {
arrayResponse: true, arrayResponse: true,
}) }
return dbUtils.queryGlobalView(
ViewName.USER_BY_EMAIL,
params,
undefined,
opts
) as Promise<User[]>
} }
const getExistingPlatformUsers = async ( const getExistingPlatformUsers = async (
emails: string[] emails: string[]
): Promise<PlatformUserByEmail[]> => { ): Promise<PlatformUserByEmail[]> => {
return dbUtils.doWithDB( const lcEmails = emails.map(email => email.toLowerCase())
StaticDatabases.PLATFORM_INFO.name, const params = {
async (infoDb: any) => { keys: lcEmails,
const response: AllDocsResponse<PlatformUserByEmail> = include_docs: true,
await infoDb.allDocs({ }
keys: emails,
include_docs: true, const opts = {
}) arrayResponse: true,
return response.rows }
.filter(row => row.doc && (row.error !== "not_found") !== null) return dbUtils.queryPlatformView(
.map((row: any) => row.doc) ViewName.PLATFORM_USERS_LOWERCASE,
} params,
) opts
) as Promise<PlatformUserByEmail[]>
} }
const getExistingAccounts = async ( const getExistingAccounts = async (
emails: string[] emails: string[]
): Promise<AccountMetadata[]> => { ): Promise<AccountMetadata[]> => {
return dbUtils.queryPlatformView(ViewName.ACCOUNT_BY_EMAIL, { const lcEmails = emails.map(email => email.toLowerCase())
keys: emails, const params = {
keys: lcEmails,
include_docs: true, include_docs: true,
}
const opts = {
arrayResponse: true, arrayResponse: true,
}) }
return dbUtils.queryPlatformView(
ViewName.ACCOUNT_BY_EMAIL,
params,
opts
) as Promise<AccountMetadata[]>
} }
/** /**
@ -332,7 +354,7 @@ const searchExistingEmails = async (emails: string[]) => {
const existingAccounts = await getExistingAccounts(emails) const existingAccounts = await getExistingAccounts(emails)
matchedEmails.push(...existingAccounts.map(account => account.email)) matchedEmails.push(...existingAccounts.map(account => account.email))
return [...new Set(matchedEmails)] return [...new Set(matchedEmails.map(email => email.toLowerCase()))]
} }
export const bulkCreate = async ( export const bulkCreate = async (
@ -351,8 +373,10 @@ export const bulkCreate = async (
for (const newUser of newUsersRequested) { for (const newUser of newUsersRequested) {
if ( if (
newUsers.find((x: any) => x.email === newUser.email) || newUsers.find(
existingEmails.includes(newUser.email) (x: User) => x.email.toLowerCase() === newUser.email.toLowerCase()
) ||
existingEmails.includes(newUser.email.toLowerCase())
) { ) {
unsuccessful.push({ unsuccessful.push({
email: newUser.email, email: newUser.email,

View File

@ -291,17 +291,16 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@1.3.15-alpha.9": "@budibase/backend-core@1.3.18":
version "1.3.15-alpha.9" version "1.3.18"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.15-alpha.9.tgz#06c582d1aac93ba689a9738ea35939ccc5bac430" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.3.18.tgz#5ef26e3283f46b321103766b0359ca72035c8942"
integrity sha512-uShQQVPE2EsfPY6BNZnl/yKQoR8VwJyiIfCNE/EYl5aIVvhrqva+vA7JeYAs4sf/vf8Y5kkZaJGzfvzEws4dHg== integrity sha512-utux5ydlnejKavU2H2HlCJkIyymVmGj+rVJp+SR1JrtPW2wkzHzpYn2k9eXFOYnAwqTF8jpCj4FVOEeDTfQcbw==
dependencies: dependencies:
"@budibase/types" "1.3.15-alpha.9" "@budibase/types" "^1.3.18"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0" aws-sdk "2.1030.0"
bcrypt "5.0.1" bcrypt "5.0.1"
bcryptjs "2.4.3"
dotenv "16.0.1" dotenv "16.0.1"
emitter-listener "1.1.2" emitter-listener "1.1.2"
ioredis "4.28.0" ioredis "4.28.0"
@ -327,21 +326,21 @@
uuid "8.3.2" uuid "8.3.2"
zlib "1.0.5" zlib "1.0.5"
"@budibase/pro@1.3.15-alpha.9": "@budibase/pro@1.3.18":
version "1.3.15-alpha.9" version "1.3.18"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.15-alpha.9.tgz#cb0ab3041df4c412eed9a8c7950f20ca0e3b62c5" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.3.18.tgz#35a59e86c00971dd77a0b196e381a5ffd3be767c"
integrity sha512-1eE7O/bYeHxSoG8V61B2OZN3bGD44RAinAFJLHvBK7ncHZSTOOH4fe5HLvFiWPm0gV5ONDHpqr9W5Vs6ZBXukQ== integrity sha512-aFGUr/ut4Ki56qLSdFaDLoEIPmwMqT5dGmY1xBT3Bwvhoq7hXevk1bcPFGj0rWBJnYG9zg/jmik9V4mE/lUOag==
dependencies: dependencies:
"@budibase/backend-core" "1.3.15-alpha.9" "@budibase/backend-core" "1.3.18"
"@budibase/types" "1.3.15-alpha.9" "@budibase/types" "1.3.18"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
joi "17.6.0" joi "17.6.0"
node-fetch "^2.6.1" node-fetch "^2.6.1"
"@budibase/types@1.3.15-alpha.9": "@budibase/types@1.3.18", "@budibase/types@^1.3.18":
version "1.3.15-alpha.9" version "1.3.18"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.15-alpha.9.tgz#443cab0ca757a37af7a45f99caac35643fe273e5" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.3.18.tgz#cd65213f476a2f37065796d0ccba18c8e8e07504"
integrity sha512-r1Z2PLPF3nR/bGzVyZ0/6RcOYb4Csmk4J/b1KJ57wzTeDdsRwgVgGO6QS+XYdwZctsKDNOm9FvpIQj3tNM8Rjw== integrity sha512-bMtS2RbbI3Ztl5aAwEVlsK+2PQ0vRSZm8n6YDRjC9Arc94B4GDl/sqowRndKYqVtUeA/gVW0HH8x+qzhuMX6Dw==
"@cspotcode/source-map-consumer@0.8.0": "@cspotcode/source-map-consumer@0.8.0":
version "0.8.0" version "0.8.0"