diff --git a/hosting/nginx.prod.conf.hbs b/hosting/nginx.prod.conf.hbs index a127dfbd5c..ac35a2020d 100644 --- a/hosting/nginx.prod.conf.hbs +++ b/hosting/nginx.prod.conf.hbs @@ -48,7 +48,7 @@ http { set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com"; set $csp_object "object-src 'none'"; set $csp_base_uri "base-uri 'self'"; - set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com"; + set $csp_connect "connect-src 'self' https://api-iam.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io wss://nexus-websocket-b.intercom.io https://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://*.s3.amazonaws.com https://*.s3.us-east-2.amazonaws.com https://*.s3.us-east-1.amazonaws.com https://*.s3.us-west-1.amazonaws.com https://*.s3.us-west-2.amazonaws.com https://*.s3.af-south-1.amazonaws.com https://*.s3.ap-east-1.amazonaws.com https://*.s3.ap-southeast-3.amazonaws.com https://*.s3.ap-south-1.amazonaws.com https://*.s3.ap-northeast-3.amazonaws.com https://*.s3.ap-northeast-2.amazonaws.com https://*.s3.ap-southeast-1.amazonaws.com https://*.s3.ap-southeast-2.amazonaws.com https://*.s3.ap-northeast-1.amazonaws.com https://*.s3.ca-central-1.amazonaws.com https://*.s3.cn-north-1.amazonaws.com https://*.s3.cn-northwest-1.amazonaws.com https://*.s3.eu-central-1.amazonaws.com https://*.s3.eu-west-1.amazonaws.com https://*.s3.eu-west-2.amazonaws.com https://*.s3.eu-south-1.amazonaws.com https://*.s3.eu-west-3.amazonaws.com https://*.s3.eu-north-1.amazonaws.com https://*.s3.sa-east-1.amazonaws.com https://*.s3.me-south-1.amazonaws.com https://*.s3.us-gov-east-1.amazonaws.com https://*.s3.us-gov-west-1.amazonaws.com"; set $csp_font "font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me https://maxcdn.bootstrapcdn.com https://js.intercomcdn.com https://fonts.intercomcdn.com"; set $csp_frame "frame-src 'self' https:"; set $csp_img "img-src http: https: data: blob:"; diff --git a/lerna.json b/lerna.json index 2c7d20bb53..0b22f120fd 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.192-alpha.1", + "version": "1.0.198", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 884fe9b53c..87435a888b 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.192-alpha.1", + "version": "1.0.198", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/middleware/passport/datasource/google.js b/packages/backend-core/src/middleware/passport/datasource/google.js index 53719b8350..8f2022c2d7 100644 --- a/packages/backend-core/src/middleware/passport/datasource/google.js +++ b/packages/backend-core/src/middleware/passport/datasource/google.js @@ -1,4 +1,5 @@ const google = require("../google") +const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy const { Cookies, Configs } = require("../../../constants") const { clearCookie, getCookie } = require("../../../utils") const { getScopedConfig, getPlatformUrl } = require("../../../db/utils") @@ -46,19 +47,20 @@ async function postAuth(passport, ctx, next) { const platformUrl = await getPlatformUrl({ tenantAware: false }) let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback` - const strategy = await google.strategyFactory( - config, - callbackUrl, - (accessToken, refreshToken, profile, done) => { - clearCookie(ctx, Cookies.DatasourceAuth) - done(null, { refreshToken }) - } - ) - const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth) return passport.authenticate( - strategy, + new GoogleStrategy( + { + clientID: config.clientID, + clientSecret: config.clientSecret, + callbackURL: callbackUrl, + }, + (accessToken, refreshToken, profile, done) => { + clearCookie(ctx, Cookies.DatasourceAuth) + done(null, { accessToken, refreshToken }) + } + ), { successRedirect: "/", failureRedirect: "/error" }, async (err, tokens) => { // update the DB for the datasource with all the user info diff --git a/packages/backend-core/src/middleware/passport/google.js b/packages/backend-core/src/middleware/passport/google.js index b12a668327..858029ca80 100644 --- a/packages/backend-core/src/middleware/passport/google.js +++ b/packages/backend-core/src/middleware/passport/google.js @@ -11,8 +11,8 @@ const buildVerifyFn = saveUserFn => { profile: profile, email: profile._json.email, oauth2: { - accessToken: accessToken, - refreshToken: refreshToken, + accessToken, + refreshToken, }, } diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 11a29766b1..3d729bf425 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.0.192-alpha.1", + "version": "1.0.198", "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.0.192-alpha.1", + "@budibase/string-templates": "^1.0.198", "@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/avatar": "^3.0.2", diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index 73ba7bb642..39a7d9d626 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -15,6 +15,7 @@ export let placeholder = null export let appendTo = undefined export let timeOnly = false + export let ignoreTimezones = false const dispatch = createEventDispatcher() const flatpickrId = `${uuid()}-wrapper` @@ -50,19 +51,35 @@ const handleChange = event => { const [dates] = event.detail + const noTimezone = enableTime && !timeOnly && ignoreTimezones let newValue = dates[0] if (newValue) { newValue = newValue.toISOString() } - // if time only set date component to 2000-01-01 + + // If time only set date component to 2000-01-01 if (timeOnly) { newValue = `2000-01-01T${newValue.split("T")[1]}` } - // date only, offset for timezone so always right date + + // For date-only fields, construct a manual timestamp string without a time + // or time zone else if (!enableTime) { - const offset = dates[0].getTimezoneOffset() * 60000 - newValue = new Date(dates[0].getTime() - offset).toISOString() + const year = dates[0].getFullYear() + const month = `${dates[0].getMonth() + 1}`.padStart(2, "0") + const day = `${dates[0].getDate()}`.padStart(2, "0") + newValue = `${year}-${month}-${day}T00:00:00.000` } + + // For non-timezone-aware fields, create an ISO 8601 timestamp of the exact + // time picked, without timezone + else if (noTimezone) { + const offset = dates[0].getTimezoneOffset() * 60000 + newValue = new Date(dates[0].getTime() - offset) + .toISOString() + .slice(0, -1) + } + dispatch("change", newValue) } @@ -112,10 +129,12 @@ // Treat as numerical timestamp date = new Date(parseInt(val)) } + time = date.getTime() if (isNaN(time)) { return null } + // By rounding to the nearest second we avoid locking up in an endless // loop in the builder, caused by potentially enriching {{ now }} to every // millisecond. diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte index 9298c49177..a4b2379782 100644 --- a/packages/bbui/src/Form/DatePicker.svelte +++ b/packages/bbui/src/Form/DatePicker.svelte @@ -12,6 +12,7 @@ export let timeOnly = false export let placeholder = null export let appendTo = undefined + export let ignoreTimezones = false const dispatch = createEventDispatcher() @@ -30,6 +31,7 @@ {enableTime} {timeOnly} {appendTo} + {ignoreTimezones} on:change={onChange} /> diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index 863dc84040..baa84c91e0 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -28,7 +28,7 @@ export let rowCount = 0 export let quiet = false export let loading = false - export let allowSelectRows = true + export let allowSelectRows export let allowEditRows = true export let allowEditColumns = true export let selectedRows = [] @@ -344,11 +344,7 @@ {/if} {#if sortedRows?.length} {#each sortedRows as row, idx} -
dispatch("click", row)} - on:click={() => toggleSelectRow(row)} - > +
{#if showEditColumn}
{ + if (!schema[field]?.preventSelectRow) { + dispatch("click", row) + toggleSelectRow(row) + } + }} > {:else if type === "attachment"} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 62a367ea7d..77ab75827f 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -14,7 +14,7 @@ } from "@budibase/bbui" import { createEventDispatcher, onMount } from "svelte" import { cloneDeep } from "lodash/fp" - import { tables } from "stores/backend" + import { tables, datasources } from "stores/backend" import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { FIELDS, @@ -63,6 +63,7 @@ let primaryDisplay = $tables.selected.primaryDisplay == null || $tables.selected.primaryDisplay === field.name + let isCreating = originalName == null let table = $tables.selected let indexes = [...($tables.selected.indexes || [])] @@ -81,6 +82,9 @@ (field.type === LINK_TYPE && !field.tableId) || Object.keys(errors).length !== 0 $: errors = checkErrors(field) + $: datasource = $datasources.list.find( + source => source._id === table?.sourceId + ) // used to select what different options can be displayed for column type $: canBeSearched = @@ -430,6 +434,18 @@ bind:value={field.constraints.datetime.earliest} /> + {#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"} +
+ + +
+ {/if} {:else if field.type === "number"} row?._id || row) + let linkedIds = (Array.isArray(linkedRows) ? linkedRows : [])?.map( + row => row?._id || row + ) $: linkedRows = linkedIds $: label = capitalise(schema.name) diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 922955591a..f906d770ca 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -182,7 +182,7 @@ export const IntegrationTypes = { ORACLE: "ORACLE", INTERNAL: "INTERNAL", GOOGLE_SHEETS: "GOOGLE_SHEETS", - FIREBASE: "FIREBASE", + FIRESTORE: "FIRESTORE", REDIS: "REDIS", } @@ -201,7 +201,7 @@ export const IntegrationNames = { [IntegrationTypes.ORACLE]: "Oracle", [IntegrationTypes.INTERNAL]: "Internal", [IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets", - [IntegrationTypes.FIREBASE]: "Firebase", + [IntegrationTypes.FIRESTORE]: "Firestore", [IntegrationTypes.REDIS]: "Redis", } diff --git a/packages/cli/package.json b/packages/cli/package.json index 90f4608841..0f763d8799 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "1.0.192-alpha.1", + "version": "1.0.198", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/manifest.json b/packages/client/manifest.json index b36563b202..73add00c02 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2620,16 +2620,22 @@ }, { "type": "boolean", - "label": "Show Time", + "label": "Show time", "key": "enableTime", "defaultValue": true }, { "type": "boolean", - "label": "Time Only", + "label": "Time only", "key": "timeOnly", "defaultValue": false }, + { + "type": "boolean", + "label": "Ignore time zones", + "key": "ignoreTimezones", + "defaultValue": false + }, { "type": "text", "label": "Default value", diff --git a/packages/client/package.json b/packages/client/package.json index 4ee45345d6..ee29745514 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "1.0.192-alpha.1", + "version": "1.0.198", "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.0.192-alpha.1", - "@budibase/frontend-core": "^1.0.192-alpha.1", - "@budibase/string-templates": "^1.0.192-alpha.1", + "@budibase/bbui": "^1.0.198", + "@budibase/frontend-core": "^1.0.198", + "@budibase/string-templates": "^1.0.198", "@spectrum-css/button": "^3.0.3", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", diff --git a/packages/client/src/components/app/forms/DateTimeField.svelte b/packages/client/src/components/app/forms/DateTimeField.svelte index 1010883cad..4ca766121d 100644 --- a/packages/client/src/components/app/forms/DateTimeField.svelte +++ b/packages/client/src/components/app/forms/DateTimeField.svelte @@ -8,6 +8,7 @@ export let disabled = false export let enableTime = false export let timeOnly = false + export let ignoreTimezones = false export let validation export let defaultValue export let onChange @@ -43,6 +44,7 @@ appendTo={document.getElementById("flatpickr-root")} {enableTime} {timeOnly} + {ignoreTimezones} {placeholder} /> {/if} diff --git a/packages/client/src/components/app/table/Table.svelte b/packages/client/src/components/app/table/Table.svelte index 9346b3ca59..f56d91d7c8 100644 --- a/packages/client/src/components/app/table/Table.svelte +++ b/packages/client/src/components/app/table/Table.svelte @@ -86,6 +86,7 @@ sortable: false, divider: true, width: "auto", + preventSelectRow: true, } } diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index 46d8a7784a..33f3ec8750 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -1,12 +1,12 @@ { "name": "@budibase/frontend-core", - "version": "1.0.192-alpha.1", + "version": "1.0.198", "description": "Budibase frontend core libraries used in builder and client", "author": "Budibase", "license": "MPL-2.0", "svelte": "src/index.js", "dependencies": { - "@budibase/bbui": "^1.0.192-alpha.1", + "@budibase/bbui": "^1.0.198", "lodash": "^4.17.21", "svelte": "^3.46.2" } diff --git a/packages/server/package.json b/packages/server/package.json index 966262b480..68fb655903 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "1.0.192-alpha.1", + "version": "1.0.198", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -70,10 +70,10 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "^10.0.3", - "@budibase/backend-core": "^1.0.192-alpha.1", - "@budibase/client": "^1.0.192-alpha.1", - "@budibase/pro": "1.0.192-alpha.1", - "@budibase/string-templates": "^1.0.192-alpha.1", + "@budibase/backend-core": "^1.0.198", + "@budibase/client": "^1.0.198", + "@budibase/pro": "1.0.198", + "@budibase/string-templates": "^1.0.198", "@bull-board/api": "^3.7.0", "@bull-board/koa": "^3.7.0", "@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 c8c8ae8e58..7983044f66 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -29,7 +29,10 @@ import { breakExternalTableId, isSQL } from "../../../integrations/utils" import { processObjectSync } from "@budibase/string-templates" // @ts-ignore import { cloneDeep } from "lodash/fp" -import { processFormulas } from "../../../utilities/rowProcessor/utils" +import { + processFormulas, + processDates, +} from "../../../utilities/rowProcessor/utils" // @ts-ignore import { getAppDB } from "@budibase/backend-core/context" @@ -434,7 +437,13 @@ module External { relationships ) } - return processFormulas(table, Object.values(finalRows)).map((row: Row) => + + // Process some additional data types + let finalRowArray = Object.values(finalRows) + finalRowArray = processDates(table, finalRowArray) + finalRowArray = processFormulas(table, finalRowArray) + + return finalRowArray.map((row: Row) => this.squashRelationshipColumns(table, row, relationships) ) } diff --git a/packages/server/src/definitions/common.ts b/packages/server/src/definitions/common.ts index 3ee6a71c8f..4aec0d103d 100644 --- a/packages/server/src/definitions/common.ts +++ b/packages/server/src/definitions/common.ts @@ -25,6 +25,7 @@ export interface FieldSchema { formula?: string formulaType?: string main?: boolean + ignoreTimezones?: boolean meta?: { toTable: string toKey: string diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index 23266991ee..2947102a02 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -48,7 +48,7 @@ export enum SourceNames { REST = "REST", ORACLE = "ORACLE", GOOGLE_SHEETS = "GOOGLE_SHEETS", - FIREBASE = "FIREBASE", + FIRESTORE = "FIRESTORE", REDIS = "REDIS", } diff --git a/packages/server/src/integrations/base/sqlTable.ts b/packages/server/src/integrations/base/sqlTable.ts index 0c63b707ae..71f9c4aa64 100644 --- a/packages/server/src/integrations/base/sqlTable.ts +++ b/packages/server/src/integrations/base/sqlTable.ts @@ -61,7 +61,9 @@ function generateSchema( schema.boolean(key) break case FieldTypes.DATETIME: - schema.datetime(key) + schema.datetime(key, { + useTz: !column.ignoreTimezones, + }) break case FieldTypes.ARRAY: schema.json(key) diff --git a/packages/server/src/integrations/googlesheets.ts b/packages/server/src/integrations/googlesheets.ts index e6d79bfdde..7b17b7246d 100644 --- a/packages/server/src/integrations/googlesheets.ts +++ b/packages/server/src/integrations/googlesheets.ts @@ -164,11 +164,15 @@ module GoogleSheetsModule { } ) + const json = await response.json() + if (response.status !== 200) { - throw new Error("Error authenticating with google sheets.") + throw new Error( + `Error authenticating with google sheets. ${json.error_description}` + ) } - return response.json() + return json } async connect() { diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts index f3e7a5fc3a..cc4429a802 100644 --- a/packages/server/src/integrations/index.ts +++ b/packages/server/src/integrations/index.ts @@ -27,7 +27,7 @@ const DEFINITIONS = { [SourceNames.MYSQL]: mysql.schema, [SourceNames.ARANGODB]: arangodb.schema, [SourceNames.REST]: rest.schema, - [SourceNames.FIREBASE]: firebase.schema, + [SourceNames.FIRESTORE]: firebase.schema, [SourceNames.REDIS]: redis.schema, } @@ -43,10 +43,9 @@ const INTEGRATIONS = { [SourceNames.MYSQL]: mysql.integration, [SourceNames.ARANGODB]: arangodb.integration, [SourceNames.REST]: rest.integration, - [SourceNames.FIREBASE]: firebase.integration, + [SourceNames.FIRESTORE]: firebase.integration, [SourceNames.GOOGLE_SHEETS]: googlesheets.integration, [SourceNames.REDIS]: redis.integration, - [SourceNames.FIREBASE]: firebase.integration, } // optionally add oracle integration if the oracle binary can be installed diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 4fe996a019..7a06592ef7 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -15,7 +15,6 @@ import { } from "./utils" import { DatasourcePlus } from "./base/datasourcePlus" import dayjs from "dayjs" -import { FieldTypes } from "../constants" const { NUMBER_REGEX } = require("../utilities") module MySQLModule { @@ -30,6 +29,7 @@ module MySQLModule { database: string ssl?: { [key: string]: any } rejectUnauthorized: boolean + typeCast: Function } const SCHEMA: Integration = { @@ -89,6 +89,8 @@ module MySQLModule { }, } + const TimezoneAwareDateTypes = ["timestamp"] + function bindingTypeCoerce(bindings: any[]) { for (let i = 0; i < bindings.length; i++) { const binding = bindings[i] @@ -131,7 +133,19 @@ module MySQLModule { } // @ts-ignore delete config.rejectUnauthorized - this.config = config + this.config = { + ...config, + typeCast: function (field: any, next: any) { + if ( + field.type == "DATETIME" || + field.type === "DATE" || + field.type === "TIMESTAMP" + ) { + return field.string() + } + return next() + }, + } } getBindingIdentifier(): string { diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index 220f35dae5..f0c5911476 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -16,10 +16,19 @@ import { import { DatasourcePlus } from "./base/datasourcePlus" module PostgresModule { - const { Client } = require("pg") + const { Client, types } = require("pg") const Sql = require("./base/sql") const { escapeDangerousCharacters } = require("../utilities") + // Return "date" and "timestamp" types as plain strings. + // This lets us reference the original stored timezone. + // types is undefined when running in a test env for some reason. + if (types) { + types.setTypeParser(1114, (val: any) => val) // timestamp + types.setTypeParser(1082, (val: any) => val) // date + types.setTypeParser(1184, (val: any) => val) // timestampz + } + const JSON_REGEX = /'{.*}'::json/s interface PostgresConfig { diff --git a/packages/server/src/utilities/rowProcessor/utils.js b/packages/server/src/utilities/rowProcessor/utils.js index 262ef40a3a..c80dae497c 100644 --- a/packages/server/src/utilities/rowProcessor/utils.js +++ b/packages/server/src/utilities/rowProcessor/utils.js @@ -65,3 +65,28 @@ exports.processFormulas = ( } return single ? rows[0] : rows } + +/** + * Processes any date columns and ensures that those without the ignoreTimezones + * flag set are parsed as UTC rather than local time. + */ +exports.processDates = (table, rows) => { + let datesWithTZ = [] + for (let [column, schema] of Object.entries(table.schema)) { + if (schema.type !== FieldTypes.DATETIME) { + continue + } + if (!schema.ignoreTimezones) { + datesWithTZ.push(column) + } + } + + for (let row of rows) { + for (let col of datesWithTZ) { + if (row[col] && typeof row[col] === "string" && !row[col].endsWith("Z")) { + row[col] = new Date(row[col]).toISOString() + } + } + } + return rows +} diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 305c3de88d..aac4b695f8 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.192-alpha.1": - version "1.0.192-alpha.1" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.192-alpha.1.tgz#62ecb0afc4cc2cb48ac93dd15a812f6f3fe1a40c" - integrity sha512-2WzYIg12ZEOcCwdywqyC9ZrJlXqg8w4xI6T+ba7Fei1RACo0y4QfUoNN+Ik1NP+cpyagVf2HbpYr/yMZqdJYUA== +"@budibase/backend-core@1.0.197": + version "1.0.197" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.197.tgz#3458d70c6d44376b7930672d6af8c6e89ddf4069" + integrity sha512-Cgzr1bJWKRg3+jqte7rnKPziWiH5Q+r/piRvHuD7EVmh2+xJLfWUz9iml72aFfcgRIOX8SyhejG7KTwxILx/vg== dependencies: "@techpass/passport-openidconnect" "^0.3.0" aws-sdk "^2.901.0" @@ -1092,12 +1092,21 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" +<<<<<<< HEAD "@budibase/pro@1.0.192-alpha.1": version "1.0.192-alpha.1" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.192-alpha.1.tgz#f3dece7ee153ac81080be9f96c7459e5dd8510ff" integrity sha512-BRzoRTIcIW/o2bckSmOrOYCz5HPJyeq4PurfEZFHh16dXPXSBSTIuIiC7Q1jic2CBq5DJOfT/SYTgj/OTVga3g== dependencies: "@budibase/backend-core" "1.0.192-alpha.1" +======= +"@budibase/pro@1.0.197": + version "1.0.197" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.197.tgz#a171b46bb8ee6251881ae9262136270533b3958d" + integrity sha512-SCVKjNgpzefmrXnLmkpQJLvYViykyzA6B9TwL7qrb6fBeXwAiSZ3hXGjNgZkVpy/v43hccPWt9BJFzFp457wgQ== + dependencies: + "@budibase/backend-core" "1.0.197" +>>>>>>> bd0ea6fd3ce54fc0f7f8774656d127a91771ea2d node-fetch "^2.6.1" "@budibase/standard-components@^0.9.139": diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index a796953180..a519680e12 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "1.0.192-alpha.1", + "version": "1.0.198", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 5df4464ba7..12e08cc06e 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "1.0.192-alpha.1", + "version": "1.0.198", "description": "Budibase background service", "main": "src/index.ts", "repository": { @@ -32,9 +32,9 @@ "author": "Budibase", "license": "GPL-3.0", "dependencies": { - "@budibase/backend-core": "^1.0.192-alpha.1", - "@budibase/pro": "1.0.192-alpha.1", - "@budibase/string-templates": "^1.0.192-alpha.1", + "@budibase/backend-core": "^1.0.198", + "@budibase/pro": "1.0.198", + "@budibase/string-templates": "^1.0.198", "@koa/router": "^8.0.0", "@sentry/node": "6.17.7", "@techpass/passport-openidconnect": "^0.3.0", diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js index 5d28d18eb7..d9791362f4 100644 --- a/packages/worker/src/api/routes/global/users.js +++ b/packages/worker/src/api/routes/global/users.js @@ -6,6 +6,7 @@ const Joi = require("joi") const cloudRestricted = require("../../../middleware/cloudRestricted") const { buildUserSaveValidation } = require("../../utilities/validation") const selfController = require("../../controllers/global/self") +const builderOrAdmin = require("../../../middleware/builderOrAdmin") const router = Router() @@ -44,7 +45,7 @@ router buildUserSaveValidation(), controller.save ) - .get("/api/global/users", adminOnly, controller.fetch) + .get("/api/global/users", builderOrAdmin, controller.fetch) .delete("/api/global/users/:id", adminOnly, controller.destroy) .get("/api/global/roles/:appId") .post( diff --git a/packages/worker/src/middleware/builderOrAdmin.js b/packages/worker/src/middleware/builderOrAdmin.js new file mode 100644 index 0000000000..6440766298 --- /dev/null +++ b/packages/worker/src/middleware/builderOrAdmin.js @@ -0,0 +1,10 @@ +module.exports = async (ctx, next) => { + if ( + !ctx.internal && + (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) && + (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) + ) { + ctx.throw(403, "Builder user only endpoint.") + } + return next() +} diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index aca32c0eff..977725108a 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.192-alpha.1": - version "1.0.192-alpha.1" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.192-alpha.1.tgz#62ecb0afc4cc2cb48ac93dd15a812f6f3fe1a40c" - integrity sha512-2WzYIg12ZEOcCwdywqyC9ZrJlXqg8w4xI6T+ba7Fei1RACo0y4QfUoNN+Ik1NP+cpyagVf2HbpYr/yMZqdJYUA== +"@budibase/backend-core@1.0.197": + version "1.0.197" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.197.tgz#3458d70c6d44376b7930672d6af8c6e89ddf4069" + integrity sha512-Cgzr1bJWKRg3+jqte7rnKPziWiH5Q+r/piRvHuD7EVmh2+xJLfWUz9iml72aFfcgRIOX8SyhejG7KTwxILx/vg== dependencies: "@techpass/passport-openidconnect" "^0.3.0" aws-sdk "^2.901.0" @@ -322,12 +322,12 @@ uuid "^8.3.2" zlib "^1.0.5" -"@budibase/pro@1.0.192-alpha.1": - version "1.0.192-alpha.1" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.192-alpha.1.tgz#f3dece7ee153ac81080be9f96c7459e5dd8510ff" - integrity sha512-BRzoRTIcIW/o2bckSmOrOYCz5HPJyeq4PurfEZFHh16dXPXSBSTIuIiC7Q1jic2CBq5DJOfT/SYTgj/OTVga3g== +"@budibase/pro@1.0.197": + version "1.0.197" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.197.tgz#a171b46bb8ee6251881ae9262136270533b3958d" + integrity sha512-SCVKjNgpzefmrXnLmkpQJLvYViykyzA6B9TwL7qrb6fBeXwAiSZ3hXGjNgZkVpy/v43hccPWt9BJFzFp457wgQ== dependencies: - "@budibase/backend-core" "1.0.192-alpha.1" + "@budibase/backend-core" "1.0.197" node-fetch "^2.6.1" "@cspotcode/source-map-consumer@0.8.0":