Merge pull request #3354 from Budibase/rory/fixes-platform-url
Fixes for google sso, cloud email url and cloud logo updates
This commit is contained in:
commit
4235aba7a2
|
@ -1,6 +1,6 @@
|
|||
const { newid } = require("../hashing")
|
||||
const Replication = require("./Replication")
|
||||
const { DEFAULT_TENANT_ID } = require("../constants")
|
||||
const { DEFAULT_TENANT_ID, Configs } = require("../constants")
|
||||
const env = require("../environment")
|
||||
const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants")
|
||||
const { getTenantId, getTenantIDFromAppID } = require("../tenancy")
|
||||
|
@ -363,13 +363,50 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) {
|
|||
}
|
||||
|
||||
// Find the config with the most granular scope based on context
|
||||
const scopedConfig = response.rows.sort(
|
||||
let scopedConfig = response.rows.sort(
|
||||
(a, b) => determineScore(a) - determineScore(b)
|
||||
)[0]
|
||||
|
||||
// custom logic for settings doc
|
||||
// always provide the platform URL
|
||||
if (type === Configs.SETTINGS) {
|
||||
if (scopedConfig && scopedConfig.doc) {
|
||||
scopedConfig.doc.config.platformUrl = await getPlatformUrl(
|
||||
scopedConfig.doc.config
|
||||
)
|
||||
} else {
|
||||
scopedConfig = {
|
||||
doc: {
|
||||
config: {
|
||||
platformUrl: await getPlatformUrl(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scopedConfig && scopedConfig.doc
|
||||
}
|
||||
|
||||
const getPlatformUrl = async settings => {
|
||||
let platformUrl = env.PLATFORM_URL
|
||||
|
||||
if (!env.SELF_HOSTED && env.MULTI_TENANCY) {
|
||||
// cloud and multi tenant - add the tenant to the default platform url
|
||||
const tenantId = getTenantId()
|
||||
if (!platformUrl.includes("localhost:")) {
|
||||
platformUrl = platformUrl.replace("://", `://${tenantId}.`)
|
||||
}
|
||||
} else {
|
||||
// self hosted - check for platform url override
|
||||
if (settings && settings.platformUrl) {
|
||||
platformUrl = settings.platformUrl
|
||||
}
|
||||
}
|
||||
|
||||
return platformUrl ? platformUrl : "http://localhost:10000"
|
||||
}
|
||||
|
||||
async function getScopedConfig(db, params) {
|
||||
const configDoc = await getScopedFullConfig(db, params)
|
||||
return configDoc && configDoc.config ? configDoc.config : configDoc
|
||||
|
|
|
@ -25,6 +25,7 @@ module.exports = {
|
|||
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
||||
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||
PLATFORM_URL: process.env.PLATFORM_URL,
|
||||
isTest,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
|
|
|
@ -6,6 +6,7 @@ exports.ObjectStoreBuckets = {
|
|||
APPS: "prod-budi-app-assets",
|
||||
TEMPLATES: "templates",
|
||||
GLOBAL: "global",
|
||||
GLOBAL_CLOUD: "prod-budi-tenant-uploads",
|
||||
}
|
||||
|
||||
exports.budibaseTempDir = function () {
|
||||
|
|
|
@ -1,16 +1,67 @@
|
|||
<script>
|
||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
|
||||
export let size = "M"
|
||||
export let tooltip = ""
|
||||
export let showTooltip = false
|
||||
</script>
|
||||
|
||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||
<slot />
|
||||
</label>
|
||||
{#if tooltip}
|
||||
<div class="container">
|
||||
<label
|
||||
for=""
|
||||
class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
<div class="icon-container">
|
||||
<div
|
||||
class="icon"
|
||||
on:mouseover={() => (showTooltip = true)}
|
||||
on:mouseleave={() => (showTooltip = false)}
|
||||
>
|
||||
<Icon name="InfoOutline" size="S" disabled={true} />
|
||||
</div>
|
||||
{#if showTooltip}
|
||||
<div class="tooltip">
|
||||
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||
<slot />
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
label {
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
.icon-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
top: 15px;
|
||||
z-index: 1;
|
||||
width: 160px;
|
||||
}
|
||||
.icon {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,12 +3,22 @@
|
|||
|
||||
export let direction = "top"
|
||||
export let text = ""
|
||||
export let textWrapping = false
|
||||
</script>
|
||||
|
||||
<span class="u-tooltip-showOnHover tooltip">
|
||||
<slot />
|
||||
<div class={`spectrum-Tooltip spectrum-Tooltip--${direction}`}>
|
||||
<!-- Showing / Hiding a text wrapped tooltip should be handled outside the component -->
|
||||
{#if textWrapping}
|
||||
<span class="spectrum-Tooltip spectrum-Tooltip--{direction} is-open">
|
||||
<span class="spectrum-Tooltip-label">{text}</span>
|
||||
<span class="spectrum-Tooltip-tip" />
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
{:else}
|
||||
<!-- The default show on hover tooltip does not support text wrapping -->
|
||||
<span class="u-tooltip-showOnHover tooltip">
|
||||
<slot />
|
||||
<div class={`spectrum-Tooltip spectrum-Tooltip--${direction}`}>
|
||||
<span class="spectrum-Tooltip-label">{text}</span>
|
||||
<span class="spectrum-Tooltip-tip" />
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
|
|
|
@ -21,26 +21,25 @@
|
|||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import api from "builderStore/api"
|
||||
import { organisation, auth, admin } from "stores/portal"
|
||||
import { organisation, admin } from "stores/portal"
|
||||
import { uuid } from "builderStore/uuid"
|
||||
import analytics, { Events } from "analytics"
|
||||
|
||||
$: tenantId = $auth.tenantId
|
||||
$: multiTenancyEnabled = $admin.multiTenancy
|
||||
|
||||
const ConfigTypes = {
|
||||
Google: "google",
|
||||
OIDC: "oidc",
|
||||
}
|
||||
|
||||
function callbackUrl(tenantId, end) {
|
||||
let url = `/api/global/auth`
|
||||
if (multiTenancyEnabled && tenantId) {
|
||||
url += `/${tenantId}`
|
||||
}
|
||||
url += end
|
||||
return url
|
||||
}
|
||||
// Some older google configs contain a manually specified value - retain the functionality to edit the field
|
||||
// When there is no value or we are in the cloud - prohibit editing the field, must use platform url to change
|
||||
$: googleCallbackUrl = undefined
|
||||
$: googleCallbackReadonly = $admin.cloud || !googleCallbackUrl
|
||||
|
||||
// Indicate to user that callback is based on platform url
|
||||
// If there is an existing value, indicate that it may be removed to return to default behaviour
|
||||
$: googleCallbackTooltip = googleCallbackReadonly
|
||||
? "Vist the organisation page to update the platform URL"
|
||||
: "Leave blank to use the default callback URL"
|
||||
|
||||
$: GoogleConfigFields = {
|
||||
Google: [
|
||||
|
@ -49,8 +48,9 @@
|
|||
{
|
||||
name: "callbackURL",
|
||||
label: "Callback URL",
|
||||
readonly: true,
|
||||
placeholder: callbackUrl(tenantId, "/google/callback"),
|
||||
readonly: googleCallbackReadonly,
|
||||
tooltip: googleCallbackTooltip,
|
||||
placeholder: $organisation.googleCallbackUrl,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -62,9 +62,10 @@
|
|||
{ name: "clientSecret", label: "Client Secret" },
|
||||
{
|
||||
name: "callbackURL",
|
||||
label: "Callback URL",
|
||||
readonly: true,
|
||||
placeholder: callbackUrl(tenantId, "/oidc/callback"),
|
||||
tooltip: "Vist the organisation page to update the platform URL",
|
||||
label: "Callback URL",
|
||||
placeholder: $organisation.oidcCallbackUrl,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -241,6 +242,8 @@
|
|||
providers.google = googleDoc
|
||||
}
|
||||
|
||||
googleCallbackUrl = providers?.google?.config?.callbackURL
|
||||
|
||||
//Get the list of user uploaded logos and push it to the dropdown options.
|
||||
//This needs to be done before the config call so they're available when the dropdown renders
|
||||
const res = await api.get(`/api/global/configs/logos_oidc`)
|
||||
|
@ -308,7 +311,7 @@
|
|||
<Layout gap="XS" noPadding>
|
||||
{#each GoogleConfigFields.Google as field}
|
||||
<div class="form-row">
|
||||
<Label size="L">{field.label}</Label>
|
||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||
<Input
|
||||
bind:value={providers.google.config[field.name]}
|
||||
readonly={field.readonly}
|
||||
|
@ -346,7 +349,7 @@
|
|||
<Layout gap="XS" noPadding>
|
||||
{#each OIDCConfigFields.Oidc as field}
|
||||
<div class="form-row">
|
||||
<Label size="L">{field.label}</Label>
|
||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||
<Input
|
||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||
readonly={field.readonly}
|
||||
|
|
|
@ -116,7 +116,11 @@
|
|||
</Layout>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<Label size="L">Platform URL</Label>
|
||||
<Label
|
||||
size="L"
|
||||
tooltip={"Update the Platform URL to match your Budibase web URL. This keeps email templates and authentication configs up to date."}
|
||||
>Platform URL</Label
|
||||
>
|
||||
<Input thin bind:value={$values.platformUrl} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -135,6 +139,7 @@
|
|||
.field {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr;
|
||||
grid-gap: var(--spacing-l);
|
||||
align-items: center;
|
||||
}
|
||||
.file {
|
||||
|
|
|
@ -3,12 +3,14 @@ import api from "builderStore/api"
|
|||
import { auth } from "stores/portal"
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
platformUrl: "http://localhost:10000",
|
||||
platformUrl: "",
|
||||
logoUrl: undefined,
|
||||
docsUrl: undefined,
|
||||
company: "Budibase",
|
||||
oidc: undefined,
|
||||
google: undefined,
|
||||
oidcCallbackUrl: "",
|
||||
googleCallbackUrl: "",
|
||||
}
|
||||
|
||||
export function createOrganisationStore() {
|
||||
|
@ -28,6 +30,13 @@ export function createOrganisationStore() {
|
|||
}
|
||||
|
||||
async function save(config) {
|
||||
// delete non-persisted fields
|
||||
const storeConfig = get(store)
|
||||
delete storeConfig.oidc
|
||||
delete storeConfig.google
|
||||
delete storeConfig.oidcCallbackUrl
|
||||
delete storeConfig.googleCallbackUrl
|
||||
|
||||
const res = await api.post("/api/global/configs", {
|
||||
type: "settings",
|
||||
config: { ...get(store), ...config },
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const authPkg = require("@budibase/auth")
|
||||
const { getScopedConfig } = require("@budibase/auth/db")
|
||||
const { google } = require("@budibase/auth/src/middleware")
|
||||
const { oidc } = require("@budibase/auth/src/middleware")
|
||||
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
||||
|
@ -21,17 +22,32 @@ const {
|
|||
} = require("@budibase/auth/tenancy")
|
||||
const env = require("../../../environment")
|
||||
|
||||
function googleCallbackUrl(config) {
|
||||
const ssoCallbackUrl = async (config, type) => {
|
||||
// incase there is a callback URL from before
|
||||
if (config && config.callbackURL) {
|
||||
return config.callbackURL
|
||||
}
|
||||
|
||||
const db = getGlobalDB()
|
||||
const publicConfig = await getScopedConfig(db, {
|
||||
type: Configs.SETTINGS,
|
||||
})
|
||||
|
||||
let callbackUrl = `/api/global/auth`
|
||||
if (isMultiTenant()) {
|
||||
callbackUrl += `/${getTenantId()}`
|
||||
}
|
||||
callbackUrl += `/google/callback`
|
||||
return callbackUrl
|
||||
callbackUrl += `/${type}/callback`
|
||||
|
||||
return `${publicConfig.platformUrl}${callbackUrl}`
|
||||
}
|
||||
|
||||
exports.googleCallbackUrl = async config => {
|
||||
return ssoCallbackUrl(config, "google")
|
||||
}
|
||||
|
||||
exports.oidcCallbackUrl = async config => {
|
||||
return ssoCallbackUrl(config, "oidc")
|
||||
}
|
||||
|
||||
async function authInternal(ctx, user, err = null, info = null) {
|
||||
|
@ -152,7 +168,7 @@ exports.googlePreAuth = async (ctx, next) => {
|
|||
type: Configs.GOOGLE,
|
||||
workspace: ctx.query.workspace,
|
||||
})
|
||||
let callbackUrl = googleCallbackUrl(config)
|
||||
let callbackUrl = await exports.googleCallbackUrl(config)
|
||||
const strategy = await google.strategyFactory(config, callbackUrl)
|
||||
|
||||
return passport.authenticate(strategy, {
|
||||
|
@ -167,7 +183,7 @@ exports.googleAuth = async (ctx, next) => {
|
|||
type: Configs.GOOGLE,
|
||||
workspace: ctx.query.workspace,
|
||||
})
|
||||
const callbackUrl = googleCallbackUrl(config)
|
||||
const callbackUrl = await exports.googleCallbackUrl(config)
|
||||
const strategy = await google.strategyFactory(config, callbackUrl)
|
||||
|
||||
return passport.authenticate(
|
||||
|
@ -189,13 +205,7 @@ async function oidcStrategyFactory(ctx, configId) {
|
|||
})
|
||||
|
||||
const chosenConfig = config.configs.filter(c => c.uuid === configId)[0]
|
||||
|
||||
const protocol = env.NODE_ENV === "production" ? "https" : "http"
|
||||
let callbackUrl = `${protocol}://${ctx.host}/api/global/auth`
|
||||
if (isMultiTenant()) {
|
||||
callbackUrl += `/${getTenantId()}`
|
||||
}
|
||||
callbackUrl += `/oidc/callback`
|
||||
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
|
||||
|
||||
return oidc.strategyFactory(chosenConfig, callbackUrl)
|
||||
}
|
||||
|
|
|
@ -9,8 +9,11 @@ const { Configs } = require("../../../constants")
|
|||
const email = require("../../../utilities/email")
|
||||
const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore
|
||||
const CouchDB = require("../../../db")
|
||||
const { getGlobalDB } = require("@budibase/auth/tenancy")
|
||||
const { getGlobalDB, getTenantId } = require("@budibase/auth/tenancy")
|
||||
const env = require("../../../environment")
|
||||
const { googleCallbackUrl, oidcCallbackUrl } = require("./auth")
|
||||
|
||||
const BB_TENANT_CDN = "https://tenants.cdn.budi.live"
|
||||
|
||||
exports.save = async function (ctx) {
|
||||
const db = getGlobalDB()
|
||||
|
@ -155,6 +158,10 @@ exports.publicSettings = async function (ctx) {
|
|||
config.config.google = false
|
||||
}
|
||||
|
||||
// callback urls
|
||||
config.config.oidcCallbackUrl = await oidcCallbackUrl()
|
||||
config.config.googleCallbackUrl = await googleCallbackUrl()
|
||||
|
||||
// oidc button flag
|
||||
if (oidcConfig && oidcConfig.config) {
|
||||
config.config.oidc = oidcConfig.config.configs[0].activated
|
||||
|
@ -182,7 +189,13 @@ exports.upload = async function (ctx) {
|
|||
bucket = ObjectStoreBuckets.GLOBAL_CLOUD
|
||||
}
|
||||
|
||||
const key = `${type}/${name}`
|
||||
let key
|
||||
if (env.MULTI_TENANCY) {
|
||||
key = `${getTenantId()}/${type}/${name}`
|
||||
} else {
|
||||
key = `${type}/${name}`
|
||||
}
|
||||
|
||||
await upload({
|
||||
bucket,
|
||||
filename: key,
|
||||
|
@ -200,7 +213,13 @@ exports.upload = async function (ctx) {
|
|||
config: {},
|
||||
}
|
||||
}
|
||||
const url = `/${bucket}/${key}`
|
||||
let url
|
||||
if (env.SELF_HOSTED) {
|
||||
url = `/${bucket}/${key}`
|
||||
} else {
|
||||
url = `${BB_TENANT_CDN}/${key}`
|
||||
}
|
||||
|
||||
cfgStructure.config[`${name}`] = url
|
||||
// write back to db with url updated
|
||||
await db.put(cfgStructure)
|
||||
|
|
|
@ -76,7 +76,7 @@ describe("/api/global/auth", () => {
|
|||
afterEach(() => {
|
||||
expect(strategyFactory).toBeCalledWith(
|
||||
chosenConfig,
|
||||
`http://127.0.0.1:4003/api/global/auth/${TENANT_ID}/oidc/callback` // calculated url
|
||||
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback`
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ const {
|
|||
EmailTemplatePurpose,
|
||||
} = require("../constants")
|
||||
const { checkSlashesInUrl } = require("./index")
|
||||
const env = require("../environment")
|
||||
const { getGlobalDB, addTenantToUrl } = require("@budibase/auth/tenancy")
|
||||
const BASE_COMPANY = "Budibase"
|
||||
|
||||
|
@ -14,9 +13,6 @@ exports.getSettingsTemplateContext = async (purpose, code = null) => {
|
|||
const db = getGlobalDB()
|
||||
// TODO: use more granular settings in the future if required
|
||||
let settings = (await getScopedConfig(db, { type: Configs.SETTINGS })) || {}
|
||||
if (!settings || !settings.platformUrl) {
|
||||
settings.platformUrl = env.PLATFORM_URL
|
||||
}
|
||||
const URL = settings.platformUrl
|
||||
const context = {
|
||||
[InternalTemplateBindings.LOGO_URL]:
|
||||
|
|
Loading…
Reference in New Issue