Merge pull request #1446 from Budibase/oauth-config-screen

Oauth config screen
This commit is contained in:
Martin McKeaveney 2021-05-05 17:28:04 +01:00 committed by GitHub
commit 657eddb9e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 223 additions and 1253 deletions

View File

@ -132,29 +132,32 @@ const determineScopedConfig = async function (db, { type, user, group }) {
} }
) )
) )
const configs = response.rows.map(row => {
function determineScore(row) {
const config = row.doc const config = row.doc
// Config is specific to a user and a group // Config is specific to a user and a group
if (config._id.includes(generateConfigID({ type, user, group }))) { if (config._id.includes(generateConfigID({ type, user, group }))) {
config.score = 4 return 4
} else if (config._id.includes(generateConfigID({ type, user }))) { } else if (config._id.includes(generateConfigID({ type, user }))) {
// Config is specific to a user only // Config is specific to a user only
config.score = 3 return 3
} else if (config._id.includes(generateConfigID({ type, group }))) { } else if (config._id.includes(generateConfigID({ type, group }))) {
// Config is specific to a group only // Config is specific to a group only
config.score = 2 return 2
} else if (config._id.includes(generateConfigID({ type }))) { } else if (config._id.includes(generateConfigID({ type }))) {
// Config is specific to a type only // Config is specific to a type only
config.score = 1 return 1
}
return 0
} }
return config
})
// Find the config with the most granular scope based on context // Find the config with the most granular scope based on context
const scopedConfig = configs.sort((a, b) => b.score - a.score)[0] const scopedConfig = response.rows.sort(
(a, b) => determineScore(a) - determineScore(b)
)[0]
return scopedConfig return scopedConfig.doc
} }
exports.generateConfigID = generateConfigID exports.generateConfigID = generateConfigID

View File

