diff --git a/packages/builder/src/pages/builder/app/[application]/settings/oauth2/MoreMenuRenderer.svelte b/packages/builder/src/pages/builder/app/[application]/settings/oauth2/MoreMenuRenderer.svelte
index 864bf40d69..7b247bef53 100644
--- a/packages/builder/src/pages/builder/app/[application]/settings/oauth2/MoreMenuRenderer.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/settings/oauth2/MoreMenuRenderer.svelte
@@ -49,5 +49,5 @@
-
+
diff --git a/packages/builder/src/pages/builder/app/[application]/settings/oauth2/OAuth2ConfigModalContent.svelte b/packages/builder/src/pages/builder/app/[application]/settings/oauth2/OAuth2ConfigModalContent.svelte
index f683adb996..da51d83c5d 100644
--- a/packages/builder/src/pages/builder/app/[application]/settings/oauth2/OAuth2ConfigModalContent.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/settings/oauth2/OAuth2ConfigModalContent.svelte
@@ -70,12 +70,28 @@
return keepOpen
}
+ const { data: configData } = validationResult
try {
+ const connectionValidation = await oauth2.validate({
+ id: config?.id,
+ url: configData.url,
+ clientId: configData.clientId,
+ clientSecret: configData.clientSecret,
+ })
+ if (!connectionValidation.valid) {
+ let message = "Connection settings could not be validated"
+ if (connectionValidation.message) {
+ message += `: ${connectionValidation.message}`
+ }
+ notifications.error(message)
+ return keepOpen
+ }
+
if (isCreation) {
- await oauth2.create(validationResult.data)
+ await oauth2.create(configData)
notifications.success("Settings created.")
} else {
- await oauth2.edit(config!.id, validationResult.data)
+ await oauth2.edit(config!.id, configData)
notifications.success("Settings saved.")
}
} catch (e: any) {
diff --git a/packages/builder/src/stores/builder/oauth2.ts b/packages/builder/src/stores/builder/oauth2.ts
index 85afe2d238..35b0c1fe6c 100644
--- a/packages/builder/src/stores/builder/oauth2.ts
+++ b/packages/builder/src/stores/builder/oauth2.ts
@@ -1,6 +1,7 @@
import { API } from "@/api"
import { BudiStore } from "@/stores/BudiStore"
import { OAuth2Config, UpsertOAuth2Config } from "@/types"
+import { ValidateConfigRequest } from "@budibase/types"
interface OAuth2StoreState {
configs: OAuth2Config[]
@@ -57,6 +58,10 @@ export class OAuth2Store extends BudiStore {
await API.oauth2.delete(id)
await this.fetch()
}
+
+ async validate(config: ValidateConfigRequest) {
+ return await API.oauth2.validate(config)
+ }
}
export const oauth2 = new OAuth2Store()
diff --git a/packages/frontend-core/src/api/oauth2.ts b/packages/frontend-core/src/api/oauth2.ts
index a4589461d8..7467418639 100644
--- a/packages/frontend-core/src/api/oauth2.ts
+++ b/packages/frontend-core/src/api/oauth2.ts
@@ -3,6 +3,8 @@ import {
OAuth2ConfigResponse,
UpsertOAuth2ConfigRequest,
UpsertOAuth2ConfigResponse,
+ ValidateConfigRequest,
+ ValidateConfigResponse,
} from "@budibase/types"
import { BaseAPIClient } from "./types"
@@ -16,6 +18,7 @@ export interface OAuth2Endpoints {
config: UpsertOAuth2ConfigRequest
) => Promise
delete: (id: string) => Promise
+ validate: (config: ValidateConfigRequest) => Promise
}
export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
@@ -32,8 +35,6 @@ export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
/**
* 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<
@@ -49,8 +50,6 @@ export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
/**
* Updates an existing OAuth2 configuration.
- * @param name the name of the row action
- * @param tableId the ID of the table
*/
update: async (id, config) => {
return await API.put(
@@ -72,4 +71,14 @@ export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
url: `/api/oauth2/${id}`,
})
},
+ validate: async function (
+ config: ValidateConfigRequest
+ ): Promise {
+ return await API.post({
+ url: `/api/oauth2/validate`,
+ body: {
+ ...config,
+ },
+ })
+ },
})
diff --git a/packages/server/src/api/controllers/oauth2.ts b/packages/server/src/api/controllers/oauth2.ts
index 6447d60f0b..dc95cbda3d 100644
--- a/packages/server/src/api/controllers/oauth2.ts
+++ b/packages/server/src/api/controllers/oauth2.ts
@@ -7,6 +7,8 @@ import {
RequiredKeys,
OAuth2ConfigResponse,
PASSWORD_REPLACEMENT,
+ ValidateConfigResponse,
+ ValidateConfigRequest,
} from "@budibase/types"
import sdk from "../../sdk"
@@ -75,3 +77,27 @@ export async function remove(
await sdk.oauth2.remove(configToRemove)
ctx.status = 204
}
+
+export async function validate(
+ ctx: Ctx
+) {
+ const { body } = ctx.request
+ const config = {
+ url: body.url,
+ clientId: body.clientId,
+ clientSecret: body.clientSecret,
+ }
+
+ if (config.clientSecret === PASSWORD_REPLACEMENT && body.id) {
+ const existingConfig = await sdk.oauth2.get(body.id)
+ if (!existingConfig) {
+ ctx.throw(`OAuth2 config with id '${body.id}' not found.`, 404)
+ }
+
+ config.clientSecret = existingConfig.clientSecret
+ }
+
+ const validation = await sdk.oauth2.validateConfig(config)
+ ctx.status = 201
+ ctx.body = validation
+}
diff --git a/packages/server/src/api/routes/oauth2.ts b/packages/server/src/api/routes/oauth2.ts
index 2ae3cbdf82..a0e68eff84 100644
--- a/packages/server/src/api/routes/oauth2.ts
+++ b/packages/server/src/api/routes/oauth2.ts
@@ -38,5 +38,10 @@ router.delete(
authorized(PermissionType.BUILDER),
controller.remove
)
+router.post(
+ "/api/oauth2/validate",
+ authorized(PermissionType.BUILDER),
+ controller.validate
+)
export default router
diff --git a/packages/server/src/sdk/app/oauth2/utils.ts b/packages/server/src/sdk/app/oauth2/utils.ts
index da36cd0478..1c280241fa 100644
--- a/packages/server/src/sdk/app/oauth2/utils.ts
+++ b/packages/server/src/sdk/app/oauth2/utils.ts
@@ -31,3 +31,34 @@ export async function generateToken(id: string) {
return `${jsonResponse.token_type} ${jsonResponse.access_token}`
}
+
+export async function validateConfig(config: {
+ url: string
+ clientId: string
+ clientSecret: string
+}): Promise<{ valid: boolean; message?: string }> {
+ try {
+ const resp = await fetch(config.url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: new URLSearchParams({
+ grant_type: "client_credentials",
+ client_id: config.clientId,
+ client_secret: config.clientSecret,
+ }),
+ redirect: "follow",
+ })
+
+ const jsonResponse = await resp.json()
+ if (!resp.ok) {
+ const message = jsonResponse.error_description ?? resp.statusText
+ return { valid: false, message }
+ }
+
+ return { valid: true }
+ } catch (e: any) {
+ return { valid: false, message: e.message }
+ }
+}
diff --git a/packages/types/src/api/web/app/oauth2.ts b/packages/types/src/api/web/app/oauth2.ts
index 1790676c0d..9c0e431b7e 100644
--- a/packages/types/src/api/web/app/oauth2.ts
+++ b/packages/types/src/api/web/app/oauth2.ts
@@ -20,3 +20,15 @@ export interface UpsertOAuth2ConfigRequest {
export interface UpsertOAuth2ConfigResponse {
config: OAuth2ConfigResponse
}
+
+export interface ValidateConfigRequest {
+ id?: string
+ url: string
+ clientId: string
+ clientSecret: string
+}
+
+export interface ValidateConfigResponse {
+ valid: boolean
+ message?: string
+}