diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 5a0d09033a..030ad6578e 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -66,7 +66,8 @@ jobs: # Run build all the projects - name: Build run: | - yarn build + yarn build:oss + yarn build:account-portal # Check the types of the projects built via esbuild - name: Check types run: | @@ -231,27 +232,34 @@ jobs: echo "pro_commit=$pro_commit" echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT" 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 echo "Nothing to do - branch to branch merge." fi - - name: Check submodule merged to base branch - if: ${{ steps.get_pro_commits.outputs.base_commit != '' }} - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const submoduleCommit = '${{ steps.get_pro_commits.outputs.pro_commit }}'; - const baseCommit = '${{ steps.get_pro_commits.outputs.base_commit }}'; + - name: Check submodule merged and latest on base branch + if: ${{ steps.get_pro_commits.outputs.base_commit_excluding_merges != '' }} + run: | + cd packages/pro + base_commit_excluding_merges='${{ steps.get_pro_commits.outputs.base_commit_excluding_merges }}' + pro_commit='${{ steps.get_pro_commits.outputs.pro_commit }}' - if (submoduleCommit !== baseCommit) { - 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') - process.exit(1); - } else { - console.log('All good, the submodule had been merged and setup correctly!') - } + any_commit=$(git log --no-merges $base_commit_excluding_merges...$pro_commit) + + if [ -n "$any_commit" ]; then + echo $any_commit + + echo "An error occurred: " + 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: runs-on: ubuntu-latest diff --git a/lerna.json b/lerna.json index 9cffdba08a..bacdcb782f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.22.12", + "version": "2.22.13", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/package.json b/package.json index 79a7b06eff..32693a0b6f 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "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", "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", "check:types": "lerna run check:types", "build:sdk": "lerna run --stream build:sdk", diff --git a/packages/account-portal b/packages/account-portal index f5b467b6b1..63ce32bca8 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit f5b467b6b1c55c48847545db41be7b1c035e167a +Subproject commit 63ce32bca871f0a752323f5f7ebb5ec16bbbacc3 diff --git a/packages/backend-core/src/platform/users.ts b/packages/backend-core/src/platform/users.ts index 6f030afb7c..ccaad76b19 100644 --- a/packages/backend-core/src/platform/users.ts +++ b/packages/backend-core/src/platform/users.ts @@ -20,7 +20,7 @@ export async function lookupTenantId(userId: string) { return user.tenantId } -async function getUserDoc(emailOrId: string): Promise { +export async function getUserDoc(emailOrId: string): Promise { const db = getPlatformDB() 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( tenantId: string, userId: string, @@ -91,9 +102,7 @@ export async function addUser( ] if (ssoId) { - promises.push( - addUserDoc(ssoId, () => newUserSsoIdDoc(ssoId, email, userId, tenantId)) - ) + promises.push(addSsoUser(ssoId, email, userId, tenantId)) } await Promise.all(promises) diff --git a/packages/bbui/rollup.config.js b/packages/bbui/rollup.config.js index e285d548d6..da274e0ba5 100644 --- a/packages/bbui/rollup.config.js +++ b/packages/bbui/rollup.config.js @@ -12,6 +12,13 @@ export default { format: "esm", file: "dist/bbui.es.js", }, + onwarn(warning, warn) { + // suppress eval warnings + if (warning.code === "EVAL") { + return + } + warn(warning) + }, plugins: [ resolve(), commonjs(), diff --git a/packages/client/rollup.config.js b/packages/client/rollup.config.js index ee839424ee..9839414f7e 100644 --- a/packages/client/rollup.config.js +++ b/packages/client/rollup.config.js @@ -45,7 +45,8 @@ export default { onwarn(warning, warn) { if ( warning.code === "THIS_IS_UNDEFINED" || - warning.code === "CIRCULAR_DEPENDENCY" + warning.code === "CIRCULAR_DEPENDENCY" || + warning.code === "EVAL" ) { return } diff --git a/packages/pro b/packages/pro index f8e8f87bd5..6b62505be0 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit f8e8f87bd52081e1303a5ae92c432ea5b38f3bb4 +Subproject commit 6b62505be0c0b50a57b4f4980d86541ebdc86428 diff --git a/packages/server/src/integrations/couchdb.ts b/packages/server/src/integrations/couchdb.ts index 079f646b60..c271fb12b2 100644 --- a/packages/server/src/integrations/couchdb.ts +++ b/packages/server/src/integrations/couchdb.ts @@ -9,6 +9,7 @@ import { QueryType, } from "@budibase/types" import { db as dbCore } from "@budibase/backend-core" +import { HOST_ADDRESS } from "./utils" interface CouchDBConfig { url: string @@ -28,7 +29,7 @@ const SCHEMA: Integration = { url: { type: DatasourceFieldType.STRING, required: true, - default: "http://localhost:5984", + default: `http://${HOST_ADDRESS}:5984`, }, database: { type: DatasourceFieldType.STRING, diff --git a/packages/server/src/integrations/elasticsearch.ts b/packages/server/src/integrations/elasticsearch.ts index 7ae0295298..af03baaef1 100644 --- a/packages/server/src/integrations/elasticsearch.ts +++ b/packages/server/src/integrations/elasticsearch.ts @@ -8,6 +8,7 @@ import { } from "@budibase/types" import { Client, ClientOptions } from "@elastic/elasticsearch" +import { HOST_ADDRESS } from "./utils" interface ElasticsearchConfig { url: string @@ -29,7 +30,7 @@ const SCHEMA: Integration = { url: { type: DatasourceFieldType.STRING, required: true, - default: "http://localhost:9200", + default: `http://${HOST_ADDRESS}:9200`, }, ssl: { type: DatasourceFieldType.BOOLEAN, diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index 831fcd3d20..8e8e84f533 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -22,6 +22,7 @@ import { finaliseExternalTables, SqlClient, checkExternalTables, + HOST_ADDRESS, } from "./utils" import Sql from "./base/sql" import { MSSQLTablesResponse, MSSQLColumn } from "./base/types" @@ -88,7 +89,6 @@ const SCHEMA: Integration = { user: { type: DatasourceFieldType.STRING, required: true, - default: "localhost", }, password: { type: DatasourceFieldType.PASSWORD, @@ -96,7 +96,7 @@ const SCHEMA: Integration = { }, server: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, }, port: { type: DatasourceFieldType.NUMBER, diff --git a/packages/server/src/integrations/mongodb.ts b/packages/server/src/integrations/mongodb.ts index c9852e4c7a..dea752502d 100644 --- a/packages/server/src/integrations/mongodb.ts +++ b/packages/server/src/integrations/mongodb.ts @@ -22,6 +22,7 @@ import { InsertManyResult, } from "mongodb" import environment from "../environment" +import { HOST_ADDRESS } from "./utils" export interface MongoDBConfig { connectionString: string @@ -51,7 +52,7 @@ const getSchema = () => { connectionString: { type: DatasourceFieldType.STRING, required: true, - default: "mongodb://localhost:27017", + default: `mongodb://${HOST_ADDRESS}:27017`, display: "Connection string", }, db: { diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index db3819f468..fd9d57d255 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -21,6 +21,7 @@ import { generateColumnDefinition, finaliseExternalTables, checkExternalTables, + HOST_ADDRESS, } from "./utils" import dayjs from "dayjs" import { NUMBER_REGEX } from "../utilities" @@ -49,7 +50,7 @@ const SCHEMA: Integration = { datasource: { host: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, required: true, }, port: { diff --git a/packages/server/src/integrations/oracle.ts b/packages/server/src/integrations/oracle.ts index 83803906be..9104aadbcc 100644 --- a/packages/server/src/integrations/oracle.ts +++ b/packages/server/src/integrations/oracle.ts @@ -22,6 +22,7 @@ import { finaliseExternalTables, getSqlQuery, SqlClient, + HOST_ADDRESS, } from "./utils" import Sql from "./base/sql" import { @@ -63,7 +64,7 @@ const SCHEMA: Integration = { datasource: { host: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, required: true, }, port: { diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index 635d834761..05a519da64 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -21,6 +21,7 @@ import { finaliseExternalTables, SqlClient, checkExternalTables, + HOST_ADDRESS, } from "./utils" import Sql from "./base/sql" import { PostgresColumn } from "./base/types" @@ -72,7 +73,7 @@ const SCHEMA: Integration = { datasource: { host: { type: DatasourceFieldType.STRING, - default: "localhost", + default: HOST_ADDRESS, required: true, }, port: { diff --git a/packages/server/src/integrations/redis.ts b/packages/server/src/integrations/redis.ts index 6a6331ccd4..e127cddd56 100644 --- a/packages/server/src/integrations/redis.ts +++ b/packages/server/src/integrations/redis.ts @@ -6,6 +6,7 @@ import { QueryType, } from "@budibase/types" import Redis from "ioredis" +import { HOST_ADDRESS } from "./utils" interface RedisConfig { host: string @@ -28,7 +29,7 @@ const SCHEMA: Integration = { host: { type: DatasourceFieldType.STRING, required: true, - default: "localhost", + default: HOST_ADDRESS, }, port: { type: DatasourceFieldType.NUMBER, diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index f73f522357..1ba379da9e 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -13,6 +13,7 @@ import { DEFAULT_BB_DATASOURCE_ID, } from "../constants" import { helpers } from "@budibase/shared-core" +import env from "../environment" const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` const ROW_ID_REGEX = /^\[.*]$/g @@ -92,6 +93,14 @@ export enum SqlClient { 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) { return tableId.includes(DocumentType.DATASOURCE) } diff --git a/packages/string-templates/rollup.config.js b/packages/string-templates/rollup.config.js index a843286a82..ee02c7a14a 100644 --- a/packages/string-templates/rollup.config.js +++ b/packages/string-templates/rollup.config.js @@ -17,6 +17,12 @@ const config = (format, outputFile) => ({ format, file: outputFile, }, + onwarn(warning, warn) { + if (warning.code === "EVAL") { + return + } + warn(warning) + }, plugins: [ typescript(), resolve({ diff --git a/packages/types/src/api/web/user.ts b/packages/types/src/api/web/user.ts index d68d687dcb..0ef7493016 100644 --- a/packages/types/src/api/web/user.ts +++ b/packages/types/src/api/web/user.ts @@ -68,6 +68,11 @@ export interface CreateAdminUserRequest { ssoId?: string } +export interface AddSSoUserRequest { + ssoId: string + email: string +} + export interface CreateAdminUserResponse { _id: string _rev: string diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 6b9e533f78..93f35b4c37 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -3,6 +3,7 @@ import env from "../../../environment" import { AcceptUserInviteRequest, AcceptUserInviteResponse, + AddSSoUserRequest, BulkUserRequest, BulkUserResponse, CloudAccount, @@ -15,6 +16,7 @@ import { LockName, LockType, MigrationType, + PlatformUserByEmail, SaveUserResponse, SearchUsersRequest, User, @@ -53,6 +55,25 @@ export const save = async (ctx: UserCtx) => { } } +export const addSsoSupport = async (ctx: Ctx) => { + 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) => { if (userIds?.indexOf(currentUserId) !== -1) { throw new Error("Unable to delete self.") diff --git a/packages/worker/src/api/index.ts b/packages/worker/src/api/index.ts index d7aef0b274..4936c104e1 100644 --- a/packages/worker/src/api/index.ts +++ b/packages/worker/src/api/index.ts @@ -41,6 +41,10 @@ const PUBLIC_ENDPOINTS = [ route: "/api/global/users/init", method: "POST", }, + { + route: "/api/global/users/sso", + method: "POST", + }, { route: "/api/global/users/invite/accept", method: "POST", @@ -81,6 +85,11 @@ const NO_TENANCY_ENDPOINTS = [ route: "/api/global/users/init", 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 { route: "/api/admin/auth/google/callback", diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index 37f5721881..2198757be1 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -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 () => { const user = await createSSOUser() 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) + }) }) }) diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts index 6b9291b88b..e7c77678fc 100644 --- a/packages/worker/src/api/routes/global/users.ts +++ b/packages/worker/src/api/routes/global/users.ts @@ -65,6 +65,12 @@ router users.buildUserSaveValidation(), controller.save ) + .post( + "/api/global/users/sso", + cloudRestricted, + users.buildAddSsoSupport(), + controller.addSsoSupport + ) .post( "/api/global/users/bulk", auth.adminOnly, diff --git a/packages/worker/src/api/routes/validation/users.ts b/packages/worker/src/api/routes/validation/users.ts index fbc85af2d3..cbd7567457 100644 --- a/packages/worker/src/api/routes/validation/users.ts +++ b/packages/worker/src/api/routes/validation/users.ts @@ -41,6 +41,15 @@ export const buildUserSaveValidation = () => { 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) => { if (!isSelf) { schema = { diff --git a/packages/worker/src/tests/api/users.ts b/packages/worker/src/tests/api/users.ts index 45105c99da..d08a4ef8c7 100644 --- a/packages/worker/src/tests/api/users.ts +++ b/packages/worker/src/tests/api/users.ts @@ -127,6 +127,20 @@ export class UserAPI extends TestAPI { .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) => { return this.request .delete(`/api/global/users/${userId}`)