commit
e26524a6b9
|
@ -1,9 +1,7 @@
|
||||||
{
|
{
|
||||||
"version": "2.7.34-alpha.10",
|
"version": "2.7.35",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": ["packages/*"],
|
||||||
"packages/*"
|
|
||||||
],
|
|
||||||
"useNx": true,
|
"useNx": true,
|
||||||
"command": {
|
"command": {
|
||||||
"publish": {
|
"publish": {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
GoogleInnerConfig,
|
GoogleInnerConfig,
|
||||||
OIDCConfig,
|
OIDCConfig,
|
||||||
OIDCInnerConfig,
|
OIDCInnerConfig,
|
||||||
|
OIDCLogosConfig,
|
||||||
SCIMConfig,
|
SCIMConfig,
|
||||||
SCIMInnerConfig,
|
SCIMInnerConfig,
|
||||||
SettingsConfig,
|
SettingsConfig,
|
||||||
|
@ -191,6 +192,10 @@ export function getDefaultGoogleConfig(): GoogleInnerConfig | undefined {
|
||||||
|
|
||||||
// OIDC
|
// OIDC
|
||||||
|
|
||||||
|
export async function getOIDCLogosDoc(): Promise<OIDCLogosConfig | undefined> {
|
||||||
|
return getConfig<OIDCLogosConfig>(ConfigType.OIDC_LOGOS)
|
||||||
|
}
|
||||||
|
|
||||||
async function getOIDCConfigDoc(): Promise<OIDCConfig | undefined> {
|
async function getOIDCConfigDoc(): Promise<OIDCConfig | undefined> {
|
||||||
return getConfig<OIDCConfig>(ConfigType.OIDC)
|
return getConfig<OIDCConfig>(ConfigType.OIDC)
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,9 +99,15 @@
|
||||||
bind:this={button}
|
bind:this={button}
|
||||||
>
|
>
|
||||||
{#if fieldIcon}
|
{#if fieldIcon}
|
||||||
<span class="option-extra icon">
|
{#if !useOptionIconImage}
|
||||||
<Icon size="S" name={fieldIcon} />
|
<span class="option-extra icon">
|
||||||
</span>
|
<Icon size="S" name={fieldIcon} />
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span class="option-extra icon field-icon">
|
||||||
|
<img src={fieldIcon} alt="icon" width="15" height="15" />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if fieldColour}
|
{#if fieldColour}
|
||||||
<span class="option-extra">
|
<span class="option-extra">
|
||||||
|
@ -311,4 +317,8 @@
|
||||||
max-width: 170px;
|
max-width: 170px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.option-extra.icon.field-icon {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
let userOnboardResponse = null
|
let userOnboardResponse = null
|
||||||
let userLimitReachedModal
|
let userLimitReachedModal
|
||||||
|
|
||||||
|
let inviteFailureResponse = ""
|
||||||
|
|
||||||
$: queryIsEmail = emailValidator(query) === true
|
$: queryIsEmail = emailValidator(query) === true
|
||||||
$: prodAppId = apps.getProdAppID($store.appId)
|
$: prodAppId = apps.getProdAppID($store.appId)
|
||||||
$: promptInvite = showInvite(
|
$: promptInvite = showInvite(
|
||||||
|
@ -308,19 +310,6 @@
|
||||||
let userInviteResponse
|
let userInviteResponse
|
||||||
try {
|
try {
|
||||||
userInviteResponse = await users.onboard(payload)
|
userInviteResponse = await users.onboard(payload)
|
||||||
|
|
||||||
const newUser = userInviteResponse?.successful.find(
|
|
||||||
user => user.email === newUserEmail
|
|
||||||
)
|
|
||||||
if (newUser) {
|
|
||||||
notifications.success(
|
|
||||||
userInviteResponse.created
|
|
||||||
? "User created successfully"
|
|
||||||
: "User invite successful"
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
throw new Error("User invite failed")
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message)
|
console.error(error.message)
|
||||||
notifications.error("Error inviting user")
|
notifications.error("Error inviting user")
|
||||||
|
@ -331,12 +320,31 @@
|
||||||
|
|
||||||
const onInviteUser = async () => {
|
const onInviteUser = async () => {
|
||||||
userOnboardResponse = await inviteUser()
|
userOnboardResponse = await inviteUser()
|
||||||
|
const originalQuery = query + ""
|
||||||
|
query = null
|
||||||
|
|
||||||
const userInviteSuccess = userOnboardResponse?.successful
|
const newUser = userOnboardResponse?.successful.find(
|
||||||
if (userInviteSuccess && userInviteSuccess[0].email === query) {
|
user => user.email === originalQuery
|
||||||
query = null
|
)
|
||||||
query = userInviteSuccess[0].email
|
if (newUser) {
|
||||||
|
query = originalQuery
|
||||||
|
notifications.success(
|
||||||
|
userOnboardResponse.created
|
||||||
|
? "User created successfully"
|
||||||
|
: "User invite successful"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const failedUser = userOnboardResponse?.unsuccessful.find(
|
||||||
|
user => user.email === originalQuery
|
||||||
|
)
|
||||||
|
inviteFailureResponse =
|
||||||
|
failedUser?.reason === "Unavailable"
|
||||||
|
? "Email already in use. Please use a different email."
|
||||||
|
: failedUser?.reason
|
||||||
|
|
||||||
|
notifications.error(inviteFailureResponse)
|
||||||
}
|
}
|
||||||
|
userOnboardResponse = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const onUpdateUserInvite = async (invite, role) => {
|
const onUpdateUserInvite = async (invite, role) => {
|
||||||
|
|
|
@ -29,14 +29,13 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$: src = !$oidc.logo
|
$: oidcLogoImageURL = preDefinedIcons[$oidc.logo] ?? $oidc.logo
|
||||||
? OidcLogo
|
$: logoSrc = oidcLogoImageURL ?? OidcLogo
|
||||||
: preDefinedIcons[$oidc.logo] || `/global/logos_oidc/${$oidc.logo}`
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show}
|
||||||
<FancyButton
|
<FancyButton
|
||||||
icon={src}
|
icon={logoSrc}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
const url = `/api/global/auth/${$auth.tenantId}/oidc/configs/${$oidc.uuid}`
|
const url = `/api/global/auth/${$auth.tenantId}/oidc/configs/${$oidc.uuid}`
|
||||||
if (samePage) {
|
if (samePage) {
|
||||||
|
|
|
@ -382,18 +382,26 @@ class MongoIntegration implements IntegrationBase {
|
||||||
return this.client.connect()
|
return this.client.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
createObjectIds(json: any) {
|
matchId(value?: string) {
|
||||||
|
return value?.match(/(?<=objectid\(['"]).*(?=['"]\))/gi)?.[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
hasObjectId(value?: any): boolean {
|
||||||
|
return (
|
||||||
|
typeof value === "string" && value.toLowerCase().startsWith("objectid")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
createObjectIds(json: any): any {
|
||||||
const self = this
|
const self = this
|
||||||
|
|
||||||
function interpolateObjectIds(json: any) {
|
function interpolateObjectIds(json: any) {
|
||||||
for (let field of Object.keys(json || {})) {
|
for (let field of Object.keys(json || {})) {
|
||||||
if (json[field] instanceof Object) {
|
if (json[field] instanceof Object) {
|
||||||
json[field] = self.createObjectIds(json[field])
|
json[field] = self.createObjectIds(json[field])
|
||||||
}
|
}
|
||||||
if (
|
if (self.hasObjectId(json[field])) {
|
||||||
typeof json[field] === "string" &&
|
const id = self.matchId(json[field])
|
||||||
json[field].toLowerCase().startsWith("objectid")
|
|
||||||
) {
|
|
||||||
const id = json[field].match(/(?<=objectid\(['"]).*(?=['"]\))/gi)?.[0]
|
|
||||||
if (id) {
|
if (id) {
|
||||||
json[field] = ObjectId.createFromHexString(id)
|
json[field] = ObjectId.createFromHexString(id)
|
||||||
}
|
}
|
||||||
|
@ -404,7 +412,14 @@ class MongoIntegration implements IntegrationBase {
|
||||||
|
|
||||||
if (Array.isArray(json)) {
|
if (Array.isArray(json)) {
|
||||||
for (let i = 0; i < json.length; i++) {
|
for (let i = 0; i < json.length; i++) {
|
||||||
json[i] = interpolateObjectIds(json[i])
|
if (self.hasObjectId(json[i])) {
|
||||||
|
const id = self.matchId(json[i])
|
||||||
|
if (id) {
|
||||||
|
json[i] = ObjectId.createFromHexString(id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
json[i] = interpolateObjectIds(json[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,12 @@ export interface OIDCConfigs {
|
||||||
configs: OIDCInnerConfig[]
|
configs: OIDCInnerConfig[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OIDCLogosInnerConfig {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OIDCLogosConfig extends Config<OIDCLogosInnerConfig> {}
|
||||||
|
|
||||||
export interface OIDCInnerConfig {
|
export interface OIDCInnerConfig {
|
||||||
configUrl: string
|
configUrl: string
|
||||||
clientID: string
|
clientID: string
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
SSOConfig,
|
SSOConfig,
|
||||||
SSOConfigType,
|
SSOConfigType,
|
||||||
UserCtx,
|
UserCtx,
|
||||||
|
OIDCLogosConfig,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as pro from "@budibase/pro"
|
import * as pro from "@budibase/pro"
|
||||||
|
|
||||||
|
@ -280,13 +281,39 @@ export async function save(ctx: UserCtx<Config>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enrichOIDCLogos(oidcLogos: OIDCLogosConfig) {
|
||||||
|
if (!oidcLogos) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oidcLogos.config = Object.keys(oidcLogos.config || {}).reduce(
|
||||||
|
(acc: any, key: string) => {
|
||||||
|
if (!key.endsWith("Etag")) {
|
||||||
|
const etag = oidcLogos.config[`${key}Etag`]
|
||||||
|
const objectStoreUrl = objectStore.getGlobalFileUrl(
|
||||||
|
oidcLogos.type,
|
||||||
|
key,
|
||||||
|
etag
|
||||||
|
)
|
||||||
|
acc[key] = objectStoreUrl
|
||||||
|
} else {
|
||||||
|
acc[key] = oidcLogos.config[key]
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export async function find(ctx: UserCtx) {
|
export async function find(ctx: UserCtx) {
|
||||||
try {
|
try {
|
||||||
// Find the config with the most granular scope based on context
|
// Find the config with the most granular scope based on context
|
||||||
const type = ctx.params.type
|
const type = ctx.params.type
|
||||||
const scopedConfig = await configs.getConfig(type)
|
let scopedConfig = await configs.getConfig(type)
|
||||||
|
|
||||||
if (scopedConfig) {
|
if (scopedConfig) {
|
||||||
|
if (type === ConfigType.OIDC_LOGOS) {
|
||||||
|
enrichOIDCLogos(scopedConfig)
|
||||||
|
}
|
||||||
ctx.body = scopedConfig
|
ctx.body = scopedConfig
|
||||||
} else {
|
} else {
|
||||||
// don't throw an error, there simply is nothing to return
|
// don't throw an error, there simply is nothing to return
|
||||||
|
@ -300,16 +327,21 @@ export async function find(ctx: UserCtx) {
|
||||||
export async function publicOidc(ctx: Ctx<void, GetPublicOIDCConfigResponse>) {
|
export async function publicOidc(ctx: Ctx<void, GetPublicOIDCConfigResponse>) {
|
||||||
try {
|
try {
|
||||||
// Find the config with the most granular scope based on context
|
// Find the config with the most granular scope based on context
|
||||||
const config = await configs.getOIDCConfig()
|
const oidcConfig = await configs.getOIDCConfig()
|
||||||
|
const oidcCustomLogos = await configs.getOIDCLogosDoc()
|
||||||
|
|
||||||
if (!config) {
|
if (oidcCustomLogos) {
|
||||||
|
enrichOIDCLogos(oidcCustomLogos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oidcConfig) {
|
||||||
ctx.body = []
|
ctx.body = []
|
||||||
} else {
|
} else {
|
||||||
ctx.body = [
|
ctx.body = [
|
||||||
{
|
{
|
||||||
logo: config.logo,
|
logo: oidcCustomLogos?.config[oidcConfig.logo] ?? oidcConfig.logo,
|
||||||
name: config.name,
|
name: oidcConfig.name,
|
||||||
uuid: config.uuid,
|
uuid: oidcConfig.uuid,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue