Merge branch 'nested-nav-links' of github.com:Budibase/budibase into nested-nav-links

This commit is contained in:
Andrew Kingston 2024-03-28 18:30:54 +00:00
commit c76fda35ab
25 changed files with 188 additions and 34 deletions

View File

@ -66,7 +66,8 @@ jobs:
# Run build all the projects # Run build all the projects
- name: Build - name: Build
run: | run: |
yarn build yarn build:oss
yarn build:account-portal
# Check the types of the projects built via esbuild # Check the types of the projects built via esbuild
- name: Check types - name: Check types
run: | run: |
@ -231,27 +232,34 @@ jobs:
echo "pro_commit=$pro_commit" echo "pro_commit=$pro_commit"
echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT" echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT"
echo "base_commit=$base_commit" echo "base_commit=$base_commit"
echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT"
base_commit_excluding_merges=$(git log --no-merges -n 1 --format=format:%H $base_commit)
echo "base_commit_excluding_merges=$base_commit_excluding_merges"
echo "base_commit_excluding_merges=$base_commit_excluding_merges" >> "$GITHUB_OUTPUT"
else else
echo "Nothing to do - branch to branch merge." echo "Nothing to do - branch to branch merge."
fi fi
- name: Check submodule merged to base branch - name: Check submodule merged and latest on base branch
if: ${{ steps.get_pro_commits.outputs.base_commit != '' }} if: ${{ steps.get_pro_commits.outputs.base_commit_excluding_merges != '' }}
uses: actions/github-script@v7 run: |
with: cd packages/pro
github-token: ${{ secrets.GITHUB_TOKEN }} base_commit_excluding_merges='${{ steps.get_pro_commits.outputs.base_commit_excluding_merges }}'
script: | pro_commit='${{ steps.get_pro_commits.outputs.pro_commit }}'
const submoduleCommit = '${{ steps.get_pro_commits.outputs.pro_commit }}';
const baseCommit = '${{ steps.get_pro_commits.outputs.base_commit }}';
if (submoduleCommit !== baseCommit) { any_commit=$(git log --no-merges $base_commit_excluding_merges...$pro_commit)
console.error('Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}" branch.');
console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/master/docs/getting_started.md') if [ -n "$any_commit" ]; then
process.exit(1); echo $any_commit
} else {
console.log('All good, the submodule had been merged and setup correctly!') echo "An error occurred: <error_message>"
} echo 'Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}" branch.'
echo 'Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/master/docs/getting_started.md'
exit 1
else
echo 'All good, the submodule had been merged and setup correctly!'
fi
check-accountportal-submodule: check-accountportal-submodule:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

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

View File

@ -34,6 +34,8 @@
"get-past-client-version": "node scripts/getPastClientVersion.js", "get-past-client-version": "node scripts/getPastClientVersion.js",
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev", "setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
"build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream", "build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream",
"build:oss": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream --ignore @budibase/account-portal --ignore @budibase/account-portal-server --ignore @budibase/account-portal-ui",
"build:account-portal": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream --scope @budibase/account-portal --scope @budibase/account-portal-server --scope @budibase/account-portal-ui",
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput", "build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
"check:types": "lerna run check:types", "check:types": "lerna run check:types",
"build:sdk": "lerna run --stream build:sdk", "build:sdk": "lerna run --stream build:sdk",

@ -1 +1 @@
Subproject commit f5b467b6b1c55c48847545db41be7b1c035e167a Subproject commit 63ce32bca871f0a752323f5f7ebb5ec16bbbacc3

View File

@ -20,7 +20,7 @@ export async function lookupTenantId(userId: string) {
return user.tenantId return user.tenantId
} }
async function getUserDoc(emailOrId: string): Promise<PlatformUser> { export async function getUserDoc(emailOrId: string): Promise<PlatformUser> {
const db = getPlatformDB() const db = getPlatformDB()
return db.get(emailOrId) return db.get(emailOrId)
} }
@ -79,6 +79,17 @@ async function addUserDoc(emailOrId: string, newDocFn: () => PlatformUser) {
} }
} }
export async function addSsoUser(
ssoId: string,
email: string,
userId: string,
tenantId: string
) {
return addUserDoc(ssoId, () =>
newUserSsoIdDoc(ssoId, email, userId, tenantId)
)
}
export async function addUser( export async function addUser(
tenantId: string, tenantId: string,
userId: string, userId: string,
@ -91,9 +102,7 @@ export async function addUser(
] ]
if (ssoId) { if (ssoId) {
promises.push( promises.push(addSsoUser(ssoId, email, userId, tenantId))
addUserDoc(ssoId, () => newUserSsoIdDoc(ssoId, email, userId, tenantId))
)
} }
await Promise.all(promises) await Promise.all(promises)

View File

@ -12,6 +12,13 @@ export default {
format: "esm", format: "esm",
file: "dist/bbui.es.js", file: "dist/bbui.es.js",
}, },
onwarn(warning, warn) {
// suppress eval warnings
if (warning.code === "EVAL") {
return
}
warn(warning)
},
plugins: [ plugins: [
resolve(), resolve(),
commonjs(), commonjs(),

View File

@ -45,7 +45,8 @@ export default {
onwarn(warning, warn) { onwarn(warning, warn) {
if ( if (
warning.code === "THIS_IS_UNDEFINED" || warning.code === "THIS_IS_UNDEFINED" ||
warning.code === "CIRCULAR_DEPENDENCY" warning.code === "CIRCULAR_DEPENDENCY" ||
warning.code === "EVAL"
) { ) {
return return
} }

@ -1 +1 @@
Subproject commit f8e8f87bd52081e1303a5ae92c432ea5b38f3bb4 Subproject commit 6b62505be0c0b50a57b4f4980d86541ebdc86428

View File

@ -9,6 +9,7 @@ import {
QueryType, QueryType,
} from "@budibase/types" } from "@budibase/types"
import { db as dbCore } from "@budibase/backend-core" import { db as dbCore } from "@budibase/backend-core"
import { HOST_ADDRESS } from "./utils"
interface CouchDBConfig { interface CouchDBConfig {
url: string url: string
@ -28,7 +29,7 @@ const SCHEMA: Integration = {
url: { url: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "http://localhost:5984", default: `http://${HOST_ADDRESS}:5984`,
}, },
database: { database: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,

View File

@ -8,6 +8,7 @@ import {
} from "@budibase/types" } from "@budibase/types"
import { Client, ClientOptions } from "@elastic/elasticsearch" import { Client, ClientOptions } from "@elastic/elasticsearch"
import { HOST_ADDRESS } from "./utils"
interface ElasticsearchConfig { interface ElasticsearchConfig {
url: string url: string
@ -29,7 +30,7 @@ const SCHEMA: Integration = {
url: { url: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "http://localhost:9200", default: `http://${HOST_ADDRESS}:9200`,
}, },
ssl: { ssl: {
type: DatasourceFieldType.BOOLEAN, type: DatasourceFieldType.BOOLEAN,

View File

@ -22,6 +22,7 @@ import {
finaliseExternalTables, finaliseExternalTables,
SqlClient, SqlClient,
checkExternalTables, checkExternalTables,
HOST_ADDRESS,
} from "./utils" } from "./utils"
import Sql from "./base/sql" import Sql from "./base/sql"
import { MSSQLTablesResponse, MSSQLColumn } from "./base/types" import { MSSQLTablesResponse, MSSQLColumn } from "./base/types"
@ -88,7 +89,6 @@ const SCHEMA: Integration = {
user: { user: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "localhost",
}, },
password: { password: {
type: DatasourceFieldType.PASSWORD, type: DatasourceFieldType.PASSWORD,
@ -96,7 +96,7 @@ const SCHEMA: Integration = {
}, },
server: { server: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: HOST_ADDRESS,
}, },
port: { port: {
type: DatasourceFieldType.NUMBER, type: DatasourceFieldType.NUMBER,

View File

@ -22,6 +22,7 @@ import {
InsertManyResult, InsertManyResult,
} from "mongodb" } from "mongodb"
import environment from "../environment" import environment from "../environment"
import { HOST_ADDRESS } from "./utils"
export interface MongoDBConfig { export interface MongoDBConfig {
connectionString: string connectionString: string
@ -51,7 +52,7 @@ const getSchema = () => {
connectionString: { connectionString: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "mongodb://localhost:27017", default: `mongodb://${HOST_ADDRESS}:27017`,
display: "Connection string", display: "Connection string",
}, },
db: { db: {

View File

@ -21,6 +21,7 @@ import {
generateColumnDefinition, generateColumnDefinition,
finaliseExternalTables, finaliseExternalTables,
checkExternalTables, checkExternalTables,
HOST_ADDRESS,
} from "./utils" } from "./utils"
import dayjs from "dayjs" import dayjs from "dayjs"
import { NUMBER_REGEX } from "../utilities" import { NUMBER_REGEX } from "../utilities"
@ -49,7 +50,7 @@ const SCHEMA: Integration = {
datasource: { datasource: {
host: { host: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: HOST_ADDRESS,
required: true, required: true,
}, },
port: { port: {

View File

@ -22,6 +22,7 @@ import {
finaliseExternalTables, finaliseExternalTables,
getSqlQuery, getSqlQuery,
SqlClient, SqlClient,
HOST_ADDRESS,
} from "./utils" } from "./utils"
import Sql from "./base/sql" import Sql from "./base/sql"
import { import {
@ -63,7 +64,7 @@ const SCHEMA: Integration = {
datasource: { datasource: {
host: { host: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: HOST_ADDRESS,
required: true, required: true,
}, },
port: { port: {

View File

@ -21,6 +21,7 @@ import {
finaliseExternalTables, finaliseExternalTables,
SqlClient, SqlClient,
checkExternalTables, checkExternalTables,
HOST_ADDRESS,
} from "./utils" } from "./utils"
import Sql from "./base/sql" import Sql from "./base/sql"
import { PostgresColumn } from "./base/types" import { PostgresColumn } from "./base/types"
@ -72,7 +73,7 @@ const SCHEMA: Integration = {
datasource: { datasource: {
host: { host: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
default: "localhost", default: HOST_ADDRESS,
required: true, required: true,
}, },
port: { port: {

View File

@ -6,6 +6,7 @@ import {
QueryType, QueryType,
} from "@budibase/types" } from "@budibase/types"
import Redis from "ioredis" import Redis from "ioredis"
import { HOST_ADDRESS } from "./utils"
interface RedisConfig { interface RedisConfig {
host: string host: string
@ -28,7 +29,7 @@ const SCHEMA: Integration = {
host: { host: {
type: DatasourceFieldType.STRING, type: DatasourceFieldType.STRING,
required: true, required: true,
default: "localhost", default: HOST_ADDRESS,
}, },
port: { port: {
type: DatasourceFieldType.NUMBER, type: DatasourceFieldType.NUMBER,

View File

@ -13,6 +13,7 @@ import {
DEFAULT_BB_DATASOURCE_ID, DEFAULT_BB_DATASOURCE_ID,
} from "../constants" } from "../constants"
import { helpers } from "@budibase/shared-core" import { helpers } from "@budibase/shared-core"
import env from "../environment"
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
const ROW_ID_REGEX = /^\[.*]$/g const ROW_ID_REGEX = /^\[.*]$/g
@ -92,6 +93,14 @@ export enum SqlClient {
ORACLE = "oracledb", ORACLE = "oracledb",
} }
const isCloud = env.isProd() && !env.SELF_HOSTED
const isSelfHost = env.isProd() && env.SELF_HOSTED
export const HOST_ADDRESS = isSelfHost
? "host.docker.internal"
: isCloud
? ""
: "localhost"
export function isExternalTableID(tableId: string) { export function isExternalTableID(tableId: string) {
return tableId.includes(DocumentType.DATASOURCE) return tableId.includes(DocumentType.DATASOURCE)
} }

View File

@ -17,6 +17,12 @@ const config = (format, outputFile) => ({
format, format,
file: outputFile, file: outputFile,
}, },
onwarn(warning, warn) {
if (warning.code === "EVAL") {
return
}
warn(warning)
},
plugins: [ plugins: [
typescript(), typescript(),
resolve({ resolve({

View File

@ -68,6 +68,11 @@ export interface CreateAdminUserRequest {
ssoId?: string ssoId?: string
} }
export interface AddSSoUserRequest {
ssoId: string
email: string
}
export interface CreateAdminUserResponse { export interface CreateAdminUserResponse {
_id: string _id: string
_rev: string _rev: string

View File

@ -3,6 +3,7 @@ import env from "../../../environment"
import { import {
AcceptUserInviteRequest, AcceptUserInviteRequest,
AcceptUserInviteResponse, AcceptUserInviteResponse,
AddSSoUserRequest,
BulkUserRequest, BulkUserRequest,
BulkUserResponse, BulkUserResponse,
CloudAccount, CloudAccount,
@ -15,6 +16,7 @@ import {
LockName, LockName,
LockType, LockType,
MigrationType, MigrationType,
PlatformUserByEmail,
SaveUserResponse, SaveUserResponse,
SearchUsersRequest, SearchUsersRequest,
User, User,
@ -53,6 +55,25 @@ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
} }
} }
export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest>) => {
const { email, ssoId } = ctx.request.body
try {
// Status is changed to 404 from getUserDoc if user is not found
let userByEmail = (await platform.users.getUserDoc(
email
)) as PlatformUserByEmail
await platform.users.addSsoUser(
ssoId,
email,
userByEmail.userId,
userByEmail.tenantId
)
ctx.status = 200
} catch (err: any) {
ctx.throw(err.status || 400, err)
}
}
const bulkDelete = async (userIds: string[], currentUserId: string) => { const bulkDelete = async (userIds: string[], currentUserId: string) => {
if (userIds?.indexOf(currentUserId) !== -1) { if (userIds?.indexOf(currentUserId) !== -1) {
throw new Error("Unable to delete self.") throw new Error("Unable to delete self.")

View File

@ -41,6 +41,10 @@ const PUBLIC_ENDPOINTS = [
route: "/api/global/users/init", route: "/api/global/users/init",
method: "POST", method: "POST",
}, },
{
route: "/api/global/users/sso",
method: "POST",
},
{ {
route: "/api/global/users/invite/accept", route: "/api/global/users/invite/accept",
method: "POST", method: "POST",
@ -81,6 +85,11 @@ const NO_TENANCY_ENDPOINTS = [
route: "/api/global/users/init", route: "/api/global/users/init",
method: "POST", method: "POST",
}, },
// tenant is retrieved from the user found by the requested email
{
route: "/api/global/users/sso",
method: "POST",
},
// deprecated single tenant sso callback // deprecated single tenant sso callback
{ {
route: "/api/admin/auth/google/callback", route: "/api/admin/auth/google/callback",

View File

@ -520,10 +520,51 @@ describe("/api/global/users", () => {
}) })
} }
function createPasswordUser() {
return config.doInTenant(() => {
const user = structures.users.user()
return userSdk.db.save(user)
})
}
it("should be able to update an sso user that has no password", async () => { it("should be able to update an sso user that has no password", async () => {
const user = await createSSOUser() const user = await createSSOUser()
await config.api.users.saveUser(user) await config.api.users.saveUser(user)
}) })
it("sso support couldn't be used by admin. It is cloud restricted and needs internal key", async () => {
const user = await config.createUser()
const ssoId = "fake-ssoId"
await config.api.users
.addSsoSupportDefaultAuth(ssoId, user.email)
.expect("Content-Type", /json/)
.expect(403)
})
it("if user email doesn't exist, SSO support couldn't be added. Not found error returned", async () => {
const ssoId = "fake-ssoId"
const email = "fake-email@budibase.com"
await config.api.users
.addSsoSupportInternalAPIAuth(ssoId, email)
.expect("Content-Type", /json/)
.expect(404)
})
it("if user email exist, SSO support is added", async () => {
const user = await createPasswordUser()
const ssoId = "fakessoId"
await config.api.users
.addSsoSupportInternalAPIAuth(ssoId, user.email)
.expect(200)
})
it("if user ssoId is already assigned, no change will be applied", async () => {
const user = await createSSOUser()
user.ssoId = "testssoId"
await config.api.users
.addSsoSupportInternalAPIAuth(user.ssoId, user.email)
.expect(200)
})
}) })
}) })

View File

@ -65,6 +65,12 @@ router
users.buildUserSaveValidation(), users.buildUserSaveValidation(),
controller.save controller.save
) )
.post(
"/api/global/users/sso",
cloudRestricted,
users.buildAddSsoSupport(),
controller.addSsoSupport
)
.post( .post(
"/api/global/users/bulk", "/api/global/users/bulk",
auth.adminOnly, auth.adminOnly,

View File

@ -41,6 +41,15 @@ export const buildUserSaveValidation = () => {
return auth.joiValidator.body(Joi.object(schema).required().unknown(true)) return auth.joiValidator.body(Joi.object(schema).required().unknown(true))
} }
export const buildAddSsoSupport = () => {
return auth.joiValidator.body(
Joi.object({
ssoId: Joi.string().required(),
email: Joi.string().required(),
}).required()
)
}
export const buildUserBulkUserValidation = (isSelf = false) => { export const buildUserBulkUserValidation = (isSelf = false) => {
if (!isSelf) { if (!isSelf) {
schema = { schema = {

View File

@ -127,6 +127,20 @@ export class UserAPI extends TestAPI {
.expect(status ? status : 200) .expect(status ? status : 200)
} }
addSsoSupportInternalAPIAuth = (ssoId: string, email: string) => {
return this.request
.post(`/api/global/users/sso`)
.send({ ssoId, email })
.set(this.config.internalAPIHeaders())
}
addSsoSupportDefaultAuth = (ssoId: string, email: string) => {
return this.request
.post(`/api/global/users/sso`)
.send({ ssoId, email })
.set(this.config.defaultHeaders())
}
deleteUser = (userId: string, status?: number) => { deleteUser = (userId: string, status?: number) => {
return this.request return this.request
.delete(`/api/global/users/${userId}`) .delete(`/api/global/users/${userId}`)