Merge pull request #15740 from Budibase/BUDI-9127/oauth2-settings
OAuth2 configuration frontend
This commit is contained in:
commit
26f7677e51
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
export let title
|
export let title = ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="side-nav">
|
<div class="side-nav">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
export let text
|
export let text = ""
|
||||||
export let url
|
export let url = ""
|
||||||
export let active = false
|
export let active = false
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { Content, SideNav, SideNavItem } from "@/components/portal/page"
|
import { Content, SideNav, SideNavItem } from "@/components/portal/page"
|
||||||
import { Page, Layout, AbsTooltip, TooltipPosition } from "@budibase/bbui"
|
import { Page, Layout, AbsTooltip, TooltipPosition } from "@budibase/bbui"
|
||||||
import { url, isActive } from "@roxi/routify"
|
import { url, isActive } from "@roxi/routify"
|
||||||
import DeleteModal from "@/components/deploy/DeleteModal.svelte"
|
import DeleteModal from "@/components/deploy/DeleteModal.svelte"
|
||||||
import { isOnlyUser, appStore } from "@/stores/builder"
|
import { isOnlyUser, appStore } from "@/stores/builder"
|
||||||
|
import { featureFlag } from "@/helpers"
|
||||||
|
import { FeatureFlag } from "@budibase/types"
|
||||||
|
|
||||||
let deleteModal
|
let deleteModal: DeleteModal
|
||||||
|
|
||||||
|
$: oauth2Enabled = featureFlag.isEnabled(FeatureFlag.OAUTH2_CONFIG)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=4 -->
|
<!-- routify:options index=4 -->
|
||||||
|
@ -44,11 +48,18 @@
|
||||||
url={$url("./version")}
|
url={$url("./version")}
|
||||||
active={$isActive("./version")}
|
active={$isActive("./version")}
|
||||||
/>
|
/>
|
||||||
|
{#if oauth2Enabled}
|
||||||
|
<SideNavItem
|
||||||
|
text="OAuth2"
|
||||||
|
url={$url("./oauth2")}
|
||||||
|
active={$isActive("./oauth2")}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<div class="delete-action">
|
<div class="delete-action">
|
||||||
<AbsTooltip
|
<AbsTooltip
|
||||||
position={TooltipPosition.Bottom}
|
position={TooltipPosition.Bottom}
|
||||||
text={$isOnlyUser
|
text={$isOnlyUser
|
||||||
? null
|
? undefined
|
||||||
: "Unavailable - another user is editing this app"}
|
: "Unavailable - another user is editing this app"}
|
||||||
>
|
>
|
||||||
<SideNavItem
|
<SideNavItem
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { oauth2 } from "@/stores/builder"
|
||||||
|
import type { CreateOAuth2Config } from "@/types"
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Heading,
|
||||||
|
Input,
|
||||||
|
Link,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
|
||||||
|
let modal: Modal
|
||||||
|
|
||||||
|
function openModal() {
|
||||||
|
modal.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
let config: Partial<CreateOAuth2Config> = {}
|
||||||
|
|
||||||
|
$: saveOAuth2Config = async () => {
|
||||||
|
await oauth2.create(config as any) // TODO
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button cta size="M" on:click={openModal}>Add OAuth2</Button>
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<ModalContent onConfirm={saveOAuth2Config} size="M">
|
||||||
|
<Heading size="S">Create new OAuth2 connection</Heading>
|
||||||
|
<Body size="S">
|
||||||
|
The OAuth 2 authentication below uses the Client Credentials (machine to
|
||||||
|
machine) grant type.
|
||||||
|
</Body>
|
||||||
|
<Divider noGrid noMargin />
|
||||||
|
<Input label="Name*" placeholder="Type here..." bind:value={config.name} />
|
||||||
|
<Input
|
||||||
|
label="Service URL*"
|
||||||
|
placeholder="E.g. www.google.com"
|
||||||
|
bind:value={config.url}
|
||||||
|
/>
|
||||||
|
<div class="field-info">
|
||||||
|
<Body size="XS" color="var(--spectrum-global-color-gray-700)">
|
||||||
|
The location where the flow sends the credentials. This field should be
|
||||||
|
a full URL.
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
label="Client ID*"
|
||||||
|
placeholder="Type here..."
|
||||||
|
bind:value={config.clientId}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
label="Client secret*"
|
||||||
|
placeholder="Type here..."
|
||||||
|
bind:value={config.clientSecret}
|
||||||
|
/>
|
||||||
|
<Body size="S"
|
||||||
|
>To learn how to configure OAuth2, our documentation <Link
|
||||||
|
href="TODO"
|
||||||
|
target="_blank"
|
||||||
|
size="M">our documentation.</Link
|
||||||
|
></Body
|
||||||
|
>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.field-info {
|
||||||
|
margin-top: calc(var(--spacing-xl) * -1 + var(--spacing-s));
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { ActionMenu, Icon, MenuItem } from "@budibase/bbui"
|
||||||
|
|
||||||
|
function onEdit() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
function onDelete() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionMenu align="right">
|
||||||
|
<div slot="control" class="control icon">
|
||||||
|
<Icon size="S" hoverable name="MoreSmallList" />
|
||||||
|
</div>
|
||||||
|
<MenuItem on:click={onEdit} icon="Edit">Edit</MenuItem>
|
||||||
|
<MenuItem on:click={onDelete} icon="Delete">Delete</MenuItem>
|
||||||
|
</ActionMenu>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Layout, Heading, Body, Divider, Table } from "@budibase/bbui"
|
||||||
|
import { oauth2 } from "@/stores/builder"
|
||||||
|
import AddButton from "./AddButton.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import MoreMenuRenderer from "./MoreMenuRenderer.svelte"
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
name: {
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
lastUsed: {
|
||||||
|
displayName: "Last used",
|
||||||
|
sortable: false,
|
||||||
|
},
|
||||||
|
more: {
|
||||||
|
width: "auto",
|
||||||
|
displayName: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const customRenderers = [{ column: "more", component: MoreMenuRenderer }]
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
oauth2.fetch()
|
||||||
|
})
|
||||||
|
|
||||||
|
$: configs = $oauth2.configs.map(c => ({
|
||||||
|
lastUsed: "Never used",
|
||||||
|
...c,
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Layout noPadding>
|
||||||
|
<Layout gap="XS" noPadding>
|
||||||
|
<div class="header">
|
||||||
|
<Heading>OAuth2</Heading>
|
||||||
|
<AddButton />
|
||||||
|
</div>
|
||||||
|
<Body
|
||||||
|
>Manage and configure OAuth 2.0 Client Credentials for secure API access.</Body
|
||||||
|
>
|
||||||
|
</Layout>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Table
|
||||||
|
data={configs}
|
||||||
|
loading={$oauth2.loading}
|
||||||
|
{schema}
|
||||||
|
{customRenderers}
|
||||||
|
allowEditRows={false}
|
||||||
|
allowEditColumns={false}
|
||||||
|
allowClickRows={false}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -37,6 +37,9 @@ import { flags } from "./flags"
|
||||||
import { rowActions } from "./rowActions"
|
import { rowActions } from "./rowActions"
|
||||||
import componentTreeNodesStore from "./componentTreeNodes"
|
import componentTreeNodesStore from "./componentTreeNodes"
|
||||||
import { appPublished } from "./published"
|
import { appPublished } from "./published"
|
||||||
|
import { oauth2 } from "./oauth2"
|
||||||
|
|
||||||
|
import { FetchAppPackageResponse } from "@budibase/types"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
componentTreeNodesStore,
|
componentTreeNodesStore,
|
||||||
|
@ -77,6 +80,7 @@ export {
|
||||||
screenComponentsList,
|
screenComponentsList,
|
||||||
screenComponentErrors,
|
screenComponentErrors,
|
||||||
screenComponentErrorList,
|
screenComponentErrorList,
|
||||||
|
oauth2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reset = () => {
|
export const reset = () => {
|
||||||
|
@ -106,7 +110,7 @@ const resetBuilderHistory = () => {
|
||||||
automationHistoryStore.reset()
|
automationHistoryStore.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = async pkg => {
|
export const initialise = async (pkg: FetchAppPackageResponse) => {
|
||||||
const { application } = pkg
|
const { application } = pkg
|
||||||
// must be first operation to make sure subsequent requests have correct app ID
|
// must be first operation to make sure subsequent requests have correct app ID
|
||||||
appStore.syncAppPackage(pkg)
|
appStore.syncAppPackage(pkg)
|
|
@ -15,7 +15,7 @@ export class NavigationStore extends BudiStore<AppNavigation> {
|
||||||
super(INITIAL_NAVIGATION_STATE)
|
super(INITIAL_NAVIGATION_STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
syncAppNavigation(nav: AppNavigation) {
|
syncAppNavigation(nav?: AppNavigation) {
|
||||||
this.update(state => ({
|
this.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
...nav,
|
...nav,
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { API } from "@/api"
|
||||||
|
import { BudiStore } from "@/stores/BudiStore"
|
||||||
|
import { CreateOAuth2Config } from "@/types"
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OAuth2StoreState {
|
||||||
|
configs: Config[]
|
||||||
|
loading: boolean
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OAuth2Store extends BudiStore<OAuth2StoreState> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
configs: [],
|
||||||
|
loading: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch() {
|
||||||
|
this.store.update(store => ({
|
||||||
|
...store,
|
||||||
|
loading: true,
|
||||||
|
}))
|
||||||
|
try {
|
||||||
|
const configs = await API.oauth2.fetch()
|
||||||
|
this.store.update(store => ({
|
||||||
|
...store,
|
||||||
|
configs: configs.map(c => ({ id: c.id, name: c.name })),
|
||||||
|
loading: false,
|
||||||
|
}))
|
||||||
|
} catch (e: any) {
|
||||||
|
this.store.update(store => ({
|
||||||
|
...store,
|
||||||
|
loading: false,
|
||||||
|
error: e.message,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(config: CreateOAuth2Config) {
|
||||||
|
await API.oauth2.create(config)
|
||||||
|
await this.fetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const oauth2 = new OAuth2Store()
|
|
@ -1 +1,2 @@
|
||||||
export * from "./bindings"
|
export * from "./bindings"
|
||||||
|
export * from "./oauth2"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { CreateOAuth2ConfigRequest } from "@budibase/types"
|
||||||
|
|
||||||
|
export interface CreateOAuth2Config extends CreateOAuth2ConfigRequest {}
|
|
@ -45,6 +45,7 @@ import { buildAuditLogEndpoints } from "./auditLogs"
|
||||||
import { buildLogsEndpoints } from "./logs"
|
import { buildLogsEndpoints } from "./logs"
|
||||||
import { buildMigrationEndpoints } from "./migrations"
|
import { buildMigrationEndpoints } from "./migrations"
|
||||||
import { buildRowActionEndpoints } from "./rowActions"
|
import { buildRowActionEndpoints } from "./rowActions"
|
||||||
|
import { buildOAuth2Endpoints } from "./oauth2"
|
||||||
|
|
||||||
export type { APIClient } from "./types"
|
export type { APIClient } from "./types"
|
||||||
|
|
||||||
|
@ -290,5 +291,6 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
||||||
...buildMigrationEndpoints(API),
|
...buildMigrationEndpoints(API),
|
||||||
viewV2: buildViewV2Endpoints(API),
|
viewV2: buildViewV2Endpoints(API),
|
||||||
rowActions: buildRowActionEndpoints(API),
|
rowActions: buildRowActionEndpoints(API),
|
||||||
|
oauth2: buildOAuth2Endpoints(API),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import {
|
||||||
|
FetchOAuth2ConfigsResponse,
|
||||||
|
CreateOAuth2ConfigResponse,
|
||||||
|
OAuth2ConfigResponse,
|
||||||
|
CreateOAuth2ConfigRequest,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { BaseAPIClient } from "./types"
|
||||||
|
|
||||||
|
export interface OAuth2Endpoints {
|
||||||
|
fetch: () => Promise<OAuth2ConfigResponse[]>
|
||||||
|
create: (
|
||||||
|
config: CreateOAuth2ConfigRequest
|
||||||
|
) => Promise<CreateOAuth2ConfigResponse>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
|
||||||
|
/**
|
||||||
|
* Gets all OAuth2 configurations for the app.
|
||||||
|
* @param tableId the ID of the table
|
||||||
|
*/
|
||||||
|
fetch: async () => {
|
||||||
|
return (
|
||||||
|
await API.get<FetchOAuth2ConfigsResponse>({
|
||||||
|
url: `/api/oauth2`,
|
||||||
|
})
|
||||||
|
).configs
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a OAuth2 configuration.
|
||||||
|
* @param name the name of the row action
|
||||||
|
* @param tableId the ID of the table
|
||||||
|
*/
|
||||||
|
create: async config => {
|
||||||
|
return await API.post<
|
||||||
|
CreateOAuth2ConfigRequest,
|
||||||
|
CreateOAuth2ConfigResponse
|
||||||
|
>({
|
||||||
|
url: `/api/oauth2`,
|
||||||
|
body: {
|
||||||
|
...config,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
|
@ -16,6 +16,7 @@ import { LayoutEndpoints } from "./layouts"
|
||||||
import { LicensingEndpoints } from "./licensing"
|
import { LicensingEndpoints } from "./licensing"
|
||||||
import { LogEndpoints } from "./logs"
|
import { LogEndpoints } from "./logs"
|
||||||
import { MigrationEndpoints } from "./migrations"
|
import { MigrationEndpoints } from "./migrations"
|
||||||
|
import { OAuth2Endpoints } from "./oauth2"
|
||||||
import { OtherEndpoints } from "./other"
|
import { OtherEndpoints } from "./other"
|
||||||
import { PermissionEndpoints } from "./permissions"
|
import { PermissionEndpoints } from "./permissions"
|
||||||
import { PluginEndpoins } from "./plugins"
|
import { PluginEndpoins } from "./plugins"
|
||||||
|
@ -132,4 +133,8 @@ export type APIClient = BaseAPIClient &
|
||||||
TableEndpoints &
|
TableEndpoints &
|
||||||
TemplateEndpoints &
|
TemplateEndpoints &
|
||||||
UserEndpoints &
|
UserEndpoints &
|
||||||
ViewEndpoints & { rowActions: RowActionEndpoints; viewV2: ViewV2Endpoints }
|
ViewEndpoints & {
|
||||||
|
rowActions: RowActionEndpoints
|
||||||
|
viewV2: ViewV2Endpoints
|
||||||
|
oauth2: OAuth2Endpoints
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
interface OAuth2ConfigResponse {
|
export interface OAuth2ConfigResponse {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
USE_ZOD_VALIDATOR = "USE_ZOD_VALIDATOR",
|
||||||
|
AI_JS_GENERATION = "AI_JS_GENERATION",
|
||||||
|
OAUTH2_CONFIG = "OAUTH2_CONFIG",
|
||||||
|
|
||||||
// Account-portal
|
// Account-portal
|
||||||
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
DIRECT_LOGIN_TO_ACCOUNT_PORTAL = "DIRECT_LOGIN_TO_ACCOUNT_PORTAL",
|
||||||
|
|
||||||
AI_JS_GENERATION = "AI_JS_GENERATION",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeatureFlagDefaults: Record<FeatureFlag, boolean> = {
|
export const FeatureFlagDefaults: Record<FeatureFlag, boolean> = {
|
||||||
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
[FeatureFlag.USE_ZOD_VALIDATOR]: false,
|
||||||
[FeatureFlag.AI_JS_GENERATION]: false,
|
[FeatureFlag.AI_JS_GENERATION]: false,
|
||||||
|
[FeatureFlag.OAUTH2_CONFIG]: false,
|
||||||
|
|
||||||
// Account-portal
|
// Account-portal
|
||||||
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
[FeatureFlag.DIRECT_LOGIN_TO_ACCOUNT_PORTAL]: false,
|
||||||
|
|
Loading…
Reference in New Issue