@ -2,6 +2,7 @@ import { getFrontendStore } from "./store/frontend"
import { getAutomationStore } from "./store/automation" import { getAutomationStore } from "./store/automation"
import { getHostingStore } from "./store/hosting" import { getHostingStore } from "./store/hosting"
import { getThemeStore } from "./store/theme" import { getThemeStore } from "./store/theme"
import { getAdminStore } from "./store/admin"
import { derived, writable } from "svelte/store" import { derived, writable } from "svelte/store"
import analytics from "analytics" import analytics from "analytics"
import { FrontendTypes, LAYOUT_NAMES } from "../constants" import { FrontendTypes, LAYOUT_NAMES } from "../constants"
@ -11,6 +12,7 @@ export const store = getFrontendStore()
export const automationStore = getAutomationStore() export const automationStore = getAutomationStore()
export const themeStore = getThemeStore() export const themeStore = getThemeStore()
export const hostingStore = getHostingStore() export const hostingStore = getHostingStore()
export const adminPanelStore = getAdminStore()
export const currentAsset = derived(store, $store => { export const currentAsset = derived(store, $store => {
const type = $store.currentFrontEndType const type = $store.currentFrontEndType

View File

@ -0,0 +1,11 @@
import { writable } from "svelte/store"
const INITIAL_ADMIN_STATE = {
oauth: [],
}
export const getAdminStore = () => {
const store = writable({ ...INITIAL_ADMIN_STATE })
store.actions = {}
return store
}

View File

@ -16,11 +16,14 @@
{#if $auth.user} {#if $auth.user}
<div class="root"> <div class="root">
<div class="ui-nav"> <div class="ui-nav">
<div class="home-logo"><img src={Logo} alt="Budibase icon" /></div> <div class="home-logo">
<img src={Logo} alt="Budibase icon" />
</div>
<div class="nav-section"> <div class="nav-section">
<div class="nav-top"> <div class="nav-top">
<Navigation> <Navigation>
<Item href="/builder/" icon="Apps" selected>Apps</Item> <Item href="/builder/" icon="Apps" selected>Apps</Item>
<Item href="/builder/oauth/" icon="OAuth" selected>OAuth</Item>
<Item external href="https://portal.budi.live/" icon="Servers"> <Item external href="https://portal.budi.live/" icon="Servers">
Hosting Hosting
</Item> </Item>

View File

@ -1,5 +1,5 @@
<script> <script>
import { isActive } from "@roxi/routify" import { isActive, url } from "@roxi/routify"
import { onMount } from "svelte" import { onMount } from "svelte"
import { import {
Icon, Icon,
@ -30,7 +30,7 @@
{ title: "Drafts", href: "/portal/drafts" }, { title: "Drafts", href: "/portal/drafts" },
{ title: "Users", href: "/portal/users", heading: "Manage" }, { title: "Users", href: "/portal/users", heading: "Manage" },
{ title: "Groups", href: "/portal/groups" }, { title: "Groups", href: "/portal/groups" },
{ title: "Auth", href: "/portal/auth" }, { title: "Auth", href: "/portal/oauth" },
{ title: "Email", href: "/portal/email" }, { title: "Email", href: "/portal/email" },
{ title: "General", href: "/portal/general", heading: "Settings" }, { title: "General", href: "/portal/general", heading: "Settings" },
{ title: "Theming", href: "/portal/theming" }, { title: "Theming", href: "/portal/theming" },
@ -53,7 +53,9 @@
<div class="menu"> <div class="menu">
<Navigation> <Navigation>
{#each menu as { title, href, heading }} {#each menu as { title, href, heading }}
<Item selected={$isActive(href)} {href} {heading}>{title}</Item> <Item selected={$isActive(href)} href={$url(href)} {heading}>
{title}
</Item>
{/each} {/each}
</Navigation> </Navigation>
</div> </div>

View File

@ -0,0 +1,118 @@
<script>
import GoogleLogo from "./logos/Google.svelte"
import {
Button,
Heading,
Divider,
Label,
notifications,
Layout,
Input,
Body,
} from "@budibase/bbui"
import { onMount } from "svelte"
import api from "builderStore/api"
const ConfigTypes = {
Google: "google",
// Github: "github",
// AzureAD: "ad",
}
const ConfigFields = {
Google: ["clientID", "clientSecret", "callbackURL"],
}
let google
async function save(doc) {
try {
// Save an oauth config
const response = await api.post(`/api/admin/configs`, doc)
const json = await response.json()
if (response.status !== 200) throw new Error(json.message)
google._rev = json._rev
google._id = json._id
notifications.success(`Settings saved.`)
} catch (err) {
notifications.error(`Failed to update OAuth settings. ${err}`)
}
}
onMount(async () => {
// fetch the configs for oauth
const googleResponse = await api.get(
`/api/admin/configs/${ConfigTypes.Google}`
)
const googleDoc = await googleResponse.json()
if (!googleDoc._id) {
google = {
type: ConfigTypes.Google,
config: {},
}
} else {
google = googleDoc
}
})
</script>
<section class="container">
<header>
<Heading size="M">OAuth</Heading>
<Body size="S">
Every budibase app comes with basic authentication (email/password)
included. You can add additional authentication methods from the options
below.
</Body>
</header>
<Divider />
{#if google}
<div class="config-form">
<Layout gap="S">
<Heading size="S">
<span>
<GoogleLogo />
Google
</span>
</Heading>
{#each ConfigFields.Google as field}
<div class="form-row">
<Label>{field}</Label>
<Input bind:value={google.config[field]} />
</div>
{/each}
</Layout>
<Button primary on:click={() => save(google)}>Save</Button>
</div>
<Divider />
{/if}
</section>
<style>
section {
margin: 60px 320px;
}
.config-form {
margin-top: 42px;
margin-bottom: 42px;
}
.form-row {
display: grid;
grid-template-columns: 20% 1fr;
grid-gap: var(--spacing-l);
}
span {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
header {
margin-bottom: 42px;
}
</style>

View File

@ -0,0 +1,38 @@
<svg
width="18"
height="18"
viewBox="0 0 268 268"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path
d="M58.8037 109.043C64.0284 93.2355 74.1116 79.4822 87.615 69.7447C101.118
60.0073 117.352 54.783 134 54.8172C152.872 54.8172 169.934 61.5172 183.334
72.4828L222.328 33.5C198.566 12.7858 168.114 0 134 0C81.1817 0 35.711
30.1277 13.8467 74.2583L58.8037 109.043Z"
fill="#EA4335" />
<path
d="M179.113 201.145C166.942 208.995 151.487 213.183 134 213.183C117.418
213.217 101.246 208.034 87.7727 198.369C74.2993 188.703 64.2077 175.044
58.9265 159.326L13.8132 193.574C24.8821 215.978 42.012 234.828 63.2572
247.984C84.5024 261.14 109.011 268.075 134 268C166.752 268 198.041 256.353
221.48 234.5L179.125 201.145H179.113Z"
fill="#34A853" />
<path
d="M221.48 234.5C245.991 211.631 261.903 177.595 261.903 134C261.903
126.072 260.686 117.552 258.866 109.634H134V161.414H205.869C202.329
178.823 192.804 192.301 179.125 201.145L221.48 234.5Z"
fill="#4A90E2" />
<path
d="M58.9265 159.326C56.1947 151.162 54.8068 142.609 54.8172 134C54.8172
125.268 56.213 116.882 58.8037 109.043L13.8467 74.2584C4.64957 92.825
-0.0915078 113.28 1.86708e-05 134C1.86708e-05 155.44 4.96919 175.652
13.8132 193.574L58.9265 159.326Z"
fill="#FBBC05" />
</g>
<defs>
<clipPath id="clip0">
<rect width="268" height="268" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

View File

@ -12,14 +12,11 @@ const GLOBAL_DB = StaticDatabases.GLOBAL.name
exports.save = async function (ctx) { exports.save = async function (ctx) {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
const { type, config } = ctx.request.body const { type, group, user, config } = ctx.request.body
const { group, user } = config
// insert the type into the doc
config.type = type
// Config does not exist yet // Config does not exist yet
if (!config._id) { if (!ctx.request.body._id) {
config._id = generateConfigID({ ctx.request.body._id = generateConfigID({
type, type,
group, group,
user, user,
@ -34,7 +31,7 @@ exports.save = async function (ctx) {
} }
try { try {
const response = await db.put(config) const response = await db.put(ctx.request.body)
ctx.body = { ctx.body = {
type, type,
_id: response.id, _id: response.id,

View File

@ -65,7 +65,7 @@ exports.sendEmail = async ctx => {
if (userId) { if (userId) {
user = db.get(userId) user = db.get(userId)
} }
const config = await determineScopedConfig(db, params) const { config } = await determineScopedConfig(db, params)
if (!config) { if (!config) {
ctx.throw(400, "Unable to find SMTP configuration") ctx.throw(400, "Unable to find SMTP configuration")
} }

View File

@ -44,6 +44,9 @@ function googleValidation() {
function buildConfigSaveValidation() { function buildConfigSaveValidation() {
// prettier-ignore // prettier-ignore
return joiValidator.body(Joi.object({ return joiValidator.body(Joi.object({
_id: Joi.string(),
_rev: Joi.string(),
group: Joi.string(),
type: Joi.string().valid(...Object.values(Configs)).required(), type: Joi.string().valid(...Object.values(Configs)).required(),
config: Joi.alternatives() config: Joi.alternatives()
.conditional("type", { .conditional("type", {