Allow user uploaded icons in oidc config

This commit is contained in:
Peter Clement 2021-07-07 13:41:09 +01:00
parent 0e6fb73b9e
commit 37b1200051
13 changed files with 154 additions and 53 deletions

View File

@ -20,5 +20,6 @@ exports.Configs = {
ACCOUNT: "account", ACCOUNT: "account",
SMTP: "smtp", SMTP: "smtp",
GOOGLE: "google", GOOGLE: "google",
OIDC: "oidc" OIDC: "oidc",
OIDC_LOGOS:"oidc_logos"
} }

View File

@ -10,18 +10,20 @@
export let disabled = false export let disabled = false
export let error = null export let error = null
export let fieldText = "" export let fieldText = ""
export let fieldIcon = ""
export let isPlaceholder = false export let isPlaceholder = false
export let placeholderOption = null export let placeholderOption = null
export let options = [] export let options = []
export let callbackOptionValue = null
export let isOptionSelected = () => false export let isOptionSelected = () => false
export let onSelectOption = () => {} export let onSelectOption = () => {}
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
export let getOptionIcon = option => option
export let open = false export let open = false
export let readonly = false export let readonly = false
export let quiet = false export let quiet = false
export let autoWidth = false export let autoWidth = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onClick = () => { const onClick = () => {
dispatch("click") dispatch("click")
@ -30,6 +32,7 @@
} }
open = true open = true
} }
console.log(fieldIcon)
</script> </script>
<button <button
@ -42,6 +45,12 @@
aria-haspopup="listbox" aria-haspopup="listbox"
on:mousedown={onClick} on:mousedown={onClick}
> >
{#if fieldIcon}
<span class="icon-Placeholder-Padding">
<img src={fieldIcon} alt="test" width="20" height="15" />
</span>
{/if}
<span <span
class="spectrum-Picker-label" class="spectrum-Picker-label"
class:is-placeholder={isPlaceholder} class:is-placeholder={isPlaceholder}
@ -104,6 +113,16 @@
tabindex="0" tabindex="0"
on:click={() => onSelectOption(getOptionValue(option, idx))} on:click={() => onSelectOption(getOptionValue(option, idx))}
> >
{#if getOptionIcon(option, idx)}
<span class="icon-Padding">
<img
src={getOptionIcon(option, idx)}
alt="test"
width="20"
height="15"
/>
</span>
{/if}
<span class="spectrum-Menu-itemLabel" <span class="spectrum-Menu-itemLabel"
>{getOptionLabel(option, idx)}</span >{getOptionLabel(option, idx)}</span
> >
@ -148,4 +167,12 @@
.spectrum-Picker-label.auto-width.is-placeholder { .spectrum-Picker-label.auto-width.is-placeholder {
padding-right: 2px; padding-right: 2px;
} }
.icon-Padding {
padding-right: 10px;
}
.icon-Placeholder-Padding {
padding-top: 5px;
padding-right: 10px;
}
</style> </style>

View File

@ -8,8 +8,10 @@
export let disabled = false export let disabled = false
export let error = null export let error = null
export let options = [] export let options = []
export let callbackOptionValue = null
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
export let getOptionIcon = option => option
export let readonly = false export let readonly = false
export let quiet = false export let quiet = false
export let autoWidth = false export let autoWidth = false
@ -17,6 +19,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let open = false let open = false
$: fieldText = getFieldText(value, options, placeholder) $: fieldText = getFieldText(value, options, placeholder)
$: fieldIcon = getFieldIcon(value, options, placeholder)
const getFieldText = (value, options, placeholder) => { const getFieldText = (value, options, placeholder) => {
// Always use placeholder if no value // Always use placeholder if no value
@ -36,6 +39,17 @@
return index !== -1 ? getOptionLabel(options[index], index) : value return index !== -1 ? getOptionLabel(options[index], index) : value
} }
const getFieldIcon = (value, options) => {
// Wait for options to load if there is a value but no options
if (!options?.length) {
return ""
}
const index = options.findIndex(
(option, idx) => getOptionValue(option, idx) === value
)
return index !== -1 ? getOptionIcon(options[index], index) : value
}
const selectOption = value => { const selectOption = value => {
dispatch("change", value) dispatch("change", value)
open = false open = false
@ -55,6 +69,9 @@
{autoWidth} {autoWidth}
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}
{getOptionIcon}
{fieldIcon}
{callbackOptionValue}
isPlaceholder={value == null || value === ""} isPlaceholder={value == null || value === ""}
placeholderOption={placeholder} placeholderOption={placeholder}
isOptionSelected={option => option === value} isOptionSelected={option => option === value}

View File

@ -13,6 +13,7 @@
export let options = [] export let options = []
export let getOptionLabel = option => extractProperty(option, "label") export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value") export let getOptionValue = option => extractProperty(option, "value")
export let getOptionIcon = option => extractObjectProperty(option, "icon")
export let quiet = false export let quiet = false
export let autoWidth = false export let autoWidth = false
@ -21,6 +22,13 @@
value = e.detail value = e.detail
dispatch("change", e.detail) dispatch("change", e.detail)
} }
const extractObjectProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
}
const extractProperty = (value, property) => { const extractProperty = (value, property) => {
if (value && typeof value === "object") { if (value && typeof value === "object") {
return value[property] return value[property]
@ -41,6 +49,7 @@
{autoWidth} {autoWidth}
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}
{getOptionIcon}
on:change={onChange} on:change={onChange}
on:click on:click
/> />

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,7 +1,7 @@
<svg <svg
width="18" width="25"
height="18" height="25 "
viewBox="0 0 268 268" viewBox="0 0 100 100"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
@ -12,9 +12,4 @@
/> />
<path d="M43,90v-75l14,-9v75z" fill="#f60" /> <path d="M43,90v-75l14,-9v75z" fill="#f60" />
</g> </g>
<defs>
<clipPath id="clip0">
<rect width="268" height="268" fill="white" />
</clipPath>
</defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 319 B

View File

@ -1,6 +1,11 @@
<script> <script>
import GoogleLogo from "./_logos/Google.svelte" import GoogleLogo from "./_logos/Google.svelte"
import OIDCLogo from "./_logos/OIDC.svelte" import OidcLogo from "./_logos/OIDC.svelte"
import MicrosoftLogo from "assets/microsoft-logo.png"
import OracleLogo from "assets/oracle-logo.png"
import Auth0Logo from "assets/auth0-logo.png"
import OidcLogoPng from "assets/oidc-logo.png"
import { import {
Button, Button,
Heading, Heading,
@ -10,10 +15,13 @@
Layout, Layout,
Input, Input,
Body, Body,
Select Select,
Dropzone,
} from "@budibase/bbui" } from "@budibase/bbui"
import { onMount } from "svelte" import { onMount } from "svelte"
import api from "builderStore/api" import api from "builderStore/api"
import { writable } from "svelte/store"
import { organisation } from "stores/portal"
const ConfigTypes = { const ConfigTypes = {
Google: "google", Google: "google",
@ -34,51 +42,70 @@
} }
const OIDCConfigFields = { const OIDCConfigFields = {
Oidc: [ Oidc: ["configUrl", "clientId", "clientSecret"],
"issuer",
"authUrl",
"tokenUrl",
"userInfoUrl",
"clientId",
"clientSecret",
"callbackUrl",
"name"
],
} }
const OIDCConfigLabels = { const OIDCConfigLabels = {
Oidc: { Oidc: {
issuer: "Issuer", configUrl: "Config URL",
authUrl: "Authorization URL",
tokenUrl: "Token URL",
userInfoUrl: "User Info URL",
clientId: "Client ID", clientId: "Client ID",
clientSecret: "Client Secret", clientSecret: "Client Secret",
callbackUrl: "Callback URL",
name: "Name"
}, },
} }
let iconDropdownOptions = [
{
label: "Azure AD",
value: "Active Directory",
icon: MicrosoftLogo,
},
{ label: "Oracle", value: "Oracle", icon: OracleLogo },
{ label: "Auth0", value: "Auth0", icon: Auth0Logo },
{ label: "OIDC", value: "Auth0", icon: OidcLogoPng },
{ label: "Upload your own", value: "Upload" },
]
let fileinput
let image
let google let google
let oidc let oidc
const providers = {google, oidc}
async function uploadLogo(file) {
let data = new FormData()
data.append("file", file)
const res = await api.post(
`/api/admin/configs/upload/oidc_logos/${file.name}`,
data,
{}
)
return await res.json()
}
const onFileSelected = e => {
image = e.target.files[0]
}
const providers = { google, oidc }
async function save(docs) { async function save(docs) {
uploadLogo(image)
let calls = [] let calls = []
docs.forEach(element => { docs.forEach(element => {
calls.push(api.post(`/api/admin/configs`, element)) calls.push(api.post(`/api/admin/configs`, element))
}) })
Promise.all(calls) Promise.all(calls)
.then(responses => { .then(responses => {
return Promise.all(responses.map(response => { return Promise.all(
return response.json() responses.map(response => {
})) return response.json()
}).then(data => { })
)
})
.then(data => {
data.forEach(res => { data.forEach(res => {
providers[res.type]._rev = res._rev providers[res.type]._rev = res._rev
providers[res.type]._id = res._id providers[res.type]._id = res._id
}) })
//res.json()._rev = res.json()._rev
//res.json().id = res.json().id
notifications.success(`Settings saved.`) notifications.success(`Settings saved.`)
}) })
.catch(err => { .catch(err => {
@ -114,8 +141,15 @@
} else { } else {
providers.oidc = oidcDoc providers.oidc = oidcDoc
} }
const res = await api.get(`/api/admin/configs/oidc_logos`)
const configSettings = await res.json()
console.log(configSettings)
const logoKeys = Object.keys(configSettings.config)
logoKeys.map(logoKey => {
const logoUrl = configSettings.config[logoKey]
iconDropdownOptions.unshift({label: logoKey, value: logoUrl, icon: logoUrl})
})
}) })
let fileInput
</script> </script>
@ -156,7 +190,7 @@
<Layout gap="XS" noPadding> <Layout gap="XS" noPadding>
<Heading size="S"> <Heading size="S">
<span> <span>
<GoogleLogo /> <OidcLogo />
OpenID Connect OpenID Connect
</span> </span>
</Heading> </Heading>
@ -171,21 +205,37 @@
<Input bind:value={providers.oidc.config[field]} /> <Input bind:value={providers.oidc.config[field]} />
</div> </div>
{/each} {/each}
<br />
<Body size="S"> <Body size="S">
To customize your login button, fill out the fields below. To customize your login button, fill out the fields below.
</Body> </Body>
<div class="form-row"> <div class="form-row">
<Label size="L">{OIDCConfigLabels.Oidc['name']}</Label> <Label size="L">Name</Label>
<Select <Input bind:value={providers.oidc.config["name"]} />
options={["Upload File"]}
placeholder={null}
/>
</div> </div>
<div class="form-row">
<Label size="L">Icon</Label>
<Select
label=""
bind:value={providers.oidc.config["iconName"]}
options={iconDropdownOptions}
on:change={e => (e.detail === "Upload" && fileinput.click())}
/>
</div>
<input
style="display:none"
type="file"
accept=".jpg, .jpeg, .png"
on:change={e => onFileSelected(e)}
bind:this={fileinput}
/>
</Layout> </Layout>
{/if} {/if}
<div> <div>
<Button cta on:click={() => save([providers.google, providers.oidc])}>Save</Button> <Button cta on:click={() => save([providers.google, providers.oidc])}
>Save</Button
>
</div> </div>
</Layout> </Layout>
@ -201,4 +251,8 @@
align-items: center; align-items: center;
gap: var(--spacing-s); gap: var(--spacing-s);
} }
input {
display: none;
}
</style> </style>

View File

@ -146,7 +146,7 @@ exports.upload = async function (ctx) {
} }
} }
const url = `/${bucket}/${key}` const url = `/${bucket}/${key}`
cfgStructure.config[`${name}Url`] = url cfgStructure.config[`${name}`] = url
// write back to db with url updated // write back to db with url updated
await db.put(cfgStructure) await db.put(cfgStructure)
@ -192,8 +192,6 @@ exports.configChecklist = async function (ctx) {
const oidcConfig = await getScopedFullConfig(db, { const oidcConfig = await getScopedFullConfig(db, {
type: Configs.OIDC, type: Configs.OIDC,
}) })
// They have set up an admin user // They have set up an admin user
const users = await db.allDocs( const users = await db.allDocs(
getGlobalUserParams(null, { getGlobalUserParams(null, {

View File

@ -79,7 +79,7 @@ function buildUploadValidation() {
// prettier-ignore // prettier-ignore
return joiValidator.params(Joi.object({ return joiValidator.params(Joi.object({
type: Joi.string().valid(...Object.values(Configs)).required(), type: Joi.string().valid(...Object.values(Configs)).required(),
name: Joi.string().valid(...Object.values(ConfigUploads)).required(), name: Joi.string().required(),
}).required()) }).required())
} }

View File

@ -16,7 +16,7 @@ exports.Configs = Configs
exports.ConfigUploads = { exports.ConfigUploads = {
LOGO: "logo", LOGO: "logo",
OIDC_LOGO: "oidc_logo" OIDC_LOGO: "oidc_logo",
} }
const TemplateTypes = { const TemplateTypes = {