From 35fef4ac7bd466dd5f12efa8b6b2742cea8cbab5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 10:39:03 +0100 Subject: [PATCH 1/9] Persist grant type from api --- packages/server/src/api/controllers/oauth2.ts | 2 ++ packages/server/src/api/routes/oauth2.ts | 9 ++++++++- packages/types/src/api/web/app/oauth2.ts | 4 +++- packages/types/src/documents/app/oauth2.ts | 5 +++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/oauth2.ts b/packages/server/src/api/controllers/oauth2.ts index 0cd10cde03..4bfef3cf61 100644 --- a/packages/server/src/api/controllers/oauth2.ts +++ b/packages/server/src/api/controllers/oauth2.ts @@ -46,6 +46,7 @@ export async function create( clientId: body.clientId, clientSecret: body.clientSecret, method: body.method, + grantType: body.grantType, } const config = await sdk.oauth2.create(newConfig) @@ -72,6 +73,7 @@ export async function edit( clientId: body.clientId, clientSecret: body.clientSecret, method: body.method, + grantType: body.grantType, } const config = await sdk.oauth2.update(toUpdate) diff --git a/packages/server/src/api/routes/oauth2.ts b/packages/server/src/api/routes/oauth2.ts index 64b4f66248..fd40c1a5f9 100644 --- a/packages/server/src/api/routes/oauth2.ts +++ b/packages/server/src/api/routes/oauth2.ts @@ -1,5 +1,9 @@ import Router from "@koa/router" -import { OAuth2CredentialsMethod, PermissionType } from "@budibase/types" +import { + OAuth2CredentialsMethod, + OAuth2GrantType, + PermissionType, +} from "@budibase/types" import { middleware } from "@budibase/backend-core" import authorized from "../../middleware/authorized" @@ -13,6 +17,9 @@ const baseSchema = { method: Joi.string() .required() .valid(...Object.values(OAuth2CredentialsMethod)), + grantType: Joi.string() + .required() + .valid(...Object.values(OAuth2GrantType)), } const insertSchema = Joi.object({ diff --git a/packages/types/src/api/web/app/oauth2.ts b/packages/types/src/api/web/app/oauth2.ts index b6d4d89ea6..7535ddb8b8 100644 --- a/packages/types/src/api/web/app/oauth2.ts +++ b/packages/types/src/api/web/app/oauth2.ts @@ -1,4 +1,4 @@ -import { OAuth2CredentialsMethod } from "@budibase/types" +import { OAuth2CredentialsMethod, OAuth2GrantType } from "@budibase/types" export interface OAuth2ConfigResponse { _id: string @@ -20,6 +20,7 @@ export interface InsertOAuth2ConfigRequest { clientId: string clientSecret: string method: OAuth2CredentialsMethod + grantType: OAuth2GrantType } export interface InsertOAuth2ConfigResponse { @@ -34,6 +35,7 @@ export interface UpdateOAuth2ConfigRequest { clientId: string clientSecret: string method: OAuth2CredentialsMethod + grantType: OAuth2GrantType } export interface UpdateOAuth2ConfigResponse { diff --git a/packages/types/src/documents/app/oauth2.ts b/packages/types/src/documents/app/oauth2.ts index d2ad895529..791fcf2ce2 100644 --- a/packages/types/src/documents/app/oauth2.ts +++ b/packages/types/src/documents/app/oauth2.ts @@ -5,10 +5,15 @@ export enum OAuth2CredentialsMethod { BODY = "BODY", } +export enum OAuth2GrantType { + CLIENT_CREDENTIALS = "client_credentials", +} + export interface OAuth2Config extends Document { name: string url: string clientId: string clientSecret: string method: OAuth2CredentialsMethod + grantType: OAuth2GrantType } From 18973f9bd06886cbbcbac6ddfccaad1cced5d099 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 10:46:34 +0100 Subject: [PATCH 2/9] Return grantType on response --- packages/server/src/api/controllers/oauth2.ts | 1 + packages/types/src/api/web/app/oauth2.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/server/src/api/controllers/oauth2.ts b/packages/server/src/api/controllers/oauth2.ts index 4bfef3cf61..c2deb2cb66 100644 --- a/packages/server/src/api/controllers/oauth2.ts +++ b/packages/server/src/api/controllers/oauth2.ts @@ -24,6 +24,7 @@ function toFetchOAuth2ConfigsResponse( clientId: config.clientId, clientSecret: PASSWORD_REPLACEMENT, method: config.method, + grantType: config.grantType, } } diff --git a/packages/types/src/api/web/app/oauth2.ts b/packages/types/src/api/web/app/oauth2.ts index 7535ddb8b8..43789c8ecc 100644 --- a/packages/types/src/api/web/app/oauth2.ts +++ b/packages/types/src/api/web/app/oauth2.ts @@ -8,6 +8,7 @@ export interface OAuth2ConfigResponse { clientId: string clientSecret: string method: OAuth2CredentialsMethod + grantType: OAuth2GrantType } export interface FetchOAuth2ConfigsResponse { From fc2a339caf2390287017fc52086839d0b851aca3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 10:59:13 +0100 Subject: [PATCH 3/9] Add grant type field --- .../oauth2/OAuth2ConfigModalContent.svelte | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) 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 21df65be12..e7ea8113ff 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 @@ -15,6 +15,7 @@ import type { InsertOAuth2ConfigRequest } from "@budibase/types" import { OAuth2CredentialsMethod, + OAuth2GrantType, PASSWORD_REPLACEMENT, } from "@budibase/types" import type { ZodType } from "zod" @@ -64,6 +65,9 @@ method: z.nativeEnum(OAuth2CredentialsMethod, { message: "Authentication method is required.", }), + grantType: z.nativeEnum(OAuth2GrantType, { + message: "Grant type is required.", + }), }) satisfies ZodType const validationResult = validator.safeParse(config) @@ -158,6 +162,24 @@ access_token property. + + Date: Mon, 24 Mar 2025 10:59:29 +0100 Subject: [PATCH 4/9] Default to Client credentials --- .../settings/oauth2/OAuth2ConfigModalContent.svelte | 2 ++ 1 file changed, 2 insertions(+) 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 e7ea8113ff..7c785c1592 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 @@ -28,6 +28,8 @@ $: data = (config as Partial) ?? {} + $: data.grantType ??= OAuth2GrantType.CLIENT_CREDENTIALS + $: isCreation = !config $: title = isCreation ? "Create new OAuth2 connection" From a21243297f78fdf7945a78461c0dafa05bed1c01 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 11:01:29 +0100 Subject: [PATCH 5/9] Pass grantType to validate --- .../settings/oauth2/OAuth2ConfigModalContent.svelte | 1 + packages/types/src/api/web/app/oauth2.ts | 1 + 2 files changed, 2 insertions(+) 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 7c785c1592..f215b58869 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 @@ -103,6 +103,7 @@ clientId: configData.clientId, clientSecret: configData.clientSecret, method: configData.method, + grantType: configData.grantType, }) if (!connectionValidation.valid) { let message = "Connection settings could not be validated" diff --git a/packages/types/src/api/web/app/oauth2.ts b/packages/types/src/api/web/app/oauth2.ts index 43789c8ecc..5fd476ec32 100644 --- a/packages/types/src/api/web/app/oauth2.ts +++ b/packages/types/src/api/web/app/oauth2.ts @@ -49,6 +49,7 @@ export interface ValidateConfigRequest { clientId: string clientSecret: string method: OAuth2CredentialsMethod + grantType: OAuth2GrantType } export interface ValidateConfigResponse { From 6c32ca181c0ca15805cc4cc3766baa1de022f9b4 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 11:04:05 +0100 Subject: [PATCH 6/9] Use grant type from config --- packages/server/src/api/controllers/oauth2.ts | 1 + packages/server/src/sdk/app/oauth2/utils.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/oauth2.ts b/packages/server/src/api/controllers/oauth2.ts index c2deb2cb66..f3ba4145dd 100644 --- a/packages/server/src/api/controllers/oauth2.ts +++ b/packages/server/src/api/controllers/oauth2.ts @@ -99,6 +99,7 @@ export async function validate( clientId: body.clientId, clientSecret: body.clientSecret, method: body.method, + grantType: body.grantType, } if (config.clientSecret === PASSWORD_REPLACEMENT && body._id) { diff --git a/packages/server/src/sdk/app/oauth2/utils.ts b/packages/server/src/sdk/app/oauth2/utils.ts index baa0620692..2e891e389e 100644 --- a/packages/server/src/sdk/app/oauth2/utils.ts +++ b/packages/server/src/sdk/app/oauth2/utils.ts @@ -1,13 +1,14 @@ import fetch, { RequestInit } from "node-fetch" import { HttpError } from "koa" import { get } from "../oauth2" -import { OAuth2CredentialsMethod } from "@budibase/types" +import { OAuth2CredentialsMethod, OAuth2GrantType } from "@budibase/types" async function fetchToken(config: { url: string clientId: string clientSecret: string method: OAuth2CredentialsMethod + grantType: OAuth2GrantType }) { const fetchConfig: RequestInit = { method: "POST", @@ -30,7 +31,7 @@ async function fetchToken(config: { } } else { fetchConfig.body = new URLSearchParams({ - grant_type: "client_credentials", + grant_type: config.grantType, client_id: config.clientId, client_secret: config.clientSecret, }) @@ -64,6 +65,7 @@ export async function validateConfig(config: { clientId: string clientSecret: string method: OAuth2CredentialsMethod + grantType: OAuth2GrantType }): Promise<{ valid: boolean; message?: string }> { try { const resp = await fetchToken(config) From e70cfef478565df5b73e4d9ed4c65864f6fad079 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 11:09:07 +0100 Subject: [PATCH 7/9] Fix tests --- .../src/api/routes/tests/oauth2.spec.ts | 8 + .../src/sdk/app/oauth2/tests/utils.spec.ts | 140 +++++++++--------- 2 files changed, 82 insertions(+), 66 deletions(-) diff --git a/packages/server/src/api/routes/tests/oauth2.spec.ts b/packages/server/src/api/routes/tests/oauth2.spec.ts index 7acd0cbdb2..ee01acec1d 100644 --- a/packages/server/src/api/routes/tests/oauth2.spec.ts +++ b/packages/server/src/api/routes/tests/oauth2.spec.ts @@ -3,6 +3,7 @@ import { InsertOAuth2ConfigRequest, OAuth2ConfigResponse, OAuth2CredentialsMethod, + OAuth2GrantType, PASSWORD_REPLACEMENT, } from "@budibase/types" import * as setup from "./utilities" @@ -19,6 +20,7 @@ describe("/oauth2", () => { clientId: generator.guid(), clientSecret: generator.hash(), method: generator.pickone(Object.values(OAuth2CredentialsMethod)), + grantType: generator.pickone(Object.values(OAuth2GrantType)), } } @@ -58,6 +60,7 @@ describe("/oauth2", () => { clientId: c.clientId, clientSecret: PASSWORD_REPLACEMENT, method: c.method, + grantType: c.grantType, })) ), }) @@ -80,6 +83,7 @@ describe("/oauth2", () => { clientId: oauth2Config.clientId, clientSecret: PASSWORD_REPLACEMENT, method: oauth2Config.method, + grantType: oauth2Config.grantType, }, ], }) @@ -102,6 +106,7 @@ describe("/oauth2", () => { clientId: oauth2Config.clientId, clientSecret: PASSWORD_REPLACEMENT, method: oauth2Config.method, + grantType: oauth2Config.grantType, }, { _id: expectOAuth2ConfigId, @@ -111,6 +116,7 @@ describe("/oauth2", () => { clientId: oauth2Config2.clientId, clientSecret: PASSWORD_REPLACEMENT, method: oauth2Config2.method, + grantType: oauth2Config2.grantType, }, ]) ) @@ -139,6 +145,7 @@ describe("/oauth2", () => { clientId: oauth2Config.clientId, clientSecret: PASSWORD_REPLACEMENT, method: oauth2Config.method, + grantType: oauth2Config.grantType, }, ]) }) @@ -177,6 +184,7 @@ describe("/oauth2", () => { clientId: configData.clientId, clientSecret: PASSWORD_REPLACEMENT, method: configData.method, + grantType: configData.grantType, }, ]) ) diff --git a/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts b/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts index 6cb33b9836..edce423940 100644 --- a/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts +++ b/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts @@ -6,7 +6,8 @@ import { generateToken } from "../utils" import path from "path" import { KEYCLOAK_IMAGE } from "../../../../integrations/tests/utils/images" import { startContainer } from "../../../../integrations/tests/utils" -import { OAuth2CredentialsMethod } from "@budibase/types" +import { OAuth2CredentialsMethod, OAuth2GrantType } from "@budibase/types" +import { method } from "lodash" const config = new TestConfiguration() @@ -42,77 +43,84 @@ describe("oauth2 utils", () => { keycloakUrl = `http://127.0.0.1:${port}` }) - describe.each(Object.values(OAuth2CredentialsMethod))( - "generateToken (in %s)", - method => { - it("successfully generates tokens", async () => { - const response = await config.doInContext(config.appId, async () => { + describe.each( + Object.values(OAuth2CredentialsMethod).flatMap(method => + Object.values(OAuth2GrantType).map< + [OAuth2CredentialsMethod, OAuth2GrantType] + >(grantType => [method, grantType]) + ) + )("generateToken (in %s, grant type %s)", (method, grantType) => { + it("successfully generates tokens", async () => { + const response = await config.doInContext(config.appId, async () => { + const oauthConfig = await sdk.oauth2.create({ + name: generator.guid(), + url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`, + clientId: "my-client", + clientSecret: "my-secret", + method, + grantType, + }) + + const response = await generateToken(oauthConfig._id) + return response + }) + + expect(response).toEqual(expect.stringMatching(/^Bearer .+/)) + }) + + it("handles wrong urls", async () => { + await expect( + config.doInContext(config.appId, async () => { + const oauthConfig = await sdk.oauth2.create({ + name: generator.guid(), + url: `${keycloakUrl}/realms/wrong/protocol/openid-connect/token`, + clientId: "my-client", + clientSecret: "my-secret", + method, + grantType, + }) + + await generateToken(oauthConfig._id) + }) + ).rejects.toThrow("Error fetching oauth2 token: Not Found") + }) + + it("handles wrong client ids", async () => { + await expect( + config.doInContext(config.appId, async () => { + const oauthConfig = await sdk.oauth2.create({ + name: generator.guid(), + url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`, + clientId: "wrong-client-id", + clientSecret: "my-secret", + method, + grantType, + }) + + await generateToken(oauthConfig._id) + }) + ).rejects.toThrow( + "Error fetching oauth2 token: Invalid client or Invalid client credentials" + ) + }) + + it("handles wrong secrets", async () => { + await expect( + config.doInContext(config.appId, async () => { const oauthConfig = await sdk.oauth2.create({ name: generator.guid(), url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`, clientId: "my-client", - clientSecret: "my-secret", + clientSecret: "wrong-secret", method, + grantType, }) - const response = await generateToken(oauthConfig._id) - return response + await generateToken(oauthConfig._id) }) - - expect(response).toEqual(expect.stringMatching(/^Bearer .+/)) - }) - - it("handles wrong urls", async () => { - await expect( - config.doInContext(config.appId, async () => { - const oauthConfig = await sdk.oauth2.create({ - name: generator.guid(), - url: `${keycloakUrl}/realms/wrong/protocol/openid-connect/token`, - clientId: "my-client", - clientSecret: "my-secret", - method, - }) - - await generateToken(oauthConfig._id) - }) - ).rejects.toThrow("Error fetching oauth2 token: Not Found") - }) - - it("handles wrong client ids", async () => { - await expect( - config.doInContext(config.appId, async () => { - const oauthConfig = await sdk.oauth2.create({ - name: generator.guid(), - url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`, - clientId: "wrong-client-id", - clientSecret: "my-secret", - method, - }) - - await generateToken(oauthConfig._id) - }) - ).rejects.toThrow( - "Error fetching oauth2 token: Invalid client or Invalid client credentials" - ) - }) - - it("handles wrong secrets", async () => { - await expect( - config.doInContext(config.appId, async () => { - const oauthConfig = await sdk.oauth2.create({ - name: generator.guid(), - url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`, - clientId: "my-client", - clientSecret: "wrong-secret", - method, - }) - - await generateToken(oauthConfig._id) - }) - ).rejects.toThrow( - "Error fetching oauth2 token: Invalid client or Invalid client credentials" - ) - }) - } - ) + ).rejects.toThrow( + "Error fetching oauth2 token: Invalid client or Invalid client credentials" + ) + }) + }) }) From ff41c7af1ffbe622bf0dd1e5988badae46794d0c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 11:11:07 +0100 Subject: [PATCH 8/9] Fix rest test --- packages/server/src/integrations/tests/rest.spec.ts | 3 +++ packages/server/src/sdk/app/oauth2/tests/utils.spec.ts | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/integrations/tests/rest.spec.ts b/packages/server/src/integrations/tests/rest.spec.ts index 95f4179bc1..71ff711352 100644 --- a/packages/server/src/integrations/tests/rest.spec.ts +++ b/packages/server/src/integrations/tests/rest.spec.ts @@ -6,6 +6,7 @@ import { BearerRestAuthConfig, BodyType, OAuth2CredentialsMethod, + OAuth2GrantType, RestAuthType, } from "@budibase/types" import { Response } from "node-fetch" @@ -286,6 +287,7 @@ describe("REST Integration", () => { clientId: generator.guid(), clientSecret: secret, method: OAuth2CredentialsMethod.HEADER, + grantType: OAuth2GrantType.CLIENT_CREDENTIALS, }) const token = generator.guid() @@ -323,6 +325,7 @@ describe("REST Integration", () => { clientId: generator.guid(), clientSecret: secret, method: OAuth2CredentialsMethod.BODY, + grantType: OAuth2GrantType.CLIENT_CREDENTIALS, }) const token = generator.guid() diff --git a/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts b/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts index edce423940..53e4e7ffd0 100644 --- a/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts +++ b/packages/server/src/sdk/app/oauth2/tests/utils.spec.ts @@ -7,7 +7,6 @@ import path from "path" import { KEYCLOAK_IMAGE } from "../../../../integrations/tests/utils/images" import { startContainer } from "../../../../integrations/tests/utils" import { OAuth2CredentialsMethod, OAuth2GrantType } from "@budibase/types" -import { method } from "lodash" const config = new TestConfiguration() From fd577d3b968eb9537730e84e0a99a299a479a731 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 24 Mar 2025 11:29:27 +0100 Subject: [PATCH 9/9] Fix build --- packages/builder/src/stores/builder/oauth2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/builder/src/stores/builder/oauth2.ts b/packages/builder/src/stores/builder/oauth2.ts index 127df995dd..f429dd109f 100644 --- a/packages/builder/src/stores/builder/oauth2.ts +++ b/packages/builder/src/stores/builder/oauth2.ts @@ -38,6 +38,7 @@ export class OAuth2Store extends BudiStore { clientId: c.clientId, clientSecret: c.clientSecret, method: c.method, + grantType: c.grantType, })), loading: false, }))