Merge pull request #15793 from Budibase/BUDI-9127/display-grant-type
Display grant type field
This commit is contained in:
commit
234d23d4f4
|
@ -15,6 +15,7 @@
|
||||||
import type { InsertOAuth2ConfigRequest } from "@budibase/types"
|
import type { InsertOAuth2ConfigRequest } from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
OAuth2CredentialsMethod,
|
OAuth2CredentialsMethod,
|
||||||
|
OAuth2GrantType,
|
||||||
PASSWORD_REPLACEMENT,
|
PASSWORD_REPLACEMENT,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import type { ZodType } from "zod"
|
import type { ZodType } from "zod"
|
||||||
|
@ -27,6 +28,8 @@
|
||||||
|
|
||||||
$: data = (config as Partial<OAuth2Config>) ?? {}
|
$: data = (config as Partial<OAuth2Config>) ?? {}
|
||||||
|
|
||||||
|
$: data.grantType ??= OAuth2GrantType.CLIENT_CREDENTIALS
|
||||||
|
|
||||||
$: isCreation = !config
|
$: isCreation = !config
|
||||||
$: title = isCreation
|
$: title = isCreation
|
||||||
? "Create new OAuth2 connection"
|
? "Create new OAuth2 connection"
|
||||||
|
@ -64,6 +67,9 @@
|
||||||
method: z.nativeEnum(OAuth2CredentialsMethod, {
|
method: z.nativeEnum(OAuth2CredentialsMethod, {
|
||||||
message: "Authentication method is required.",
|
message: "Authentication method is required.",
|
||||||
}),
|
}),
|
||||||
|
grantType: z.nativeEnum(OAuth2GrantType, {
|
||||||
|
message: "Grant type is required.",
|
||||||
|
}),
|
||||||
}) satisfies ZodType<InsertOAuth2ConfigRequest>
|
}) satisfies ZodType<InsertOAuth2ConfigRequest>
|
||||||
|
|
||||||
const validationResult = validator.safeParse(config)
|
const validationResult = validator.safeParse(config)
|
||||||
|
@ -97,6 +103,7 @@
|
||||||
clientId: configData.clientId,
|
clientId: configData.clientId,
|
||||||
clientSecret: configData.clientSecret,
|
clientSecret: configData.clientSecret,
|
||||||
method: configData.method,
|
method: configData.method,
|
||||||
|
grantType: configData.grantType,
|
||||||
})
|
})
|
||||||
if (!connectionValidation.valid) {
|
if (!connectionValidation.valid) {
|
||||||
let message = "Connection settings could not be validated"
|
let message = "Connection settings could not be validated"
|
||||||
|
@ -158,6 +165,24 @@
|
||||||
access_token property.
|
access_token property.
|
||||||
</Body>
|
</Body>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="Grant type*"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: "Client credentials",
|
||||||
|
value: OAuth2GrantType.CLIENT_CREDENTIALS,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
bind:value={data.grantType}
|
||||||
|
error={errors.grantType}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<div class="field-info">
|
||||||
|
<Body size="XS" color="var(--spectrum-global-color-gray-700)">
|
||||||
|
Only client credentials mode is supported currently.
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
<Input
|
<Input
|
||||||
label="Service URL*"
|
label="Service URL*"
|
||||||
placeholder="E.g. www.google.com"
|
placeholder="E.g. www.google.com"
|
||||||
|
|
|
@ -38,6 +38,7 @@ export class OAuth2Store extends BudiStore<OAuth2StoreState> {
|
||||||
clientId: c.clientId,
|
clientId: c.clientId,
|
||||||
clientSecret: c.clientSecret,
|
clientSecret: c.clientSecret,
|
||||||
method: c.method,
|
method: c.method,
|
||||||
|
grantType: c.grantType,
|
||||||
lastUsage: c.lastUsage,
|
lastUsage: c.lastUsage,
|
||||||
})),
|
})),
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
|
@ -24,6 +24,7 @@ function toFetchOAuth2ConfigsResponse(
|
||||||
clientId: config.clientId,
|
clientId: config.clientId,
|
||||||
clientSecret: PASSWORD_REPLACEMENT,
|
clientSecret: PASSWORD_REPLACEMENT,
|
||||||
method: config.method,
|
method: config.method,
|
||||||
|
grantType: config.grantType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ export async function create(
|
||||||
clientId: body.clientId,
|
clientId: body.clientId,
|
||||||
clientSecret: body.clientSecret,
|
clientSecret: body.clientSecret,
|
||||||
method: body.method,
|
method: body.method,
|
||||||
|
grantType: body.grantType,
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await sdk.oauth2.create(newConfig)
|
const config = await sdk.oauth2.create(newConfig)
|
||||||
|
@ -80,6 +82,7 @@ export async function edit(
|
||||||
clientId: body.clientId,
|
clientId: body.clientId,
|
||||||
clientSecret: body.clientSecret,
|
clientSecret: body.clientSecret,
|
||||||
method: body.method,
|
method: body.method,
|
||||||
|
grantType: body.grantType,
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await sdk.oauth2.update(toUpdate)
|
const config = await sdk.oauth2.update(toUpdate)
|
||||||
|
@ -104,6 +107,7 @@ export async function validate(
|
||||||
clientId: body.clientId,
|
clientId: body.clientId,
|
||||||
clientSecret: body.clientSecret,
|
clientSecret: body.clientSecret,
|
||||||
method: body.method,
|
method: body.method,
|
||||||
|
grantType: body.grantType,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.clientSecret === PASSWORD_REPLACEMENT && body._id) {
|
if (config.clientSecret === PASSWORD_REPLACEMENT && body._id) {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import Router from "@koa/router"
|
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 { middleware } from "@budibase/backend-core"
|
||||||
import authorized from "../../middleware/authorized"
|
import authorized from "../../middleware/authorized"
|
||||||
|
|
||||||
|
@ -13,6 +17,9 @@ const baseSchema = {
|
||||||
method: Joi.string()
|
method: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.valid(...Object.values(OAuth2CredentialsMethod)),
|
.valid(...Object.values(OAuth2CredentialsMethod)),
|
||||||
|
grantType: Joi.string()
|
||||||
|
.required()
|
||||||
|
.valid(...Object.values(OAuth2GrantType)),
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertSchema = Joi.object({
|
const insertSchema = Joi.object({
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
InsertOAuth2ConfigRequest,
|
InsertOAuth2ConfigRequest,
|
||||||
OAuth2ConfigResponse,
|
OAuth2ConfigResponse,
|
||||||
OAuth2CredentialsMethod,
|
OAuth2CredentialsMethod,
|
||||||
|
OAuth2GrantType,
|
||||||
PASSWORD_REPLACEMENT,
|
PASSWORD_REPLACEMENT,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
|
@ -19,6 +20,7 @@ describe("/oauth2", () => {
|
||||||
clientId: generator.guid(),
|
clientId: generator.guid(),
|
||||||
clientSecret: generator.hash(),
|
clientSecret: generator.hash(),
|
||||||
method: generator.pickone(Object.values(OAuth2CredentialsMethod)),
|
method: generator.pickone(Object.values(OAuth2CredentialsMethod)),
|
||||||
|
grantType: generator.pickone(Object.values(OAuth2GrantType)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +60,7 @@ describe("/oauth2", () => {
|
||||||
clientId: c.clientId,
|
clientId: c.clientId,
|
||||||
clientSecret: PASSWORD_REPLACEMENT,
|
clientSecret: PASSWORD_REPLACEMENT,
|
||||||
method: c.method,
|
method: c.method,
|
||||||
|
grantType: c.grantType,
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
@ -80,6 +83,7 @@ describe("/oauth2", () => {
|
||||||
clientId: oauth2Config.clientId,
|
clientId: oauth2Config.clientId,
|
||||||
clientSecret: PASSWORD_REPLACEMENT,
|
clientSecret: PASSWORD_REPLACEMENT,
|
||||||
method: oauth2Config.method,
|
method: oauth2Config.method,
|
||||||
|
grantType: oauth2Config.grantType,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -102,6 +106,7 @@ describe("/oauth2", () => {
|
||||||
clientId: oauth2Config.clientId,
|
clientId: oauth2Config.clientId,
|
||||||
clientSecret: PASSWORD_REPLACEMENT,
|
clientSecret: PASSWORD_REPLACEMENT,
|
||||||
method: oauth2Config.method,
|
method: oauth2Config.method,
|
||||||
|
grantType: oauth2Config.grantType,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: expectOAuth2ConfigId,
|
_id: expectOAuth2ConfigId,
|
||||||
|
@ -111,6 +116,7 @@ describe("/oauth2", () => {
|
||||||
clientId: oauth2Config2.clientId,
|
clientId: oauth2Config2.clientId,
|
||||||
clientSecret: PASSWORD_REPLACEMENT,
|
clientSecret: PASSWORD_REPLACEMENT,
|
||||||
method: oauth2Config2.method,
|
method: oauth2Config2.method,
|
||||||
|
grantType: oauth2Config2.grantType,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
@ -139,6 +145,7 @@ describe("/oauth2", () => {
|
||||||
clientId: oauth2Config.clientId,
|
clientId: oauth2Config.clientId,
|
||||||
clientSecret: PASSWORD_REPLACEMENT,
|
clientSecret: PASSWORD_REPLACEMENT,
|
||||||
method: oauth2Config.method,
|
method: oauth2Config.method,
|
||||||
|
grantType: oauth2Config.grantType,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
@ -177,6 +184,7 @@ describe("/oauth2", () => {
|
||||||
clientId: configData.clientId,
|
clientId: configData.clientId,
|
||||||
clientSecret: PASSWORD_REPLACEMENT,
|
clientSecret: PASSWORD_REPLACEMENT,
|
||||||
method: configData.method,
|
method: configData.method,
|
||||||
|
grantType: configData.grantType,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
BearerRestAuthConfig,
|
BearerRestAuthConfig,
|
||||||
BodyType,
|
BodyType,
|
||||||
OAuth2CredentialsMethod,
|
OAuth2CredentialsMethod,
|
||||||
|
OAuth2GrantType,
|
||||||
RestAuthType,
|
RestAuthType,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { Response } from "node-fetch"
|
import { Response } from "node-fetch"
|
||||||
|
@ -286,6 +287,7 @@ describe("REST Integration", () => {
|
||||||
clientId: generator.guid(),
|
clientId: generator.guid(),
|
||||||
clientSecret: secret,
|
clientSecret: secret,
|
||||||
method: OAuth2CredentialsMethod.HEADER,
|
method: OAuth2CredentialsMethod.HEADER,
|
||||||
|
grantType: OAuth2GrantType.CLIENT_CREDENTIALS,
|
||||||
})
|
})
|
||||||
|
|
||||||
const token = generator.guid()
|
const token = generator.guid()
|
||||||
|
@ -323,6 +325,7 @@ describe("REST Integration", () => {
|
||||||
clientId: generator.guid(),
|
clientId: generator.guid(),
|
||||||
clientSecret: secret,
|
clientSecret: secret,
|
||||||
method: OAuth2CredentialsMethod.BODY,
|
method: OAuth2CredentialsMethod.BODY,
|
||||||
|
grantType: OAuth2GrantType.CLIENT_CREDENTIALS,
|
||||||
})
|
})
|
||||||
|
|
||||||
const token = generator.guid()
|
const token = generator.guid()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { getToken } from "../utils"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { KEYCLOAK_IMAGE } from "../../../../integrations/tests/utils/images"
|
import { KEYCLOAK_IMAGE } from "../../../../integrations/tests/utils/images"
|
||||||
import { startContainer } from "../../../../integrations/tests/utils"
|
import { startContainer } from "../../../../integrations/tests/utils"
|
||||||
import { OAuth2CredentialsMethod } from "@budibase/types"
|
import { OAuth2CredentialsMethod, OAuth2GrantType } from "@budibase/types"
|
||||||
import { cache } from "@budibase/backend-core"
|
import { cache } from "@budibase/backend-core"
|
||||||
import tk from "timekeeper"
|
import tk from "timekeeper"
|
||||||
|
|
||||||
|
@ -44,169 +44,175 @@ describe("oauth2 utils", () => {
|
||||||
keycloakUrl = `http://127.0.0.1:${port}`
|
keycloakUrl = `http://127.0.0.1:${port}`
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.each(Object.values(OAuth2CredentialsMethod))(
|
describe.each(
|
||||||
"getToken (in %s)",
|
Object.values(OAuth2CredentialsMethod).flatMap(method =>
|
||||||
method => {
|
Object.values(OAuth2GrantType).map<
|
||||||
it("successfully generates tokens", async () => {
|
[OAuth2CredentialsMethod, OAuth2GrantType]
|
||||||
const response = await config.doInContext(config.appId, async () => {
|
>(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 getToken(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({
|
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 getToken(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 getToken(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,
|
||||||
|
grantType,
|
||||||
|
})
|
||||||
|
|
||||||
|
await getToken(oauthConfig._id)
|
||||||
|
})
|
||||||
|
).rejects.toThrow(
|
||||||
|
"Error fetching oauth2 token: Invalid client or Invalid client credentials"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("track usages", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
tk.freeze(Date.now())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("tracks usages on generation", async () => {
|
||||||
|
const oauthConfig = await config.doInContext(config.appId, () =>
|
||||||
|
sdk.oauth2.create({
|
||||||
name: generator.guid(),
|
name: generator.guid(),
|
||||||
url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`,
|
url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`,
|
||||||
clientId: "my-client",
|
clientId: "my-client",
|
||||||
clientSecret: "my-secret",
|
clientSecret: "my-secret",
|
||||||
method,
|
method,
|
||||||
|
grantType,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await getToken(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,
|
|
||||||
})
|
|
||||||
|
|
||||||
await getToken(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 getToken(oauthConfig._id)
|
|
||||||
})
|
|
||||||
).rejects.toThrow(
|
|
||||||
"Error fetching oauth2 token: Invalid client or Invalid client credentials"
|
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
it("handles wrong secrets", async () => {
|
await config.doInContext(config.appId, () => getToken(oauthConfig._id))
|
||||||
await expect(
|
await testUtils.queue.processMessages(
|
||||||
config.doInContext(config.appId, async () => {
|
cache.docWritethrough.DocWritethroughProcessor.queue
|
||||||
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 getToken(oauthConfig._id)
|
|
||||||
})
|
|
||||||
).rejects.toThrow(
|
|
||||||
"Error fetching oauth2 token: Invalid client or Invalid client credentials"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const usageLog = await config.doInContext(config.appId, () =>
|
||||||
|
sdk.oauth2.getLastUsages([oauthConfig._id])
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(usageLog[oauthConfig._id]).toEqual(Date.now())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("track usages", () => {
|
it("does not track on failed usages", async () => {
|
||||||
beforeAll(() => {
|
const oauthConfig = await config.doInContext(config.appId, () =>
|
||||||
tk.freeze(Date.now())
|
sdk.oauth2.create({
|
||||||
})
|
name: generator.guid(),
|
||||||
|
url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`,
|
||||||
|
clientId: "wrong-client",
|
||||||
|
clientSecret: "my-secret",
|
||||||
|
method,
|
||||||
|
grantType,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
it("tracks usages on generation", async () => {
|
await expect(
|
||||||
const oauthConfig = await config.doInContext(config.appId, () =>
|
config.doInContext(config.appId, () => getToken(oauthConfig._id))
|
||||||
sdk.oauth2.create({
|
).rejects.toThrow()
|
||||||
name: generator.guid(),
|
await testUtils.queue.processMessages(
|
||||||
url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`,
|
cache.docWritethrough.DocWritethroughProcessor.queue
|
||||||
clientId: "my-client",
|
)
|
||||||
clientSecret: "my-secret",
|
|
||||||
method,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
await config.doInContext(config.appId, () =>
|
const usageLog = await config.doInContext(config.appId, () =>
|
||||||
getToken(oauthConfig._id)
|
sdk.oauth2.getLastUsages([oauthConfig._id])
|
||||||
)
|
)
|
||||||
await testUtils.queue.processMessages(
|
|
||||||
cache.docWritethrough.DocWritethroughProcessor.queue
|
|
||||||
)
|
|
||||||
|
|
||||||
const usageLog = await config.doInContext(config.appId, () =>
|
expect(usageLog[oauthConfig._id]).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("tracks usages between prod and dev, keeping always the latest", async () => {
|
||||||
|
const oauthConfig = await config.doInContext(config.appId, () =>
|
||||||
|
sdk.oauth2.create({
|
||||||
|
name: generator.guid(),
|
||||||
|
url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`,
|
||||||
|
clientId: "my-client",
|
||||||
|
clientSecret: "my-secret",
|
||||||
|
method,
|
||||||
|
grantType,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
await config.doInContext(config.appId, () => getToken(oauthConfig._id))
|
||||||
|
|
||||||
|
await config.publish()
|
||||||
|
|
||||||
|
tk.travel(Date.now() + 100)
|
||||||
|
await config.doInContext(config.prodAppId, () =>
|
||||||
|
getToken(oauthConfig._id)
|
||||||
|
)
|
||||||
|
await testUtils.queue.processMessages(
|
||||||
|
cache.docWritethrough.DocWritethroughProcessor.queue
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const appId of [config.appId, config.prodAppId]) {
|
||||||
|
const usageLog = await config.doInContext(appId, () =>
|
||||||
sdk.oauth2.getLastUsages([oauthConfig._id])
|
sdk.oauth2.getLastUsages([oauthConfig._id])
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(usageLog[oauthConfig._id]).toEqual(Date.now())
|
expect(usageLog).toEqual({
|
||||||
})
|
[oauthConfig._id]: Date.now(),
|
||||||
|
})
|
||||||
it("does not track on failed usages", async () => {
|
}
|
||||||
const oauthConfig = await config.doInContext(config.appId, () =>
|
|
||||||
sdk.oauth2.create({
|
|
||||||
name: generator.guid(),
|
|
||||||
url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`,
|
|
||||||
clientId: "wrong-client",
|
|
||||||
clientSecret: "my-secret",
|
|
||||||
method,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
config.doInContext(config.appId, () => getToken(oauthConfig._id))
|
|
||||||
).rejects.toThrow()
|
|
||||||
await testUtils.queue.processMessages(
|
|
||||||
cache.docWritethrough.DocWritethroughProcessor.queue
|
|
||||||
)
|
|
||||||
|
|
||||||
const usageLog = await config.doInContext(config.appId, () =>
|
|
||||||
sdk.oauth2.getLastUsages([oauthConfig._id])
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(usageLog[oauthConfig._id]).toBeUndefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("tracks usages between prod and dev, keeping always the latest", async () => {
|
|
||||||
const oauthConfig = await config.doInContext(config.appId, () =>
|
|
||||||
sdk.oauth2.create({
|
|
||||||
name: generator.guid(),
|
|
||||||
url: `${keycloakUrl}/realms/myrealm/protocol/openid-connect/token`,
|
|
||||||
clientId: "my-client",
|
|
||||||
clientSecret: "my-secret",
|
|
||||||
method,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
await config.doInContext(config.appId, () =>
|
|
||||||
getToken(oauthConfig._id)
|
|
||||||
)
|
|
||||||
|
|
||||||
await config.publish()
|
|
||||||
|
|
||||||
tk.travel(Date.now() + 100)
|
|
||||||
await config.doInContext(config.prodAppId, () =>
|
|
||||||
getToken(oauthConfig._id)
|
|
||||||
)
|
|
||||||
await testUtils.queue.processMessages(
|
|
||||||
cache.docWritethrough.DocWritethroughProcessor.queue
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const appId of [config.appId, config.prodAppId]) {
|
|
||||||
const usageLog = await config.doInContext(appId, () =>
|
|
||||||
sdk.oauth2.getLastUsages([oauthConfig._id])
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(usageLog).toEqual({
|
|
||||||
[oauthConfig._id]: Date.now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import fetch, { RequestInit } from "node-fetch"
|
import fetch, { RequestInit } from "node-fetch"
|
||||||
import { HttpError } from "koa"
|
import { HttpError } from "koa"
|
||||||
import { get } from "../oauth2"
|
import { get } from "../oauth2"
|
||||||
import { Document, OAuth2CredentialsMethod } from "@budibase/types"
|
import {
|
||||||
|
Document,
|
||||||
|
OAuth2CredentialsMethod,
|
||||||
|
OAuth2GrantType,
|
||||||
|
} from "@budibase/types"
|
||||||
import { cache, context, docIds } from "@budibase/backend-core"
|
import { cache, context, docIds } from "@budibase/backend-core"
|
||||||
|
|
||||||
interface OAuth2LogDocument extends Document {
|
interface OAuth2LogDocument extends Document {
|
||||||
|
@ -15,6 +19,7 @@ async function fetchToken(config: {
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
method: OAuth2CredentialsMethod
|
method: OAuth2CredentialsMethod
|
||||||
|
grantType: OAuth2GrantType
|
||||||
}) {
|
}) {
|
||||||
const fetchConfig: RequestInit = {
|
const fetchConfig: RequestInit = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -37,7 +42,7 @@ async function fetchToken(config: {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchConfig.body = new URLSearchParams({
|
fetchConfig.body = new URLSearchParams({
|
||||||
grant_type: "client_credentials",
|
grant_type: config.grantType,
|
||||||
client_id: config.clientId,
|
client_id: config.clientId,
|
||||||
client_secret: config.clientSecret,
|
client_secret: config.clientSecret,
|
||||||
})
|
})
|
||||||
|
@ -82,6 +87,7 @@ export async function validateConfig(config: {
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
method: OAuth2CredentialsMethod
|
method: OAuth2CredentialsMethod
|
||||||
|
grantType: OAuth2GrantType
|
||||||
}): Promise<{ valid: boolean; message?: string }> {
|
}): Promise<{ valid: boolean; message?: string }> {
|
||||||
try {
|
try {
|
||||||
const resp = await fetchToken(config)
|
const resp = await fetchToken(config)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { OAuth2CredentialsMethod } from "@budibase/types"
|
import { OAuth2CredentialsMethod, OAuth2GrantType } from "@budibase/types"
|
||||||
|
|
||||||
export interface OAuth2ConfigResponse {
|
export interface OAuth2ConfigResponse {
|
||||||
_id: string
|
_id: string
|
||||||
|
@ -8,6 +8,7 @@ export interface OAuth2ConfigResponse {
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
method: OAuth2CredentialsMethod
|
method: OAuth2CredentialsMethod
|
||||||
|
grantType: OAuth2GrantType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FetchOAuth2ConfigsResponse {
|
export interface FetchOAuth2ConfigsResponse {
|
||||||
|
@ -20,6 +21,7 @@ export interface InsertOAuth2ConfigRequest {
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
method: OAuth2CredentialsMethod
|
method: OAuth2CredentialsMethod
|
||||||
|
grantType: OAuth2GrantType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InsertOAuth2ConfigResponse {
|
export interface InsertOAuth2ConfigResponse {
|
||||||
|
@ -34,6 +36,7 @@ export interface UpdateOAuth2ConfigRequest {
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
method: OAuth2CredentialsMethod
|
method: OAuth2CredentialsMethod
|
||||||
|
grantType: OAuth2GrantType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateOAuth2ConfigResponse {
|
export interface UpdateOAuth2ConfigResponse {
|
||||||
|
@ -46,6 +49,7 @@ export interface ValidateConfigRequest {
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
method: OAuth2CredentialsMethod
|
method: OAuth2CredentialsMethod
|
||||||
|
grantType: OAuth2GrantType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ValidateConfigResponse {
|
export interface ValidateConfigResponse {
|
||||||
|
|
|
@ -5,10 +5,15 @@ export enum OAuth2CredentialsMethod {
|
||||||
BODY = "BODY",
|
BODY = "BODY",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OAuth2GrantType {
|
||||||
|
CLIENT_CREDENTIALS = "client_credentials",
|
||||||
|
}
|
||||||
|
|
||||||
export interface OAuth2Config extends Document {
|
export interface OAuth2Config extends Document {
|
||||||
name: string
|
name: string
|
||||||
url: string
|
url: string
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
method: OAuth2CredentialsMethod
|
method: OAuth2CredentialsMethod
|
||||||
|
grantType: OAuth2GrantType
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue