Merge remote-tracking branch 'origin/master' into feature/multistep-form-block

This commit is contained in:
Dean 2024-01-03 15:31:50 +00:00
commit c358344611
42 changed files with 307 additions and 721 deletions

View File

@ -38,10 +38,10 @@ jobs:
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
- run: yarn lint
@ -56,10 +56,10 @@ jobs:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
fetch-depth: 0
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
@ -84,7 +84,7 @@ jobs:
with:
fetch-depth: 0
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: azure/setup-helm@v3
- run: cd charts/budibase && helm lint .
@ -98,10 +98,10 @@ jobs:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
fetch-depth: 0
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
- name: Test
@ -122,10 +122,10 @@ jobs:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
fetch-depth: 0
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
- name: Test worker
@ -146,10 +146,10 @@ jobs:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
fetch-depth: 0
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
- name: Test server
@ -171,10 +171,10 @@ jobs:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
fetch-depth: 0
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
- name: Test
@ -194,10 +194,10 @@ jobs:
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
- name: Use Node.js 18.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 18.x
node-version: 20.x
cache: yarn
- run: yarn --frozen-lockfile
- name: Build packages

View File

@ -16,8 +16,8 @@ jobs:
days-before-pr-stale: 7
stale-issue-label: stale
exempt-pr-labels: pinned,security,roadmap
days-before-pr-close: 7
days-before-issue-close: 30
- uses: actions/stale@v8
with:
@ -26,6 +26,7 @@ jobs:
days-before-stale: 30
only-issue-labels: bug,High priority
stale-issue-label: warn
days-before-close: 30
- uses: actions/stale@v8
with:
@ -34,6 +35,7 @@ jobs:
days-before-stale: 90
only-issue-labels: bug,Medium priority
stale-issue-label: warn
days-before-close: 30
- uses: actions/stale@v8
with:
@ -43,5 +45,4 @@ jobs:
stale-issue-label: stale
only-issue-labels: bug
stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for six months."
days-before-close: 30

2
.nvmrc
View File

@ -1 +1 @@
v18.17.0
v20.10.0

View File

@ -1,3 +1,3 @@
nodejs 18.17.0
nodejs 20.10.0
python 3.10.0
yarn 1.22.19

View File

@ -90,7 +90,7 @@ Component libraries are collections of components as well as the definition of t
#### 1. Prerequisites
- NodeJS version `18.x.x`
- NodeJS version `20.x.x`
- Python version `3.x`
### Using asdf (recommended)

View File

@ -1,4 +1,4 @@
FROM node:18-slim as build
FROM node:20-slim as build
# install node-gyp dependencies
RUN apt-get update && apt-get install -y --no-install-recommends g++ make python3 jq
@ -42,7 +42,7 @@ COPY packages/string-templates packages/string-templates
FROM budibase/couchdb as runner
ARG TARGETARCH
ENV TARGETARCH $TARGETARCH
ENV NODE_MAJOR 18
ENV NODE_MAJOR 20
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
# e.g. docker build --build-arg TARGETBUILD=aas ....
ARG TARGETBUILD=single

View File

@ -1,5 +1,5 @@
{
"version": "2.13.52",
"version": "2.14.0",
"npmClient": "yarn",
"packages": [
"packages/*",

View File

@ -6,6 +6,7 @@
"@babel/eslint-parser": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
"@types/node": "20.10.0",
"@typescript-eslint/parser": "6.9.0",
"esbuild": "^0.18.17",
"esbuild-node-externals": "^1.8.0",
@ -99,7 +100,7 @@
"@budibase/types": "0.0.0"
},
"engines": {
"node": ">=18.0.0 <19.0.0"
"node": ">=20.0.0 <21.0.0"
},
"dependencies": {}
}

@ -1 +1 @@
Subproject commit c1a53bb2f4cafcb4c55ad7181146617b449907f2
Subproject commit b11e6b47370d9b77c63648b45929c86bfed6360c

View File

@ -65,7 +65,6 @@
"@types/cookies": "0.7.8",
"@types/jest": "29.5.5",
"@types/lodash": "4.14.200",
"@types/node": "18.17.0",
"@types/node-fetch": "2.6.4",
"@types/pouchdb": "6.4.0",
"@types/redlock": "4.0.3",

View File

@ -134,7 +134,7 @@ export async function doInContext(appId: string, task: any): Promise<any> {
}
export async function doInTenant<T>(
tenantId: string | null,
tenantId: string | undefined,
task: () => T
): Promise<T> {
// make sure default always selected in single tenancy

View File

@ -33,6 +33,7 @@ export * as docUpdates from "./docUpdates"
export * from "./utils/Duration"
export { SearchParams } from "./db"
export * as docIds from "./docIds"
export * as security from "./security"
// Add context to tenancy for backwards compatibility
// only do this for external usages to prevent internal
// circular dependencies

View File

@ -0,0 +1,24 @@
import { env } from ".."
export const PASSWORD_MIN_LENGTH = +(process.env.PASSWORD_MIN_LENGTH || 8)
export const PASSWORD_MAX_LENGTH = +(process.env.PASSWORD_MAX_LENGTH || 512)
export function validatePassword(
password: string
): { valid: true } | { valid: false; error: string } {
if (!password || password.length < PASSWORD_MIN_LENGTH) {
return {
valid: false,
error: `Password invalid. Minimum ${PASSWORD_MIN_LENGTH} characters.`,
}
}
if (password.length > PASSWORD_MAX_LENGTH) {
return {
valid: false,
error: `Password invalid. Maximum ${PASSWORD_MAX_LENGTH} characters.`,
}
}
return { valid: true }
}

View File

@ -0,0 +1 @@
export * from "./auth"

View File

@ -0,0 +1,45 @@
import { generator } from "../../../tests"
import { PASSWORD_MAX_LENGTH, validatePassword } from "../auth"
describe("auth", () => {
describe("validatePassword", () => {
it("a valid password returns successful", () => {
expect(validatePassword("password")).toEqual({ valid: true })
})
it.each([
["undefined", undefined],
["null", null],
["empty", ""],
])("%s returns unsuccessful", (_, password) => {
expect(validatePassword(password as string)).toEqual({
valid: false,
error: "Password invalid. Minimum 8 characters.",
})
})
it.each([
generator.word({ length: PASSWORD_MAX_LENGTH }),
generator.paragraph().substring(0, PASSWORD_MAX_LENGTH),
])(`can use passwords up to 512 characters in length`, password => {
expect(validatePassword(password)).toEqual({
valid: true,
})
})
it.each([
generator.word({ length: PASSWORD_MAX_LENGTH + 1 }),
generator
.paragraph({ sentences: 50 })
.substring(0, PASSWORD_MAX_LENGTH + 1),
])(
`passwords cannot have more than ${PASSWORD_MAX_LENGTH} characters`,
password => {
expect(validatePassword(password)).toEqual({
valid: false,
error: "Password invalid. Maximum 512 characters.",
})
}
)
})
})

View File

@ -39,7 +39,7 @@ const ALL_STRATEGIES = Object.values(TenantResolutionStrategy)
export const getTenantIDFromCtx = (
ctx: BBContext,
opts: GetTenantIdOptions
): string | null => {
): string | undefined => {
// exit early if not multi-tenant
if (!isMultiTenant()) {
return DEFAULT_TENANT_ID
@ -144,5 +144,5 @@ export const getTenantIDFromCtx = (
ctx.throw(403, "Tenant id not set")
}
return null
return undefined
}

View File

@ -157,12 +157,12 @@ describe("getTenantIDFromCtx", () => {
TenantResolutionStrategy.PATH,
],
}
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeNull()
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeUndefined()
expect(ctx.throw).toBeCalledTimes(1)
expect(ctx.throw).toBeCalledWith(403, "Tenant id not set")
})
it("returns null if allowNoTenant is true", () => {
it("returns undefined if allowNoTenant is true", () => {
const ctx = createCtx({})
mockOpts = {
allowNoTenant: true,
@ -172,7 +172,7 @@ describe("getTenantIDFromCtx", () => {
TenantResolutionStrategy.PATH,
],
}
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeNull()
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeUndefined()
})
})

View File

@ -27,6 +27,7 @@ import {
} from "./utils"
import { searchExistingEmails } from "./lookup"
import { hash } from "../utils"
import { validatePassword } from "../security"
type QuotaUpdateFn = (
change: number,
@ -110,6 +111,12 @@ export class UserDB {
if (await UserDB.isPreventPasswordActions(user, account)) {
throw new HTTPError("Password change is disabled for this user", 400)
}
const passwordValidation = validatePassword(password)
if (!passwordValidation.valid) {
throw new HTTPError(passwordValidation.error, 400)
}
hashedPassword = opts.hashPassword ? await hash(password) : password
} else if (dbUser) {
hashedPassword = dbUser.password

View File

@ -31,8 +31,8 @@ export async function resolveAppUrl(ctx: Ctx) {
const appUrl = ctx.path.split("/")[2]
let possibleAppUrl = `/${appUrl.toLowerCase()}`
let tenantId: string | null = context.getTenantId()
if (env.MULTI_TENANCY) {
let tenantId: string | undefined = context.getTenantId()
if (!env.isDev() && env.MULTI_TENANCY) {
// always use the tenant id from the subdomain in multi tenancy
// this ensures the logged-in user tenant id doesn't overwrite
// e.g. in the case of viewing a public app while already logged-in to another tenant
@ -41,7 +41,7 @@ export async function resolveAppUrl(ctx: Ctx) {
})
}
// search prod apps for a url that matches
// search prod apps for an url that matches
const apps: App[] = await context.doInTenant(
tenantId,
() => getAllApps({ dev: false }) as Promise<App[]>

View File

@ -21,7 +21,7 @@ export const user = (userProps?: Partial<Omit<User, "userId">>): User => {
_id: userId,
userId,
email: newEmail(),
password: "test",
password: "password",
roles: { app_test: "admin" },
firstName: generator.first(),
lastName: generator.last(),

View File

@ -1,4 +1,4 @@
import { string, number } from "yup"
import { string, number, object } from "yup"
const propertyValidator = type => {
if (type === "number") {
@ -9,6 +9,10 @@ const propertyValidator = type => {
return string().email().nullable()
}
if (type === "object") {
return object().nullable()
}
return string().nullable()
}

View File

@ -41,7 +41,7 @@
<div class="filter-editor">
<ActionButton on:click={drawer.show}>{text}</ActionButton>
</div>
<Drawer bind:this={drawer} title="Filtering">
<Drawer bind:this={drawer} title="Filtering" on:drawerHide on:drawerShow>
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
<FilterDrawer
slot="body"

View File

@ -32,4 +32,4 @@
$: schema = linkedTable?.schema
</script>
<FilterEditor on:change {...$$props} {schema} />
<FilterEditor on:change {...$$props} {schema} on:drawerHide on:drawerShow />

View File

@ -38,7 +38,7 @@
$goto("../portal")
} catch (error) {
submitted = false
notifications.error("Failed to create admin user")
notifications.error(error.message || "Failed to create admin user")
}
}
</script>

View File

@ -45,7 +45,7 @@
}
} catch (err) {
submitted = false
notifications.error("Unable to reset password")
notifications.error(err.message || "Unable to reset password")
}
}

View File

@ -5,7 +5,6 @@ build/
docker-error.log
envoy.yaml
*.tar.gz
prebuilds/
dist/
budibase-automation/
budibase-component/

View File

@ -9,26 +9,11 @@
"author": "Budibase",
"license": "GPL-3.0",
"scripts": {
"prebuild": "rm -rf prebuilds 2> /dev/null && cp -r ../../node_modules/leveldown/prebuilds prebuilds",
"rename": "renamer --find .node --replace .fake 'prebuilds/**'",
"tsc": "node ../../scripts/build.js",
"pkg": "pkg . --out-path build --no-bytecode --public --public-packages \"*\" -C GZip",
"build": "yarn prebuild && yarn rename && yarn tsc && yarn pkg && yarn postbuild",
"build": "yarn tsc",
"check:types": "tsc -p tsconfig.json --noEmit --paths null",
"postbuild": "rm -rf prebuilds 2> /dev/null",
"start": "ts-node ./src/index.ts"
},
"pkg": {
"targets": [
"node18-linux",
"node18-win",
"node18-macos"
],
"assets": [
"prebuilds/**/*"
],
"outputPath": "build"
},
"dependencies": {
"@budibase/backend-core": "0.0.0",
"@budibase/string-templates": "0.0.0",
@ -43,7 +28,6 @@
"inquirer": "8.0.0",
"lookpath": "1.1.0",
"node-fetch": "2.6.7",
"pkg": "5.8.0",
"posthog-node": "1.3.0",
"pouchdb": "7.3.0",
"pouchdb-replication-stream": "1.2.9",
@ -55,7 +39,6 @@
"@types/jest": "29.5.5",
"@types/node-fetch": "2.6.4",
"@types/pouchdb": "^6.4.0",
"renamer": "^4.0.0",
"ts-node": "10.8.1",
"typescript": "5.2.2"
}

View File

@ -1,10 +1,9 @@
#!/usr/bin/env node
process.env.DISABLE_PINO_LOGGER = "1"
import "./prebuilds"
import "./environment"
import { getCommands } from "./options"
import { Command } from "commander"
import { getHelpDescription } from "./utils"
import { getHelpDescription, error } from "./utils"
import { version } from "../package.json"
// add hosting config
@ -21,6 +20,23 @@ async function init() {
await program.parseAsync(process.argv)
}
const events = ["exit", "SIGINT", "SIGUSR1", "SIGUSR2", "uncaughtException"]
events.forEach(event => {
process.on(event, (evt?: number) => {
if (evt && !isNaN(evt)) {
return
}
if (evt) {
console.error(
error(
"Failed to run CLI command - please report with the following message:"
)
)
console.error(error(evt))
}
})
})
init().catch(err => {
console.error(`Unexpected error - `, err)
})

View File

@ -1,57 +0,0 @@
import os from "os"
import { join } from "path"
import fs from "fs"
import { error } from "./utils"
const PREBUILDS = "prebuilds"
const ARCH = `${os.platform()}-${os.arch()}`
const PREBUILD_DIR = join(process.execPath, "..", "cli", PREBUILDS, ARCH)
// running as built CLI pkg bundle
if (!process.argv[0].includes("node")) {
checkForBinaries()
}
function localPrebuildPath() {
return join(process.execPath, "..", PREBUILDS)
}
function checkForBinaries() {
const readDir = join(__filename, "..", "..", "..", "cli", PREBUILDS, ARCH)
if (fs.existsSync(PREBUILD_DIR) || !fs.existsSync(readDir)) {
return
}
const natives = fs.readdirSync(readDir)
if (fs.existsSync(readDir)) {
const writePath = join(localPrebuildPath(), ARCH)
fs.mkdirSync(writePath, { recursive: true })
for (let native of natives) {
const filename = `${native.split(".fake")[0]}.node`
fs.cpSync(join(readDir, native), join(writePath, filename))
}
}
}
function cleanup(evt?: number) {
// cleanup prebuilds first
const path = localPrebuildPath()
if (fs.existsSync(path)) {
fs.rmSync(path, { recursive: true })
}
if (evt && !isNaN(evt)) {
return
}
if (evt) {
console.error(
error(
"Failed to run CLI command - please report with the following message:"
)
)
console.error(error(evt))
}
}
const events = ["exit", "SIGINT", "SIGUSR1", "SIGUSR2", "uncaughtException"]
events.forEach(event => {
process.on(event, cleanup)
})

@ -1 +1 @@
Subproject commit 992486c10044a7495496b97bdf5f454d4020bfba
Subproject commit dc2b1b22e7f9bac705746bf1fb72c817db043fa3

View File

@ -1,4 +1,4 @@
FROM node:18-slim
FROM node:20-slim
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"

View File

@ -86,7 +86,7 @@
"lodash": "4.17.21",
"memorystream": "0.3.1",
"mongodb": "5.7",
"mssql": "9.1.1",
"mssql": "10.0.1",
"mysql2": "3.5.2",
"node-fetch": "2.6.7",
"object-sizeof": "2.6.1",
@ -121,8 +121,7 @@
"@types/koa": "2.13.4",
"@types/koa__router": "8.0.8",
"@types/lodash": "4.14.200",
"@types/mssql": "8.1.2",
"@types/node": "18.17.0",
"@types/mssql": "9.1.4",
"@types/node-fetch": "2.6.4",
"@types/oracledb": "5.2.2",
"@types/pg": "8.6.6",

View File

@ -48,6 +48,7 @@ async function init() {
HTTP_MIGRATIONS: "0",
HTTP_LOGGING: "0",
VERSION: "0.0.0+local",
PASSWORD_MIN_LENGTH: "1",
}
config = { ...config, ...existingConfig }

View File

@ -17,7 +17,6 @@
"devDependencies": {
"@budibase/nano": "10.1.4",
"@types/koa": "2.13.4",
"@types/node": "18.17.0",
"@types/pouchdb": "6.4.0",
"@types/redlock": "4.0.3",
"rimraf": "3.0.2",

View File

@ -1,4 +1,4 @@
FROM node:18-alpine
FROM node:20-alpine
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"

View File

@ -79,7 +79,6 @@
"@types/koa": "2.13.4",
"@types/koa__router": "8.0.8",
"@types/lodash": "4.14.200",
"@types/node": "18.17.0",
"@types/node-fetch": "2.6.4",
"@types/server-destroy": "1.0.1",
"@types/supertest": "2.0.14",

View File

@ -30,6 +30,7 @@ async function init() {
ENABLE_EMAIL_TEST_MODE: "1",
HTTP_LOGGING: "0",
VERSION: "0.0.0+local",
PASSWORD_MIN_LENGTH: "1",
}
config = { ...config, ...existingConfig }

View File

@ -122,10 +122,10 @@ export const resetUpdate = async (ctx: Ctx<PasswordResetUpdateRequest>) => {
ctx.body = {
message: "password reset successfully.",
}
} catch (err) {
} catch (err: any) {
console.warn(err)
// hide any details of the error for security
ctx.throw(400, "Cannot reset password.")
ctx.throw(400, err.message || "Cannot reset password.")
}
}

View File

@ -229,7 +229,7 @@ describe("/api/global/auth", () => {
)
expect(res.body).toEqual({
message: "Cannot reset password.",
message: "Password change is disabled for this user",
status: 400,
})
}
@ -261,8 +261,12 @@ describe("/api/global/auth", () => {
)
// convert to account owner now that password has been requested
const account = structures.accounts.ssoAccount() as CloudAccount
mocks.accounts.getAccount.mockReturnValueOnce(
const account: CloudAccount = {
...structures.accounts.ssoAccount(),
budibaseUserId: "budibaseUserId",
email: user.email,
}
mocks.accounts.getAccountByTenantId.mockReturnValueOnce(
Promise.resolve(account)
)

View File

@ -45,7 +45,7 @@ class TestConfiguration {
tenantId: string
user?: User
apiKey?: string
userPassword = "test"
userPassword = "password"
constructor(opts: { openServer: boolean } = { openServer: true }) {
// default to cloud hosting

View File

@ -101,7 +101,7 @@ export class UserAPI extends TestAPI {
if (!request) {
request = {
email: structures.email(),
password: generator.string(),
password: generator.string({ length: 8 }),
tenantId: structures.tenant.id(),
}
}

733
yarn.lock

File diff suppressed because it is too large Load Diff