Merge pull request #1446 from Budibase/oauth-config-screen
Oauth config screen
This commit is contained in:
commit
df4bbd8205
|
@ -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
|
||||
|
||||
// Config is specific to a user and a group
|
||||
if (config._id.includes(generateConfigID({ type, user, group }))) {
|
||||
config.score = 4
|
||||
return 4
|
||||
} else if (config._id.includes(generateConfigID({ type, user }))) {
|
||||
// Config is specific to a user only
|
||||
config.score = 3
|
||||
return 3
|
||||
} else if (config._id.includes(generateConfigID({ type, group }))) {
|
||||
// Config is specific to a group only
|
||||
config.score = 2
|
||||
return 2
|
||||
} else if (config._id.includes(generateConfigID({ type }))) {
|
||||
// Config is specific to a type only
|
||||
config.score = 1
|
||||
return 1
|
||||
}
|
||||
return config
|
||||
})
|
||||
return 0
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -2,6 +2,7 @@ import { getFrontendStore } from "./store/frontend"
|
|||
import { getAutomationStore } from "./store/automation"
|
||||
import { getHostingStore } from "./store/hosting"
|
||||
import { getThemeStore } from "./store/theme"
|
||||
import { getAdminStore } from "./store/admin"
|
||||
import { derived, writable } from "svelte/store"
|
||||
import analytics from "analytics"
|
||||
import { FrontendTypes, LAYOUT_NAMES } from "../constants"
|
||||
|
@ -11,6 +12,7 @@ export const store = getFrontendStore()
|
|||
export const automationStore = getAutomationStore()
|
||||
export const themeStore = getThemeStore()
|
||||
export const hostingStore = getHostingStore()
|
||||
export const adminPanelStore = getAdminStore()
|
||||
|
||||
export const currentAsset = derived(store, $store => {
|
||||
const type = $store.currentFrontEndType
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -16,11 +16,14 @@
|
|||
{#if $auth.user}
|
||||
<div class="root">
|
||||
<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-top">
|
||||
<Navigation>
|
||||
<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">
|
||||
Hosting
|
||||
</Item>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { isActive } from "@roxi/routify"
|
||||
import { isActive, url } from "@roxi/routify"
|
||||
import { onMount } from "svelte"
|
||||
import {
|
||||
Icon,
|
||||
|
@ -30,7 +30,7 @@
|
|||
{ title: "Drafts", href: "/portal/drafts" },
|
||||
{ title: "Users", href: "/portal/users", heading: "Manage" },
|
||||
{ title: "Groups", href: "/portal/groups" },
|
||||
{ title: "Auth", href: "/portal/auth" },
|
||||
{ title: "Auth", href: "/portal/oauth" },
|
||||
{ title: "Email", href: "/portal/email" },
|
||||
{ title: "General", href: "/portal/general", heading: "Settings" },
|
||||
{ title: "Theming", href: "/portal/theming" },
|
||||
|
@ -53,7 +53,9 @@
|
|||
<div class="menu">
|
||||
<Navigation>
|
||||
{#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}
|
||||
</Navigation>
|
||||
</div>
|
||||
|
|
|
@ -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>
|
|
@ -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
|
@ -12,14 +12,11 @@ const GLOBAL_DB = StaticDatabases.GLOBAL.name
|
|||
|
||||
exports.save = async function (ctx) {
|
||||
const db = new CouchDB(GLOBAL_DB)
|
||||
const { type, config } = ctx.request.body
|
||||
const { group, user } = config
|
||||
// insert the type into the doc
|
||||
config.type = type
|
||||
const { type, group, user, config } = ctx.request.body
|
||||
|
||||
// Config does not exist yet
|
||||
if (!config._id) {
|
||||
config._id = generateConfigID({
|
||||
if (!ctx.request.body._id) {
|
||||
ctx.request.body._id = generateConfigID({
|
||||
type,
|
||||
group,
|
||||
user,
|
||||
|
@ -34,7 +31,7 @@ exports.save = async function (ctx) {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await db.put(config)
|
||||
const response = await db.put(ctx.request.body)
|
||||
ctx.body = {
|
||||
type,
|
||||
_id: response.id,
|
||||
|
|
|
@ -65,7 +65,7 @@ exports.sendEmail = async ctx => {
|
|||
if (userId) {
|
||||
user = db.get(userId)
|
||||
}
|
||||
const config = await determineScopedConfig(db, params)
|
||||
const { config } = await determineScopedConfig(db, params)
|
||||
if (!config) {
|
||||
ctx.throw(400, "Unable to find SMTP configuration")
|
||||
}
|
||||
|
|
|
@ -44,6 +44,9 @@ function googleValidation() {
|
|||
function buildConfigSaveValidation() {
|
||||
// prettier-ignore
|
||||
return joiValidator.body(Joi.object({
|
||||
_id: Joi.string(),
|
||||
_rev: Joi.string(),
|
||||
group: Joi.string(),
|
||||
type: Joi.string().valid(...Object.values(Configs)).required(),
|
||||
config: Joi.alternatives()
|
||||
.conditional("type", {
|
||||
|
|
Loading…
Reference in New Issue