diff --git a/packages/backend-core/src/db/index.js b/packages/backend-core/src/db/index.js index 1a29c8c2b0..7d54b881b1 100644 --- a/packages/backend-core/src/db/index.js +++ b/packages/backend-core/src/db/index.js @@ -41,7 +41,8 @@ exports.closeDB = async db => { return } try { - return db.close() + // specifically await so that if there is an error, it can be ignored + return await db.close() } catch (err) { // ignore error, already closed } diff --git a/packages/backend-core/src/db/pouch.js b/packages/backend-core/src/db/pouch.js index 722913aa67..9c1ada8d76 100644 --- a/packages/backend-core/src/db/pouch.js +++ b/packages/backend-core/src/db/pouch.js @@ -1,29 +1,19 @@ const PouchDB = require("pouchdb") const env = require("../environment") -exports.getCouchUrl = () => { - if (!env.COUCH_DB_URL) return - - // username and password already exist in URL - if (env.COUCH_DB_URL.includes("@")) { - return env.COUCH_DB_URL - } - - const [protocol, ...rest] = env.COUCH_DB_URL.split("://") - - if (!env.COUCH_DB_USERNAME || !env.COUCH_DB_PASSWORD) { - throw new Error( - "CouchDB configuration invalid. You must provide a fully qualified CouchDB url, or the COUCH_DB_USER and COUCH_DB_PASSWORD environment variables." - ) - } - - return `${protocol}://${env.COUCH_DB_USERNAME}:${env.COUCH_DB_PASSWORD}@${rest}` -} - -exports.splitCouchUrl = url => { +function getUrlInfo() { + let url = env.COUCH_DB_URL + let username, password, host const [protocol, rest] = url.split("://") - const [auth, host] = rest.split("@") - const [username, password] = auth.split(":") + if (url.includes("@")) { + const hostParts = rest.split("@") + host = hostParts[1] + const authParts = hostParts[0].split(":") + username = authParts[0] + password = authParts[1] + } else { + host = rest + } return { url: `${protocol}://${host}`, auth: { @@ -33,32 +23,51 @@ exports.splitCouchUrl = url => { } } +exports.getCouchInfo = () => { + const urlInfo = getUrlInfo() + let username + let password + if (env.COUCH_DB_USERNAME) { + // set from env + username = env.COUCH_DB_USERNAME + } else if (urlInfo.auth.username) { + // set from url + username = urlInfo.auth.username + } else if (!env.isTest()) { + throw new Error("CouchDB username not set") + } + if (env.COUCH_DB_PASSWORD) { + // set from env + password = env.COUCH_DB_PASSWORD + } else if (urlInfo.auth.password) { + // set from url + password = urlInfo.auth.password + } else if (!env.isTest()) { + throw new Error("CouchDB password not set") + } + const authCookie = Buffer.from(`${username}:${password}`).toString("base64") + return { + url: urlInfo.url, + auth: { + username: username, + password: password, + }, + cookie: `Basic ${authCookie}`, + } +} + /** * Return a constructor for PouchDB. * This should be rarely used outside of the main application config. * Exposed for exceptional cases such as in-memory views. */ exports.getPouch = (opts = {}) => { - let auth = { - username: env.COUCH_DB_USERNAME, - password: env.COUCH_DB_PASSWORD, - } - let url = exports.getCouchUrl() || "http://localhost:4005" - // need to update security settings - if (!auth.username || !auth.password || url.includes("@")) { - const split = exports.splitCouchUrl(url) - url = split.url - auth = split.auth - } - - const authCookie = Buffer.from(`${auth.username}:${auth.password}`).toString( - "base64" - ) + let { url, cookie } = exports.getCouchInfo() let POUCH_DB_DEFAULTS = { prefix: url, fetch: (url, opts) => { // use a specific authorization cookie - be very explicit about how we authenticate - opts.headers.set("Authorization", `Basic ${authCookie}`) + opts.headers.set("Authorization", cookie) return PouchDB.fetch(url, opts) }, } diff --git a/packages/backend-core/src/db/utils.js b/packages/backend-core/src/db/utils.js index 9e2a06d065..1e2ba846de 100644 --- a/packages/backend-core/src/db/utils.js +++ b/packages/backend-core/src/db/utils.js @@ -12,7 +12,7 @@ const { const { getTenantId, getGlobalDBName } = require("../tenancy") const fetch = require("node-fetch") const { doWithDB, allDbs } = require("./index") -const { getCouchUrl } = require("./pouch") +const { getCouchInfo } = require("./pouch") const { getAppMetadata } = require("../cache/appMetadata") const { checkSlashesInUrl } = require("../helpers") const { @@ -169,8 +169,14 @@ exports.getAllDbs = async (opts = { efficient: false }) => { return allDbs() } let dbs = [] - async function addDbs(url) { - const response = await fetch(checkSlashesInUrl(encodeURI(url))) + let { url, cookie } = getCouchInfo() + async function addDbs(couchUrl) { + const response = await fetch(checkSlashesInUrl(encodeURI(couchUrl)), { + method: "GET", + headers: { + Authorization: cookie, + }, + }) if (response.status === 200) { let json = await response.json() dbs = dbs.concat(json) @@ -178,7 +184,7 @@ exports.getAllDbs = async (opts = { efficient: false }) => { throw "Cannot connect to CouchDB instance" } } - let couchUrl = `${getCouchUrl()}/_all_dbs` + let couchUrl = `${url}/_all_dbs` let tenantId = getTenantId() if (!env.MULTI_TENANCY || (!efficient && tenantId === DEFAULT_TENANT_ID)) { // just get all DBs when: diff --git a/packages/backend-core/src/environment.js b/packages/backend-core/src/environment.js index 8a92e39469..26e0d486f7 100644 --- a/packages/backend-core/src/environment.js +++ b/packages/backend-core/src/environment.js @@ -8,7 +8,7 @@ function isTest() { module.exports = { JWT_SECRET: process.env.JWT_SECRET, - COUCH_DB_URL: process.env.COUCH_DB_URL, + COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005", COUCH_DB_USERNAME: process.env.COUCH_DB_USER, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 6a1befd7e4..1f461ebad3 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -165,7 +165,7 @@ {/key} diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 2f5f26e476..922955591a 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -144,7 +144,11 @@ export const RelationshipTypes = { MANY_TO_ONE: "many-to-one", } -export const ALLOWABLE_STRING_OPTIONS = [FIELDS.STRING, FIELDS.OPTIONS] +export const ALLOWABLE_STRING_OPTIONS = [ + FIELDS.STRING, + FIELDS.OPTIONS, + FIELDS.LONGFORM, +] export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map( opt => opt.type ) diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 4e6d37765f..aa76dd403c 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -406,11 +406,14 @@ const destroyApp = async (ctx: any) => { if (!env.isTest() && !isUnpublish) { await deleteApp(appId) } + // automations only in production if (isUnpublish) { await cleanupAutomations(appId) } - // make sure the app/role doesn't stick around after the app has been deleted - await removeAppFromUserRoles(ctx, appId) + // remove app role when the dev app is deleted (no trace of app anymore) + else { + await removeAppFromUserRoles(ctx, appId) + } await appCache.invalidateAppMetadata(appId) return result } diff --git a/packages/server/src/api/controllers/row/internalSearch.js b/packages/server/src/api/controllers/row/internalSearch.js index ad95a25fc5..5f1dc25faa 100644 --- a/packages/server/src/api/controllers/row/internalSearch.js +++ b/packages/server/src/api/controllers/row/internalSearch.js @@ -1,6 +1,6 @@ const { SearchIndexes } = require("../../../db/utils") const fetch = require("node-fetch") -const { getCouchUrl } = require("@budibase/backend-core/db") +const { getCouchInfo } = require("@budibase/backend-core/db") const { getAppId } = require("@budibase/backend-core/context") /** @@ -242,11 +242,10 @@ class QueryBuilder { async run() { const appId = getAppId() - const url = `${getCouchUrl()}/${appId}/_design/database/_search/${ - SearchIndexes.ROWS - }` + const { url, cookie } = getCouchInfo() + const fullPath = `${url}/${appId}/_design/database/_search/${SearchIndexes.ROWS}` const body = this.buildSearchBody() - return await runQuery(url, body) + return await runQuery(fullPath, body, cookie) } } @@ -254,12 +253,16 @@ class QueryBuilder { * Executes a lucene search query. * @param url The query URL * @param body The request body defining search criteria + * @param cookie The auth cookie for CouchDB * @returns {Promise<{rows: []}>} */ -const runQuery = async (url, body) => { +const runQuery = async (url, body, cookie) => { const response = await fetch(url, { body: JSON.stringify(body), method: "POST", + headers: { + Authorization: cookie, + }, }) const json = await response.json() diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 0d52eecb5b..60f3c981d6 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -47,7 +47,11 @@ exports.FieldTypes = { exports.CanSwitchTypes = [ [exports.FieldTypes.JSON, exports.FieldTypes.ARRAY], - [exports.FieldTypes.STRING, exports.FieldTypes.OPTIONS], + [ + exports.FieldTypes.STRING, + exports.FieldTypes.OPTIONS, + exports.FieldTypes.LONGFORM, + ], [exports.FieldTypes.BOOLEAN, exports.FieldTypes.NUMBER], ] diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index f7f18c6fb9..326b213bc7 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -42,9 +42,9 @@ const SQL_STRING_TYPE_MAP = { nvarchar: FieldTypes.STRING, ntext: FieldTypes.STRING, enum: FieldTypes.STRING, - blob: FieldTypes.LONGFORM, - long: FieldTypes.LONGFORM, - text: FieldTypes.LONGFORM, + blob: FieldTypes.STRING, + long: FieldTypes.STRING, + text: FieldTypes.STRING, } const SQL_BOOLEAN_TYPE_MAP = { @@ -207,11 +207,20 @@ function shouldCopySpecialColumn( column: { type: string }, fetchedColumn: { type: string } | undefined ) { + const specialTypes = [ + FieldTypes.OPTIONS, + FieldTypes.LONGFORM, + FieldTypes.ARRAY, + FieldTypes.FORMULA, + ] + if (column && !fetchedColumn) { + return true + } + const fetchedIsNumber = + !fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER return ( - column.type === FieldTypes.OPTIONS || - column.type === FieldTypes.ARRAY || - ((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) && - column.type === FieldTypes.BOOLEAN) + specialTypes.indexOf(column.type) !== -1 || + (fetchedIsNumber && column.type === FieldTypes.BOOLEAN) ) } diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 64f7be12e9..239b342eff 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1014,10 +1014,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.0.124-alpha.0": - version "1.0.124-alpha.0" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.124-alpha.0.tgz#33a9408206088da49154710910dafc8088d864d2" - integrity sha512-0ZUkDeqaoXS9qyK91SjwokYEA1wUPhi48nFE0+UwBloF8i7zVDFp2kOX7VNUrUer4gLuND9BoihEdpqsdQDvAg== +"@budibase/backend-core@1.0.127": + version "1.0.127" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.127.tgz#5d1f4b18b31436ddb770dc1ddf16f201ec95dda7" + integrity sha512-3INFkAIxL0Q8Sa65ELRGQqPs+4baykKyb1z/XuO1MyuDPnbFKXGOjl1V61EMy622gsmLk90IJL6aQfh3Grwhvw== dependencies: "@techpass/passport-openidconnect" "^0.3.0" aws-sdk "^2.901.0" @@ -1091,12 +1091,12 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" -"@budibase/pro@1.0.124-alpha.0": - version "1.0.124-alpha.0" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.124-alpha.0.tgz#6287a51fa7c19754e44374c209c4aa3480fc3ac9" - integrity sha512-EgMuh+XSd/9tb3Ej9EZa4Y8hgiS6fHG+tuUwUcTuP6zvHbTijQGPb9075yImUbSc10bS3o41AP2qa2/ZdZKV2w== +"@budibase/pro@1.0.127": + version "1.0.127" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.127.tgz#a8bcffb8ccc6afde64370b3a3dc22e2d0dd04f46" + integrity sha512-dj0SFTmO8JuMQ97/Ik6jVPQsh9AW7U5Wkgpa4yeNfwWw3DvSoktCxpeZ9mND6BR/DWTaeZljFKsQ6uk6nimzdQ== dependencies: - "@budibase/backend-core" "1.0.124-alpha.0" + "@budibase/backend-core" "1.0.127" node-fetch "^2.6.1" "@budibase/standard-components@^0.9.139": diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index 405845b896..dfdcc129d9 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -293,10 +293,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.0.124-alpha.0": - version "1.0.124-alpha.0" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.124-alpha.0.tgz#33a9408206088da49154710910dafc8088d864d2" - integrity sha512-0ZUkDeqaoXS9qyK91SjwokYEA1wUPhi48nFE0+UwBloF8i7zVDFp2kOX7VNUrUer4gLuND9BoihEdpqsdQDvAg== +"@budibase/backend-core@1.0.127": + version "1.0.127" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.127.tgz#5d1f4b18b31436ddb770dc1ddf16f201ec95dda7" + integrity sha512-3INFkAIxL0Q8Sa65ELRGQqPs+4baykKyb1z/XuO1MyuDPnbFKXGOjl1V61EMy622gsmLk90IJL6aQfh3Grwhvw== dependencies: "@techpass/passport-openidconnect" "^0.3.0" aws-sdk "^2.901.0" @@ -321,12 +321,12 @@ uuid "^8.3.2" zlib "^1.0.5" -"@budibase/pro@1.0.124-alpha.0": - version "1.0.124-alpha.0" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.124-alpha.0.tgz#6287a51fa7c19754e44374c209c4aa3480fc3ac9" - integrity sha512-EgMuh+XSd/9tb3Ej9EZa4Y8hgiS6fHG+tuUwUcTuP6zvHbTijQGPb9075yImUbSc10bS3o41AP2qa2/ZdZKV2w== +"@budibase/pro@1.0.127": + version "1.0.127" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.127.tgz#a8bcffb8ccc6afde64370b3a3dc22e2d0dd04f46" + integrity sha512-dj0SFTmO8JuMQ97/Ik6jVPQsh9AW7U5Wkgpa4yeNfwWw3DvSoktCxpeZ9mND6BR/DWTaeZljFKsQ6uk6nimzdQ== dependencies: - "@budibase/backend-core" "1.0.124-alpha.0" + "@budibase/backend-core" "1.0.127" node-fetch "^2.6.1" "@cspotcode/source-map-consumer@0.8.0":