Add copy button to sso callback urls, e2e unit testing for OIDC, stub out other auth tests
This commit is contained in:
parent
e57c115f86
commit
9c169087e6
|
@ -16,8 +16,7 @@
|
|||
"dist",
|
||||
"public",
|
||||
"*.spec.js",
|
||||
"bundle.js",
|
||||
"coverage"
|
||||
"bundle.js"
|
||||
],
|
||||
"plugins": ["svelte3"],
|
||||
"extends": ["eslint:recommended"],
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
jest.mock("node-fetch", () => jest.fn())
|
|
@ -56,7 +56,7 @@
|
|||
"@types/chance": "1.1.3",
|
||||
"@types/ioredis": "4.28.0",
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/koa": "2.0.52",
|
||||
"@types/koa": "2.13.4",
|
||||
"@types/lodash": "4.14.180",
|
||||
"@types/node": "14.18.20",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
|
@ -68,7 +68,7 @@
|
|||
"chance": "1.1.3",
|
||||
"ioredis-mock": "5.8.0",
|
||||
"jest": "28.1.1",
|
||||
"koa": "2.7.0",
|
||||
"koa": "2.13.4",
|
||||
"nodemon": "2.0.16",
|
||||
"pouchdb-adapter-memory": "7.2.2",
|
||||
"timekeeper": "2.2.0",
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
import * as matchers from "../matchers"
|
||||
import { structures } from "../../../tests"
|
||||
|
||||
describe("matchers", () => {
|
||||
it("matches by path and method", () => {
|
||||
const pattern = [
|
||||
{
|
||||
route: "/api/tests",
|
||||
method: "POST",
|
||||
},
|
||||
]
|
||||
const ctx = structures.koa.newContext()
|
||||
ctx.request.url = "/api/tests"
|
||||
ctx.request.method = "POST"
|
||||
|
||||
const built = matchers.buildMatcherRegex(pattern)
|
||||
|
||||
expect(!!matchers.matches(ctx, built)).toBe(true)
|
||||
})
|
||||
|
||||
it("wildcards path", () => {
|
||||
const pattern = [
|
||||
{
|
||||
route: "/api/tests",
|
||||
method: "POST",
|
||||
},
|
||||
]
|
||||
const ctx = structures.koa.newContext()
|
||||
ctx.request.url = "/api/tests/id/something/else"
|
||||
ctx.request.method = "POST"
|
||||
|
||||
const built = matchers.buildMatcherRegex(pattern)
|
||||
|
||||
expect(!!matchers.matches(ctx, built)).toBe(true)
|
||||
})
|
||||
|
||||
it("doesn't wildcard path with strict", () => {
|
||||
const pattern = [
|
||||
{
|
||||
route: "/api/tests",
|
||||
method: "POST",
|
||||
strict: true,
|
||||
},
|
||||
]
|
||||
const ctx = structures.koa.newContext()
|
||||
ctx.request.url = "/api/tests/id/something/else"
|
||||
ctx.request.method = "POST"
|
||||
|
||||
const built = matchers.buildMatcherRegex(pattern)
|
||||
|
||||
expect(!!matchers.matches(ctx, built)).toBe(false)
|
||||
})
|
||||
|
||||
it("matches with param", () => {
|
||||
const pattern = [
|
||||
{
|
||||
route: "/api/tests/:testId",
|
||||
method: "GET",
|
||||
},
|
||||
]
|
||||
const ctx = structures.koa.newContext()
|
||||
ctx.request.url = "/api/tests/id"
|
||||
ctx.request.method = "GET"
|
||||
|
||||
const built = matchers.buildMatcherRegex(pattern)
|
||||
|
||||
expect(!!matchers.matches(ctx, built)).toBe(true)
|
||||
})
|
||||
|
||||
// TODO: Support the below behaviour
|
||||
// Strict does not work when a param is present
|
||||
// it("matches with param with strict", () => {
|
||||
// const pattern = [{
|
||||
// route: "/api/tests/:testId",
|
||||
// method: "GET",
|
||||
// strict: true
|
||||
// }]
|
||||
// const ctx = structures.koa.newContext()
|
||||
// ctx.request.url = "/api/tests/id"
|
||||
// ctx.request.method = "GET"
|
||||
//
|
||||
// const built = matchers.buildMatcherRegex(pattern)
|
||||
//
|
||||
// expect(!!matchers.matches(ctx, built)).toBe(true)
|
||||
// })
|
||||
|
||||
it("doesn't match by path", () => {
|
||||
const pattern = [
|
||||
{
|
||||
route: "/api/tests",
|
||||
method: "POST",
|
||||
},
|
||||
]
|
||||
const ctx = structures.koa.newContext()
|
||||
ctx.request.url = "/api/unknown"
|
||||
ctx.request.method = "POST"
|
||||
|
||||
const built = matchers.buildMatcherRegex(pattern)
|
||||
|
||||
expect(!!matchers.matches(ctx, built)).toBe(false)
|
||||
})
|
||||
|
||||
it("doesn't match by method", () => {
|
||||
const pattern = [
|
||||
{
|
||||
route: "/api/tests",
|
||||
method: "POST",
|
||||
},
|
||||
]
|
||||
const ctx = structures.koa.newContext()
|
||||
ctx.request.url = "/api/tests"
|
||||
ctx.request.method = "GET"
|
||||
|
||||
const built = matchers.buildMatcherRegex(pattern)
|
||||
|
||||
expect(!!matchers.matches(ctx, built)).toBe(false)
|
||||
})
|
||||
|
||||
it("matches by path and wildcard method", () => {
|
||||
const pattern = [
|
||||
{
|
||||
route: "/api/tests",
|
||||
method: "ALL",
|
||||
},
|
||||
]
|
||||
const ctx = structures.koa.newContext()
|
||||
ctx.request.url = "/api/tests"
|
||||
ctx.request.method = "GET"
|
||||
|
||||
const built = matchers.buildMatcherRegex(pattern)
|
||||
|
||||
expect(!!matchers.matches(ctx, built)).toBe(true)
|
||||
})
|
||||
})
|
|
@ -231,12 +231,18 @@ export const getTenantIDFromCtx = (
|
|||
const match = ctx.matched.find(
|
||||
(m: any) => !!m.paramNames.find((p: any) => p.name === "tenantId")
|
||||
)
|
||||
|
||||
// get the raw path url - without any query params
|
||||
const ctxUrl = ctx.originalUrl
|
||||
let url
|
||||
if (ctxUrl.includes("?")) {
|
||||
url = ctxUrl.split("?")[0]
|
||||
} else {
|
||||
url = ctxUrl
|
||||
}
|
||||
|
||||
if (match) {
|
||||
const params = match.params(
|
||||
ctx.originalUrl,
|
||||
match.captures(ctx.originalUrl),
|
||||
{}
|
||||
)
|
||||
const params = match.params(url, match.captures(url), {})
|
||||
if (params.tenantId) {
|
||||
return params.tenantId
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ async function resolveAppUrl(ctx: BBContext) {
|
|||
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
||||
|
||||
let tenantId = tenancy.getTenantId()
|
||||
if (!env.SELF_HOSTED) {
|
||||
// always use the tenant id from the subdomain in cloud
|
||||
if (env.MULTI_TENANCY) {
|
||||
// always use the tenant id from the subdomain in multi tenancy
|
||||
// this ensures the logged-in user tenant id doesn't overwrite
|
||||
// e.g. in the case of viewing a public app while already logged-in to another tenant
|
||||
tenantId = tenancy.getTenantIDFromCtx(ctx, {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
const mockFetch = jest.fn()
|
||||
jest.mock("node-fetch", () => mockFetch)
|
||||
|
||||
export default mockFetch
|
|
@ -2,3 +2,4 @@ import "./posthog"
|
|||
import "./events"
|
||||
export * as accounts from "./accounts"
|
||||
export * as date from "./date"
|
||||
export { default as fetch } from "./fetch"
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { createMockContext } from "@shopify/jest-koa-mocks"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
export const newContext = () => {
|
||||
return createMockContext()
|
||||
export const newContext = (): BBContext => {
|
||||
const ctx = createMockContext()
|
||||
return {
|
||||
...ctx,
|
||||
request: {
|
||||
...ctx.request,
|
||||
body: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1079,7 +1079,7 @@
|
|||
dependencies:
|
||||
"@types/koa" "*"
|
||||
|
||||
"@types/koa@*":
|
||||
"@types/koa@*", "@types/koa@2.13.4":
|
||||
version "2.13.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
|
||||
integrity sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==
|
||||
|
@ -1093,18 +1093,6 @@
|
|||
"@types/koa-compose" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/koa@2.0.52":
|
||||
version "2.0.52"
|
||||
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.0.52.tgz#7dd11de4189ab339ad66c4ccad153716b14e525f"
|
||||
integrity sha512-cp/GTOhOYwomlSKqEoG0kaVEVJEzP4ojYmfa7EKaGkmkkRwJ4B/1VBLbQZ49Z+WJNvzXejQB/9GIKqMo9XLgFQ==
|
||||
dependencies:
|
||||
"@types/accepts" "*"
|
||||
"@types/cookies" "*"
|
||||
"@types/http-assert" "*"
|
||||
"@types/keygrip" "*"
|
||||
"@types/koa-compose" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/lodash@4.14.180":
|
||||
version "4.14.180"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
|
||||
|
@ -1478,11 +1466,6 @@ ansi-styles@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
any-promise@^1.1.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
|
||||
|
||||
anymatch@^3.0.3, anymatch@~3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
|
@ -2056,14 +2039,6 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
|||
dependencies:
|
||||
safe-buffer "~5.1.1"
|
||||
|
||||
cookies@~0.7.1:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.3.tgz#7912ce21fbf2e8c2da70cf1c3f351aecf59dadfa"
|
||||
integrity sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
keygrip "~1.0.3"
|
||||
|
||||
cookies@~0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
|
||||
|
@ -2134,13 +2109,6 @@ debug@^3.2.7:
|
|||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@~3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debuglog@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
||||
|
@ -2201,7 +2169,7 @@ denque@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
||||
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
||||
|
||||
depd@^1.1.0, depd@^1.1.2, depd@~1.1.2:
|
||||
depd@^1.1.0, depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
|
||||
|
@ -2353,11 +2321,6 @@ error-ex@^1.3.1:
|
|||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
error-inject@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
|
||||
integrity sha512-JM8N6PytDbmIYm1IhPWlo8vr3NtfjhDY/1MhD/a5b/aad/USE8a0+NsqE9d5n+GVGmuNkPQWm4bFQWv18d8tMg==
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
|
@ -3602,11 +3565,6 @@ jws@^3.0.0, jws@^3.1.4, jws@^3.2.2:
|
|||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
keygrip@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc"
|
||||
integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==
|
||||
|
||||
keygrip@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
|
||||
|
@ -3626,26 +3584,11 @@ kleur@^3.0.3:
|
|||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
koa-compose@^3.0.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7"
|
||||
integrity sha512-8gen2cvKHIZ35eDEik5WOo8zbVp9t4cP8p4hW4uE55waxolLRexKKrqfCpwhGVppnB40jWeF8bZeTVg99eZgPw==
|
||||
dependencies:
|
||||
any-promise "^1.1.0"
|
||||
|
||||
koa-compose@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
|
||||
integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
|
||||
|
||||
koa-convert@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0"
|
||||
integrity sha512-K9XqjmEDStGX09v3oxR7t5uPRy0jqJdvodHa6wxWTHrTfDq0WUNnYTOOUZN6g8OM8oZQXprQASbiIXG2Ez8ehA==
|
||||
dependencies:
|
||||
co "^4.6.0"
|
||||
koa-compose "^3.0.0"
|
||||
|
||||
koa-convert@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5"
|
||||
|
@ -3654,11 +3597,6 @@ koa-convert@^2.0.0:
|
|||
co "^4.6.0"
|
||||
koa-compose "^4.1.0"
|
||||
|
||||
koa-is-json@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14"
|
||||
integrity sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw==
|
||||
|
||||
koa-passport@4.1.4:
|
||||
version "4.1.4"
|
||||
resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa"
|
||||
|
@ -3666,37 +3604,7 @@ koa-passport@4.1.4:
|
|||
dependencies:
|
||||
passport "^0.4.0"
|
||||
|
||||
koa@2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/koa/-/koa-2.7.0.tgz#7e00843506942b9d82c6cc33749f657c6e5e7adf"
|
||||
integrity sha512-7ojD05s2Q+hFudF8tDLZ1CpCdVZw8JQELWSkcfG9bdtoTDzMmkRF6BQBU7JzIzCCOY3xd3tftiy/loHBUYaY2Q==
|
||||
dependencies:
|
||||
accepts "^1.3.5"
|
||||
cache-content-type "^1.0.0"
|
||||
content-disposition "~0.5.2"
|
||||
content-type "^1.0.4"
|
||||
cookies "~0.7.1"
|
||||
debug "~3.1.0"
|
||||
delegates "^1.0.0"
|
||||
depd "^1.1.2"
|
||||
destroy "^1.0.4"
|
||||
error-inject "^1.0.0"
|
||||
escape-html "^1.0.3"
|
||||
fresh "~0.5.2"
|
||||
http-assert "^1.3.0"
|
||||
http-errors "^1.6.3"
|
||||
is-generator-function "^1.0.7"
|
||||
koa-compose "^4.1.0"
|
||||
koa-convert "^1.2.0"
|
||||
koa-is-json "^1.0.0"
|
||||
on-finished "^2.3.0"
|
||||
only "~0.0.2"
|
||||
parseurl "^1.3.2"
|
||||
statuses "^1.5.0"
|
||||
type-is "^1.6.16"
|
||||
vary "^1.1.2"
|
||||
|
||||
koa@^2.13.4:
|
||||
koa@2.13.4, koa@^2.13.4:
|
||||
version "2.13.4"
|
||||
resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e"
|
||||
integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==
|
||||
|
@ -4079,11 +3987,6 @@ mkdirp@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
|
|
|
@ -20,11 +20,12 @@
|
|||
Toggle,
|
||||
Tag,
|
||||
Tags,
|
||||
Icon,
|
||||
Helpers,
|
||||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import { API } from "api"
|
||||
import { organisation, admin } from "stores/portal"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
||||
const ConfigTypes = {
|
||||
Google: "google",
|
||||
|
@ -40,7 +41,9 @@
|
|||
|
||||
// Indicate to user that callback is based on platform url
|
||||
// If there is an existing value, indicate that it may be removed to return to default behaviour
|
||||
$: googleCallbackTooltip = googleCallbackReadonly
|
||||
$: googleCallbackTooltip = $admin.cloud
|
||||
? null
|
||||
: googleCallbackReadonly
|
||||
? "Vist the organisation page to update the platform URL"
|
||||
: "Leave blank to use the default callback URL"
|
||||
|
||||
|
@ -54,6 +57,7 @@
|
|||
readonly: googleCallbackReadonly,
|
||||
tooltip: googleCallbackTooltip,
|
||||
placeholder: $organisation.googleCallbackUrl,
|
||||
copyButton: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -66,9 +70,12 @@
|
|||
{
|
||||
name: "callbackURL",
|
||||
readonly: true,
|
||||
tooltip: "Vist the organisation page to update the platform URL",
|
||||
tooltip: $admin.cloud
|
||||
? null
|
||||
: "Vist the organisation page to update the platform URL",
|
||||
label: "Callback URL",
|
||||
placeholder: $organisation.oidcCallbackUrl,
|
||||
copyButton: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -231,6 +238,11 @@
|
|||
},
|
||||
]
|
||||
|
||||
const copyToClipboard = async value => {
|
||||
await Helpers.copyToClipboard(value)
|
||||
notifications.success("Copied")
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await organisation.init()
|
||||
|
@ -336,11 +348,23 @@
|
|||
{#each GoogleConfigFields.Google as field}
|
||||
<div class="form-row">
|
||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||
<Input
|
||||
bind:value={providers.google.config[field.name]}
|
||||
readonly={field.readonly}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
<div class="inputContainer">
|
||||
<div class="input">
|
||||
<Input
|
||||
bind:value={providers.google.config[field.name]}
|
||||
readonly={field.readonly}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
</div>
|
||||
{#if field.copyButton}
|
||||
<div
|
||||
class="copy"
|
||||
on:click={() => copyToClipboard(field.placeholder)}
|
||||
>
|
||||
<Icon size="S" name="Copy" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="form-row">
|
||||
|
@ -375,12 +399,23 @@
|
|||
{#each OIDCConfigFields.Oidc as field}
|
||||
<div class="form-row">
|
||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||
<Input
|
||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||
readonly={field.readonly}
|
||||
placeholder={field.placeholder}
|
||||
dataCy={field.name}
|
||||
/>
|
||||
<div class="inputContainer">
|
||||
<div class="input">
|
||||
<Input
|
||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||
readonly={field.readonly}
|
||||
placeholder={field.placeholder}
|
||||
/>
|
||||
</div>
|
||||
{#if field.copyButton}
|
||||
<div
|
||||
class="copy"
|
||||
on:click={() => copyToClipboard(field.placeholder)}
|
||||
>
|
||||
<Icon size="S" name="Copy" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</Layout>
|
||||
|
@ -557,4 +592,16 @@
|
|||
.provider-title span {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.inputContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.input {
|
||||
flex: 1;
|
||||
}
|
||||
.copy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -31,5 +31,5 @@ export interface ScannedSession {
|
|||
export interface PlatformLogoutOpts {
|
||||
ctx: BBContext
|
||||
userId: string
|
||||
keepActiveSession: boolean
|
||||
keepActiveSession?: boolean
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
jest.mock("node-fetch", () => jest.fn())
|
|
@ -0,0 +1,57 @@
|
|||
import * as jwt from "jsonwebtoken"
|
||||
|
||||
const mockOAuth2 = {
|
||||
getOAuthAccessToken: (code: string, p: any, cb: any) => {
|
||||
const err = null
|
||||
const accessToken = "access_token"
|
||||
const refreshToken = "refresh_token"
|
||||
|
||||
const exp = new Date()
|
||||
exp.setDate(exp.getDate() + 1)
|
||||
|
||||
const iat = new Date()
|
||||
iat.setDate(iat.getDate() - 1)
|
||||
|
||||
const claims = {
|
||||
iss: "test",
|
||||
sub: "sub",
|
||||
aud: "clientId",
|
||||
exp: exp.getTime() / 1000,
|
||||
iat: iat.getTime() / 1000,
|
||||
email: "oauth@example.com",
|
||||
}
|
||||
|
||||
const idToken = jwt.sign(claims, "secret")
|
||||
|
||||
const params = {
|
||||
id_token: idToken,
|
||||
}
|
||||
return cb(err, accessToken, refreshToken, params)
|
||||
},
|
||||
_request: (
|
||||
method: string,
|
||||
url: string,
|
||||
headers: any,
|
||||
postBody: any,
|
||||
accessToken: string,
|
||||
cb: any
|
||||
) => {
|
||||
const err = null
|
||||
const body = {
|
||||
sub: "sub",
|
||||
user_id: "userId",
|
||||
name: "OAuth",
|
||||
family_name: "2",
|
||||
given_name: "OAuth",
|
||||
middle_name: "",
|
||||
}
|
||||
const res = {}
|
||||
return cb(err, JSON.stringify(body), res)
|
||||
},
|
||||
}
|
||||
|
||||
const oauth = {
|
||||
OAuth2: jest.fn(() => mockOAuth2),
|
||||
}
|
||||
|
||||
export = oauth
|
|
@ -71,9 +71,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "26.0.23",
|
||||
"@types/jsonwebtoken": "8.5.1",
|
||||
"@types/koa": "2.13.4",
|
||||
"@types/koa__router": "8.0.11",
|
||||
"@types/node": "14.18.20",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/pouchdb": "6.4.0",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@typescript-eslint/parser": "5.12.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const core = require("@budibase/backend-core")
|
||||
import core from "@budibase/backend-core"
|
||||
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
||||
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
||||
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
|
||||
|
|
|
@ -29,11 +29,13 @@ function buildResetUpdateValidation() {
|
|||
}
|
||||
|
||||
router
|
||||
// PASSWORD
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/login",
|
||||
buildAuthValidation(),
|
||||
authController.authenticate
|
||||
)
|
||||
.post("/api/global/auth/logout", authController.logout)
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/reset",
|
||||
buildResetValidation(),
|
||||
|
@ -44,36 +46,43 @@ router
|
|||
buildResetUpdateValidation(),
|
||||
authController.resetUpdate
|
||||
)
|
||||
.post("/api/global/auth/logout", authController.logout)
|
||||
// INIT
|
||||
.post("/api/global/auth/init", authController.setInitInfo)
|
||||
.get("/api/global/auth/init", authController.getInitInfo)
|
||||
.get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
|
||||
|
||||
// DATASOURCE - MULTI TENANT
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/datasource/:provider",
|
||||
authController.datasourcePreAuth
|
||||
)
|
||||
// single tenancy endpoint
|
||||
.get("/api/global/auth/google/callback", authController.googleAuth)
|
||||
.get(
|
||||
"/api/global/auth/datasource/:provider/callback",
|
||||
authController.datasourceAuth
|
||||
)
|
||||
// multi-tenancy endpoint
|
||||
.get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/datasource/:provider/callback",
|
||||
authController.datasourceAuth
|
||||
)
|
||||
|
||||
// DATASOURCE - SINGLE TENANT - DEPRECATED
|
||||
.get(
|
||||
"/api/global/auth/datasource/:provider/callback",
|
||||
authController.datasourceAuth
|
||||
)
|
||||
|
||||
// GOOGLE - MULTI TENANT
|
||||
.get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
|
||||
.get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
|
||||
|
||||
// GOOGLE - SINGLE TENANT - DEPRECATED
|
||||
.get("/api/global/auth/google/callback", authController.googleAuth)
|
||||
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||
|
||||
// OIDC - MULTI TENANT
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/oidc/configs/:configId",
|
||||
authController.oidcPreAuth
|
||||
)
|
||||
// single tenancy endpoint
|
||||
.get("/api/global/auth/oidc/callback", authController.oidcAuth)
|
||||
// multi-tenancy endpoint
|
||||
.get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth)
|
||||
// deprecated - used by the default system before tenancy
|
||||
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||
|
||||
// OIDC - SINGLE TENANT - DEPRECATED
|
||||
.get("/api/global/auth/oidc/callback", authController.oidcAuth)
|
||||
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -3,6 +3,12 @@ import { TestConfiguration, mocks } from "../../../../tests"
|
|||
const sendMailMock = mocks.email.mock()
|
||||
import { events } from "@budibase/backend-core"
|
||||
|
||||
const expectSetAuthCookie = (res: any) => {
|
||||
expect(
|
||||
res.get("Set-Cookie").find((c: string) => c.startsWith("budibase:auth"))
|
||||
).toBeDefined()
|
||||
}
|
||||
|
||||
describe("/api/global/auth", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
|
@ -18,92 +24,155 @@ describe("/api/global/auth", () => {
|
|||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should logout", async () => {
|
||||
await config.api.auth.logout()
|
||||
expect(events.auth.logout).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should be able to generate password reset email", async () => {
|
||||
const { res, code } = await config.api.auth.requestPasswordReset(
|
||||
sendMailMock
|
||||
)
|
||||
const user = await config.getUser("test@test.com")
|
||||
|
||||
expect(res.body).toEqual({
|
||||
message: "Please check your email for a reset link.",
|
||||
describe("password", () => {
|
||||
describe("POST /api/global/auth/:tenantId/login", () => {
|
||||
it("should login", () => {})
|
||||
})
|
||||
expect(sendMailMock).toHaveBeenCalled()
|
||||
|
||||
expect(code).toBeDefined()
|
||||
expect(events.user.passwordResetRequested).toBeCalledTimes(1)
|
||||
expect(events.user.passwordResetRequested).toBeCalledWith(user)
|
||||
describe("POST /api/global/auth/logout", () => {
|
||||
it("should logout", async () => {
|
||||
await config.api.auth.logout()
|
||||
expect(events.auth.logout).toBeCalledTimes(1)
|
||||
|
||||
// TODO: Verify sessions deleted
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /api/global/auth/:tenantId/reset", () => {
|
||||
it("should generate password reset email", async () => {
|
||||
const { res, code } = await config.api.auth.requestPasswordReset(
|
||||
sendMailMock
|
||||
)
|
||||
const user = await config.getUser("test@test.com")
|
||||
|
||||
expect(res.body).toEqual({
|
||||
message: "Please check your email for a reset link.",
|
||||
})
|
||||
expect(sendMailMock).toHaveBeenCalled()
|
||||
|
||||
expect(code).toBeDefined()
|
||||
expect(events.user.passwordResetRequested).toBeCalledTimes(1)
|
||||
expect(events.user.passwordResetRequested).toBeCalledWith(user)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /api/global/auth/:tenantId/reset/update", () => {
|
||||
it("should reset password", async () => {
|
||||
const { code } = await config.api.auth.requestPasswordReset(
|
||||
sendMailMock
|
||||
)
|
||||
const user = await config.getUser("test@test.com")
|
||||
delete user.password
|
||||
|
||||
const res = await config.api.auth.updatePassword(code)
|
||||
|
||||
expect(res.body).toEqual({ message: "password reset successfully." })
|
||||
expect(events.user.passwordReset).toBeCalledTimes(1)
|
||||
expect(events.user.passwordReset).toBeCalledWith(user)
|
||||
|
||||
// TODO: Login using new password
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow resetting user password with code", async () => {
|
||||
const { code } = await config.api.auth.requestPasswordReset(sendMailMock)
|
||||
const user = await config.getUser("test@test.com")
|
||||
delete user.password
|
||||
describe("init", () => {
|
||||
describe("POST /api/global/auth/init", () => {})
|
||||
|
||||
const res = await config.api.auth.updatePassword(code)
|
||||
describe("GET /api/global/auth/init", () => {})
|
||||
})
|
||||
|
||||
expect(res.body).toEqual({ message: "password reset successfully." })
|
||||
expect(events.user.passwordReset).toBeCalledTimes(1)
|
||||
expect(events.user.passwordReset).toBeCalledWith(user)
|
||||
describe("datasource", () => {
|
||||
// MULTI TENANT
|
||||
|
||||
describe("GET /api/global/auth/:tenantId/datasource/:provider", () => {})
|
||||
|
||||
describe("GET /api/global/auth/:tenantId/datasource/:provider/callback", () => {})
|
||||
|
||||
// SINGLE TENANT
|
||||
|
||||
describe("GET /api/global/auth/datasource/:provider/callback", () => {})
|
||||
})
|
||||
|
||||
describe("google", () => {
|
||||
// MULTI TENANT
|
||||
|
||||
describe("GET /api/global/auth/:tenantId/google", () => {})
|
||||
|
||||
describe("GET /api/global/auth/:tenantId/google/callback", () => {})
|
||||
|
||||
// SINGLE TENANT
|
||||
|
||||
describe("GET /api/global/auth/google/callback", () => {})
|
||||
|
||||
describe("GET /api/admin/auth/google/callback", () => {})
|
||||
})
|
||||
|
||||
describe("oidc", () => {
|
||||
const auth = require("@budibase/backend-core/auth")
|
||||
|
||||
const passportSpy = jest.spyOn(auth.passport, "authenticate")
|
||||
let oidcConf
|
||||
let chosenConfig: any
|
||||
let configId: string
|
||||
|
||||
// mock the oidc strategy implementation and return value
|
||||
let strategyFactory = jest.fn()
|
||||
let mockStrategyReturn = jest.fn()
|
||||
let mockStrategyConfig = jest.fn()
|
||||
auth.oidc.fetchStrategyConfig = mockStrategyConfig
|
||||
|
||||
strategyFactory.mockReturnValue(mockStrategyReturn)
|
||||
auth.oidc.strategyFactory = strategyFactory
|
||||
|
||||
beforeEach(async () => {
|
||||
oidcConf = await config.saveOIDCConfig()
|
||||
chosenConfig = oidcConf.config.configs[0]
|
||||
configId = chosenConfig.uuid
|
||||
mockStrategyConfig.mockReturnValue(chosenConfig)
|
||||
jest.clearAllMocks()
|
||||
mockGetWellKnownConfig()
|
||||
|
||||
// see: __mocks__/oauth
|
||||
// for associated mocking inside passport
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
expect(strategyFactory).toBeCalledWith(chosenConfig, expect.any(Function))
|
||||
})
|
||||
const generateOidcConfig = async () => {
|
||||
const oidcConf = await config.saveOIDCConfig()
|
||||
const chosenConfig = oidcConf.config.configs[0]
|
||||
return chosenConfig.uuid
|
||||
}
|
||||
|
||||
describe("oidc configs", () => {
|
||||
it("should load strategy and delegate to passport", async () => {
|
||||
await config.api.configs.getOIDCConfig(configId)
|
||||
const mockGetWellKnownConfig = () => {
|
||||
mocks.fetch.mockReturnValue({
|
||||
ok: true,
|
||||
json: () => ({
|
||||
issuer: "test",
|
||||
authorization_endpoint: "http://localhost/auth",
|
||||
token_endpoint: "http://localhost/token",
|
||||
userinfo_endpoint: "http://localhost/userinfo",
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
|
||||
scope: ["profile", "email", "offline_access"],
|
||||
})
|
||||
expect(passportSpy.mock.calls.length).toBe(1)
|
||||
// MULTI TENANT
|
||||
describe("GET /api/global/auth/:tenantId/oidc/configs/:configId", () => {
|
||||
it("redirects to auth provider", async () => {
|
||||
const configId = await generateOidcConfig()
|
||||
|
||||
const res = await config.api.configs.getOIDCConfig(configId)
|
||||
|
||||
expect(res.status).toBe(302)
|
||||
const location: string = res.get("location")
|
||||
expect(
|
||||
location.startsWith(
|
||||
"http://localhost/auth?response_type=code&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A10000%2Fapi%2Fglobal%2Fauth%2Fdefault%2Foidc%2Fcallback&scope=openid%20profile%20email%20offline_access"
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("oidc callback", () => {
|
||||
it("should load strategy and delegate to passport", async () => {
|
||||
await config.api.configs.OIDCCallback(configId)
|
||||
describe("GET /api/global/auth/:tenantId/oidc/callback", () => {
|
||||
it("logs in", async () => {
|
||||
const configId = await generateOidcConfig()
|
||||
const preAuthRes = await config.api.configs.getOIDCConfig(configId)
|
||||
|
||||
expect(passportSpy).toBeCalledWith(
|
||||
mockStrategyReturn,
|
||||
{
|
||||
successRedirect: "/",
|
||||
failureRedirect: "/error",
|
||||
},
|
||||
expect.anything()
|
||||
)
|
||||
expect(passportSpy.mock.calls.length).toBe(1)
|
||||
const res = await config.api.configs.OIDCCallback(configId, preAuthRes)
|
||||
|
||||
expect(events.auth.login).toBeCalledWith("oidc")
|
||||
expect(events.auth.login).toBeCalledTimes(1)
|
||||
expect(res.status).toBe(302)
|
||||
const location: string = res.get("location")
|
||||
expect(location).toBe("/")
|
||||
expectSetAuthCookie(res)
|
||||
})
|
||||
})
|
||||
|
||||
// SINGLE TENANT
|
||||
|
||||
describe("GET /api/global/auth/oidc/callback", () => {})
|
||||
|
||||
describe("GET /api/global/auth/oidc/callback", () => {})
|
||||
|
||||
describe("GET /api/admin/auth/oidc/callback", () => {})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
import { tenancy } from "@budibase/backend-core"
|
||||
|
||||
describe("/api/global/workspaces", () => {
|
||||
describe("/api/global/tenants", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
|
@ -171,8 +171,11 @@ class TestConfiguration {
|
|||
}
|
||||
|
||||
cookieHeader(cookies: any) {
|
||||
if (!Array.isArray(cookies)) {
|
||||
cookies = [cookies]
|
||||
}
|
||||
return {
|
||||
Cookie: [cookies],
|
||||
Cookie: cookies,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,11 +291,6 @@ class TestConfiguration {
|
|||
|
||||
// CONFIGS - OIDC
|
||||
|
||||
getOIDConfigCookie(configId: string) {
|
||||
const token = auth.jwt.sign(configId, env.JWT_SECRET)
|
||||
return this.cookieHeader([[`${Cookies.OIDC_CONFIG}=${token}`]])
|
||||
}
|
||||
|
||||
async saveOIDCConfig() {
|
||||
await this.deleteConfig(Configs.OIDC)
|
||||
const config = structures.configs.oidc()
|
||||
|
|
|
@ -23,10 +23,20 @@ export class ConfigAPI extends TestAPI {
|
|||
.expect(200)
|
||||
}
|
||||
|
||||
OIDCCallback = (configId: string) => {
|
||||
OIDCCallback = (configId: string, preAuthRes: any) => {
|
||||
const cookie = this.config.cookieHeader(preAuthRes.get("set-cookie"))
|
||||
const setKoaSession = cookie.Cookie.find((c: string) =>
|
||||
c.includes("koa:sess")
|
||||
)
|
||||
const koaSession = setKoaSession.split("=")[1] + "=="
|
||||
const sessionContent = JSON.parse(
|
||||
Buffer.from(koaSession, "base64").toString("utf-8")
|
||||
)
|
||||
const handle = sessionContent["openidconnect:localhost"].state.handle
|
||||
return this.request
|
||||
.get(`/api/global/auth/${this.config.getTenantId()}/oidc/callback`)
|
||||
.set(this.config.getOIDConfigCookie(configId))
|
||||
.query({ code: "test", state: handle })
|
||||
.set(cookie)
|
||||
}
|
||||
|
||||
getOIDCConfig = (configId: string) => {
|
||||
|
|
|
@ -1284,6 +1284,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json-buffer/-/json-buffer-3.0.0.tgz#85c1ff0f0948fc159810d4b5be35bf8c20875f64"
|
||||
integrity sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==
|
||||
|
||||
"@types/jsonwebtoken@8.5.1":
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#56958cb2d80f6d74352bd2e501a018e2506a8a84"
|
||||
integrity sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/keygrip@*":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
|
||||
|
@ -1334,6 +1341,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/node-fetch@2.6.1":
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975"
|
||||
integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*":
|
||||
version "17.0.41"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b"
|
||||
|
@ -3412,6 +3427,15 @@ forever-agent@~0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
|
|
|
@ -114,8 +114,6 @@ export default class AppApi {
|
|||
return [response, json]
|
||||
}
|
||||
|
||||
|
||||
|
||||
async delete(appId: string): Promise<[Response, any]> {
|
||||
const response = await this.api.del(`/applications/${appId}`)
|
||||
const json = await response.json()
|
||||
|
@ -123,7 +121,11 @@ export default class AppApi {
|
|||
return [response, json]
|
||||
}
|
||||
|
||||
async update(appId: string, oldName: string, body: any): Promise<[Response, Application]> {
|
||||
async update(
|
||||
appId: string,
|
||||
oldName: string,
|
||||
body: any
|
||||
): Promise<[Response, Application]> {
|
||||
const response = await this.api.put(`/applications/${appId}`, { body })
|
||||
const json = await response.json()
|
||||
expect(response).toHaveStatusCode(200)
|
||||
|
@ -142,7 +144,6 @@ export default class AppApi {
|
|||
const json = await response.json()
|
||||
expect(response).toHaveStatusCode(200)
|
||||
if (screenExists) {
|
||||
|
||||
expect(json.routes["/test"]).toBeTruthy()
|
||||
} else {
|
||||
expect(json.routes["/test"]).toBeUndefined()
|
||||
|
|
|
@ -46,9 +46,7 @@ export default class TablesApi {
|
|||
const response = await this.api.del(`/tables/${id}/${revId}`)
|
||||
const json = await response.json()
|
||||
expect(response).toHaveStatusCode(200)
|
||||
expect(json.message).toEqual(
|
||||
`Table ${id} deleted.`
|
||||
)
|
||||
expect(json.message).toEqual(`Table ${id} deleted.`)
|
||||
return [response, json]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import generator from "../../generator"
|
||||
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
|
||||
|
||||
|
||||
const generate = (
|
||||
overrides: Partial<Application> = {}
|
||||
): Partial<Application> => ({
|
||||
|
|
|
@ -48,7 +48,8 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
})
|
||||
config.applications.api.appId = app.appId
|
||||
|
||||
const [appPackageResponse, appPackageJson] = await config.applications.getAppPackage(<string>app.appId)
|
||||
const [appPackageResponse, appPackageJson] =
|
||||
await config.applications.getAppPackage(<string>app.appId)
|
||||
expect(appPackageJson.application.name).toEqual(app.name)
|
||||
expect(appPackageJson.application.version).toEqual(app.version)
|
||||
expect(appPackageJson.application.url).toEqual(app.url)
|
||||
|
@ -72,7 +73,6 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
config.applications.api.appId = db.getProdAppID(app.appId)
|
||||
await config.applications.canRender()
|
||||
|
||||
|
||||
// unpublish app
|
||||
await config.applications.unpublish(<string>app.appId)
|
||||
})
|
||||
|
@ -109,22 +109,16 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
|
||||
config.applications.api.appId = app.appId
|
||||
|
||||
await config.applications.update(
|
||||
<string>app.appId,
|
||||
<string>app.name,
|
||||
{
|
||||
name: generator.word(),
|
||||
}
|
||||
)
|
||||
await config.applications.update(<string>app.appId, <string>app.name, {
|
||||
name: generator.word(),
|
||||
})
|
||||
})
|
||||
|
||||
it("POST - Revert Changes without changes", async () => {
|
||||
const app = await config.applications.create(generateApp())
|
||||
config.applications.api.appId = app.appId
|
||||
|
||||
await config.applications.revertUnpublished(
|
||||
<string>app.appId
|
||||
)
|
||||
await config.applications.revertUnpublished(<string>app.appId)
|
||||
})
|
||||
|
||||
it("POST - Revert Changes", async () => {
|
||||
|
@ -134,20 +128,14 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
// publish app
|
||||
await config.applications.publish(<string>app.url)
|
||||
|
||||
|
||||
// Change/add component to the app
|
||||
await config.screen.create(
|
||||
generateScreen("BASIC")
|
||||
)
|
||||
await config.screen.create(generateScreen("BASIC"))
|
||||
|
||||
// // Revert the app to published state
|
||||
await config.applications.revertPublished(
|
||||
<string>app.appId
|
||||
)
|
||||
await config.applications.revertPublished(<string>app.appId)
|
||||
|
||||
// Check screen is removed
|
||||
await config.applications.getRoutes()
|
||||
|
||||
})
|
||||
|
||||
it("DELETE - Delete an application", async () => {
|
||||
|
@ -155,5 +143,4 @@ describe("Internal API - Application creation, update, publish and delete", () =
|
|||
|
||||
await config.applications.delete(<string>app.appId)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -38,9 +38,7 @@ describe("Internal API - /screens endpoints", () => {
|
|||
|
||||
// Create Screen
|
||||
appConfig.applications.api.appId = app.appId
|
||||
await config.screen.create(
|
||||
generateScreen("BASIC")
|
||||
)
|
||||
await config.screen.create(generateScreen("BASIC"))
|
||||
|
||||
// Check screen exists
|
||||
await appConfig.applications.getRoutes(true)
|
||||
|
@ -58,6 +56,5 @@ describe("Internal API - /screens endpoints", () => {
|
|||
|
||||
// Delete Screen
|
||||
await config.screen.delete(screen._id!, screen._rev!)
|
||||
|
||||
})
|
||||
})
|
||||
|
|
|
@ -3,93 +3,87 @@ import { Application } from "@budibase/server/api/controllers/public/mapping/typ
|
|||
import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient"
|
||||
import generator from "../../../config/generator"
|
||||
import {
|
||||
generateTable,
|
||||
generateNewColumnForTable,
|
||||
generateTable,
|
||||
generateNewColumnForTable,
|
||||
} from "../../../config/internal-api/fixtures/table"
|
||||
import { generateNewRowForTable } from "../../../config/internal-api/fixtures/rows"
|
||||
|
||||
describe("Internal API - Application creation, update, publish and delete", () => {
|
||||
const api = new InternalAPIClient()
|
||||
const config = new TestConfiguration<Application>(api)
|
||||
const api = new InternalAPIClient()
|
||||
const config = new TestConfiguration<Application>(api)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
async function createAppFromTemplate() {
|
||||
return config.applications.create({
|
||||
name: generator.word(),
|
||||
url: `/${generator.word()}`,
|
||||
useTemplate: "true",
|
||||
templateName: "Near Miss Register",
|
||||
templateKey: "app/near-miss-register",
|
||||
templateFile: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
it("Operations on Tables", async () => {
|
||||
// create the app
|
||||
const appName = generator.word()
|
||||
const app = await createAppFromTemplate()
|
||||
config.applications.api.appId = app.appId
|
||||
|
||||
async function createAppFromTemplate() {
|
||||
return config.applications.create({
|
||||
name: generator.word(),
|
||||
url: `/${generator.word()}`,
|
||||
useTemplate: "true",
|
||||
templateName: "Near Miss Register",
|
||||
templateKey: "app/near-miss-register",
|
||||
templateFile: undefined,
|
||||
})
|
||||
// Get current tables: expect 2 in this template
|
||||
await config.tables.getAll(2)
|
||||
|
||||
// Add new table
|
||||
const [createdTableResponse, createdTableData] = await config.tables.save(
|
||||
generateTable()
|
||||
)
|
||||
|
||||
//Table was added
|
||||
await config.tables.getAll(3)
|
||||
|
||||
//Get information about the table
|
||||
await config.tables.getTableById(<string>createdTableData._id)
|
||||
|
||||
//Add Column to table
|
||||
const newColumn = generateNewColumnForTable(createdTableData)
|
||||
const [addColumnResponse, addColumnData] = await config.tables.save(
|
||||
newColumn,
|
||||
true
|
||||
)
|
||||
|
||||
//Add Row to table
|
||||
const newRow = generateNewRowForTable(<string>addColumnData._id)
|
||||
await config.rows.add(<string>addColumnData._id, newRow)
|
||||
|
||||
//Get Row from table
|
||||
const [getRowResponse, getRowData] = await config.rows.getAll(
|
||||
<string>addColumnData._id
|
||||
)
|
||||
|
||||
//Delete Row from table
|
||||
const rowToDelete = {
|
||||
rows: [getRowData[0]],
|
||||
}
|
||||
const [deleteRowResponse, deleteRowData] = await config.rows.delete(
|
||||
<string>addColumnData._id,
|
||||
rowToDelete
|
||||
)
|
||||
expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
|
||||
|
||||
it("Operations on Tables", async () => {
|
||||
// create the app
|
||||
const appName = generator.word()
|
||||
const app = await createAppFromTemplate()
|
||||
config.applications.api.appId = app.appId
|
||||
//Delete the table
|
||||
const [deleteTableResponse, deleteTable] = await config.tables.delete(
|
||||
<string>addColumnData._id,
|
||||
<string>addColumnData._rev
|
||||
)
|
||||
|
||||
// Get current tables: expect 2 in this template
|
||||
await config.tables.getAll(2)
|
||||
|
||||
// Add new table
|
||||
const [createdTableResponse, createdTableData] = await config.tables.save(
|
||||
generateTable()
|
||||
)
|
||||
|
||||
//Table was added
|
||||
await config.tables.getAll(3)
|
||||
|
||||
//Get information about the table
|
||||
await config.tables.getTableById(
|
||||
<string>createdTableData._id
|
||||
)
|
||||
|
||||
//Add Column to table
|
||||
const newColumn = generateNewColumnForTable(createdTableData)
|
||||
const [addColumnResponse, addColumnData] = await config.tables.save(
|
||||
newColumn,
|
||||
true
|
||||
)
|
||||
|
||||
//Add Row to table
|
||||
const newRow = generateNewRowForTable(<string>addColumnData._id)
|
||||
await config.rows.add(
|
||||
<string>addColumnData._id,
|
||||
newRow
|
||||
)
|
||||
|
||||
//Get Row from table
|
||||
const [getRowResponse, getRowData] = await config.rows.getAll(
|
||||
<string>addColumnData._id
|
||||
)
|
||||
|
||||
//Delete Row from table
|
||||
const rowToDelete = {
|
||||
rows: [getRowData[0]],
|
||||
}
|
||||
const [deleteRowResponse, deleteRowData] = await config.rows.delete(
|
||||
<string>addColumnData._id,
|
||||
rowToDelete
|
||||
)
|
||||
expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
|
||||
|
||||
//Delete the table
|
||||
const [deleteTableResponse, deleteTable] = await config.tables.delete(
|
||||
<string>addColumnData._id,
|
||||
<string>addColumnData._rev
|
||||
)
|
||||
|
||||
|
||||
//Table was deleted
|
||||
await config.tables.getAll(2)
|
||||
})
|
||||
//Table was deleted
|
||||
await config.tables.getAll(2)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue