Merge branch 'develop' of github.com:Budibase/budibase into feature/replace-pouch
This commit is contained in:
commit
29dd98a7fc
|
@ -58,12 +58,15 @@ http {
|
|||
}
|
||||
|
||||
location ~ ^/api/(system|admin|global)/ {
|
||||
proxy_pass http://worker-service;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_pass http://worker-service;
|
||||
}
|
||||
|
||||
location /api/backups/ {
|
||||
|
@ -78,60 +81,78 @@ http {
|
|||
location /api/ {
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_pass http://app-service;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_pass http://app-service;
|
||||
}
|
||||
|
||||
location = / {
|
||||
proxy_pass http://app-service;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_pass http://app-service;
|
||||
}
|
||||
|
||||
location /app_ {
|
||||
proxy_pass http://app-service;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_pass http://app-service;
|
||||
}
|
||||
|
||||
location /app {
|
||||
proxy_pass http://app-service;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_pass http://app-service;
|
||||
}
|
||||
|
||||
location /builder {
|
||||
proxy_pass http://builder;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_pass http://builder;
|
||||
rewrite ^/builder(.*)$ /builder/$1 break;
|
||||
}
|
||||
|
||||
location /builder/ {
|
||||
proxy_pass http://builder;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
|
||||
proxy_pass http://builder;
|
||||
}
|
||||
|
||||
location /vite/ {
|
||||
|
|
|
@ -100,18 +100,25 @@ http {
|
|||
|
||||
location ~ ^/(builder|app_) {
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://$apps:4002;
|
||||
}
|
||||
|
||||
location ~ ^/api/(system|admin|global)/ {
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://$worker:4003;
|
||||
}
|
||||
|
||||
location /worker/ {
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://$worker:4003;
|
||||
rewrite ^/worker/(.*)$ /$1 break;
|
||||
}
|
||||
|
@ -139,6 +146,7 @@ http {
|
|||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://$apps:4002;
|
||||
}
|
||||
|
@ -158,6 +166,7 @@ http {
|
|||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://$apps:4002;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -75,8 +75,8 @@
|
|||
"env:multi:disable": "lerna run env:multi:disable",
|
||||
"env:selfhost:enable": "lerna run env:selfhost:enable",
|
||||
"env:selfhost:disable": "lerna run env:selfhost:disable",
|
||||
"env:localdomain:enable": "lerna run env:localdomain:enable",
|
||||
"env:localdomain:disable": "lerna run env:localdomain:disable",
|
||||
"env:localdomain:enable": "./scripts/localdomain.sh enable",
|
||||
"env:localdomain:disable": "./scripts/localdomain.sh disable",
|
||||
"env:account:enable": "lerna run env:account:enable",
|
||||
"env:account:disable": "lerna run env:account:disable",
|
||||
"mode:self": "yarn env:selfhost:enable && yarn env:multi:disable && yarn env:account:disable",
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
jest.mock("node-fetch", () => jest.fn())
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/backend-core",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Budibase backend core libraries used in server and worker",
|
||||
"main": "dist/src/index.js",
|
||||
"types": "dist/src/index.d.ts",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"test:watch": "jest --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/types": "2.1.22-alpha.3",
|
||||
"@budibase/types": "2.1.22-alpha.6",
|
||||
"@shopify/jest-koa-mocks": "5.0.1",
|
||||
"@techpass/passport-openidconnect": "0.3.2",
|
||||
"aws-sdk": "2.1030.0",
|
||||
|
@ -57,7 +57,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",
|
||||
|
@ -69,7 +69,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",
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { getGlobalUserParams, getAllApps } from "../db/utils"
|
||||
import { doWithDB, PouchLike } from "../db"
|
||||
import {
|
||||
getGlobalUserParams,
|
||||
getAllApps,
|
||||
doWithDB,
|
||||
StaticDatabases,
|
||||
PouchLike,
|
||||
} from "../db"
|
||||
import { doWithGlobalDB } from "../tenancy"
|
||||
import { StaticDatabases } from "../db/constants"
|
||||
import { User } from "@budibase/types"
|
||||
import { App, Tenants, User } from "@budibase/types"
|
||||
|
||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||
|
@ -10,10 +14,8 @@ const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
|||
async function removeTenantFromInfoDB(tenantId: string) {
|
||||
try {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: PouchLike) => {
|
||||
let tenants = await infoDb.get(TENANT_DOC)
|
||||
tenants.tenantIds = tenants.tenantIds.filter(
|
||||
(id: string) => id !== tenantId
|
||||
)
|
||||
const tenants = (await infoDb.get(TENANT_DOC)) as Tenants
|
||||
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
|
||||
|
||||
await infoDb.put(tenants)
|
||||
})
|
||||
|
@ -23,18 +25,35 @@ async function removeTenantFromInfoDB(tenantId: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function removeUserFromInfoDB(dbUser: User) {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: PouchLike) => {
|
||||
const keys = [dbUser._id!, dbUser.email]
|
||||
const userDocs = await infoDb.allDocs({
|
||||
keys,
|
||||
include_docs: true,
|
||||
})
|
||||
const toDelete = userDocs.rows.map((row: any) => {
|
||||
return {
|
||||
...row.doc,
|
||||
_deleted: true,
|
||||
}
|
||||
})
|
||||
await infoDb.bulkDocs(toDelete)
|
||||
})
|
||||
}
|
||||
|
||||
async function removeUsersFromInfoDB(tenantId: string) {
|
||||
return doWithGlobalDB(tenantId, async (db: PouchLike) => {
|
||||
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||
try {
|
||||
const allUsers = await db.allDocs(
|
||||
getGlobalUserParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: PouchLike) => {
|
||||
const allEmails = allUsers.rows.map(row => row.doc.email)
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||
const allEmails = allUsers.rows.map((row: any) => row.doc.email)
|
||||
// get the id docs
|
||||
let keys = allUsers.rows.map(row => row.id)
|
||||
let keys = allUsers.rows.map((row: any) => row.id)
|
||||
// and the email docs
|
||||
keys = keys.concat(allEmails)
|
||||
// retrieve the docs and delete them
|
||||
|
@ -42,7 +61,7 @@ async function removeUsersFromInfoDB(tenantId: string) {
|
|||
keys,
|
||||
include_docs: true,
|
||||
})
|
||||
const toDelete = userDocs.rows.map(row => {
|
||||
const toDelete = userDocs.rows.map((row: any) => {
|
||||
return {
|
||||
...row.doc,
|
||||
_deleted: true,
|
||||
|
@ -70,7 +89,7 @@ async function removeGlobalDB(tenantId: string) {
|
|||
|
||||
async function removeTenantApps(tenantId: string) {
|
||||
try {
|
||||
const apps = await getAllApps({ all: true })
|
||||
const apps = (await getAllApps({ all: true })) as App[]
|
||||
const destroyPromises = apps.map(app =>
|
||||
doWithDB(app.appId, (db: PouchLike) => db.destroy())
|
||||
)
|
||||
|
@ -81,23 +100,6 @@ async function removeTenantApps(tenantId: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function removeUserFromInfoDB(dbUser: User) {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: PouchLike) => {
|
||||
const keys = [dbUser._id!, dbUser.email]
|
||||
const userDocs = await infoDb.allDocs({
|
||||
keys,
|
||||
include_docs: true,
|
||||
})
|
||||
const toDelete = userDocs.rows.map(row => {
|
||||
return {
|
||||
...row.doc,
|
||||
_deleted: true,
|
||||
}
|
||||
})
|
||||
await infoDb.bulkDocs(toDelete)
|
||||
})
|
||||
}
|
||||
|
||||
// can't live in tenancy package due to circular dependency on db/utils
|
||||
export async function deleteTenant(tenantId: string) {
|
||||
await removeTenantFromInfoDB(tenantId)
|
||||
|
|
|
@ -16,6 +16,7 @@ import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
|||
import { APP_PREFIX } from "./constants"
|
||||
import * as events from "../events"
|
||||
import { PouchLike } from "./couch"
|
||||
import { App } from "@budibase/types"
|
||||
|
||||
export * from "./constants"
|
||||
export * from "./conversions"
|
||||
|
@ -302,7 +303,12 @@ export async function getAllDbs(opts = { efficient: false }) {
|
|||
*
|
||||
* @return {Promise<object[]>} returns the app information document stored in each app database.
|
||||
*/
|
||||
export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) {
|
||||
export async function getAllApps({
|
||||
dev,
|
||||
all,
|
||||
idsOnly,
|
||||
efficient,
|
||||
}: any = {}): Promise<App[] | string[]> {
|
||||
let tenantId = getTenantId()
|
||||
if (!env.MULTI_TENANCY && !tenantId) {
|
||||
tenantId = DEFAULT_TENANT_ID
|
||||
|
@ -374,18 +380,16 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) {
|
|||
* Utility function for getAllApps but filters to production apps only.
|
||||
*/
|
||||
export async function getProdAppIDs() {
|
||||
return (await getAllApps({ idsOnly: true })).filter(
|
||||
(id: any) => !isDevAppID(id)
|
||||
)
|
||||
const apps = (await getAllApps({ idsOnly: true })) as string[]
|
||||
return apps.filter((id: any) => !isDevAppID(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for the inverse of above.
|
||||
*/
|
||||
export async function getDevAppIDs() {
|
||||
return (await getAllApps({ idsOnly: true })).filter((id: any) =>
|
||||
isDevAppID(id)
|
||||
)
|
||||
const apps = (await getAllApps({ idsOnly: true })) as string[]
|
||||
return apps.filter((id: any) => isDevAppID(id))
|
||||
}
|
||||
|
||||
export async function dbExists(dbName: any) {
|
||||
|
|
|
@ -1,27 +1,34 @@
|
|||
import { BBContext, EndpointMatcher, RegexMatcher } from "@budibase/types"
|
||||
|
||||
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
|
||||
|
||||
exports.buildMatcherRegex = patterns => {
|
||||
export const buildMatcherRegex = (
|
||||
patterns: EndpointMatcher[]
|
||||
): RegexMatcher[] => {
|
||||
if (!patterns) {
|
||||
return []
|
||||
}
|
||||
return patterns.map(pattern => {
|
||||
const isObj = typeof pattern === "object" && pattern.route
|
||||
const method = isObj ? pattern.method : "GET"
|
||||
let route = pattern.route
|
||||
const method = pattern.method
|
||||
const strict = pattern.strict ? pattern.strict : false
|
||||
let route = isObj ? pattern.route : pattern
|
||||
|
||||
// if there is a param in the route
|
||||
// use a wildcard pattern
|
||||
const matches = route.match(PARAM_REGEX)
|
||||
if (matches) {
|
||||
for (let match of matches) {
|
||||
const pattern = "/.*" + (match.endsWith("/") ? "/" : "")
|
||||
const suffix = match.endsWith("/") ? "/" : ""
|
||||
const pattern = "/.*" + suffix
|
||||
route = route.replace(match, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
return { regex: new RegExp(route), method, strict, route }
|
||||
})
|
||||
}
|
||||
|
||||
exports.matches = (ctx, options) => {
|
||||
export const matches = (ctx: BBContext, options: RegexMatcher[]) => {
|
||||
return options.find(({ regex, method, strict, route }) => {
|
||||
let urlMatch
|
||||
if (strict) {
|
|
@ -1,52 +0,0 @@
|
|||
const { doInTenant, isMultiTenant, DEFAULT_TENANT_ID } = require("../tenancy")
|
||||
const { buildMatcherRegex, matches } = require("./matchers")
|
||||
const { Header } = require("../constants")
|
||||
|
||||
const getTenantID = (ctx, opts = { allowQs: false, allowNoTenant: false }) => {
|
||||
// exit early if not multi-tenant
|
||||
if (!isMultiTenant()) {
|
||||
return DEFAULT_TENANT_ID
|
||||
}
|
||||
|
||||
let tenantId
|
||||
const allowQs = opts && opts.allowQs
|
||||
const allowNoTenant = opts && opts.allowNoTenant
|
||||
const header = ctx.request.headers[Header.TENANT_ID]
|
||||
const user = ctx.user || {}
|
||||
if (allowQs) {
|
||||
const query = ctx.request.query || {}
|
||||
tenantId = query.tenantId
|
||||
}
|
||||
// override query string (if allowed) by user, or header
|
||||
// URL params cannot be used in a middleware, as they are
|
||||
// processed later in the chain
|
||||
tenantId = user.tenantId || header || tenantId
|
||||
|
||||
// Set the tenantId from the subdomain
|
||||
if (!tenantId) {
|
||||
tenantId = ctx.subdomains && ctx.subdomains[0]
|
||||
}
|
||||
|
||||
if (!tenantId && !allowNoTenant) {
|
||||
ctx.throw(403, "Tenant id not set")
|
||||
}
|
||||
|
||||
return tenantId
|
||||
}
|
||||
|
||||
module.exports = (
|
||||
allowQueryStringPatterns,
|
||||
noTenancyPatterns,
|
||||
opts = { noTenancyRequired: false }
|
||||
) => {
|
||||
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
||||
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
||||
|
||||
return async function (ctx, next) {
|
||||
const allowNoTenant =
|
||||
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
||||
const allowQs = !!matches(ctx, allowQsOptions)
|
||||
const tenantId = getTenantID(ctx, { allowQs, allowNoTenant })
|
||||
return doInTenant(tenantId, next)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { doInTenant, getTenantIDFromCtx } from "../tenancy"
|
||||
import { buildMatcherRegex, matches } from "./matchers"
|
||||
import { Headers } from "../constants"
|
||||
import {
|
||||
BBContext,
|
||||
EndpointMatcher,
|
||||
GetTenantIdOptions,
|
||||
TenantResolutionStrategy,
|
||||
} from "@budibase/types"
|
||||
|
||||
const tenancy = (
|
||||
allowQueryStringPatterns: EndpointMatcher[],
|
||||
noTenancyPatterns: EndpointMatcher[],
|
||||
opts = { noTenancyRequired: false }
|
||||
) => {
|
||||
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
||||
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
||||
|
||||
return async function (ctx: BBContext, next: any) {
|
||||
const allowNoTenant =
|
||||
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
||||
const tenantOpts: GetTenantIdOptions = {
|
||||
allowNoTenant,
|
||||
}
|
||||
|
||||
const allowQs = !!matches(ctx, allowQsOptions)
|
||||
if (!allowQs) {
|
||||
tenantOpts.excludeStrategies = [TenantResolutionStrategy.QUERY]
|
||||
}
|
||||
|
||||
const tenantId = getTenantIDFromCtx(ctx, tenantOpts)
|
||||
ctx.set(Headers.TENANT_ID, tenantId as string)
|
||||
return doInTenant(tenantId, next)
|
||||
}
|
||||
}
|
||||
|
||||
export = tenancy
|
|
@ -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)
|
||||
})
|
||||
})
|
|
@ -12,6 +12,7 @@ import {
|
|||
MigrationOptions,
|
||||
MigrationType,
|
||||
MigrationNoOpOptions,
|
||||
App,
|
||||
} from "@budibase/types"
|
||||
|
||||
export const getMigrationsDoc = async (db: any) => {
|
||||
|
@ -55,14 +56,17 @@ export const runMigration = async (
|
|||
}
|
||||
|
||||
// get the db to store the migration in
|
||||
let dbNames
|
||||
let dbNames: string[]
|
||||
if (migrationType === MigrationType.GLOBAL) {
|
||||
dbNames = [getGlobalDBName()]
|
||||
} else if (migrationType === MigrationType.APP) {
|
||||
if (options.noOp) {
|
||||
if (!options.noOp.appId) {
|
||||
throw new Error("appId is required for noOp app migration")
|
||||
}
|
||||
dbNames = [options.noOp.appId]
|
||||
} else {
|
||||
const apps = await getAllApps(migration.appOpts)
|
||||
const apps = (await getAllApps(migration.appOpts)) as App[]
|
||||
dbNames = apps.map(app => app.appId)
|
||||
}
|
||||
} else if (migrationType === MigrationType.INSTALLATION) {
|
||||
|
|
|
@ -9,7 +9,13 @@ import {
|
|||
isMultiTenant,
|
||||
} from "../context"
|
||||
import env from "../environment"
|
||||
import { PlatformUser } from "@budibase/types"
|
||||
import {
|
||||
BBContext,
|
||||
PlatformUser,
|
||||
TenantResolutionStrategy,
|
||||
GetTenantIdOptions,
|
||||
} from "@budibase/types"
|
||||
import { Headers } from "../constants"
|
||||
|
||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||
|
@ -151,3 +157,108 @@ export async function getTenantIds() {
|
|||
return (tenants && tenants.tenantIds) || []
|
||||
})
|
||||
}
|
||||
|
||||
const ALL_STRATEGIES = Object.values(TenantResolutionStrategy)
|
||||
|
||||
export const getTenantIDFromCtx = (
|
||||
ctx: BBContext,
|
||||
opts: GetTenantIdOptions
|
||||
): string | null => {
|
||||
// exit early if not multi-tenant
|
||||
if (!isMultiTenant()) {
|
||||
return DEFAULT_TENANT_ID
|
||||
}
|
||||
|
||||
// opt defaults
|
||||
if (opts.allowNoTenant === undefined) {
|
||||
opts.allowNoTenant = false
|
||||
}
|
||||
if (!opts.includeStrategies) {
|
||||
opts.includeStrategies = ALL_STRATEGIES
|
||||
}
|
||||
if (!opts.excludeStrategies) {
|
||||
opts.excludeStrategies = []
|
||||
}
|
||||
|
||||
const isAllowed = (strategy: TenantResolutionStrategy) => {
|
||||
// excluded takes precedence
|
||||
if (opts.excludeStrategies?.includes(strategy)) {
|
||||
return false
|
||||
}
|
||||
if (opts.includeStrategies?.includes(strategy)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// always use user first
|
||||
if (isAllowed(TenantResolutionStrategy.USER)) {
|
||||
const userTenantId = ctx.user?.tenantId
|
||||
if (userTenantId) {
|
||||
return userTenantId
|
||||
}
|
||||
}
|
||||
|
||||
// header
|
||||
if (isAllowed(TenantResolutionStrategy.HEADER)) {
|
||||
const headerTenantId = ctx.request.headers[Headers.TENANT_ID]
|
||||
if (headerTenantId) {
|
||||
return headerTenantId as string
|
||||
}
|
||||
}
|
||||
|
||||
// query param
|
||||
if (isAllowed(TenantResolutionStrategy.QUERY)) {
|
||||
const queryTenantId = ctx.request.query.tenantId
|
||||
if (queryTenantId) {
|
||||
return queryTenantId as string
|
||||
}
|
||||
}
|
||||
|
||||
// subdomain
|
||||
if (isAllowed(TenantResolutionStrategy.SUBDOMAIN)) {
|
||||
// e.g. budibase.app or local.com:10000
|
||||
const platformHost = new URL(env.PLATFORM_URL).host.split(":")[0]
|
||||
// e.g. tenant.budibase.app or tenant.local.com
|
||||
const requestHost = ctx.host
|
||||
// parse the tenant id from the difference
|
||||
if (requestHost.includes(platformHost)) {
|
||||
const tenantId = requestHost.substring(
|
||||
0,
|
||||
requestHost.indexOf(`.${platformHost}`)
|
||||
)
|
||||
if (tenantId) {
|
||||
return tenantId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// path
|
||||
if (isAllowed(TenantResolutionStrategy.PATH)) {
|
||||
// params - have to parse manually due to koa-router not run yet
|
||||
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(url, match.captures(url), {})
|
||||
if (params.tenantId) {
|
||||
return params.tenantId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.allowNoTenant) {
|
||||
ctx.throw(403, "Tenant id not set")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,38 +1,51 @@
|
|||
const { DocumentType, SEPARATOR, ViewName, getAllApps } = require("./db/utils")
|
||||
import {
|
||||
DocumentType,
|
||||
SEPARATOR,
|
||||
ViewName,
|
||||
getAllApps,
|
||||
queryGlobalView,
|
||||
} from "./db"
|
||||
import { options } from "./middleware/passport/jwt"
|
||||
import { Header, Cookie, MAX_VALID_DATE } from "./constants"
|
||||
import env from "./environment"
|
||||
import userCache from "./cache/user"
|
||||
import { getSessionsForUser, invalidateSessions } from "./security/sessions"
|
||||
import * as events from "./events"
|
||||
import tenancy from "./tenancy"
|
||||
import {
|
||||
App,
|
||||
BBContext,
|
||||
PlatformLogoutOpts,
|
||||
TenantResolutionStrategy,
|
||||
} from "@budibase/types"
|
||||
import { SetOption } from "cookies"
|
||||
const jwt = require("jsonwebtoken")
|
||||
const { options } = require("./middleware/passport/jwt")
|
||||
const { queryGlobalView } = require("./db/views")
|
||||
const { Header, Cookie, MAX_VALID_DATE } = require("./constants")
|
||||
const env = require("./environment")
|
||||
const userCache = require("./cache/user")
|
||||
const {
|
||||
getSessionsForUser,
|
||||
invalidateSessions,
|
||||
} = require("./security/sessions")
|
||||
const events = require("./events")
|
||||
const tenancy = require("./tenancy")
|
||||
|
||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||
const PROD_APP_PREFIX = "/app/"
|
||||
|
||||
function confirmAppId(possibleAppId) {
|
||||
function confirmAppId(possibleAppId: string | undefined) {
|
||||
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
||||
? possibleAppId
|
||||
: undefined
|
||||
}
|
||||
|
||||
async function resolveAppUrl(ctx) {
|
||||
async function resolveAppUrl(ctx: BBContext) {
|
||||
const appUrl = ctx.path.split("/")[2]
|
||||
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
||||
|
||||
let tenantId = tenancy.getTenantId()
|
||||
if (!env.SELF_HOSTED && ctx.subdomains.length) {
|
||||
// always use the tenant id from the url in cloud
|
||||
tenantId = ctx.subdomains[0]
|
||||
let tenantId: string | null = tenancy.getTenantId()
|
||||
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, {
|
||||
includeStrategies: [TenantResolutionStrategy.SUBDOMAIN],
|
||||
})
|
||||
}
|
||||
|
||||
// search prod apps for a url that matches
|
||||
const apps = await tenancy.doInTenant(tenantId, () =>
|
||||
const apps: App[] = await tenancy.doInTenant(tenantId, () =>
|
||||
getAllApps({ dev: false })
|
||||
)
|
||||
const app = apps.filter(
|
||||
|
@ -42,7 +55,7 @@ async function resolveAppUrl(ctx) {
|
|||
return app && app.appId ? app.appId : undefined
|
||||
}
|
||||
|
||||
exports.isServingApp = ctx => {
|
||||
export function isServingApp(ctx: BBContext) {
|
||||
// dev app
|
||||
if (ctx.path.startsWith(`/${APP_PREFIX}`)) {
|
||||
return true
|
||||
|
@ -59,12 +72,12 @@ exports.isServingApp = ctx => {
|
|||
* @param {object} ctx The main request body to look through.
|
||||
* @returns {string|undefined} If an appId was found it will be returned.
|
||||
*/
|
||||
exports.getAppIdFromCtx = async ctx => {
|
||||
export async function getAppIdFromCtx(ctx: BBContext) {
|
||||
// look in headers
|
||||
const options = [ctx.headers[Header.APP_ID]]
|
||||
let appId
|
||||
for (let option of options) {
|
||||
appId = confirmAppId(option)
|
||||
appId = confirmAppId(option as string)
|
||||
if (appId) {
|
||||
break
|
||||
}
|
||||
|
@ -95,7 +108,7 @@ exports.getAppIdFromCtx = async ctx => {
|
|||
* opens the contents of the specified encrypted JWT.
|
||||
* @return {object} the contents of the token.
|
||||
*/
|
||||
exports.openJwt = token => {
|
||||
export function openJwt(token: string) {
|
||||
if (!token) {
|
||||
return token
|
||||
}
|
||||
|
@ -107,14 +120,14 @@ exports.openJwt = token => {
|
|||
* @param {object} ctx The request which is to be manipulated.
|
||||
* @param {string} name The name of the cookie to get.
|
||||
*/
|
||||
exports.getCookie = (ctx, name) => {
|
||||
export function getCookie(ctx: BBContext, name: string) {
|
||||
const cookie = ctx.cookies.get(name)
|
||||
|
||||
if (!cookie) {
|
||||
return cookie
|
||||
}
|
||||
|
||||
return exports.openJwt(cookie)
|
||||
return openJwt(cookie)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,12 +137,17 @@ exports.getCookie = (ctx, name) => {
|
|||
* @param {string|object} value The value of cookie which will be set.
|
||||
* @param {object} opts options like whether to sign.
|
||||
*/
|
||||
exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => {
|
||||
export function setCookie(
|
||||
ctx: BBContext,
|
||||
value: any,
|
||||
name = "builder",
|
||||
opts = { sign: true }
|
||||
) {
|
||||
if (value && opts && opts.sign) {
|
||||
value = jwt.sign(value, options.secretOrKey)
|
||||
}
|
||||
|
||||
const config = {
|
||||
const config: SetOption = {
|
||||
expires: MAX_VALID_DATE,
|
||||
path: "/",
|
||||
httpOnly: false,
|
||||
|
@ -146,8 +164,8 @@ exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => {
|
|||
/**
|
||||
* Utility function, simply calls setCookie with an empty string for value
|
||||
*/
|
||||
exports.clearCookie = (ctx, name) => {
|
||||
exports.setCookie(ctx, null, name)
|
||||
export function clearCookie(ctx: BBContext, name: string) {
|
||||
setCookie(ctx, null, name)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,11 +174,11 @@ exports.clearCookie = (ctx, name) => {
|
|||
* @param {object} ctx The koa context object to be tested.
|
||||
* @return {boolean} returns true if the call is from the client lib (a built app rather than the builder).
|
||||
*/
|
||||
exports.isClient = ctx => {
|
||||
export function isClient(ctx: BBContext) {
|
||||
return ctx.headers[Header.TYPE] === "client"
|
||||
}
|
||||
|
||||
const getBuilders = async () => {
|
||||
async function getBuilders() {
|
||||
const builders = await queryGlobalView(ViewName.USER_BY_BUILDERS, {
|
||||
include_docs: false,
|
||||
})
|
||||
|
@ -176,7 +194,7 @@ const getBuilders = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
exports.getBuildersCount = async () => {
|
||||
export async function getBuildersCount() {
|
||||
const builders = await getBuilders()
|
||||
return builders.length
|
||||
}
|
||||
|
@ -184,10 +202,14 @@ exports.getBuildersCount = async () => {
|
|||
/**
|
||||
* Logs a user out from budibase. Re-used across account portal and builder.
|
||||
*/
|
||||
exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
||||
export async function platformLogout(opts: PlatformLogoutOpts) {
|
||||
const ctx = opts.ctx
|
||||
const userId = opts.userId
|
||||
const keepActiveSession = opts.keepActiveSession
|
||||
|
||||
if (!ctx) throw new Error("Koa context must be supplied to logout.")
|
||||
|
||||
const currentSession = exports.getCookie(ctx, Cookie.Auth)
|
||||
const currentSession = getCookie(ctx, Cookie.Auth)
|
||||
let sessions = await getSessionsForUser(userId)
|
||||
|
||||
if (keepActiveSession) {
|
||||
|
@ -196,8 +218,8 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
|||
)
|
||||
} else {
|
||||
// clear cookies
|
||||
exports.clearCookie(ctx, Cookie.Auth)
|
||||
exports.clearCookie(ctx, Cookie.CurrentApp)
|
||||
clearCookie(ctx, Cookie.Auth)
|
||||
clearCookie(ctx, Cookie.CurrentApp)
|
||||
}
|
||||
|
||||
const sessionIds = sessions.map(({ sessionId }) => sessionId)
|
||||
|
@ -206,6 +228,6 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
|||
await userCache.invalidateUser(userId)
|
||||
}
|
||||
|
||||
exports.timeout = timeMs => {
|
||||
export function timeout(timeMs: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, timeMs))
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import env from "../src/environment"
|
||||
import { mocks } from "./utilities"
|
||||
|
||||
// must explicitly enable fetch mock
|
||||
mocks.fetch.enable()
|
||||
|
||||
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||
// use tk.reset() to use real dates in individual tests
|
||||
import tk from "timekeeper"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export * as mocks from "./mocks"
|
||||
export * as structures from "./structures"
|
||||
export { generator } from "./structures"
|
||||
|
||||
import * as dbConfig from "./db"
|
||||
dbConfig.init()
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
export const getAccount = jest.fn()
|
||||
export const getAccountByTenantId = jest.fn()
|
||||
export const getStatus = jest.fn()
|
||||
|
||||
jest.mock("../../../src/cloud/accounts", () => ({
|
||||
getAccount,
|
||||
getAccountByTenantId,
|
||||
getStatus,
|
||||
}))
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
const mockFetch = jest.fn()
|
||||
|
||||
const enable = () => {
|
||||
jest.mock("node-fetch", () => mockFetch)
|
||||
}
|
||||
|
||||
export default {
|
||||
...mockFetch,
|
||||
enable,
|
||||
}
|
|
@ -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,23 +1,29 @@
|
|||
import { generator, uuid } from "."
|
||||
import { AuthType, CloudAccount, Hosting } from "@budibase/types"
|
||||
import * as db from "../../../src/db/utils"
|
||||
import { Account, AuthType, CloudAccount, Hosting } from "@budibase/types"
|
||||
|
||||
export const cloudAccount = (): CloudAccount => {
|
||||
export const account = (): Account => {
|
||||
return {
|
||||
accountId: uuid(),
|
||||
tenantId: generator.word(),
|
||||
email: generator.email(),
|
||||
tenantName: generator.word(),
|
||||
hosting: Hosting.SELF,
|
||||
createdAt: Date.now(),
|
||||
verified: true,
|
||||
verificationSent: true,
|
||||
tier: "",
|
||||
email: generator.email(),
|
||||
tenantId: generator.word(),
|
||||
hosting: Hosting.CLOUD,
|
||||
tier: "FREE", // DEPRECATED
|
||||
authType: AuthType.PASSWORD,
|
||||
password: generator.word(),
|
||||
tenantName: generator.word(),
|
||||
name: generator.name(),
|
||||
size: "10+",
|
||||
profession: "Software Engineer",
|
||||
}
|
||||
}
|
||||
|
||||
export const cloudAccount = (): CloudAccount => {
|
||||
return {
|
||||
...account(),
|
||||
hosting: Hosting.CLOUD,
|
||||
budibaseUserId: db.generateGlobalUserID(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
import { v4 as uuid } from "uuid"
|
||||
|
||||
export { v4 as uuid } from "uuid"
|
||||
|
||||
export const email = () => {
|
||||
return `${uuid()}@test.com`
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import { createMockContext } from "@shopify/jest-koa-mocks"
|
||||
import { createMockContext, createMockCookies } from "@shopify/jest-koa-mocks"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
export const newContext = () => {
|
||||
return createMockContext()
|
||||
export const newContext = (): BBContext => {
|
||||
const ctx = createMockContext()
|
||||
return {
|
||||
...ctx,
|
||||
cookies: createMockCookies(),
|
||||
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"
|
||||
|
@ -1483,11 +1471,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"
|
||||
|
@ -2078,14 +2061,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"
|
||||
|
@ -2156,13 +2131,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"
|
||||
|
@ -2223,7 +2191,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==
|
||||
|
@ -2375,11 +2343,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"
|
||||
|
@ -3654,11 +3617,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"
|
||||
|
@ -3678,26 +3636,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"
|
||||
|
@ -3706,11 +3649,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"
|
||||
|
@ -3718,37 +3656,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==
|
||||
|
@ -4131,11 +4039,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"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
@ -38,7 +38,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||
"@budibase/string-templates": "2.1.22-alpha.3",
|
||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||
"@spectrum-css/actionbutton": "^1.0.1",
|
||||
"@spectrum-css/actiongroup": "^1.0.1",
|
||||
"@spectrum-css/avatar": "^3.0.2",
|
||||
|
|
|
@ -10,7 +10,7 @@ filterTests(['smoke', 'all'], () => {
|
|||
})
|
||||
|
||||
if (!(Cypress.env("TEST_ENV"))) {
|
||||
it("should show the new user UI/UX", () => {
|
||||
it.skip("should show the new user UI/UX", () => {
|
||||
cy.visit(`${Cypress.config().baseUrl}/builder/portal/apps/create`, { timeout: 5000 }) //added /portal/apps/create
|
||||
cy.wait(1000)
|
||||
cy.get(interact.CREATE_APP_BUTTON, { timeout: 10000 }).contains('Start from scratch').should("exist")
|
||||
|
@ -83,7 +83,7 @@ filterTests(['smoke', 'all'], () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should create the first application from scratch", () => {
|
||||
it.skip("should create the first application from scratch", () => {
|
||||
const appName = "Cypress Tests"
|
||||
cy.createApp(appName, false)
|
||||
|
||||
|
@ -93,7 +93,7 @@ filterTests(['smoke', 'all'], () => {
|
|||
cy.deleteApp(appName)
|
||||
})
|
||||
|
||||
it("should create the first application from scratch with a default name", () => {
|
||||
it.skip("should create the first application from scratch with a default name", () => {
|
||||
cy.updateUserInformation("", "")
|
||||
cy.createApp("", false)
|
||||
cy.applicationInAppTable("My app")
|
||||
|
|
|
@ -12,7 +12,7 @@ filterTests(['smoke', 'all'], () => {
|
|||
cy.createTestTableWithData()
|
||||
cy.wait(2000)
|
||||
cy.contains("Automate").click()
|
||||
cy.get(interact.ADD_BUTTON_SPECTRUM).click()
|
||||
cy.get(interact.SPECTRUM_BUTTON_TEMPLATE).contains("Add automation").click({ force: true })
|
||||
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
||||
cy.get("input").type("Add Row")
|
||||
cy.contains("Row Created").click({ force: true })
|
||||
|
@ -24,7 +24,7 @@ filterTests(['smoke', 'all'], () => {
|
|||
cy.wait(500)
|
||||
cy.contains("dog").click()
|
||||
// Create action
|
||||
cy.get('[aria-label="AddCircle"]', { timeout: 2000 }).eq(1).click()
|
||||
cy.get('[aria-label="AddCircle"]', { timeout: 2000 }).click()
|
||||
cy.get(interact.MODAL_INNER_WRAPPER).within(() => {
|
||||
cy.wait(1000)
|
||||
cy.contains("Create Row").trigger('mouseover').click().click()
|
||||
|
|
|
@ -9,7 +9,7 @@ filterTests(["smoke", "all"], () => {
|
|||
cy.navigateToFrontend()
|
||||
})
|
||||
|
||||
it("Should successfully create a screen", () => {
|
||||
it.skip("Should successfully create a screen", () => {
|
||||
cy.createScreen("test")
|
||||
cy.get(interact.BODY).within(() => {
|
||||
cy.contains("/test").should("exist")
|
||||
|
@ -23,7 +23,7 @@ filterTests(["smoke", "all"], () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should delete all screens then create first screen via button", () => {
|
||||
it.skip("should delete all screens then create first screen via button", () => {
|
||||
cy.deleteAllScreens()
|
||||
|
||||
cy.contains("Create first screen").click()
|
||||
|
|
|
@ -2,7 +2,7 @@ import filterTests from "../support/filterTests"
|
|||
const interact = require('../support/interact')
|
||||
|
||||
filterTests(['smoke', 'all'], () => {
|
||||
context("Revert apps", () => {
|
||||
xcontext("Revert apps", () => {
|
||||
before(() => {
|
||||
cy.login()
|
||||
cy.createTestApp()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -71,10 +71,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.1.22-alpha.3",
|
||||
"@budibase/client": "2.1.22-alpha.3",
|
||||
"@budibase/frontend-core": "2.1.22-alpha.3",
|
||||
"@budibase/string-templates": "2.1.22-alpha.3",
|
||||
"@budibase/bbui": "2.1.22-alpha.6",
|
||||
"@budibase/client": "2.1.22-alpha.6",
|
||||
"@budibase/frontend-core": "2.1.22-alpha.6",
|
||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
@ -26,9 +26,9 @@
|
|||
"outputPath": "build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/backend-core": "2.1.22-alpha.3",
|
||||
"@budibase/string-templates": "2.1.22-alpha.3",
|
||||
"@budibase/types": "2.1.22-alpha.3",
|
||||
"@budibase/backend-core": "2.1.22-alpha.6",
|
||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||
"@budibase/types": "2.1.22-alpha.6",
|
||||
"axios": "0.21.2",
|
||||
"chalk": "4.1.0",
|
||||
"cli-progress": "3.11.2",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.1.22-alpha.3",
|
||||
"@budibase/frontend-core": "2.1.22-alpha.3",
|
||||
"@budibase/string-templates": "2.1.22-alpha.3",
|
||||
"@budibase/bbui": "2.1.22-alpha.6",
|
||||
"@budibase/frontend-core": "2.1.22-alpha.6",
|
||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||
"@spectrum-css/button": "^3.0.3",
|
||||
"@spectrum-css/card": "^3.0.3",
|
||||
"@spectrum-css/divider": "^1.0.3",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@budibase/frontend-core",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Budibase frontend core libraries used in builder and client",
|
||||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.1.22-alpha.3",
|
||||
"@budibase/bbui": "2.1.22-alpha.6",
|
||||
"lodash": "^4.17.21",
|
||||
"svelte": "^3.46.2"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/sdk",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Budibase Public API SDK",
|
||||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -43,11 +43,11 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "10.0.3",
|
||||
"@budibase/backend-core": "2.1.22-alpha.3",
|
||||
"@budibase/client": "2.1.22-alpha.3",
|
||||
"@budibase/pro": "2.1.22-alpha.3",
|
||||
"@budibase/string-templates": "2.1.22-alpha.3",
|
||||
"@budibase/types": "2.1.22-alpha.3",
|
||||
"@budibase/backend-core": "2.1.22-alpha.6",
|
||||
"@budibase/client": "2.1.22-alpha.6",
|
||||
"@budibase/pro": "2.1.22-alpha.6",
|
||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||
"@budibase/types": "2.1.22-alpha.6",
|
||||
"@bull-board/api": "3.7.0",
|
||||
"@bull-board/koa": "3.9.4",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
|
|
|
@ -2,6 +2,36 @@
|
|||
const updateDotEnv = require("update-dotenv")
|
||||
|
||||
const arg = process.argv.slice(2)[0]
|
||||
const isEnable = arg === "enable"
|
||||
|
||||
let domain = process.argv.slice(2)[1]
|
||||
if (!domain) {
|
||||
domain = "local.com"
|
||||
}
|
||||
|
||||
const getAccountPortalUrl = () => {
|
||||
if (isEnable) {
|
||||
return `http://account.${domain}:10001`
|
||||
} else {
|
||||
return `http://localhost:10001`
|
||||
}
|
||||
}
|
||||
|
||||
const getBudibaseUrl = () => {
|
||||
if (isEnable) {
|
||||
return `http://${domain}:10000`
|
||||
} else {
|
||||
return `http://localhost:10000`
|
||||
}
|
||||
}
|
||||
|
||||
const getCookieDomain = () => {
|
||||
if (isEnable) {
|
||||
return `.${domain}`
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing multi tenancy sub domains locally.
|
||||
|
@ -16,9 +46,7 @@ const arg = process.argv.slice(2)[0]
|
|||
* 127.0.0.1 t2.local.com
|
||||
*/
|
||||
updateDotEnv({
|
||||
ACCOUNT_PORTAL_URL:
|
||||
arg === "enable"
|
||||
? "http://account.local.com:10001"
|
||||
: "http://localhost:10001",
|
||||
COOKIE_DOMAIN: arg === "enable" ? ".local.com" : "",
|
||||
}).then(() => console.log("Updated worker!"))
|
||||
ACCOUNT_PORTAL_URL: getAccountPortalUrl(),
|
||||
COOKIE_DOMAIN: getCookieDomain(),
|
||||
PLATFORM_URL: getBudibaseUrl(),
|
||||
}).then(() => console.log("Updated server!"))
|
||||
|
|
|
@ -149,7 +149,7 @@ export const run = async (db: any) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const allApps: App[] = await dbUtils.getAllApps({ dev: true })
|
||||
const allApps = (await dbUtils.getAllApps({ dev: true })) as App[]
|
||||
totals.apps = allApps.length
|
||||
|
||||
totals.usage = await quotas.backfill(allApps)
|
||||
|
|
|
@ -2,11 +2,11 @@ import { getTenantId } from "@budibase/backend-core/tenancy"
|
|||
import { getAllApps } from "@budibase/backend-core/db"
|
||||
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
|
||||
import { quotas } from "@budibase/pro"
|
||||
import { StaticQuotaName, QuotaUsageType } from "@budibase/types"
|
||||
import { StaticQuotaName, QuotaUsageType, App } from "@budibase/types"
|
||||
|
||||
export const run = async () => {
|
||||
// get all rows in all apps
|
||||
const allApps = await getAllApps({ all: true })
|
||||
const allApps = (await getAllApps({ all: true })) as App[]
|
||||
const appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : []
|
||||
const { appRows } = await getUniqueRows(appIds)
|
||||
|
||||
|
|
|
@ -1273,12 +1273,12 @@
|
|||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@budibase/backend-core@2.1.22-alpha.3":
|
||||
version "2.1.22-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.22-alpha.3.tgz#5cdff781cf6a448677304aa80a9658e9e51c36a5"
|
||||
integrity sha512-je1mKTb1h9f+tyCAiFNJykg9O8ZM3smVQ3JJf24rXDYvLYN8GG8hxZ5fOJuWoeF/aGu8m+w3v3+EdWuTlQPcKg==
|
||||
"@budibase/backend-core@2.1.22-alpha.6":
|
||||
version "2.1.22-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.22-alpha.6.tgz#e9886620fcbee6fe0348365cb96ac6484fc5f8fd"
|
||||
integrity sha512-P3NSgNuQXKmdeT8MLfeCji3ibRSeIIMSOQeNSQBWpaOTA69rpXQk753lHRwUWMpqil/ybsOuE/h1/Y3eTa+/UA==
|
||||
dependencies:
|
||||
"@budibase/types" "2.1.22-alpha.3"
|
||||
"@budibase/types" "2.1.22-alpha.6"
|
||||
"@shopify/jest-koa-mocks" "5.0.1"
|
||||
"@techpass/passport-openidconnect" "0.3.2"
|
||||
aws-sdk "2.1030.0"
|
||||
|
@ -1360,13 +1360,13 @@
|
|||
svelte-flatpickr "^3.2.3"
|
||||
svelte-portal "^1.0.0"
|
||||
|
||||
"@budibase/pro@2.1.22-alpha.3":
|
||||
version "2.1.22-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.22-alpha.3.tgz#8f87ffc1c6c158ad467216d5cfcb7844e9a08363"
|
||||
integrity sha512-IE0eHPswBycPYzvduZCAp4T6U8t1qTcT1A9jVJ8TeX2jiL22hPOQ+8nVNIaqTGfQvJ7foGiqtDcugQbk3QLGOg==
|
||||
"@budibase/pro@2.1.22-alpha.6":
|
||||
version "2.1.22-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.22-alpha.6.tgz#e4d8238f1727eab85ac72b3ce2fcaec2a5c18456"
|
||||
integrity sha512-1CKqQ2HMX+/5p24aHpPlUgxoMjKRZxRoyK5fPD/X35Z0mDj+9Ohny3oqbF1fC20pl/20bmcaE4J+q2ph/pbxdQ==
|
||||
dependencies:
|
||||
"@budibase/backend-core" "2.1.22-alpha.3"
|
||||
"@budibase/types" "2.1.22-alpha.3"
|
||||
"@budibase/backend-core" "2.1.22-alpha.6"
|
||||
"@budibase/types" "2.1.22-alpha.6"
|
||||
"@koa/router" "8.0.8"
|
||||
bull "4.10.1"
|
||||
joi "17.6.0"
|
||||
|
@ -1390,10 +1390,10 @@
|
|||
svelte-apexcharts "^1.0.2"
|
||||
svelte-flatpickr "^3.1.0"
|
||||
|
||||
"@budibase/types@2.1.22-alpha.3":
|
||||
version "2.1.22-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.22-alpha.3.tgz#30cfcb6a58989a94dfba43ec7f9052e4855b6803"
|
||||
integrity sha512-3WKZ5DVkygUi9H3KJTL8geQf9cjmssM8tsbDEFi8KJHogfszJPia9di/DnN8rd/CXbsx3Zbsbe8LHiAAADz5og==
|
||||
"@budibase/types@2.1.22-alpha.6":
|
||||
version "2.1.22-alpha.6"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.22-alpha.6.tgz#a280321373c26a5bf1a52ed119de9178292893c0"
|
||||
integrity sha512-rVrhs9u7OTzlCxgUFqBu5H6jsMHhwH8uduPhT8Eo7J+Wr4J/0Io7WeuAt1egK6t83JiEPxDBH3WLzAH+04hPVA==
|
||||
|
||||
"@bull-board/api@3.7.0":
|
||||
version "3.7.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/types",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Budibase types",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -51,3 +51,9 @@ export interface SearchUsersRequest {
|
|||
appId?: string
|
||||
userIds?: string[]
|
||||
}
|
||||
|
||||
export interface CreateAdminUserRequest {
|
||||
email: string
|
||||
password: string
|
||||
tenantId: string
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./info"
|
||||
export * from "./users"
|
||||
export * from "./accounts"
|
||||
export * from "./tenants"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { Document } from "../document"
|
||||
|
||||
export interface Tenants extends Document {
|
||||
tenantIds: string[]
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import { BBContext } from "./koa"
|
||||
|
||||
export interface AuthToken {
|
||||
userId: string
|
||||
tenantId: string
|
||||
|
@ -25,3 +27,9 @@ export interface SessionKey {
|
|||
export interface ScannedSession {
|
||||
value: Session
|
||||
}
|
||||
|
||||
export interface PlatformLogoutOpts {
|
||||
ctx: BBContext
|
||||
userId: string
|
||||
keepActiveSession?: boolean
|
||||
}
|
||||
|
|
|
@ -9,3 +9,4 @@ export * from "./koa"
|
|||
export * from "./auth"
|
||||
export * from "./locks"
|
||||
export * from "./db"
|
||||
export * from "./middleware"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./matchers"
|
||||
export * from "./tenancy"
|
|
@ -0,0 +1,22 @@
|
|||
export interface EndpointMatcher {
|
||||
/**
|
||||
* The HTTP Path. e.g. /api/things/:thingId
|
||||
*/
|
||||
route: string
|
||||
/**
|
||||
* The HTTP Verb. e.g. GET, POST, etc.
|
||||
* ALL is also accepted to cover all verbs.
|
||||
*/
|
||||
method: string
|
||||
/**
|
||||
* The route must match exactly - not just begins with
|
||||
*/
|
||||
strict?: boolean
|
||||
}
|
||||
|
||||
export interface RegexMatcher {
|
||||
regex: RegExp
|
||||
method: string
|
||||
strict: boolean
|
||||
route: string
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
export interface GetTenantIdOptions {
|
||||
allowNoTenant?: boolean
|
||||
excludeStrategies?: TenantResolutionStrategy[]
|
||||
includeStrategies?: TenantResolutionStrategy[]
|
||||
}
|
||||
|
||||
export enum TenantResolutionStrategy {
|
||||
USER = "user",
|
||||
HEADER = "header",
|
||||
QUERY = "query",
|
||||
SUBDOMAIN = "subdomain",
|
||||
PATH = "path",
|
||||
}
|
|
@ -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
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "2.1.22-alpha.3",
|
||||
"version": "2.1.22-alpha.6",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -36,10 +36,10 @@
|
|||
"author": "Budibase",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@budibase/backend-core": "2.1.22-alpha.3",
|
||||
"@budibase/pro": "2.1.22-alpha.3",
|
||||
"@budibase/string-templates": "2.1.22-alpha.3",
|
||||
"@budibase/types": "2.1.22-alpha.3",
|
||||
"@budibase/backend-core": "2.1.22-alpha.6",
|
||||
"@budibase/pro": "2.1.22-alpha.6",
|
||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||
"@budibase/types": "2.1.22-alpha.6",
|
||||
"@koa/router": "8.0.8",
|
||||
"@sentry/node": "6.17.7",
|
||||
"@techpass/passport-openidconnect": "0.3.2",
|
||||
|
@ -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",
|
||||
|
|
|
@ -2,6 +2,36 @@
|
|||
const updateDotEnv = require("update-dotenv")
|
||||
|
||||
const arg = process.argv.slice(2)[0]
|
||||
const isEnable = arg === "enable"
|
||||
|
||||
let domain = process.argv.slice(2)[1]
|
||||
if (!domain) {
|
||||
domain = "local.com"
|
||||
}
|
||||
|
||||
const getAccountPortalUrl = () => {
|
||||
if (isEnable) {
|
||||
return `http://account.${domain}:10001`
|
||||
} else {
|
||||
return `http://localhost:10001`
|
||||
}
|
||||
}
|
||||
|
||||
const getBudibaseUrl = () => {
|
||||
if (isEnable) {
|
||||
return `http://${domain}:10000`
|
||||
} else {
|
||||
return `http://localhost:10000`
|
||||
}
|
||||
}
|
||||
|
||||
const getCookieDomain = () => {
|
||||
if (isEnable) {
|
||||
return `.${domain}`
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing multi tenancy sub domains locally.
|
||||
|
@ -16,11 +46,7 @@ const arg = process.argv.slice(2)[0]
|
|||
* 127.0.0.1 t2.local.com
|
||||
*/
|
||||
updateDotEnv({
|
||||
ACCOUNT_PORTAL_URL:
|
||||
arg === "enable"
|
||||
? "http://account.local.com:10001"
|
||||
: "http://localhost:10001",
|
||||
COOKIE_DOMAIN: arg === "enable" ? ".local.com" : "",
|
||||
PLATFORM_URL:
|
||||
arg === "enable" ? "http://local.com:10000" : "http://localhost:10000",
|
||||
ACCOUNT_PORTAL_URL: getAccountPortalUrl(),
|
||||
COOKIE_DOMAIN: getCookieDomain(),
|
||||
PLATFORM_URL: getBudibaseUrl(),
|
||||
}).then(() => console.log("Updated worker!"))
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
const core = require("@budibase/backend-core")
|
||||
const { Config, EmailTemplatePurpose } = require("../../../constants")
|
||||
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
||||
import core from "@budibase/backend-core"
|
||||
import {
|
||||
events,
|
||||
users as usersCore,
|
||||
context,
|
||||
tenancy,
|
||||
} from "@budibase/backend-core"
|
||||
import { Config, EmailTemplatePurpose } from "../../../constants"
|
||||
import { sendEmail, isEmailConfigured } from "../../../utilities/email"
|
||||
import { checkResetPasswordCode } from "../../../utilities/redis"
|
||||
import env from "../../../environment"
|
||||
import sdk from "../../../sdk"
|
||||
import { User } from "@budibase/types"
|
||||
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
|
||||
const { Cookie, Header } = core.constants
|
||||
const { passport, ssoCallbackUrl, google, oidc } = core.auth
|
||||
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
||||
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
||||
const env = require("../../../environment")
|
||||
import { events, users as usersCore, context } from "@budibase/backend-core"
|
||||
import sdk from "../../../sdk"
|
||||
import { User } from "@budibase/types"
|
||||
|
||||
export const googleCallbackUrl = async (config: any) => {
|
||||
return ssoCallbackUrl(getGlobalDB(), config, "google")
|
||||
return ssoCallbackUrl(tenancy.getGlobalDB(), config, "google")
|
||||
}
|
||||
|
||||
export const oidcCallbackUrl = async (config: any) => {
|
||||
return ssoCallbackUrl(getGlobalDB(), config, "oidc")
|
||||
return ssoCallbackUrl(tenancy.getGlobalDB(), config, "oidc")
|
||||
}
|
||||
|
||||
async function authInternal(ctx: any, user: any, err = null, info = null) {
|
||||
|
@ -106,7 +110,7 @@ export const resetUpdate = async (ctx: any) => {
|
|||
const { resetCode, password } = ctx.request.body
|
||||
try {
|
||||
const { userId } = await checkResetPasswordCode(resetCode)
|
||||
const db = getGlobalDB()
|
||||
const db = tenancy.getGlobalDB()
|
||||
const user = await db.get(userId)
|
||||
user.password = await hash(password)
|
||||
await db.put(user)
|
||||
|
@ -160,7 +164,7 @@ export const datasourceAuth = async (ctx: any, next: any) => {
|
|||
* On a successful login, you will be redirected to the googleAuth callback route.
|
||||
*/
|
||||
export const googlePreAuth = async (ctx: any, next: any) => {
|
||||
const db = getGlobalDB()
|
||||
const db = tenancy.getGlobalDB()
|
||||
|
||||
const config = await core.db.getScopedConfig(db, {
|
||||
type: Config.GOOGLE,
|
||||
|
@ -181,7 +185,7 @@ export const googlePreAuth = async (ctx: any, next: any) => {
|
|||
}
|
||||
|
||||
export const googleAuth = async (ctx: any, next: any) => {
|
||||
const db = getGlobalDB()
|
||||
const db = tenancy.getGlobalDB()
|
||||
|
||||
const config = await core.db.getScopedConfig(db, {
|
||||
type: Config.GOOGLE,
|
||||
|
@ -208,7 +212,7 @@ export const googleAuth = async (ctx: any, next: any) => {
|
|||
}
|
||||
|
||||
export const oidcStrategyFactory = async (ctx: any, configId: any) => {
|
||||
const db = getGlobalDB()
|
||||
const db = tenancy.getGlobalDB()
|
||||
const config = await core.db.getScopedConfig(db, {
|
||||
type: Config.OIDC,
|
||||
group: ctx.query.group,
|
||||
|
@ -235,7 +239,7 @@ export const oidcPreAuth = async (ctx: any, next: any) => {
|
|||
|
||||
setCookie(ctx, configId, Cookie.OIDC_CONFIG)
|
||||
|
||||
const db = getGlobalDB()
|
||||
const db = tenancy.getGlobalDB()
|
||||
const config = await core.db.getScopedConfig(db, {
|
||||
type: Config.OIDC,
|
||||
group: ctx.query.group,
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
BulkUserRequest,
|
||||
BulkUserResponse,
|
||||
CloudAccount,
|
||||
CreateAdminUserRequest,
|
||||
InviteUserRequest,
|
||||
InviteUsersRequest,
|
||||
SearchUsersRequest,
|
||||
|
@ -67,7 +68,8 @@ const parseBooleanParam = (param: any) => {
|
|||
}
|
||||
|
||||
export const adminUser = async (ctx: any) => {
|
||||
const { email, password, tenantId } = ctx.request.body
|
||||
const { email, password, tenantId } = ctx.request
|
||||
.body as CreateAdminUserRequest
|
||||
await tenancy.doInTenant(tenantId, async () => {
|
||||
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
||||
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const env = require("../../../environment")
|
||||
import { BBContext } from "@budibase/types"
|
||||
import env from "../../../environment"
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
export const fetch = async (ctx: BBContext) => {
|
||||
ctx.body = {
|
||||
multiTenancy: !!env.MULTI_TENANCY,
|
||||
cloud: !env.SELF_HOSTED,
|
|
@ -1,7 +1,8 @@
|
|||
const accounts = require("@budibase/backend-core/accounts")
|
||||
const env = require("../../../environment")
|
||||
import { accounts } from "@budibase/backend-core"
|
||||
import env from "../../../environment"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
export const fetch = async (ctx: BBContext) => {
|
||||
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||
const status = await accounts.getStatus()
|
||||
ctx.body = status
|
|
@ -1,61 +1,18 @@
|
|||
const { StaticDatabases, doWithDB } = require("@budibase/backend-core/db")
|
||||
const { getTenantId } = require("@budibase/backend-core/tenancy")
|
||||
const { deleteTenant } = require("@budibase/backend-core/deprovision")
|
||||
import { BBContext } from "@budibase/types"
|
||||
import { deprovisioning } from "@budibase/backend-core"
|
||||
import { quotas } from "@budibase/pro"
|
||||
|
||||
export const exists = async (ctx: any) => {
|
||||
const tenantId = ctx.request.params
|
||||
ctx.body = {
|
||||
exists: await doWithDB(
|
||||
StaticDatabases.PLATFORM_INFO.name,
|
||||
async (db: any) => {
|
||||
let exists = false
|
||||
try {
|
||||
const tenantsDoc = await db.get(
|
||||
StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
)
|
||||
if (tenantsDoc) {
|
||||
exists = tenantsDoc.tenantIds.indexOf(tenantId) !== -1
|
||||
}
|
||||
} catch (err) {
|
||||
// if error it doesn't exist
|
||||
}
|
||||
return exists
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
const _delete = async (ctx: BBContext) => {
|
||||
const user = ctx.user!
|
||||
const tenantId = ctx.params.tenantId
|
||||
|
||||
export const fetch = async (ctx: any) => {
|
||||
ctx.body = await doWithDB(
|
||||
StaticDatabases.PLATFORM_INFO.name,
|
||||
async (db: any) => {
|
||||
let tenants = []
|
||||
try {
|
||||
const tenantsDoc = await db.get(
|
||||
StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
)
|
||||
if (tenantsDoc) {
|
||||
tenants = tenantsDoc.tenantIds
|
||||
}
|
||||
} catch (err) {
|
||||
// if error it doesn't exist
|
||||
}
|
||||
return tenants
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const _delete = async (ctx: any) => {
|
||||
const tenantId = getTenantId()
|
||||
|
||||
if (ctx.params.tenantId !== tenantId) {
|
||||
ctx.throw(403, "Unauthorized")
|
||||
if (tenantId !== user.tenantId) {
|
||||
ctx.throw(403, "Tenant ID does not match current user")
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteTenant(tenantId)
|
||||
await quotas.bustCache()
|
||||
await deprovisioning.deleteTenant(tenantId)
|
||||
ctx.status = 204
|
||||
} catch (err) {
|
||||
ctx.log.error(err)
|
||||
|
|
|
@ -7,11 +7,12 @@ import { errors, auth, middleware } from "@budibase/backend-core"
|
|||
import { APIError } from "@budibase/types"
|
||||
|
||||
const PUBLIC_ENDPOINTS = [
|
||||
// old deprecated endpoints kept for backwards compat
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/admin/auth/google/callback",
|
||||
method: "GET",
|
||||
},
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/admin/auth/oidc/callback",
|
||||
method: "GET",
|
||||
|
@ -44,17 +45,19 @@ const PUBLIC_ENDPOINTS = [
|
|||
method: "POST",
|
||||
},
|
||||
{
|
||||
route: "api/system/environment",
|
||||
route: "/api/system/environment",
|
||||
method: "GET",
|
||||
},
|
||||
{
|
||||
route: "api/system/status",
|
||||
route: "/api/system/status",
|
||||
method: "GET",
|
||||
},
|
||||
// TODO: This should be an internal api
|
||||
{
|
||||
route: "/api/global/users/tenant/:id",
|
||||
method: "GET",
|
||||
},
|
||||
// TODO: This should be an internal api
|
||||
{
|
||||
route: "/api/system/restored",
|
||||
method: "POST",
|
||||
|
@ -62,17 +65,37 @@ const PUBLIC_ENDPOINTS = [
|
|||
]
|
||||
|
||||
const NO_TENANCY_ENDPOINTS = [
|
||||
...PUBLIC_ENDPOINTS,
|
||||
// system endpoints are not specific to any tenant
|
||||
{
|
||||
route: "/api/system",
|
||||
method: "ALL",
|
||||
},
|
||||
// tenant is determined in request body
|
||||
// used for creating the tenant
|
||||
{
|
||||
route: "/api/global/users/self",
|
||||
route: "/api/global/users/init",
|
||||
method: "POST",
|
||||
},
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/admin/auth/google/callback",
|
||||
method: "GET",
|
||||
},
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/global/self",
|
||||
route: "/api/admin/auth/oidc/callback",
|
||||
method: "GET",
|
||||
},
|
||||
// tenant is determined from code in redis
|
||||
{
|
||||
route: "/api/global/users/invite/accept",
|
||||
method: "POST",
|
||||
},
|
||||
// global user search - no tenancy
|
||||
// :id is user id
|
||||
// TODO: this should really be `/api/system/users/:id`
|
||||
{
|
||||
route: "/api/global/users/tenant/:id",
|
||||
method: "GET",
|
||||
},
|
||||
]
|
||||
|
|
|
@ -2,7 +2,6 @@ const Router = require("@koa/router")
|
|||
const authController = require("../../controllers/global/auth")
|
||||
const { joiValidator } = require("@budibase/backend-core/auth")
|
||||
const Joi = require("joi")
|
||||
const { updateTenantId } = require("@budibase/backend-core/tenancy")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
|
@ -29,77 +28,61 @@ function buildResetUpdateValidation() {
|
|||
}).required().unknown(false))
|
||||
}
|
||||
|
||||
function updateTenant(ctx, next) {
|
||||
if (ctx.params) {
|
||||
updateTenantId(ctx.params.tenantId)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
router
|
||||
// PASSWORD
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/login",
|
||||
buildAuthValidation(),
|
||||
updateTenant,
|
||||
authController.authenticate
|
||||
)
|
||||
.post("/api/global/auth/logout", authController.logout)
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/reset",
|
||||
buildResetValidation(),
|
||||
updateTenant,
|
||||
authController.reset
|
||||
)
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/reset/update",
|
||||
buildResetUpdateValidation(),
|
||||
updateTenant,
|
||||
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",
|
||||
updateTenant,
|
||||
authController.googlePreAuth
|
||||
)
|
||||
|
||||
// DATASOURCE - MULTI TENANT
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/datasource/:provider",
|
||||
updateTenant,
|
||||
authController.datasourcePreAuth
|
||||
)
|
||||
// single tenancy endpoint
|
||||
.get("/api/global/auth/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
|
||||
)
|
||||
// multi-tenancy endpoint
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/google/callback",
|
||||
updateTenant,
|
||||
authController.googleAuth
|
||||
)
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/datasource/:provider/callback",
|
||||
updateTenant,
|
||||
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",
|
||||
updateTenant,
|
||||
authController.oidcPreAuth
|
||||
)
|
||||
// single tenancy endpoint
|
||||
.get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth)
|
||||
|
||||
// OIDC - SINGLE TENANT - DEPRECATED
|
||||
.get("/api/global/auth/oidc/callback", authController.oidcAuth)
|
||||
// multi-tenancy endpoint
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/oidc/callback",
|
||||
updateTenant,
|
||||
authController.oidcAuth
|
||||
)
|
||||
// deprecated - used by the default system before tenancy
|
||||
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../../controllers/global/templates"
|
||||
import { TemplatePurpose, TemplateTypes } from "../../../constants"
|
||||
import { TemplatePurpose, TemplateType } from "../../../constants"
|
||||
import { auth as authCore } from "@budibase/backend-core"
|
||||
import Joi from "joi"
|
||||
const { adminOnly, joiValidator } = authCore
|
||||
|
@ -16,7 +16,7 @@ function buildTemplateSaveValidation() {
|
|||
name: Joi.string().allow(null, ""),
|
||||
contents: Joi.string().required(),
|
||||
purpose: Joi.string().required().valid(...Object.values(TemplatePurpose)),
|
||||
type: Joi.string().required().valid(...Object.values(TemplateTypes)),
|
||||
type: Joi.string().required().valid(...Object.values(TemplateType)),
|
||||
}).required().unknown(true).optional())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
jest.mock("nodemailer")
|
||||
import { TestConfiguration, mocks, API } from "../../../../tests"
|
||||
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()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
|
@ -19,90 +24,155 @@ describe("/api/global/auth", () => {
|
|||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should logout", async () => {
|
||||
await api.auth.logout()
|
||||
expect(events.auth.logout).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should be able to generate password reset email", async () => {
|
||||
const { res, code } = await 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 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 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 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 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,12 +1,11 @@
|
|||
// mock the email system
|
||||
jest.mock("nodemailer")
|
||||
import { TestConfiguration, structures, mocks, API } from "../../../../tests"
|
||||
import { TestConfiguration, structures, mocks } from "../../../../tests"
|
||||
mocks.email.mock()
|
||||
import { Config, events } from "@budibase/backend-core"
|
||||
|
||||
describe("configs", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
|
@ -28,7 +27,7 @@ describe("configs", () => {
|
|||
_rev,
|
||||
}
|
||||
|
||||
const res = await api.configs.saveConfig(data)
|
||||
const res = await config.api.configs.saveConfig(data)
|
||||
|
||||
return {
|
||||
...data,
|
||||
|
@ -235,7 +234,7 @@ describe("configs", () => {
|
|||
expect(events.org.nameUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.platformURLUpdated).toBeCalledTimes(1)
|
||||
config.modeAccount()
|
||||
config.modeCloud()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -257,7 +256,7 @@ describe("configs", () => {
|
|||
expect(events.org.nameUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.platformURLUpdated).toBeCalledTimes(1)
|
||||
config.modeAccount()
|
||||
config.modeCloud()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -266,7 +265,7 @@ describe("configs", () => {
|
|||
it("should return the correct checklist status based on the state of the budibase installation", async () => {
|
||||
await config.saveSmtpConfig()
|
||||
|
||||
const res = await api.configs.getConfigChecklist()
|
||||
const res = await config.api.configs.getConfigChecklist()
|
||||
const checklist = res.body
|
||||
|
||||
expect(checklist.apps.checked).toBeFalsy()
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
jest.mock("nodemailer")
|
||||
import { TestConfiguration, mocks, API } from "../../../../tests"
|
||||
import { TestConfiguration, mocks } from "../../../../tests"
|
||||
const sendMailMock = mocks.email.mock()
|
||||
import { EmailTemplatePurpose } from "../../../../constants"
|
||||
|
||||
describe("/api/global/email", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
|
@ -20,7 +19,9 @@ describe("/api/global/email", () => {
|
|||
await config.saveSmtpConfig()
|
||||
await config.saveSettingsConfig()
|
||||
|
||||
const res = await api.emails.sendEmail(EmailTemplatePurpose.INVITATION)
|
||||
const res = await config.api.emails.sendEmail(
|
||||
EmailTemplatePurpose.INVITATION
|
||||
)
|
||||
|
||||
expect(res.body.message).toBeDefined()
|
||||
expect(sendMailMock).toHaveBeenCalled()
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
|
||||
// TODO
|
||||
|
||||
describe("/api/global/license", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("POST /api/global/license/activate", () => {
|
||||
it("activates license", () => {})
|
||||
})
|
||||
|
||||
describe("POST /api/global/license/refresh", () => {})
|
||||
|
||||
describe("GET /api/global/license/info", () => {})
|
||||
|
||||
describe("DELETE /api/global/license/info", () => {})
|
||||
|
||||
describe("GET /api/global/license/usage", () => {})
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
import { TestConfiguration } from "../../../../tests"
|
||||
import { EmailTemplatePurpose } from "../../../../constants"
|
||||
const nodemailer = require("nodemailer")
|
||||
const fetch = require("node-fetch")
|
||||
|
@ -8,7 +8,6 @@ jest.setTimeout(30000)
|
|||
|
||||
describe("/api/global/email", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
|
@ -35,7 +34,7 @@ describe("/api/global/email", () => {
|
|||
await Promise.race([config.saveEtherealSmtpConfig(), timeout()])
|
||||
await Promise.race([config.saveSettingsConfig(), timeout()])
|
||||
|
||||
const res = await api.emails.sendEmail(purpose).timeout(20000)
|
||||
const res = await config.api.emails.sendEmail(purpose).timeout(20000)
|
||||
// ethereal hiccup, can't test right now
|
||||
if (res.status >= 300) {
|
||||
return
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
|
||||
// TODO
|
||||
|
||||
describe("/api/global/roles", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/global/roles", () => {
|
||||
it("retrieves roles", () => {})
|
||||
})
|
||||
|
||||
describe("GET /api/global/roles/:appId", () => {})
|
||||
|
||||
describe("DELETE /api/global/roles/:appId", () => {})
|
||||
})
|
|
@ -1,10 +1,9 @@
|
|||
jest.mock("nodemailer")
|
||||
import { TestConfiguration, API, mocks } from "../../../../tests"
|
||||
import { TestConfiguration, mocks } from "../../../../tests"
|
||||
import { events } from "@budibase/backend-core"
|
||||
|
||||
describe("/api/global/self", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
|
@ -24,7 +23,7 @@ describe("/api/global/self", () => {
|
|||
await config.createSession(user)
|
||||
|
||||
delete user.password
|
||||
const res = await api.self.updateSelf(user)
|
||||
const res = await config.api.self.updateSelf(user)
|
||||
|
||||
const dbUser = await config.getUser(user.email)
|
||||
user._rev = dbUser._rev
|
||||
|
@ -40,7 +39,7 @@ describe("/api/global/self", () => {
|
|||
await config.createSession(user)
|
||||
|
||||
user.password = "newPassword"
|
||||
const res = await api.self.updateSelf(user)
|
||||
const res = await config.api.self.updateSelf(user)
|
||||
|
||||
const dbUser = await config.getUser(user.email)
|
||||
user._rev = dbUser._rev
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
|
||||
// TODO
|
||||
|
||||
describe("/api/global/template", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/global/template/definitions", () => {
|
||||
it("retrieves definitions", () => {})
|
||||
})
|
||||
|
||||
describe("POST /api/global/template", () => {})
|
||||
|
||||
describe("GET /api/global/template", () => {})
|
||||
|
||||
describe("GET /api/global/template/:type", () => {})
|
||||
|
||||
describe("GET /api/global/template/:ownerId", () => {})
|
||||
|
||||
describe("GET /api/global/template/:id", () => {})
|
||||
|
||||
describe("DELETE /api/global/template/:id/:rev", () => {})
|
||||
})
|
|
@ -6,14 +6,12 @@ import {
|
|||
mocks,
|
||||
structures,
|
||||
TENANT_1,
|
||||
API,
|
||||
} from "../../../../tests"
|
||||
const sendMailMock = mocks.email.mock()
|
||||
import { events, tenancy } from "@budibase/backend-core"
|
||||
|
||||
describe("/api/global/users", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
|
@ -30,7 +28,10 @@ describe("/api/global/users", () => {
|
|||
describe("invite", () => {
|
||||
it("should be able to generate an invitation", async () => {
|
||||
const email = structures.users.newEmail()
|
||||
const { code, res } = await api.users.sendUserInvite(sendMailMock, email)
|
||||
const { code, res } = await config.api.users.sendUserInvite(
|
||||
sendMailMock,
|
||||
email
|
||||
)
|
||||
|
||||
expect(res.body).toEqual({ message: "Invitation has been sent." })
|
||||
expect(sendMailMock).toHaveBeenCalled()
|
||||
|
@ -39,7 +40,7 @@ describe("/api/global/users", () => {
|
|||
})
|
||||
|
||||
it("should not be able to generate an invitation for existing user", async () => {
|
||||
const { code, res } = await api.users.sendUserInvite(
|
||||
const { code, res } = await config.api.users.sendUserInvite(
|
||||
sendMailMock,
|
||||
config.defaultUser!.email,
|
||||
400
|
||||
|
@ -53,9 +54,12 @@ describe("/api/global/users", () => {
|
|||
|
||||
it("should be able to create new user from invite", async () => {
|
||||
const email = structures.users.newEmail()
|
||||
const { code } = await api.users.sendUserInvite(sendMailMock, email)
|
||||
const { code } = await config.api.users.sendUserInvite(
|
||||
sendMailMock,
|
||||
email
|
||||
)
|
||||
|
||||
const res = await api.users.acceptInvite(code)
|
||||
const res = await config.api.users.acceptInvite(code)
|
||||
|
||||
expect(res.body._id).toBeDefined()
|
||||
const user = await config.getUser(email)
|
||||
|
@ -74,7 +78,7 @@ describe("/api/global/users", () => {
|
|||
})
|
||||
const request = [newUserInvite(), newUserInvite()]
|
||||
|
||||
const res = await api.users.sendMultiUserInvite(request)
|
||||
const res = await config.api.users.sendMultiUserInvite(request)
|
||||
|
||||
const body = res.body as InviteUsersResponse
|
||||
expect(body.successful.length).toBe(2)
|
||||
|
@ -86,7 +90,7 @@ describe("/api/global/users", () => {
|
|||
it("should not be able to generate an invitation for existing user", async () => {
|
||||
const request = [{ email: config.defaultUser!.email, userInfo: {} }]
|
||||
|
||||
const res = await api.users.sendMultiUserInvite(request)
|
||||
const res = await config.api.users.sendMultiUserInvite(request)
|
||||
|
||||
const body = res.body as InviteUsersResponse
|
||||
expect(body.successful.length).toBe(0)
|
||||
|
@ -102,7 +106,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser()
|
||||
jest.clearAllMocks()
|
||||
|
||||
const response = await api.users.bulkCreateUsers([user])
|
||||
const response = await config.api.users.bulkCreateUsers([user])
|
||||
|
||||
expect(response.created?.successful.length).toBe(0)
|
||||
expect(response.created?.unsuccessful.length).toBe(1)
|
||||
|
@ -115,7 +119,7 @@ describe("/api/global/users", () => {
|
|||
jest.resetAllMocks()
|
||||
|
||||
await tenancy.doInTenant(TENANT_1, async () => {
|
||||
const response = await api.users.bulkCreateUsers([user])
|
||||
const response = await config.api.users.bulkCreateUsers([user])
|
||||
|
||||
expect(response.created?.successful.length).toBe(0)
|
||||
expect(response.created?.unsuccessful.length).toBe(1)
|
||||
|
@ -126,11 +130,11 @@ describe("/api/global/users", () => {
|
|||
|
||||
it("should ignore accounts using the same email", async () => {
|
||||
const account = structures.accounts.account()
|
||||
const resp = await api.accounts.saveMetadata(account)
|
||||
const resp = await config.api.accounts.saveMetadata(account)
|
||||
const user = structures.users.user({ email: resp.email })
|
||||
jest.clearAllMocks()
|
||||
|
||||
const response = await api.users.bulkCreateUsers([user])
|
||||
const response = await config.api.users.bulkCreateUsers([user])
|
||||
|
||||
expect(response.created?.successful.length).toBe(0)
|
||||
expect(response.created?.unsuccessful.length).toBe(1)
|
||||
|
@ -143,7 +147,11 @@ describe("/api/global/users", () => {
|
|||
const admin = structures.users.adminUser()
|
||||
const user = structures.users.user()
|
||||
|
||||
const response = await api.users.bulkCreateUsers([builder, admin, user])
|
||||
const response = await config.api.users.bulkCreateUsers([
|
||||
builder,
|
||||
admin,
|
||||
user,
|
||||
])
|
||||
|
||||
expect(response.created?.successful.length).toBe(3)
|
||||
expect(response.created?.successful[0].email).toBe(builder.email)
|
||||
|
@ -160,7 +168,7 @@ describe("/api/global/users", () => {
|
|||
it("should be able to create a basic user", async () => {
|
||||
const user = structures.users.user()
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
expect(events.user.created).toBeCalledTimes(1)
|
||||
expect(events.user.updated).not.toBeCalled()
|
||||
|
@ -171,7 +179,7 @@ describe("/api/global/users", () => {
|
|||
it("should be able to create an admin user", async () => {
|
||||
const user = structures.users.adminUser()
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
expect(events.user.created).toBeCalledTimes(1)
|
||||
expect(events.user.updated).not.toBeCalled()
|
||||
|
@ -182,7 +190,7 @@ describe("/api/global/users", () => {
|
|||
it("should be able to create a builder user", async () => {
|
||||
const user = structures.users.builderUser()
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
expect(events.user.created).toBeCalledTimes(1)
|
||||
expect(events.user.updated).not.toBeCalled()
|
||||
|
@ -197,7 +205,7 @@ describe("/api/global/users", () => {
|
|||
app_456: "role2",
|
||||
}
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
const savedUser = await config.getUser(user.email)
|
||||
expect(events.user.created).toBeCalledTimes(1)
|
||||
|
@ -213,7 +221,7 @@ describe("/api/global/users", () => {
|
|||
delete user._id
|
||||
delete user._rev
|
||||
|
||||
const response = await api.users.saveUser(user, 400)
|
||||
const response = await config.api.users.saveUser(user, 400)
|
||||
|
||||
expect(response.body.message).toBe(`Unavailable`)
|
||||
expect(events.user.created).toBeCalledTimes(0)
|
||||
|
@ -225,7 +233,7 @@ describe("/api/global/users", () => {
|
|||
|
||||
await tenancy.doInTenant(TENANT_1, async () => {
|
||||
delete user._id
|
||||
const response = await api.users.saveUser(user, 400)
|
||||
const response = await config.api.users.saveUser(user, 400)
|
||||
|
||||
expect(response.body.message).toBe(`Unavailable`)
|
||||
expect(events.user.created).toBeCalledTimes(0)
|
||||
|
@ -237,7 +245,7 @@ describe("/api/global/users", () => {
|
|||
const account = structures.accounts.cloudAccount()
|
||||
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
||||
|
||||
const response = await api.users.saveUser(user, 400)
|
||||
const response = await config.api.users.saveUser(user, 400)
|
||||
|
||||
expect(response.body.message).toBe(`Unavailable`)
|
||||
expect(events.user.created).toBeCalledTimes(0)
|
||||
|
@ -245,20 +253,20 @@ describe("/api/global/users", () => {
|
|||
|
||||
it("should not be able to create a user with the same email and different casing", async () => {
|
||||
const user = structures.users.user()
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
user.email = user.email.toUpperCase()
|
||||
await api.users.saveUser(user, 400)
|
||||
await config.api.users.saveUser(user, 400)
|
||||
|
||||
expect(events.user.created).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should not be able to bulk create a user with the same email and different casing", async () => {
|
||||
const user = structures.users.user()
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
user.email = user.email.toUpperCase()
|
||||
await api.users.bulkCreateUsers([user])
|
||||
await config.api.users.bulkCreateUsers([user])
|
||||
|
||||
expect(events.user.created).toBeCalledTimes(1)
|
||||
})
|
||||
|
@ -269,7 +277,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser()
|
||||
jest.clearAllMocks()
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
expect(events.user.updated).toBeCalledTimes(1)
|
||||
|
@ -284,7 +292,7 @@ describe("/api/global/users", () => {
|
|||
|
||||
user.forceResetPassword = true
|
||||
user.password = "tempPassword"
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
expect(events.user.updated).toBeCalledTimes(1)
|
||||
|
@ -297,7 +305,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser()
|
||||
jest.clearAllMocks()
|
||||
|
||||
await api.users.saveUser(structures.users.adminUser(user))
|
||||
await config.api.users.saveUser(structures.users.adminUser(user))
|
||||
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
expect(events.user.updated).toBeCalledTimes(1)
|
||||
|
@ -309,7 +317,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser()
|
||||
jest.clearAllMocks()
|
||||
|
||||
await api.users.saveUser(structures.users.builderUser(user))
|
||||
await config.api.users.saveUser(structures.users.builderUser(user))
|
||||
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
expect(events.user.updated).toBeCalledTimes(1)
|
||||
|
@ -323,7 +331,7 @@ describe("/api/global/users", () => {
|
|||
user.admin!.global = false
|
||||
user.builder!.global = false
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
expect(events.user.updated).toBeCalledTimes(1)
|
||||
|
@ -336,7 +344,7 @@ describe("/api/global/users", () => {
|
|||
jest.clearAllMocks()
|
||||
user.builder!.global = false
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
expect(events.user.updated).toBeCalledTimes(1)
|
||||
|
@ -352,7 +360,7 @@ describe("/api/global/users", () => {
|
|||
app_456: "role2",
|
||||
}
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
const savedUser = await config.getUser(user.email)
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
|
@ -372,7 +380,7 @@ describe("/api/global/users", () => {
|
|||
jest.clearAllMocks()
|
||||
user.roles = {}
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
const savedUser = await config.getUser(user.email)
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
|
@ -395,7 +403,7 @@ describe("/api/global/users", () => {
|
|||
app_456: "role2-edit",
|
||||
}
|
||||
|
||||
await api.users.saveUser(user)
|
||||
await config.api.users.saveUser(user)
|
||||
|
||||
const savedUser = await config.getUser(user.email)
|
||||
expect(events.user.created).not.toBeCalled()
|
||||
|
@ -411,7 +419,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser(structures.users.user({ email }))
|
||||
user.email = "new@test.com"
|
||||
|
||||
const response = await api.users.saveUser(user, 400)
|
||||
const response = await config.api.users.saveUser(user, 400)
|
||||
|
||||
const dbUser = await config.getUser(email)
|
||||
user.email = email
|
||||
|
@ -424,7 +432,7 @@ describe("/api/global/users", () => {
|
|||
it("should not be able to bulk delete current user", async () => {
|
||||
const user = await config.defaultUser!
|
||||
|
||||
const response = await api.users.bulkDeleteUsers([user._id!], 400)
|
||||
const response = await config.api.users.bulkDeleteUsers([user._id!], 400)
|
||||
|
||||
expect(response.message).toBe("Unable to delete self.")
|
||||
expect(events.user.deleted).not.toBeCalled()
|
||||
|
@ -436,7 +444,7 @@ describe("/api/global/users", () => {
|
|||
account.budibaseUserId = user._id!
|
||||
mocks.accounts.getAccountByTenantId.mockReturnValue(account)
|
||||
|
||||
const response = await api.users.bulkDeleteUsers([user._id!])
|
||||
const response = await config.api.users.bulkDeleteUsers([user._id!])
|
||||
|
||||
expect(response.deleted?.successful.length).toBe(0)
|
||||
expect(response.deleted?.unsuccessful.length).toBe(1)
|
||||
|
@ -454,7 +462,7 @@ describe("/api/global/users", () => {
|
|||
const builder = structures.users.builderUser()
|
||||
const admin = structures.users.adminUser()
|
||||
const user = structures.users.user()
|
||||
const createdUsers = await api.users.bulkCreateUsers([
|
||||
const createdUsers = await config.api.users.bulkCreateUsers([
|
||||
builder,
|
||||
admin,
|
||||
user,
|
||||
|
@ -463,7 +471,7 @@ describe("/api/global/users", () => {
|
|||
const toDelete = createdUsers.created?.successful.map(
|
||||
u => u._id!
|
||||
) as string[]
|
||||
const response = await api.users.bulkDeleteUsers(toDelete)
|
||||
const response = await config.api.users.bulkDeleteUsers(toDelete)
|
||||
|
||||
expect(response.deleted?.successful.length).toBe(3)
|
||||
expect(response.deleted?.unsuccessful.length).toBe(0)
|
||||
|
@ -478,7 +486,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser()
|
||||
jest.clearAllMocks()
|
||||
|
||||
await api.users.deleteUser(user._id!)
|
||||
await config.api.users.deleteUser(user._id!)
|
||||
|
||||
expect(events.user.deleted).toBeCalledTimes(1)
|
||||
expect(events.user.permissionBuilderRemoved).not.toBeCalled()
|
||||
|
@ -489,7 +497,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser(structures.users.adminUser())
|
||||
jest.clearAllMocks()
|
||||
|
||||
await api.users.deleteUser(user._id!)
|
||||
await config.api.users.deleteUser(user._id!)
|
||||
|
||||
expect(events.user.deleted).toBeCalledTimes(1)
|
||||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
||||
|
@ -500,7 +508,7 @@ describe("/api/global/users", () => {
|
|||
const user = await config.createUser(structures.users.builderUser())
|
||||
jest.clearAllMocks()
|
||||
|
||||
await api.users.deleteUser(user._id!)
|
||||
await config.api.users.deleteUser(user._id!)
|
||||
|
||||
expect(events.user.deleted).toBeCalledTimes(1)
|
||||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
||||
|
@ -512,7 +520,7 @@ describe("/api/global/users", () => {
|
|||
const account = structures.accounts.cloudAccount()
|
||||
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
||||
|
||||
const response = await api.users.deleteUser(user._id!, 400)
|
||||
const response = await config.api.users.deleteUser(user._id!, 400)
|
||||
|
||||
expect(response.body.message).toBe("Account holder cannot be deleted")
|
||||
})
|
||||
|
@ -523,7 +531,7 @@ describe("/api/global/users", () => {
|
|||
account.email = user.email
|
||||
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
||||
|
||||
const response = await api.users.deleteUser(user._id!, 400)
|
||||
const response = await config.api.users.deleteUser(user._id!, 400)
|
||||
|
||||
expect(response.body.message).toBe("Unable to delete self.")
|
||||
})
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
|
||||
// TODO
|
||||
|
||||
describe("/api/global/workspaces", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/global/workspaces", () => {
|
||||
it("retrieves workspaces", () => {})
|
||||
})
|
||||
|
||||
describe("DELETE /api/global/workspaces/:id", () => {})
|
||||
|
||||
describe("GET /api/global/workspaces", () => {})
|
||||
|
||||
describe("GET /api/global/workspaces/:id", () => {})
|
||||
})
|
|
@ -1,8 +0,0 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../../controllers/system/environment")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/environment", controller.fetch)
|
||||
|
||||
module.exports = router
|
|
@ -0,0 +1,8 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../../controllers/system/environment"
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/environment", controller.fetch)
|
||||
|
||||
export default router
|
|
@ -1,8 +0,0 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../../controllers/system/status")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/status", controller.fetch)
|
||||
|
||||
module.exports = router
|
|
@ -0,0 +1,8 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../../controllers/system/status"
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/status", controller.fetch)
|
||||
|
||||
export default router
|
|
@ -1,12 +0,0 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../../controllers/system/tenants")
|
||||
const { adminOnly } = require("@budibase/backend-core/auth")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router
|
||||
.get("/api/system/tenants/:tenantId/exists", controller.exists)
|
||||
.get("/api/system/tenants", adminOnly, controller.fetch)
|
||||
.delete("/api/system/tenants/:tenantId", adminOnly, controller.delete)
|
||||
|
||||
module.exports = router
|
|
@ -0,0 +1,13 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../../controllers/system/tenants"
|
||||
import { middleware } from "@budibase/backend-core"
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.delete(
|
||||
"/api/system/tenants/:tenantId",
|
||||
middleware.adminOnly,
|
||||
controller.delete
|
||||
)
|
||||
|
||||
export default router
|
|
@ -1,10 +1,9 @@
|
|||
import sdk from "../../../../sdk"
|
||||
import { TestConfiguration, structures, API } from "../../../../tests"
|
||||
import { TestConfiguration, structures } from "../../../../tests"
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
describe("accounts", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
|
@ -23,7 +22,7 @@ describe("accounts", () => {
|
|||
it("saves account metadata", async () => {
|
||||
let account = structures.accounts.account()
|
||||
|
||||
const response = await api.accounts.saveMetadata(account)
|
||||
const response = await config.api.accounts.saveMetadata(account)
|
||||
|
||||
const id = sdk.accounts.formatAccountMetadataId(account.accountId)
|
||||
const metadata = await sdk.accounts.getMetadata(id)
|
||||
|
@ -34,9 +33,9 @@ describe("accounts", () => {
|
|||
describe("destroyMetadata", () => {
|
||||
it("destroys account metadata", async () => {
|
||||
const account = structures.accounts.account()
|
||||
await api.accounts.saveMetadata(account)
|
||||
await config.api.accounts.saveMetadata(account)
|
||||
|
||||
await api.accounts.destroyMetadata(account.accountId)
|
||||
await config.api.accounts.destroyMetadata(account.accountId)
|
||||
|
||||
const deleted = await sdk.accounts.getMetadata(account.accountId)
|
||||
expect(deleted).toBe(undefined)
|
||||
|
@ -45,7 +44,7 @@ describe("accounts", () => {
|
|||
it("destroys account metadata that does not exist", async () => {
|
||||
const id = uuid()
|
||||
|
||||
const response = await api.accounts.destroyMetadata(id)
|
||||
const response = await config.api.accounts.destroyMetadata(id)
|
||||
|
||||
expect(response.status).toBe(204)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
|
||||
describe("/api/system/environment", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/system/environment", () => {
|
||||
it("returns the expected environment", async () => {
|
||||
const env = await config.api.environment.getEnvironment()
|
||||
expect(env.body).toEqual({
|
||||
cloud: true,
|
||||
disableAccountPortal: false,
|
||||
isDev: false,
|
||||
multiTenancy: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,63 @@
|
|||
const migrateFn = jest.fn()
|
||||
|
||||
import { TestConfiguration } from "../../../../tests"
|
||||
|
||||
jest.mock("../../../../migrations", () => {
|
||||
return {
|
||||
...jest.requireActual("../../../../migrations"),
|
||||
migrate: migrateFn,
|
||||
}
|
||||
})
|
||||
|
||||
describe("/api/system/migrations", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("POST /api/system/migrations/run", () => {
|
||||
it("fails with no internal api key", async () => {
|
||||
const res = await config.api.migrations.runMigrations({
|
||||
headers: {},
|
||||
status: 403,
|
||||
})
|
||||
expect(res.text).toBe("Unauthorized - no public worker access")
|
||||
expect(migrateFn).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("runs migrations", async () => {
|
||||
const res = await config.api.migrations.runMigrations()
|
||||
expect(res.text).toBe("OK")
|
||||
expect(migrateFn).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /api/system/migrations/definitions", () => {
|
||||
it("fails with no internal api key", async () => {
|
||||
const res = await config.api.migrations.getMigrationDefinitions({
|
||||
headers: {},
|
||||
status: 403,
|
||||
})
|
||||
expect(res.text).toBe("Unauthorized - no public worker access")
|
||||
})
|
||||
|
||||
it("returns definitions", async () => {
|
||||
const res = await config.api.migrations.getMigrationDefinitions()
|
||||
expect(res.body).toEqual([
|
||||
{
|
||||
name: "global_info_sync_users",
|
||||
type: "global",
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,36 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
|
||||
describe("/api/system/restore", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("POST /api/global/restore", () => {
|
||||
it("doesn't allow restore in cloud", async () => {
|
||||
const res = await config.api.restore.restored({ status: 405 })
|
||||
expect(res.body).toEqual({
|
||||
message: "This operation is not allowed in cloud.",
|
||||
status: 405,
|
||||
})
|
||||
})
|
||||
|
||||
it("restores in self host", async () => {
|
||||
config.modeSelf()
|
||||
const res = await config.api.restore.restored()
|
||||
expect(res.body).toEqual({
|
||||
message: "System prepared after restore.",
|
||||
})
|
||||
config.modeCloud()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,48 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
import { accounts } from "@budibase/backend-core"
|
||||
import { mocks } from "@budibase/backend-core/tests"
|
||||
|
||||
describe("/api/system/status", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/system/status", () => {
|
||||
it("returns status in self host", async () => {
|
||||
config.modeSelf()
|
||||
const res = await config.api.status.getStatus()
|
||||
expect(res.body).toEqual({
|
||||
health: {
|
||||
passing: true,
|
||||
},
|
||||
})
|
||||
expect(accounts.getStatus).toBeCalledTimes(0)
|
||||
config.modeCloud()
|
||||
})
|
||||
|
||||
it("returns status in cloud", async () => {
|
||||
const value = {
|
||||
health: {
|
||||
passing: false,
|
||||
},
|
||||
}
|
||||
|
||||
mocks.accounts.getStatus.mockReturnValueOnce(value)
|
||||
|
||||
const res = await config.api.status.getStatus()
|
||||
|
||||
expect(accounts.getStatus).toBeCalledTimes(1)
|
||||
expect(res.body).toEqual(value)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,61 @@
|
|||
import { TestConfiguration } from "../../../../tests"
|
||||
import { tenancy } from "@budibase/backend-core"
|
||||
|
||||
describe("/api/global/tenants", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("DELETE /api/system/tenants/:tenantId", () => {
|
||||
it("allows deleting the current tenant", async () => {
|
||||
const user = await config.createTenant()
|
||||
|
||||
await config.api.tenants.delete(user.tenantId, {
|
||||
headers: config.authHeaders(user),
|
||||
})
|
||||
})
|
||||
|
||||
it("rejects deleting another tenant", async () => {
|
||||
const user1 = await config.createTenant()
|
||||
// create a second user in another tenant
|
||||
const user2 = await config.createTenant()
|
||||
|
||||
const status = 403
|
||||
const res = await config.api.tenants.delete(user1.tenantId, {
|
||||
status,
|
||||
headers: config.authHeaders(user2),
|
||||
})
|
||||
|
||||
expect(res.body).toEqual({
|
||||
message: "Tenant ID does not match current user",
|
||||
status,
|
||||
})
|
||||
})
|
||||
|
||||
it("rejects non-admin", async () => {
|
||||
const user1 = await config.createTenant()
|
||||
// create an internal non-admin user
|
||||
const user2 = await tenancy.doInTenant(user1.tenantId, () => {
|
||||
return config.createUser()
|
||||
})
|
||||
await config.createSession(user2)
|
||||
|
||||
const res = await config.api.tenants.delete(user1.tenantId, {
|
||||
status: 403,
|
||||
headers: config.authHeaders(user2),
|
||||
})
|
||||
|
||||
expect(res.body).toEqual(config.adminOnlyResponse())
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,73 @@
|
|||
import { TestConfiguration, structures } from "../../tests"
|
||||
import { constants } from "@budibase/backend-core"
|
||||
|
||||
describe("tenancy middleware", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should get tenant id from user", async () => {
|
||||
const user = await config.createTenant()
|
||||
await config.createSession(user)
|
||||
const res = await config.api.self.getSelf(user)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(user.tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from header", async () => {
|
||||
const tenantId = structures.uuid()
|
||||
const headers = {
|
||||
[constants.Headers.TENANT_ID]: tenantId,
|
||||
}
|
||||
const res = await config.request
|
||||
.get(`/api/global/configs/checklist`)
|
||||
.set(headers)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from query param", async () => {
|
||||
const tenantId = structures.uuid()
|
||||
const res = await config.request.get(
|
||||
`/api/global/configs/checklist?tenantId=${tenantId}`
|
||||
)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from subdomain", async () => {
|
||||
const tenantId = structures.uuid()
|
||||
const headers = {
|
||||
host: `${tenantId}.localhost:10000`,
|
||||
}
|
||||
const res = await config.request
|
||||
.get(`/api/global/configs/checklist`)
|
||||
.set(headers)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from path variable", async () => {
|
||||
const user = await config.createTenant()
|
||||
const res = await config.request
|
||||
.post(`/api/global/auth/${user.tenantId}/login`)
|
||||
.send({
|
||||
username: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(user.tenantId)
|
||||
})
|
||||
|
||||
it("should throw when no tenant id is found", async () => {
|
||||
const res = await config.request.get(`/api/global/configs/checklist`)
|
||||
expect(res.status).toBe(403)
|
||||
expect(res.text).toBe("Tenant id not set")
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(undefined)
|
||||
})
|
||||
})
|
|
@ -8,34 +8,35 @@ import { Config } from "../constants"
|
|||
import {
|
||||
users,
|
||||
tenancy,
|
||||
Cookie,
|
||||
Header,
|
||||
sessions,
|
||||
auth,
|
||||
constants,
|
||||
env as coreEnv,
|
||||
} from "@budibase/backend-core"
|
||||
import { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures"
|
||||
import structures from "./structures"
|
||||
import structures, { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures"
|
||||
import { CreateUserResponse, User, AuthToken } from "@budibase/types"
|
||||
import API from "./api"
|
||||
|
||||
enum Mode {
|
||||
ACCOUNT = "account",
|
||||
CLOUD = "cloud",
|
||||
SELF = "self",
|
||||
}
|
||||
|
||||
class TestConfiguration {
|
||||
server: any
|
||||
request: any
|
||||
api: API
|
||||
defaultUser?: User
|
||||
tenant1User?: User
|
||||
|
||||
constructor(
|
||||
opts: { openServer: boolean; mode: Mode } = {
|
||||
openServer: true,
|
||||
mode: Mode.ACCOUNT,
|
||||
mode: Mode.CLOUD,
|
||||
}
|
||||
) {
|
||||
if (opts.mode === Mode.ACCOUNT) {
|
||||
this.modeAccount()
|
||||
if (opts.mode === Mode.CLOUD) {
|
||||
this.modeCloud()
|
||||
} else if (opts.mode === Mode.SELF) {
|
||||
this.modeSelf()
|
||||
}
|
||||
|
@ -46,6 +47,8 @@ class TestConfiguration {
|
|||
// we need the request for logging in, involves cookies, hard to fake
|
||||
this.request = supertest(this.server)
|
||||
}
|
||||
|
||||
this.api = new API(this)
|
||||
}
|
||||
|
||||
getRequest() {
|
||||
|
@ -54,20 +57,24 @@ class TestConfiguration {
|
|||
|
||||
// MODES
|
||||
|
||||
modeAccount = () => {
|
||||
env.SELF_HOSTED = false
|
||||
// @ts-ignore
|
||||
env.MULTI_TENANCY = true
|
||||
// @ts-ignore
|
||||
env.DISABLE_ACCOUNT_PORTAL = false
|
||||
setMultiTenancy = (value: boolean) => {
|
||||
env._set("MULTI_TENANCY", value)
|
||||
coreEnv._set("MULTI_TENANCY", value)
|
||||
}
|
||||
|
||||
setSelfHosted = (value: boolean) => {
|
||||
env._set("SELF_HOSTED", value)
|
||||
coreEnv._set("SELF_HOSTED", value)
|
||||
}
|
||||
|
||||
modeCloud = () => {
|
||||
this.setSelfHosted(false)
|
||||
this.setMultiTenancy(true)
|
||||
}
|
||||
|
||||
modeSelf = () => {
|
||||
env.SELF_HOSTED = true
|
||||
// @ts-ignore
|
||||
env.MULTI_TENANCY = false
|
||||
// @ts-ignore
|
||||
env.DISABLE_ACCOUNT_PORTAL = true
|
||||
this.setSelfHosted(true)
|
||||
this.setMultiTenancy(false)
|
||||
}
|
||||
|
||||
// UTILS
|
||||
|
@ -114,6 +121,25 @@ class TestConfiguration {
|
|||
|
||||
// TENANCY
|
||||
|
||||
createTenant = async (): Promise<User> => {
|
||||
// create user / new tenant
|
||||
const res = await this.api.users.createAdminUser()
|
||||
|
||||
// return the created user
|
||||
const userRes = await this.api.users.getUser(res.userId, {
|
||||
headers: {
|
||||
...this.internalAPIHeaders(),
|
||||
[constants.Header.TENANT_ID]: res.tenantId,
|
||||
},
|
||||
})
|
||||
|
||||
// create a session for the new user
|
||||
const user = userRes.body
|
||||
await this.createSession(user)
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
getTenantId() {
|
||||
try {
|
||||
return tenancy.getTenantId()
|
||||
|
@ -122,7 +148,69 @@ class TestConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
// USER / AUTH
|
||||
// AUTH
|
||||
|
||||
async _createSession({
|
||||
userId,
|
||||
tenantId,
|
||||
}: {
|
||||
userId: string
|
||||
tenantId: string
|
||||
}) {
|
||||
await sessions.createASession(userId!, {
|
||||
sessionId: "sessionid",
|
||||
tenantId: tenantId,
|
||||
csrfToken: CSRF_TOKEN,
|
||||
})
|
||||
}
|
||||
|
||||
async createSession(user: User) {
|
||||
return this._createSession({ userId: user._id!, tenantId: user.tenantId })
|
||||
}
|
||||
|
||||
cookieHeader(cookies: any) {
|
||||
if (!Array.isArray(cookies)) {
|
||||
cookies = [cookies]
|
||||
}
|
||||
return {
|
||||
Cookie: cookies,
|
||||
}
|
||||
}
|
||||
|
||||
authHeaders(user: User) {
|
||||
const authToken: AuthToken = {
|
||||
userId: user._id!,
|
||||
sessionId: "sessionid",
|
||||
tenantId: user.tenantId,
|
||||
}
|
||||
const authCookie = auth.jwt.sign(authToken, env.JWT_SECRET)
|
||||
return {
|
||||
Accept: "application/json",
|
||||
...this.cookieHeader([`${constants.Cookie.Auth}=${authCookie}`]),
|
||||
[constants.Header.CSRF_TOKEN]: CSRF_TOKEN,
|
||||
}
|
||||
}
|
||||
|
||||
defaultHeaders() {
|
||||
const tenantId = this.getTenantId()
|
||||
if (tenantId === TENANT_ID) {
|
||||
return this.authHeaders(this.defaultUser!)
|
||||
} else if (tenantId === TENANT_1) {
|
||||
return this.authHeaders(this.tenant1User!)
|
||||
} else {
|
||||
throw new Error("could not determine auth headers to use")
|
||||
}
|
||||
}
|
||||
|
||||
internalAPIHeaders() {
|
||||
return { [constants.Header.API_KEY]: env.INTERNAL_API_KEY }
|
||||
}
|
||||
|
||||
adminOnlyResponse = () => {
|
||||
return { message: "Admin user only endpoint.", status: 403 }
|
||||
}
|
||||
|
||||
// USERS
|
||||
|
||||
async createDefaultUser() {
|
||||
const user = structures.users.adminUser({
|
||||
|
@ -140,45 +228,6 @@ class TestConfiguration {
|
|||
this.tenant1User = await this.createUser(user)
|
||||
}
|
||||
|
||||
async createSession(user: User) {
|
||||
await sessions.createASession(user._id!, {
|
||||
sessionId: "sessionid",
|
||||
tenantId: user.tenantId,
|
||||
csrfToken: CSRF_TOKEN,
|
||||
})
|
||||
}
|
||||
|
||||
cookieHeader(cookies: any) {
|
||||
return {
|
||||
Cookie: [cookies],
|
||||
}
|
||||
}
|
||||
|
||||
authHeaders(user: User) {
|
||||
const authToken: AuthToken = {
|
||||
userId: user._id!,
|
||||
sessionId: "sessionid",
|
||||
tenantId: user.tenantId,
|
||||
}
|
||||
const authCookie = auth.jwt.sign(authToken, env.JWT_SECRET)
|
||||
return {
|
||||
Accept: "application/json",
|
||||
...this.cookieHeader([`${Cookie.Auth}=${authCookie}`]),
|
||||
[Header.CSRF_TOKEN]: CSRF_TOKEN,
|
||||
}
|
||||
}
|
||||
|
||||
defaultHeaders() {
|
||||
const tenantId = this.getTenantId()
|
||||
if (tenantId === TENANT_ID) {
|
||||
return this.authHeaders(this.defaultUser!)
|
||||
} else if (tenantId === TENANT_1) {
|
||||
return this.authHeaders(this.tenant1User!)
|
||||
} else {
|
||||
throw new Error("could not determine auth headers to use")
|
||||
}
|
||||
}
|
||||
|
||||
async getUser(email: string): Promise<User> {
|
||||
return tenancy.doInTenant(this.getTenantId(), () => {
|
||||
return users.getGlobalUserByEmail(email)
|
||||
|
@ -242,7 +291,7 @@ class TestConfiguration {
|
|||
|
||||
getOIDConfigCookie(configId: string) {
|
||||
const token = auth.jwt.sign(configId, env.JWT_SECRET)
|
||||
return this.cookieHeader([[`${Cookie.OIDC_CONFIG}=${token}`]])
|
||||
return this.cookieHeader([[`${constants.Cookie.OIDC_CONFIG}=${token}`]])
|
||||
}
|
||||
|
||||
async saveOIDCConfig() {
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
import { Account, AccountMetadata } from "@budibase/types"
|
||||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI } from "./base"
|
||||
|
||||
export class AccountAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
export class AccountAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
super(config)
|
||||
}
|
||||
|
||||
saveMetadata = async (account: Account) => {
|
||||
const res = await this.request
|
||||
.put(`/api/system/accounts/${account.accountId}/metadata`)
|
||||
.send(account)
|
||||
.set(this.config.defaultHeaders())
|
||||
.set(this.config.internalAPIHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
return res.body as AccountMetadata
|
||||
|
@ -23,6 +20,6 @@ export class AccountAPI {
|
|||
destroyMetadata = (accountId: string) => {
|
||||
return this.request
|
||||
.del(`/api/system/accounts/${accountId}/metadata`)
|
||||
.set(this.config.defaultHeaders())
|
||||
.set(this.config.internalAPIHeaders())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI } from "./base"
|
||||
|
||||
export class AuthAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
export class AuthAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
super(config)
|
||||
}
|
||||
|
||||
updatePassword = (code: string) => {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
|
||||
export interface TestAPIOpts {
|
||||
headers?: any
|
||||
status?: number
|
||||
}
|
||||
|
||||
export abstract class TestAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
protected constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
}
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI } from "./base"
|
||||
|
||||
export class ConfigAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
export class ConfigAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
super(config)
|
||||
}
|
||||
|
||||
getConfigChecklist = () => {
|
||||
|
@ -26,10 +23,20 @@ export class ConfigAPI {
|
|||
.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) => {
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI } from "./base"
|
||||
|
||||
export class EmailAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
export class EmailAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
super(config)
|
||||
}
|
||||
|
||||
sendEmail = (purpose: string) => {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI } from "./base"
|
||||
|
||||
export class EnvironmentAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
getEnvironment = () => {
|
||||
return this.request
|
||||
.get(`/api/system/environment`)
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
}
|
|
@ -5,6 +5,11 @@ import { ConfigAPI } from "./configs"
|
|||
import { EmailAPI } from "./email"
|
||||
import { SelfAPI } from "./self"
|
||||
import { UserAPI } from "./users"
|
||||
import { EnvironmentAPI } from "./environment"
|
||||
import { MigrationAPI } from "./migrations"
|
||||
import { StatusAPI } from "./status"
|
||||
import { RestoreAPI } from "./restore"
|
||||
import { TenantAPI } from "./tenants"
|
||||
|
||||
export default class API {
|
||||
accounts: AccountAPI
|
||||
|
@ -13,6 +18,11 @@ export default class API {
|
|||
emails: EmailAPI
|
||||
self: SelfAPI
|
||||
users: UserAPI
|
||||
environment: EnvironmentAPI
|
||||
migrations: MigrationAPI
|
||||
status: StatusAPI
|
||||
restore: RestoreAPI
|
||||
tenants: TenantAPI
|
||||
|
||||
constructor(config: TestConfiguration) {
|
||||
this.accounts = new AccountAPI(config)
|
||||
|
@ -21,5 +31,10 @@ export default class API {
|
|||
this.emails = new EmailAPI(config)
|
||||
this.self = new SelfAPI(config)
|
||||
this.users = new UserAPI(config)
|
||||
this.environment = new EnvironmentAPI(config)
|
||||
this.migrations = new MigrationAPI(config)
|
||||
this.status = new StatusAPI(config)
|
||||
this.restore = new RestoreAPI(config)
|
||||
this.tenants = new TenantAPI(config)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI, TestAPIOpts } from "./base"
|
||||
|
||||
export class MigrationAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
runMigrations = (opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.post(`/api/system/migrations/run`)
|
||||
.set(opts?.headers ? opts.headers : this.config.internalAPIHeaders())
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
|
||||
getMigrationDefinitions = (opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.get(`/api/system/migrations/definitions`)
|
||||
.set(opts?.headers ? opts.headers : this.config.internalAPIHeaders())
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI, TestAPIOpts } from "./base"
|
||||
|
||||
export class RestoreAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
restored = (opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.post(`/api/system/restored`)
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
}
|
|
@ -1,13 +1,10 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { User } from "@budibase/types"
|
||||
import { TestAPI } from "./base"
|
||||
|
||||
export class SelfAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
export class SelfAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
super(config)
|
||||
}
|
||||
|
||||
updateSelf = (user: User) => {
|
||||
|
@ -18,4 +15,12 @@ export class SelfAPI {
|
|||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
|
||||
getSelf = (user: User) => {
|
||||
return this.request
|
||||
.get(`/api/global/self`)
|
||||
.set(this.config.authHeaders(user))
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI } from "./base"
|
||||
|
||||
export class StatusAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
getStatus = () => {
|
||||
return this.request.get(`/api/system/status`).expect(200)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI, TestAPIOpts } from "./base"
|
||||
|
||||
export class TenantAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
delete = (tenantId: string, opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.delete(`/api/system/tenants/${tenantId}`)
|
||||
.set(opts?.headers)
|
||||
.expect(opts?.status ? opts.status : 204)
|
||||
}
|
||||
}
|
|
@ -3,16 +3,16 @@ import {
|
|||
BulkUserRequest,
|
||||
InviteUsersRequest,
|
||||
User,
|
||||
CreateAdminUserRequest,
|
||||
} from "@budibase/types"
|
||||
import * as structures from "../structures"
|
||||
import { generator } from "@budibase/backend-core/tests"
|
||||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI, TestAPIOpts } from "./base"
|
||||
|
||||
export class UserAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
export class UserAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
super(config)
|
||||
}
|
||||
|
||||
// INVITE
|
||||
|
@ -91,6 +91,30 @@ export class UserAPI {
|
|||
|
||||
// USER
|
||||
|
||||
createAdminUser = async (
|
||||
request?: CreateAdminUserRequest,
|
||||
opts?: TestAPIOpts
|
||||
) => {
|
||||
if (!request) {
|
||||
request = {
|
||||
email: structures.email(),
|
||||
password: generator.string(),
|
||||
tenantId: structures.uuid(),
|
||||
}
|
||||
}
|
||||
const res = await this.request
|
||||
.post(`/api/global/users/init`)
|
||||
.send(request)
|
||||
.set(this.config.internalAPIHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
|
||||
return {
|
||||
...request,
|
||||
userId: res.body._id,
|
||||
}
|
||||
}
|
||||
|
||||
saveUser = (user: User, status?: number) => {
|
||||
return this.request
|
||||
.post(`/api/global/users`)
|
||||
|
@ -107,4 +131,12 @@ export class UserAPI {
|
|||
.expect("Content-Type", /json/)
|
||||
.expect(status ? status : 200)
|
||||
}
|
||||
|
||||
getUser = (userId: string, opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.get(`/api/global/users/${userId}`)
|
||||
.set(opts?.headers ? opts.headers : this.config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue