1
0
Fork 0
mirror of synced 2024-06-02 18:44:54 +12:00
budibase/packages/builder/src/pages/builder/portal/manage/auth/index.svelte

438 lines
12 KiB
Svelte
Raw Normal View History

2021-05-05 04:31:06 +12:00
<script>
2021-07-08 03:18:18 +12:00
import GoogleLogo from "./_logos/Google.svelte"
import OidcLogo from "./_logos/OIDC.svelte"
import MicrosoftLogo from "assets/microsoft-logo.png"
import Auth0Logo from "assets/auth0-logo.png"
2021-07-16 21:15:38 +12:00
import OktaLogo from "assets/okta-logo.png"
import OneLoginLogo from "assets/onelogin-logo.png"
import OidcLogoPng from "assets/oidc-logo.png"
import { isEqual, cloneDeep } from "lodash/fp"
2021-05-05 04:31:06 +12:00
import {
Button,
Heading,
Divider,
2021-05-05 05:14:13 +12:00
Label,
2021-05-05 04:31:06 +12:00
notifications,
2021-05-05 05:14:13 +12:00
Layout,
2021-05-05 04:31:06 +12:00
Input,
Body,
Select,
Toggle,
2021-05-05 04:31:06 +12:00
} from "@budibase/bbui"
import { onMount } from "svelte"
import api from "builderStore/api"
2021-07-23 02:26:14 +12:00
import { organisation, auth, admin } from "stores/portal"
import { uuid } from "builderStore/uuid"
2021-07-10 02:05:39 +12:00
$: tenantId = $auth.tenantId
2021-07-23 02:26:14 +12:00
$: multiTenancyEnabled = $admin.multiTenancy
2021-05-05 04:31:06 +12:00
const ConfigTypes = {
Google: "google",
2021-07-06 01:24:13 +12:00
OIDC: "oidc",
2021-05-05 04:31:06 +12:00
}
2021-07-23 02:26:14 +12:00
function callbackUrl(tenantId, end) {
let url = `/api/global/auth`
if (multiTenancyEnabled && tenantId) {
url += `/${tenantId}`
}
url += end
return url
}
2021-07-21 04:57:07 +12:00
$: GoogleConfigFields = {
Google: [
2021-07-21 05:03:16 +12:00
{ name: "clientID", label: "Client ID" },
{ name: "clientSecret", label: "Client secret" },
{
name: "callbackURL",
label: "Callback URL",
readonly: true,
2021-07-23 02:26:14 +12:00
placeholder: callbackUrl(tenantId, "/google/callback"),
2021-07-21 05:03:16 +12:00
},
2021-07-21 04:57:07 +12:00
],
}
2021-05-05 04:31:06 +12:00
2021-07-21 04:57:07 +12:00
$: OIDCConfigFields = {
Oidc: [
2021-07-21 05:03:16 +12:00
{ name: "configUrl", label: "Config URL" },
{ name: "clientID", label: "Client ID" },
{ name: "clientSecret", label: "Client Secret" },
{
name: "callbackURL",
label: "Callback URL",
readonly: true,
2021-07-23 02:26:14 +12:00
placeholder: callbackUrl(tenantId, "/oidc/callback"),
2021-07-21 05:03:16 +12:00
},
2021-07-21 04:57:07 +12:00
],
2021-07-06 01:24:13 +12:00
}
let iconDropdownOptions = [
{
2021-07-14 01:54:20 +12:00
label: "Microsoft",
value: "Microsoft",
icon: MicrosoftLogo,
},
2021-07-16 21:15:38 +12:00
{
label: "Okta",
value: "Okta",
icon: OktaLogo,
},
{
label: "OneLogin",
value: "OneLogin",
icon: OneLoginLogo,
},
{
label: "Auth0",
value: "Auth0",
icon: Auth0Logo,
},
{
label: "OIDC",
value: "Oidc",
icon: OidcLogoPng,
},
{
label: "Upload your own",
value: "Upload",
},
]
let fileinput
let image
2021-07-14 08:46:50 +12:00
2021-05-05 04:31:06 +12:00
let google
2021-07-06 01:24:13 +12:00
let oidc
2021-07-14 08:46:50 +12:00
const providers = { google, oidc }
// control the state of the save button depending on whether form has changed
let originalGoogleDoc
let originalOidcDoc
let googleSaveButtonDisabled
let oidcSaveButtonDisabled
$: {
2021-07-20 23:30:11 +12:00
isEqual(providers.google?.config, originalGoogleDoc?.config)
? (googleSaveButtonDisabled = true)
: (googleSaveButtonDisabled = false)
2021-07-20 23:30:11 +12:00
isEqual(providers.oidc?.config, originalOidcDoc?.config)
? (oidcSaveButtonDisabled = true)
: (oidcSaveButtonDisabled = false)
}
2021-07-14 08:46:50 +12:00
// Create a flag so that it will only try to save completed forms
$: partialGoogle =
2021-07-22 05:24:58 +12:00
providers.google?.config?.clientID || providers.google?.config?.clientSecret
$: partialOidc =
providers.oidc?.config?.configs[0].configUrl ||
providers.oidc?.config?.configs[0].clientID ||
providers.oidc?.config?.configs[0].clientSecret
2021-07-14 08:46:50 +12:00
$: googleComplete =
2021-07-22 05:24:58 +12:00
providers.google?.config?.clientID && providers.google?.config?.clientSecret
2021-07-14 08:46:50 +12:00
$: oidcComplete =
providers.oidc?.config?.configs[0].configUrl &&
providers.oidc?.config?.configs[0].clientID &&
providers.oidc?.config?.configs[0].clientSecret
async function uploadLogo(file) {
let data = new FormData()
data.append("file", file)
const res = await api.post(
`/api/global/configs/upload/logos_oidc/${file.name}`,
data,
{}
)
return await res.json()
}
const onFileSelected = e => {
2021-07-08 03:18:18 +12:00
let fileName = e.target.files[0].name
image = e.target.files[0]
2021-07-14 01:54:20 +12:00
providers.oidc.config.configs[0].logo = fileName
2021-07-08 23:36:09 +12:00
iconDropdownOptions.unshift({ label: fileName, value: fileName })
}
2021-07-06 01:24:13 +12:00
async function save(docs) {
2021-07-08 03:18:18 +12:00
// only if the user has provided an image, upload it.
image && uploadLogo(image)
2021-07-06 01:24:13 +12:00
let calls = []
docs.forEach(element => {
2021-07-14 08:46:50 +12:00
if (element.type === ConfigTypes.OIDC) {
//Add a UUID here so each config is distinguishable when it arrives at the login page
for (let config of element.config.configs) {
if (!config.uuid) {
config.uuid = uuid()
}
// callback urls shouldn't be included
delete config.callbackURL
}
if (partialOidc) {
if (!oidcComplete) {
notifications.error(
`Please fill in all required ${ConfigTypes.OIDC} fields`
)
} else {
calls.push(api.post(`/api/global/configs`, element))
2021-07-20 23:30:11 +12:00
// turn the save button grey when clicked
oidcSaveButtonDisabled = true
originalOidcDoc = cloneDeep(providers.oidc)
}
}
2021-07-14 08:46:50 +12:00
}
if (element.type === ConfigTypes.Google) {
if (partialGoogle) {
if (!googleComplete) {
notifications.error(
`Please fill in all required ${ConfigTypes.Google} fields`
)
} else {
delete element.config.callbackURL
calls.push(api.post(`/api/global/configs`, element))
2021-07-20 23:30:11 +12:00
googleSaveButtonDisabled = true
originalGoogleDoc = cloneDeep(providers.google)
}
}
}
2021-07-06 01:24:13 +12:00
})
calls.length &&
Promise.all(calls)
.then(responses => {
return Promise.all(
responses.map(response => {
return response.json()
})
)
})
.then(data => {
data.forEach(res => {
providers[res.type]._rev = res._rev
providers[res.type]._id = res._id
})
notifications.success(`Settings saved.`)
})
.catch(err => {
notifications.error(`Failed to update auth settings. ${err}`)
throw new Error(err.message)
2021-07-06 01:24:13 +12:00
})
2021-05-05 04:31:06 +12:00
}
onMount(async () => {
2021-07-10 02:05:39 +12:00
await organisation.init()
2021-05-05 04:31:06 +12:00
// fetch the configs for oauth
const googleResponse = await api.get(
`/api/global/configs/${ConfigTypes.Google}`
2021-05-05 04:31:06 +12:00
)
const googleDoc = await googleResponse.json()
if (!googleDoc._id) {
2021-07-06 01:24:13 +12:00
providers.google = {
2021-05-05 22:03:45 +12:00
type: ConfigTypes.Google,
config: { activated: true },
2021-05-05 04:31:06 +12:00
}
2021-07-20 23:30:11 +12:00
originalGoogleDoc = cloneDeep(googleDoc)
2021-05-05 04:31:06 +12:00
} else {
// default activated to true for older configs
if (googleDoc.config.activated === undefined) {
googleDoc.config.activated = true
}
2021-07-20 23:30:11 +12:00
originalGoogleDoc = cloneDeep(googleDoc)
2021-07-06 01:24:13 +12:00
providers.google = googleDoc
}
2021-07-08 03:18:18 +12:00
//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`)
2021-07-08 03:18:18 +12:00
const configSettings = await res.json()
if (configSettings.config) {
2021-07-08 23:36:09 +12:00
const logoKeys = Object.keys(configSettings.config)
logoKeys.map(logoKey => {
const logoUrl = configSettings.config[logoKey]
iconDropdownOptions.unshift({
label: logoKey,
value: logoKey,
icon: logoUrl,
})
2021-07-08 03:18:18 +12:00
})
2021-07-08 23:36:09 +12:00
}
2021-07-23 01:29:30 +12:00
const oidcResponse = await api.get(
`/api/global/configs/${ConfigTypes.OIDC}`
)
2021-07-06 01:24:13 +12:00
const oidcDoc = await oidcResponse.json()
if (!oidcDoc._id) {
providers.oidc = {
type: ConfigTypes.OIDC,
config: { configs: [{ activated: true }] },
2021-07-06 01:24:13 +12:00
}
} else {
originalOidcDoc = cloneDeep(oidcDoc)
2021-07-06 01:24:13 +12:00
providers.oidc = oidcDoc
2021-05-05 04:31:06 +12:00
}
})
2021-05-05 04:31:06 +12:00
</script>
<Layout>
<Layout gap="XS" noPadding>
2021-07-14 04:32:57 +12:00
<Heading size="M">Authentication</Heading>
<Body>
Every budibase app comes with basic authentication (email/password)
included. You can add additional authentication methods from the options
below.
</Body>
</Layout>
2021-07-06 01:24:13 +12:00
{#if providers.google}
2021-05-07 22:16:09 +12:00
<Divider />
<Layout gap="XS" noPadding>
<Heading size="S">
<div>
<GoogleLogo />
Google
<div class="google-save-button">
<div>
<Button
disabled={googleSaveButtonDisabled}
size="s"
cta
on:click={() => save([providers.google])}>Save</Button
>
</div>
</div>
</div>
</Heading>
<Body size="S">
To allow users to authenticate using their Google accounts, fill out the
fields below.
</Body>
</Layout>
<Layout gap="XS" noPadding>
2021-07-06 01:24:13 +12:00
{#each GoogleConfigFields.Google as field}
<div class="form-row">
2021-07-21 04:57:07 +12:00
<Label size="L">{field.label}</Label>
2021-07-21 05:03:16 +12:00
<Input
bind:value={providers.google.config[field.name]}
readonly={field.readonly}
placeholder={field.placeholder}
/>
2021-07-06 01:24:13 +12:00
</div>
{/each}
<div class="form-row">
<div class="field">
<Label size="L">Activated</Label>
<span class="alignedToggle">
<Toggle text="" bind:value={providers.google.config.activated} />
</span>
</div>
</div>
2021-07-06 01:24:13 +12:00
</Layout>
{/if}
{#if providers.oidc}
<Divider />
<Layout gap="XS" noPadding>
<Heading size="S">
<div>
<OidcLogo />
2021-07-06 01:24:13 +12:00
OpenID Connect
<div class="oidc-save-button">
<div>
<Button
disabled={oidcSaveButtonDisabled}
size="s"
cta
on:click={() => save([providers.oidc])}>Save</Button
>
</div>
</div>
</div></Heading
>
2021-07-06 01:24:13 +12:00
<Body size="S">
To allow users to authenticate using OIDC, fill out the fields below.
</Body>
</Layout>
<Layout gap="XS" noPadding>
{#each OIDCConfigFields.Oidc as field}
2021-05-07 22:16:09 +12:00
<div class="form-row">
2021-07-21 04:57:07 +12:00
<Label size="L">{field.label}</Label>
2021-07-21 05:03:16 +12:00
<Input
bind:value={providers.oidc.config.configs[0][field.name]}
readonly={field.readonly}
placeholder={field.placeholder}
/>
2021-05-07 22:16:09 +12:00
</div>
{/each}
<br />
2021-07-06 01:24:13 +12:00
<Body size="S">
To customize your login button, fill out the fields below.
2021-07-06 01:24:13 +12:00
</Body>
<div class="form-row">
<Label size="L">Name</Label>
2021-07-14 01:54:20 +12:00
<Input bind:value={providers.oidc.config.configs[0].name} />
</div>
<div class="form-row">
<Label size="L">Icon</Label>
2021-07-06 01:24:13 +12:00
<Select
label=""
2021-07-14 01:54:20 +12:00
bind:value={providers.oidc.config.configs[0].logo}
options={iconDropdownOptions}
2021-07-08 03:18:18 +12:00
on:change={e => e.detail === "Upload" && fileinput.click()}
2021-07-06 01:24:13 +12:00
/>
</div>
<input
type="file"
accept=".jpg, .jpeg, .png"
on:change={e => onFileSelected(e)}
bind:this={fileinput}
/>
</Layout>
<div class="form-row">
<div class="field">
<Label size="L">Activated</Label>
<span class="alignedToggle">
<Toggle
text=""
bind:value={providers.oidc.config.configs[0].activated}
/>
</span>
</div>
</div>
{/if}
</Layout>
2021-05-05 04:31:06 +12:00
<style>
.field {
display: flex;
align-items: center;
}
.alignedToggle {
margin-left: 63%;
}
2021-05-05 05:14:13 +12:00
.form-row {
display: grid;
grid-template-columns: 20% 1fr;
grid-gap: var(--spacing-l);
2021-05-07 22:16:09 +12:00
align-items: center;
2021-05-05 04:31:06 +12:00
}
2021-05-06 04:21:35 +12:00
span {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
input {
display: none;
}
.google-save-button {
display: inline-block;
margin-left: 400px;
}
.oidc-save-button {
display: inline-block;
margin-left: 320px;
}
2021-05-05 04:31:06 +12:00
</style>