Merge remote-tracking branch 'origin/develop' into feature/default-internal-datasource
This commit is contained in:
commit
8657aaf922
108 changed files with 1952 additions and 832 deletions
|
@ -58,12 +58,15 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/(system|admin|global)/ {
|
location ~ ^/api/(system|admin|global)/ {
|
||||||
proxy_pass http://worker-service;
|
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_pass http://worker-service;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/backups/ {
|
location /api/backups/ {
|
||||||
|
@ -78,60 +81,78 @@ http {
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
proxy_pass http://app-service;
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_pass http://app-service;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = / {
|
location = / {
|
||||||
proxy_pass http://app-service;
|
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_pass http://app-service;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /app_ {
|
location /app_ {
|
||||||
proxy_pass http://app-service;
|
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_pass http://app-service;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /app {
|
location /app {
|
||||||
proxy_pass http://app-service;
|
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_pass http://app-service;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /builder {
|
location /builder {
|
||||||
proxy_pass http://builder;
|
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_pass http://builder;
|
||||||
rewrite ^/builder(.*)$ /builder/$1 break;
|
rewrite ^/builder(.*)$ /builder/$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /builder/ {
|
location /builder/ {
|
||||||
proxy_pass http://builder;
|
|
||||||
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
proxy_read_timeout 120s;
|
proxy_read_timeout 120s;
|
||||||
proxy_connect_timeout 120s;
|
proxy_connect_timeout 120s;
|
||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
|
|
||||||
|
proxy_pass http://builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /vite/ {
|
location /vite/ {
|
||||||
|
|
|
@ -100,18 +100,25 @@ http {
|
||||||
|
|
||||||
location ~ ^/(builder|app_) {
|
location ~ ^/(builder|app_) {
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass http://$apps:4002;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/(system|admin|global)/ {
|
location ~ ^/api/(system|admin|global)/ {
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$worker:4003;
|
proxy_pass http://$worker:4003;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /worker/ {
|
location /worker/ {
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$worker:4003;
|
proxy_pass http://$worker:4003;
|
||||||
rewrite ^/worker/(.*)$ /$1 break;
|
rewrite ^/worker/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
@ -139,6 +146,7 @@ http {
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass http://$apps:4002;
|
||||||
}
|
}
|
||||||
|
@ -158,6 +166,7 @@ http {
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://$apps:4002;
|
proxy_pass http://$apps:4002;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -75,8 +75,8 @@
|
||||||
"env:multi:disable": "lerna run env:multi:disable",
|
"env:multi:disable": "lerna run env:multi:disable",
|
||||||
"env:selfhost:enable": "lerna run env:selfhost:enable",
|
"env:selfhost:enable": "lerna run env:selfhost:enable",
|
||||||
"env:selfhost:disable": "lerna run env:selfhost:disable",
|
"env:selfhost:disable": "lerna run env:selfhost:disable",
|
||||||
"env:localdomain:enable": "lerna run env:localdomain:enable",
|
"env:localdomain:enable": "./scripts/localdomain.sh enable",
|
||||||
"env:localdomain:disable": "lerna run env:localdomain:disable",
|
"env:localdomain:disable": "./scripts/localdomain.sh disable",
|
||||||
"env:account:enable": "lerna run env:account:enable",
|
"env:account:enable": "lerna run env:account:enable",
|
||||||
"env:account:disable": "lerna run env:account:disable",
|
"env:account:disable": "lerna run env:account:disable",
|
||||||
"mode:self": "yarn env:selfhost:enable && yarn env:multi:disable && yarn 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",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/types": "2.1.22-alpha.4",
|
"@budibase/types": "2.1.22-alpha.6",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1030.0",
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
"@types/chance": "1.1.3",
|
"@types/chance": "1.1.3",
|
||||||
"@types/ioredis": "4.28.0",
|
"@types/ioredis": "4.28.0",
|
||||||
"@types/jest": "27.5.1",
|
"@types/jest": "27.5.1",
|
||||||
"@types/koa": "2.0.52",
|
"@types/koa": "2.13.4",
|
||||||
"@types/lodash": "4.14.180",
|
"@types/lodash": "4.14.180",
|
||||||
"@types/node": "14.18.20",
|
"@types/node": "14.18.20",
|
||||||
"@types/node-fetch": "2.6.1",
|
"@types/node-fetch": "2.6.1",
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
"chance": "1.1.3",
|
"chance": "1.1.3",
|
||||||
"ioredis-mock": "5.8.0",
|
"ioredis-mock": "5.8.0",
|
||||||
"jest": "28.1.1",
|
"jest": "28.1.1",
|
||||||
"koa": "2.7.0",
|
"koa": "2.13.4",
|
||||||
"nodemon": "2.0.16",
|
"nodemon": "2.0.16",
|
||||||
"pouchdb-adapter-memory": "7.2.2",
|
"pouchdb-adapter-memory": "7.2.2",
|
||||||
"timekeeper": "2.2.0",
|
"timekeeper": "2.2.0",
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
const { getGlobalUserParams, getAllApps } = require("../db/utils")
|
import { getGlobalUserParams, getAllApps } from "../db/utils"
|
||||||
const { doWithDB } = require("../db")
|
import { doWithDB } from "../db"
|
||||||
const { doWithGlobalDB } = require("../tenancy")
|
import { doWithGlobalDB } from "../tenancy"
|
||||||
const { StaticDatabases } = require("../db/constants")
|
import { StaticDatabases } from "../db/constants"
|
||||||
|
import { App, Tenants, User } from "@budibase/types"
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||||
|
|
||||||
const removeTenantFromInfoDB = async tenantId => {
|
const removeTenantFromInfoDB = async (tenantId: string) => {
|
||||||
try {
|
try {
|
||||||
await doWithDB(PLATFORM_INFO_DB, async infoDb => {
|
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||||
let tenants = await infoDb.get(TENANT_DOC)
|
const tenants = (await infoDb.get(TENANT_DOC)) as Tenants
|
||||||
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
|
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
|
||||||
|
|
||||||
await infoDb.put(tenants)
|
await infoDb.put(tenants)
|
||||||
|
@ -20,14 +21,14 @@ const removeTenantFromInfoDB = async tenantId => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.removeUserFromInfoDB = async dbUser => {
|
export const removeUserFromInfoDB = async (dbUser: User) => {
|
||||||
await doWithDB(PLATFORM_INFO_DB, async infoDb => {
|
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||||
const keys = [dbUser._id, dbUser.email]
|
const keys = [dbUser._id, dbUser.email]
|
||||||
const userDocs = await infoDb.allDocs({
|
const userDocs = await infoDb.allDocs({
|
||||||
keys,
|
keys,
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
const toDelete = userDocs.rows.map(row => {
|
const toDelete = userDocs.rows.map((row: any) => {
|
||||||
return {
|
return {
|
||||||
...row.doc,
|
...row.doc,
|
||||||
_deleted: true,
|
_deleted: true,
|
||||||
|
@ -37,18 +38,18 @@ exports.removeUserFromInfoDB = async dbUser => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeUsersFromInfoDB = async tenantId => {
|
const removeUsersFromInfoDB = async (tenantId: string) => {
|
||||||
return doWithGlobalDB(tenantId, async db => {
|
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||||
try {
|
try {
|
||||||
const allUsers = await db.allDocs(
|
const allUsers = await db.allDocs(
|
||||||
getGlobalUserParams(null, {
|
getGlobalUserParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
await doWithDB(PLATFORM_INFO_DB, async infoDb => {
|
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||||
const allEmails = allUsers.rows.map(row => row.doc.email)
|
const allEmails = allUsers.rows.map((row: any) => row.doc.email)
|
||||||
// get the id docs
|
// 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
|
// and the email docs
|
||||||
keys = keys.concat(allEmails)
|
keys = keys.concat(allEmails)
|
||||||
// retrieve the docs and delete them
|
// retrieve the docs and delete them
|
||||||
|
@ -56,7 +57,7 @@ const removeUsersFromInfoDB = async tenantId => {
|
||||||
keys,
|
keys,
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
const toDelete = userDocs.rows.map(row => {
|
const toDelete = userDocs.rows.map((row: any) => {
|
||||||
return {
|
return {
|
||||||
...row.doc,
|
...row.doc,
|
||||||
_deleted: true,
|
_deleted: true,
|
||||||
|
@ -71,8 +72,8 @@ const removeUsersFromInfoDB = async tenantId => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeGlobalDB = async tenantId => {
|
const removeGlobalDB = async (tenantId: string) => {
|
||||||
return doWithGlobalDB(tenantId, async db => {
|
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||||
try {
|
try {
|
||||||
await db.destroy()
|
await db.destroy()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -82,11 +83,11 @@ const removeGlobalDB = async tenantId => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeTenantApps = async tenantId => {
|
const removeTenantApps = async (tenantId: string) => {
|
||||||
try {
|
try {
|
||||||
const apps = await getAllApps({ all: true })
|
const apps = (await getAllApps({ all: true })) as App[]
|
||||||
const destroyPromises = apps.map(app =>
|
const destroyPromises = apps.map(app =>
|
||||||
doWithDB(app.appId, db => db.destroy())
|
doWithDB(app.appId, (db: any) => db.destroy())
|
||||||
)
|
)
|
||||||
await Promise.allSettled(destroyPromises)
|
await Promise.allSettled(destroyPromises)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -96,7 +97,7 @@ const removeTenantApps = async tenantId => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// can't live in tenancy package due to circular dependency on db/utils
|
// can't live in tenancy package due to circular dependency on db/utils
|
||||||
exports.deleteTenant = async tenantId => {
|
export const deleteTenant = async (tenantId: string) => {
|
||||||
await removeTenantFromInfoDB(tenantId)
|
await removeTenantFromInfoDB(tenantId)
|
||||||
await removeUsersFromInfoDB(tenantId)
|
await removeUsersFromInfoDB(tenantId)
|
||||||
await removeGlobalDB(tenantId)
|
await removeGlobalDB(tenantId)
|
|
@ -15,6 +15,7 @@ import { getAppMetadata } from "../cache/appMetadata"
|
||||||
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
||||||
import { APP_PREFIX } from "./constants"
|
import { APP_PREFIX } from "./constants"
|
||||||
import * as events from "../events"
|
import * as events from "../events"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
|
|
||||||
export * from "./constants"
|
export * from "./constants"
|
||||||
export * from "./conversions"
|
export * from "./conversions"
|
||||||
|
@ -301,7 +302,12 @@ export async function getAllDbs(opts = { efficient: false }) {
|
||||||
*
|
*
|
||||||
* @return {Promise<object[]>} returns the app information document stored in each app database.
|
* @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()
|
let tenantId = getTenantId()
|
||||||
if (!env.MULTI_TENANCY && !tenantId) {
|
if (!env.MULTI_TENANCY && !tenantId) {
|
||||||
tenantId = DEFAULT_TENANT_ID
|
tenantId = DEFAULT_TENANT_ID
|
||||||
|
@ -373,18 +379,16 @@ export async function getAllApps({ dev, all, idsOnly, efficient }: any = {}) {
|
||||||
* Utility function for getAllApps but filters to production apps only.
|
* Utility function for getAllApps but filters to production apps only.
|
||||||
*/
|
*/
|
||||||
export async function getProdAppIDs() {
|
export async function getProdAppIDs() {
|
||||||
return (await getAllApps({ idsOnly: true })).filter(
|
const apps = (await getAllApps({ idsOnly: true })) as string[]
|
||||||
(id: any) => !isDevAppID(id)
|
return apps.filter((id: any) => !isDevAppID(id))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function for the inverse of above.
|
* Utility function for the inverse of above.
|
||||||
*/
|
*/
|
||||||
export async function getDevAppIDs() {
|
export async function getDevAppIDs() {
|
||||||
return (await getAllApps({ idsOnly: true })).filter((id: any) =>
|
const apps = (await getAllApps({ idsOnly: true })) as string[]
|
||||||
isDevAppID(id)
|
return apps.filter((id: any) => isDevAppID(id))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function dbExists(dbName: any) {
|
export async function dbExists(dbName: any) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import env from "./environment"
|
||||||
import tenancy from "./tenancy"
|
import tenancy from "./tenancy"
|
||||||
import featureFlags from "./featureFlags"
|
import featureFlags from "./featureFlags"
|
||||||
import * as sessions from "./security/sessions"
|
import * as sessions from "./security/sessions"
|
||||||
import deprovisioning from "./context/deprovision"
|
import * as deprovisioning from "./context/deprovision"
|
||||||
import auth from "./auth"
|
import auth from "./auth"
|
||||||
import constants from "./constants"
|
import constants from "./constants"
|
||||||
import * as dbConstants from "./db/constants"
|
import * as dbConstants from "./db/constants"
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
|
import { BBContext, EndpointMatcher, RegexMatcher } from "@budibase/types"
|
||||||
|
|
||||||
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
|
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
|
||||||
|
|
||||||
exports.buildMatcherRegex = patterns => {
|
export const buildMatcherRegex = (
|
||||||
|
patterns: EndpointMatcher[]
|
||||||
|
): RegexMatcher[] => {
|
||||||
if (!patterns) {
|
if (!patterns) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return patterns.map(pattern => {
|
return patterns.map(pattern => {
|
||||||
const isObj = typeof pattern === "object" && pattern.route
|
let route = pattern.route
|
||||||
const method = isObj ? pattern.method : "GET"
|
const method = pattern.method
|
||||||
const strict = pattern.strict ? pattern.strict : false
|
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)
|
const matches = route.match(PARAM_REGEX)
|
||||||
if (matches) {
|
if (matches) {
|
||||||
for (let match of matches) {
|
for (let match of matches) {
|
||||||
const pattern = "/.*" + (match.endsWith("/") ? "/" : "")
|
const suffix = match.endsWith("/") ? "/" : ""
|
||||||
|
const pattern = "/.*" + suffix
|
||||||
route = route.replace(match, pattern)
|
route = route.replace(match, pattern)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { regex: new RegExp(route), method, strict, route }
|
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 }) => {
|
return options.find(({ regex, method, strict, route }) => {
|
||||||
let urlMatch
|
let urlMatch
|
||||||
if (strict) {
|
if (strict) {
|
|
@ -1,52 +0,0 @@
|
||||||
const { doInTenant, isMultiTenant, DEFAULT_TENANT_ID } = require("../tenancy")
|
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
|
||||||
const { Headers } = 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[Headers.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)
|
|
||||||
}
|
|
||||||
}
|
|
37
packages/backend-core/src/middleware/tenancy.ts
Normal file
37
packages/backend-core/src/middleware/tenancy.ts
Normal file
|
@ -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
|
134
packages/backend-core/src/middleware/tests/matchers.spec.ts
Normal file
134
packages/backend-core/src/middleware/tests/matchers.spec.ts
Normal file
|
@ -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,
|
MigrationOptions,
|
||||||
MigrationType,
|
MigrationType,
|
||||||
MigrationNoOpOptions,
|
MigrationNoOpOptions,
|
||||||
|
App,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
export const getMigrationsDoc = async (db: any) => {
|
export const getMigrationsDoc = async (db: any) => {
|
||||||
|
@ -55,14 +56,17 @@ export const runMigration = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the db to store the migration in
|
// get the db to store the migration in
|
||||||
let dbNames
|
let dbNames: string[]
|
||||||
if (migrationType === MigrationType.GLOBAL) {
|
if (migrationType === MigrationType.GLOBAL) {
|
||||||
dbNames = [getGlobalDBName()]
|
dbNames = [getGlobalDBName()]
|
||||||
} else if (migrationType === MigrationType.APP) {
|
} else if (migrationType === MigrationType.APP) {
|
||||||
if (options.noOp) {
|
if (options.noOp) {
|
||||||
|
if (!options.noOp.appId) {
|
||||||
|
throw new Error("appId is required for noOp app migration")
|
||||||
|
}
|
||||||
dbNames = [options.noOp.appId]
|
dbNames = [options.noOp.appId]
|
||||||
} else {
|
} else {
|
||||||
const apps = await getAllApps(migration.appOpts)
|
const apps = (await getAllApps(migration.appOpts)) as App[]
|
||||||
dbNames = apps.map(app => app.appId)
|
dbNames = apps.map(app => app.appId)
|
||||||
}
|
}
|
||||||
} else if (migrationType === MigrationType.INSTALLATION) {
|
} else if (migrationType === MigrationType.INSTALLATION) {
|
||||||
|
|
|
@ -9,7 +9,13 @@ import {
|
||||||
getTenantIDFromAppID,
|
getTenantIDFromAppID,
|
||||||
} from "../context"
|
} from "../context"
|
||||||
import env from "../environment"
|
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 TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||||
|
@ -144,3 +150,108 @@ export const getTenantIds = async () => {
|
||||||
return (tenants && tenants.tenantIds) || []
|
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,46 @@
|
||||||
const { DocumentType, SEPARATOR, ViewName, getAllApps } = require("./db/utils")
|
import { DocumentType, SEPARATOR, ViewName, getAllApps } from "./db/utils"
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const { options } = require("./middleware/passport/jwt")
|
import { options } from "./middleware/passport/jwt"
|
||||||
const { queryGlobalView } = require("./db/views")
|
import { queryGlobalView } from "./db/views"
|
||||||
const { Headers, Cookies, MAX_VALID_DATE } = require("./constants")
|
import { Headers, Cookies, MAX_VALID_DATE } from "./constants"
|
||||||
const env = require("./environment")
|
import env from "./environment"
|
||||||
const userCache = require("./cache/user")
|
import userCache from "./cache/user"
|
||||||
const {
|
import { getSessionsForUser, invalidateSessions } from "./security/sessions"
|
||||||
getSessionsForUser,
|
import * as events from "./events"
|
||||||
invalidateSessions,
|
import tenancy from "./tenancy"
|
||||||
} = require("./security/sessions")
|
import {
|
||||||
const events = require("./events")
|
App,
|
||||||
const tenancy = require("./tenancy")
|
BBContext,
|
||||||
|
PlatformLogoutOpts,
|
||||||
|
TenantResolutionStrategy,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { SetOption } from "cookies"
|
||||||
|
|
||||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||||
const PROD_APP_PREFIX = "/app/"
|
const PROD_APP_PREFIX = "/app/"
|
||||||
|
|
||||||
function confirmAppId(possibleAppId) {
|
function confirmAppId(possibleAppId: string | undefined) {
|
||||||
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
return possibleAppId && possibleAppId.startsWith(APP_PREFIX)
|
||||||
? possibleAppId
|
? possibleAppId
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveAppUrl(ctx) {
|
async function resolveAppUrl(ctx: BBContext) {
|
||||||
const appUrl = ctx.path.split("/")[2]
|
const appUrl = ctx.path.split("/")[2]
|
||||||
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
||||||
|
|
||||||
let tenantId = tenancy.getTenantId()
|
let tenantId = tenancy.getTenantId()
|
||||||
if (!env.SELF_HOSTED && ctx.subdomains.length) {
|
if (env.MULTI_TENANCY) {
|
||||||
// always use the tenant id from the url in cloud
|
// always use the tenant id from the subdomain in multi tenancy
|
||||||
tenantId = ctx.subdomains[0]
|
// 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
|
// search prod apps for a url that matches
|
||||||
const apps = await tenancy.doInTenant(tenantId, () =>
|
const apps: App[] = await tenancy.doInTenant(tenantId, () =>
|
||||||
getAllApps({ dev: false })
|
getAllApps({ dev: false })
|
||||||
)
|
)
|
||||||
const app = apps.filter(
|
const app = apps.filter(
|
||||||
|
@ -42,7 +50,7 @@ async function resolveAppUrl(ctx) {
|
||||||
return app && app.appId ? app.appId : undefined
|
return app && app.appId ? app.appId : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isServingApp = ctx => {
|
export const isServingApp = (ctx: BBContext) => {
|
||||||
// dev app
|
// dev app
|
||||||
if (ctx.path.startsWith(`/${APP_PREFIX}`)) {
|
if (ctx.path.startsWith(`/${APP_PREFIX}`)) {
|
||||||
return true
|
return true
|
||||||
|
@ -59,12 +67,12 @@ exports.isServingApp = ctx => {
|
||||||
* @param {object} ctx The main request body to look through.
|
* @param {object} ctx The main request body to look through.
|
||||||
* @returns {string|undefined} If an appId was found it will be returned.
|
* @returns {string|undefined} If an appId was found it will be returned.
|
||||||
*/
|
*/
|
||||||
exports.getAppIdFromCtx = async ctx => {
|
export const getAppIdFromCtx = async (ctx: BBContext) => {
|
||||||
// look in headers
|
// look in headers
|
||||||
const options = [ctx.headers[Headers.APP_ID]]
|
const options = [ctx.headers[Headers.APP_ID]]
|
||||||
let appId
|
let appId
|
||||||
for (let option of options) {
|
for (let option of options) {
|
||||||
appId = confirmAppId(option)
|
appId = confirmAppId(option as string)
|
||||||
if (appId) {
|
if (appId) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -95,7 +103,7 @@ exports.getAppIdFromCtx = async ctx => {
|
||||||
* opens the contents of the specified encrypted JWT.
|
* opens the contents of the specified encrypted JWT.
|
||||||
* @return {object} the contents of the token.
|
* @return {object} the contents of the token.
|
||||||
*/
|
*/
|
||||||
exports.openJwt = token => {
|
export const openJwt = (token: string) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
@ -107,14 +115,14 @@ exports.openJwt = token => {
|
||||||
* @param {object} ctx The request which is to be manipulated.
|
* @param {object} ctx The request which is to be manipulated.
|
||||||
* @param {string} name The name of the cookie to get.
|
* @param {string} name The name of the cookie to get.
|
||||||
*/
|
*/
|
||||||
exports.getCookie = (ctx, name) => {
|
export const getCookie = (ctx: BBContext, name: string) => {
|
||||||
const cookie = ctx.cookies.get(name)
|
const cookie = ctx.cookies.get(name)
|
||||||
|
|
||||||
if (!cookie) {
|
if (!cookie) {
|
||||||
return cookie
|
return cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
return exports.openJwt(cookie)
|
return openJwt(cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,12 +132,17 @@ exports.getCookie = (ctx, name) => {
|
||||||
* @param {string|object} value The value of cookie which will be set.
|
* @param {string|object} value The value of cookie which will be set.
|
||||||
* @param {object} opts options like whether to sign.
|
* @param {object} opts options like whether to sign.
|
||||||
*/
|
*/
|
||||||
exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => {
|
export const setCookie = (
|
||||||
|
ctx: BBContext,
|
||||||
|
value: any,
|
||||||
|
name = "builder",
|
||||||
|
opts = { sign: true }
|
||||||
|
) => {
|
||||||
if (value && opts && opts.sign) {
|
if (value && opts && opts.sign) {
|
||||||
value = jwt.sign(value, options.secretOrKey)
|
value = jwt.sign(value, options.secretOrKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config: SetOption = {
|
||||||
expires: MAX_VALID_DATE,
|
expires: MAX_VALID_DATE,
|
||||||
path: "/",
|
path: "/",
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
|
@ -146,8 +159,8 @@ exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => {
|
||||||
/**
|
/**
|
||||||
* Utility function, simply calls setCookie with an empty string for value
|
* Utility function, simply calls setCookie with an empty string for value
|
||||||
*/
|
*/
|
||||||
exports.clearCookie = (ctx, name) => {
|
export const clearCookie = (ctx: BBContext, name: string) => {
|
||||||
exports.setCookie(ctx, null, name)
|
setCookie(ctx, null, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -156,7 +169,7 @@ exports.clearCookie = (ctx, name) => {
|
||||||
* @param {object} ctx The koa context object to be tested.
|
* @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).
|
* @return {boolean} returns true if the call is from the client lib (a built app rather than the builder).
|
||||||
*/
|
*/
|
||||||
exports.isClient = ctx => {
|
export const isClient = (ctx: BBContext) => {
|
||||||
return ctx.headers[Headers.TYPE] === "client"
|
return ctx.headers[Headers.TYPE] === "client"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +189,7 @@ const getBuilders = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getBuildersCount = async () => {
|
export const getBuildersCount = async () => {
|
||||||
const builders = await getBuilders()
|
const builders = await getBuilders()
|
||||||
return builders.length
|
return builders.length
|
||||||
}
|
}
|
||||||
|
@ -184,10 +197,14 @@ exports.getBuildersCount = async () => {
|
||||||
/**
|
/**
|
||||||
* Logs a user out from budibase. Re-used across account portal and builder.
|
* Logs a user out from budibase. Re-used across account portal and builder.
|
||||||
*/
|
*/
|
||||||
exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
export const platformLogout = async (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.")
|
if (!ctx) throw new Error("Koa context must be supplied to logout.")
|
||||||
|
|
||||||
const currentSession = exports.getCookie(ctx, Cookies.Auth)
|
const currentSession = getCookie(ctx, Cookies.Auth)
|
||||||
let sessions = await getSessionsForUser(userId)
|
let sessions = await getSessionsForUser(userId)
|
||||||
|
|
||||||
if (keepActiveSession) {
|
if (keepActiveSession) {
|
||||||
|
@ -196,8 +213,8 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// clear cookies
|
// clear cookies
|
||||||
exports.clearCookie(ctx, Cookies.Auth)
|
clearCookie(ctx, Cookies.Auth)
|
||||||
exports.clearCookie(ctx, Cookies.CurrentApp)
|
clearCookie(ctx, Cookies.CurrentApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionIds = sessions.map(({ sessionId }) => sessionId)
|
const sessionIds = sessions.map(({ sessionId }) => sessionId)
|
||||||
|
@ -206,6 +223,6 @@ exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
||||||
await userCache.invalidateUser(userId)
|
await userCache.invalidateUser(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.timeout = timeMs => {
|
export const timeout = (timeMs: number) => {
|
||||||
return new Promise(resolve => setTimeout(resolve, timeMs))
|
return new Promise(resolve => setTimeout(resolve, timeMs))
|
||||||
}
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
import env from "../src/environment"
|
import env from "../src/environment"
|
||||||
import { mocks } from "./utilities"
|
import { mocks } from "./utilities"
|
||||||
|
|
||||||
|
// must explicitly enable fetch mock
|
||||||
|
mocks.fetch.enable()
|
||||||
|
|
||||||
// mock all dates to 2020-01-01T00:00:00.000Z
|
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||||
// use tk.reset() to use real dates in individual tests
|
// use tk.reset() to use real dates in individual tests
|
||||||
import tk from "timekeeper"
|
import tk from "timekeeper"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export * as mocks from "./mocks"
|
export * as mocks from "./mocks"
|
||||||
export * as structures from "./structures"
|
export * as structures from "./structures"
|
||||||
|
export { generator } from "./structures"
|
||||||
|
|
||||||
import * as dbConfig from "./db"
|
import * as dbConfig from "./db"
|
||||||
dbConfig.init()
|
dbConfig.init()
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
export const getAccount = jest.fn()
|
export const getAccount = jest.fn()
|
||||||
export const getAccountByTenantId = jest.fn()
|
export const getAccountByTenantId = jest.fn()
|
||||||
|
export const getStatus = jest.fn()
|
||||||
|
|
||||||
jest.mock("../../../src/cloud/accounts", () => ({
|
jest.mock("../../../src/cloud/accounts", () => ({
|
||||||
getAccount,
|
getAccount,
|
||||||
getAccountByTenantId,
|
getAccountByTenantId,
|
||||||
|
getStatus,
|
||||||
}))
|
}))
|
||||||
|
|
10
packages/backend-core/tests/utilities/mocks/fetch.ts
Normal file
10
packages/backend-core/tests/utilities/mocks/fetch.ts
Normal file
|
@ -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"
|
import "./events"
|
||||||
export * as accounts from "./accounts"
|
export * as accounts from "./accounts"
|
||||||
export * as date from "./date"
|
export * as date from "./date"
|
||||||
|
export { default as fetch } from "./fetch"
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
import { generator, uuid } from "."
|
import { generator, uuid } from "."
|
||||||
import { AuthType, CloudAccount, Hosting } from "@budibase/types"
|
|
||||||
import * as db from "../../../src/db/utils"
|
import * as db from "../../../src/db/utils"
|
||||||
|
import { Account, AuthType, CloudAccount, Hosting } from "@budibase/types"
|
||||||
|
|
||||||
export const cloudAccount = (): CloudAccount => {
|
export const account = (): Account => {
|
||||||
return {
|
return {
|
||||||
accountId: uuid(),
|
accountId: uuid(),
|
||||||
|
tenantId: generator.word(),
|
||||||
|
email: generator.email(),
|
||||||
|
tenantName: generator.word(),
|
||||||
|
hosting: Hosting.SELF,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
verified: true,
|
verified: true,
|
||||||
verificationSent: true,
|
verificationSent: true,
|
||||||
tier: "",
|
tier: "FREE", // DEPRECATED
|
||||||
email: generator.email(),
|
|
||||||
tenantId: generator.word(),
|
|
||||||
hosting: Hosting.CLOUD,
|
|
||||||
authType: AuthType.PASSWORD,
|
authType: AuthType.PASSWORD,
|
||||||
password: generator.word(),
|
|
||||||
tenantName: generator.word(),
|
|
||||||
name: generator.name(),
|
name: generator.name(),
|
||||||
size: "10+",
|
size: "10+",
|
||||||
profession: "Software Engineer",
|
profession: "Software Engineer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cloudAccount = (): CloudAccount => {
|
||||||
|
return {
|
||||||
|
...account(),
|
||||||
|
hosting: Hosting.CLOUD,
|
||||||
budibaseUserId: db.generateGlobalUserID(),
|
budibaseUserId: db.generateGlobalUserID(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
export { 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 = () => {
|
export const newContext = (): BBContext => {
|
||||||
return createMockContext()
|
const ctx = createMockContext()
|
||||||
|
return {
|
||||||
|
...ctx,
|
||||||
|
cookies: createMockCookies(),
|
||||||
|
request: {
|
||||||
|
...ctx.request,
|
||||||
|
body: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1079,7 +1079,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/koa" "*"
|
"@types/koa" "*"
|
||||||
|
|
||||||
"@types/koa@*":
|
"@types/koa@*", "@types/koa@2.13.4":
|
||||||
version "2.13.4"
|
version "2.13.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
|
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
|
||||||
integrity sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==
|
integrity sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==
|
||||||
|
@ -1093,18 +1093,6 @@
|
||||||
"@types/koa-compose" "*"
|
"@types/koa-compose" "*"
|
||||||
"@types/node" "*"
|
"@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":
|
"@types/lodash@4.14.180":
|
||||||
version "4.14.180"
|
version "4.14.180"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
|
||||||
|
@ -1478,11 +1466,6 @@ ansi-styles@^5.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
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:
|
anymatch@^3.0.3, anymatch@~3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||||
|
@ -2056,14 +2039,6 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.1"
|
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:
|
cookies@~0.8.0:
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
|
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
|
||||||
|
@ -2134,13 +2109,6 @@ debug@^3.2.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
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:
|
debuglog@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
||||||
|
@ -2201,7 +2169,7 @@ denque@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
||||||
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
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"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
|
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
|
||||||
|
@ -2353,11 +2321,6 @@ error-ex@^1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.2.1"
|
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:
|
escalade@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||||
|
@ -3602,11 +3565,6 @@ jws@^3.0.0, jws@^3.1.4, jws@^3.2.2:
|
||||||
jwa "^1.4.1"
|
jwa "^1.4.1"
|
||||||
safe-buffer "^5.0.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:
|
keygrip@~1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
|
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
|
||||||
|
@ -3626,26 +3584,11 @@ kleur@^3.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
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:
|
koa-compose@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
|
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
|
||||||
integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
|
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:
|
koa-convert@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5"
|
resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5"
|
||||||
|
@ -3654,11 +3597,6 @@ koa-convert@^2.0.0:
|
||||||
co "^4.6.0"
|
co "^4.6.0"
|
||||||
koa-compose "^4.1.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:
|
koa-passport@4.1.4:
|
||||||
version "4.1.4"
|
version "4.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa"
|
resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa"
|
||||||
|
@ -3666,37 +3604,7 @@ koa-passport@4.1.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
passport "^0.4.0"
|
passport "^0.4.0"
|
||||||
|
|
||||||
koa@2.7.0:
|
koa@2.13.4, koa@^2.13.4:
|
||||||
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:
|
|
||||||
version "2.13.4"
|
version "2.13.4"
|
||||||
resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e"
|
resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e"
|
||||||
integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==
|
integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==
|
||||||
|
@ -4079,11 +3987,6 @@ mkdirp@^1.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
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:
|
ms@2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.4",
|
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.1.22-alpha.4",
|
"@budibase/bbui": "2.1.22-alpha.6",
|
||||||
"@budibase/client": "2.1.22-alpha.4",
|
"@budibase/client": "2.1.22-alpha.6",
|
||||||
"@budibase/frontend-core": "2.1.22-alpha.4",
|
"@budibase/frontend-core": "2.1.22-alpha.6",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.4",
|
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -20,11 +20,12 @@
|
||||||
Toggle,
|
Toggle,
|
||||||
Tag,
|
Tag,
|
||||||
Tags,
|
Tags,
|
||||||
|
Icon,
|
||||||
|
Helpers,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { organisation, admin } from "stores/portal"
|
import { organisation, admin } from "stores/portal"
|
||||||
import { Helpers } from "@budibase/bbui"
|
|
||||||
|
|
||||||
const ConfigTypes = {
|
const ConfigTypes = {
|
||||||
Google: "google",
|
Google: "google",
|
||||||
|
@ -40,7 +41,9 @@
|
||||||
|
|
||||||
// Indicate to user that callback is based on platform url
|
// 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
|
// 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"
|
? "Vist the organisation page to update the platform URL"
|
||||||
: "Leave blank to use the default callback URL"
|
: "Leave blank to use the default callback URL"
|
||||||
|
|
||||||
|
@ -54,6 +57,7 @@
|
||||||
readonly: googleCallbackReadonly,
|
readonly: googleCallbackReadonly,
|
||||||
tooltip: googleCallbackTooltip,
|
tooltip: googleCallbackTooltip,
|
||||||
placeholder: $organisation.googleCallbackUrl,
|
placeholder: $organisation.googleCallbackUrl,
|
||||||
|
copyButton: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -66,9 +70,12 @@
|
||||||
{
|
{
|
||||||
name: "callbackURL",
|
name: "callbackURL",
|
||||||
readonly: true,
|
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",
|
label: "Callback URL",
|
||||||
placeholder: $organisation.oidcCallbackUrl,
|
placeholder: $organisation.oidcCallbackUrl,
|
||||||
|
copyButton: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -231,6 +238,11 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const copyToClipboard = async value => {
|
||||||
|
await Helpers.copyToClipboard(value)
|
||||||
|
notifications.success("Copied")
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
|
@ -336,11 +348,23 @@
|
||||||
{#each GoogleConfigFields.Google as field}
|
{#each GoogleConfigFields.Google as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||||
<Input
|
<div class="inputContainer">
|
||||||
bind:value={providers.google.config[field.name]}
|
<div class="input">
|
||||||
readonly={field.readonly}
|
<Input
|
||||||
placeholder={field.placeholder}
|
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>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
|
@ -375,12 +399,23 @@
|
||||||
{#each OIDCConfigFields.Oidc as field}
|
{#each OIDCConfigFields.Oidc as field}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
<Label size="L" tooltip={field.tooltip}>{field.label}</Label>
|
||||||
<Input
|
<div class="inputContainer">
|
||||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
<div class="input">
|
||||||
readonly={field.readonly}
|
<Input
|
||||||
placeholder={field.placeholder}
|
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||||
dataCy={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>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -557,4 +592,16 @@
|
||||||
.provider-title span {
|
.provider-title span {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
.inputContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.copy {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.1.22-alpha.4",
|
"@budibase/backend-core": "2.1.22-alpha.6",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.4",
|
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||||
"@budibase/types": "2.1.22-alpha.4",
|
"@budibase/types": "2.1.22-alpha.6",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.1.22-alpha.4",
|
"@budibase/bbui": "2.1.22-alpha.6",
|
||||||
"@budibase/frontend-core": "2.1.22-alpha.4",
|
"@budibase/frontend-core": "2.1.22-alpha.6",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.4",
|
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.1.22-alpha.4",
|
"@budibase/bbui": "2.1.22-alpha.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -43,11 +43,11 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.1.22-alpha.4",
|
"@budibase/backend-core": "2.1.22-alpha.6",
|
||||||
"@budibase/client": "2.1.22-alpha.4",
|
"@budibase/client": "2.1.22-alpha.6",
|
||||||
"@budibase/pro": "2.1.22-alpha.4",
|
"@budibase/pro": "2.1.22-alpha.6",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.4",
|
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||||
"@budibase/types": "2.1.22-alpha.4",
|
"@budibase/types": "2.1.22-alpha.6",
|
||||||
"@bull-board/api": "3.7.0",
|
"@bull-board/api": "3.7.0",
|
||||||
"@bull-board/koa": "3.9.4",
|
"@bull-board/koa": "3.9.4",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -2,6 +2,36 @@
|
||||||
const updateDotEnv = require("update-dotenv")
|
const updateDotEnv = require("update-dotenv")
|
||||||
|
|
||||||
const arg = process.argv.slice(2)[0]
|
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.
|
* 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
|
* 127.0.0.1 t2.local.com
|
||||||
*/
|
*/
|
||||||
updateDotEnv({
|
updateDotEnv({
|
||||||
ACCOUNT_PORTAL_URL:
|
ACCOUNT_PORTAL_URL: getAccountPortalUrl(),
|
||||||
arg === "enable"
|
COOKIE_DOMAIN: getCookieDomain(),
|
||||||
? "http://account.local.com:10001"
|
PLATFORM_URL: getBudibaseUrl(),
|
||||||
: "http://localhost:10001",
|
}).then(() => console.log("Updated server!"))
|
||||||
COOKIE_DOMAIN: arg === "enable" ? ".local.com" : "",
|
|
||||||
}).then(() => console.log("Updated worker!"))
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ export const run = async (db: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const allApps: App[] = await dbUtils.getAllApps({ dev: true })
|
const allApps = (await dbUtils.getAllApps({ dev: true })) as App[]
|
||||||
totals.apps = allApps.length
|
totals.apps = allApps.length
|
||||||
|
|
||||||
totals.usage = await quotas.backfill(allApps)
|
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 { getAllApps } from "@budibase/backend-core/db"
|
||||||
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../../utilities/usageQuota/rows"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { StaticQuotaName, QuotaUsageType } from "@budibase/types"
|
import { StaticQuotaName, QuotaUsageType, App } from "@budibase/types"
|
||||||
|
|
||||||
export const run = async () => {
|
export const run = async () => {
|
||||||
// get all rows in all apps
|
// 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 appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : []
|
||||||
const { appRows } = await getUniqueRows(appIds)
|
const { appRows } = await getUniqueRows(appIds)
|
||||||
|
|
||||||
|
|
|
@ -1273,12 +1273,12 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@2.1.22-alpha.4":
|
"@budibase/backend-core@2.1.22-alpha.6":
|
||||||
version "2.1.22-alpha.4"
|
version "2.1.22-alpha.6"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.22-alpha.4.tgz#25e186273d2416f1fe8ed639146d3d8e977c8bc8"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.22-alpha.6.tgz#e9886620fcbee6fe0348365cb96ac6484fc5f8fd"
|
||||||
integrity sha512-uii4hL/KvgMH8o713x54B2bGdtgYWE4AUxkzPs/2LCEa6Z3eeahziaElsRyc8oELqh0f71PYDdRC6gSJZZ1hdQ==
|
integrity sha512-P3NSgNuQXKmdeT8MLfeCji3ibRSeIIMSOQeNSQBWpaOTA69rpXQk753lHRwUWMpqil/ybsOuE/h1/Y3eTa+/UA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/types" "2.1.22-alpha.4"
|
"@budibase/types" "2.1.22-alpha.6"
|
||||||
"@shopify/jest-koa-mocks" "5.0.1"
|
"@shopify/jest-koa-mocks" "5.0.1"
|
||||||
"@techpass/passport-openidconnect" "0.3.2"
|
"@techpass/passport-openidconnect" "0.3.2"
|
||||||
aws-sdk "2.1030.0"
|
aws-sdk "2.1030.0"
|
||||||
|
@ -1360,13 +1360,13 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/pro@2.1.22-alpha.4":
|
"@budibase/pro@2.1.22-alpha.6":
|
||||||
version "2.1.22-alpha.4"
|
version "2.1.22-alpha.6"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.22-alpha.4.tgz#860edf8e6f022ea4bf33440d1c3173cc3f0ee9a1"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.22-alpha.6.tgz#e4d8238f1727eab85ac72b3ce2fcaec2a5c18456"
|
||||||
integrity sha512-L1EezM8ZdUWb+XlBgrslwCZ67wxUBRgTH3S/uGTZqGL1jGvEm7w4IQTyH55AqTABx5ZyNyAlOYKCipGjb5fplw==
|
integrity sha512-1CKqQ2HMX+/5p24aHpPlUgxoMjKRZxRoyK5fPD/X35Z0mDj+9Ohny3oqbF1fC20pl/20bmcaE4J+q2ph/pbxdQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "2.1.22-alpha.4"
|
"@budibase/backend-core" "2.1.22-alpha.6"
|
||||||
"@budibase/types" "2.1.22-alpha.4"
|
"@budibase/types" "2.1.22-alpha.6"
|
||||||
"@koa/router" "8.0.8"
|
"@koa/router" "8.0.8"
|
||||||
bull "4.10.1"
|
bull "4.10.1"
|
||||||
joi "17.6.0"
|
joi "17.6.0"
|
||||||
|
@ -1390,10 +1390,10 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/types@2.1.22-alpha.4":
|
"@budibase/types@2.1.22-alpha.6":
|
||||||
version "2.1.22-alpha.4"
|
version "2.1.22-alpha.6"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.22-alpha.4.tgz#cadcafae414610e0bec101380dc0300ad6ad0702"
|
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.22-alpha.6.tgz#a280321373c26a5bf1a52ed119de9178292893c0"
|
||||||
integrity sha512-xkrgqzkd4a9xb94Z05yh91fD7/2LSzrvvkS7eNV5rL7+9ddMWHUFVCCm/ggK+qnXKPhqIMq5TmGeLu28HjcJJQ==
|
integrity sha512-rVrhs9u7OTzlCxgUFqBu5H6jsMHhwH8uduPhT8Eo7J+Wr4J/0Io7WeuAt1egK6t83JiEPxDBH3WLzAH+04hPVA==
|
||||||
|
|
||||||
"@bull-board/api@3.7.0":
|
"@bull-board/api@3.7.0":
|
||||||
version "3.7.0"
|
version "3.7.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/types",
|
"name": "@budibase/types",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Budibase types",
|
"description": "Budibase types",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
@ -51,3 +51,9 @@ export interface SearchUsersRequest {
|
||||||
appId?: string
|
appId?: string
|
||||||
userIds?: string[]
|
userIds?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateAdminUserRequest {
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
tenantId: string
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from "./info"
|
export * from "./info"
|
||||||
export * from "./users"
|
export * from "./users"
|
||||||
export * from "./accounts"
|
export * from "./accounts"
|
||||||
|
export * from "./tenants"
|
||||||
|
|
5
packages/types/src/documents/platform/tenants.ts
Normal file
5
packages/types/src/documents/platform/tenants.ts
Normal file
|
@ -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 {
|
export interface AuthToken {
|
||||||
userId: string
|
userId: string
|
||||||
tenantId: string
|
tenantId: string
|
||||||
|
@ -25,3 +27,9 @@ export interface SessionKey {
|
||||||
export interface ScannedSession {
|
export interface ScannedSession {
|
||||||
value: Session
|
value: Session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlatformLogoutOpts {
|
||||||
|
ctx: BBContext
|
||||||
|
userId: string
|
||||||
|
keepActiveSession?: boolean
|
||||||
|
}
|
||||||
|
|
|
@ -9,3 +9,4 @@ export * from "./koa"
|
||||||
export * from "./auth"
|
export * from "./auth"
|
||||||
export * from "./locks"
|
export * from "./locks"
|
||||||
export * from "./db"
|
export * from "./db"
|
||||||
|
export * from "./middleware"
|
||||||
|
|
2
packages/types/src/sdk/middleware/index.ts
Normal file
2
packages/types/src/sdk/middleware/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./matchers"
|
||||||
|
export * from "./tenancy"
|
22
packages/types/src/sdk/middleware/matchers.ts
Normal file
22
packages/types/src/sdk/middleware/matchers.ts
Normal file
|
@ -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
|
||||||
|
}
|
13
packages/types/src/sdk/middleware/tenancy.ts
Normal file
13
packages/types/src/sdk/middleware/tenancy.ts
Normal file
|
@ -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())
|
|
57
packages/worker/__mocks__/oauth.ts
Normal file
57
packages/worker/__mocks__/oauth.ts
Normal file
|
@ -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",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.1.22-alpha.4",
|
"version": "2.1.22-alpha.6",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.1.22-alpha.4",
|
"@budibase/backend-core": "2.1.22-alpha.6",
|
||||||
"@budibase/pro": "2.1.22-alpha.4",
|
"@budibase/pro": "2.1.22-alpha.6",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.4",
|
"@budibase/string-templates": "2.1.22-alpha.6",
|
||||||
"@budibase/types": "2.1.22-alpha.4",
|
"@budibase/types": "2.1.22-alpha.6",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "8.0.8",
|
||||||
"@sentry/node": "6.17.7",
|
"@sentry/node": "6.17.7",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
|
@ -71,9 +71,11 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "26.0.23",
|
"@types/jest": "26.0.23",
|
||||||
|
"@types/jsonwebtoken": "8.5.1",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa__router": "8.0.11",
|
"@types/koa__router": "8.0.11",
|
||||||
"@types/node": "14.18.20",
|
"@types/node": "14.18.20",
|
||||||
|
"@types/node-fetch": "2.6.1",
|
||||||
"@types/pouchdb": "6.4.0",
|
"@types/pouchdb": "6.4.0",
|
||||||
"@types/uuid": "8.3.4",
|
"@types/uuid": "8.3.4",
|
||||||
"@typescript-eslint/parser": "5.12.0",
|
"@typescript-eslint/parser": "5.12.0",
|
||||||
|
|
|
@ -2,6 +2,36 @@
|
||||||
const updateDotEnv = require("update-dotenv")
|
const updateDotEnv = require("update-dotenv")
|
||||||
|
|
||||||
const arg = process.argv.slice(2)[0]
|
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.
|
* 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
|
* 127.0.0.1 t2.local.com
|
||||||
*/
|
*/
|
||||||
updateDotEnv({
|
updateDotEnv({
|
||||||
ACCOUNT_PORTAL_URL:
|
ACCOUNT_PORTAL_URL: getAccountPortalUrl(),
|
||||||
arg === "enable"
|
COOKIE_DOMAIN: getCookieDomain(),
|
||||||
? "http://account.local.com:10001"
|
PLATFORM_URL: getBudibaseUrl(),
|
||||||
: "http://localhost:10001",
|
|
||||||
COOKIE_DOMAIN: arg === "enable" ? ".local.com" : "",
|
|
||||||
PLATFORM_URL:
|
|
||||||
arg === "enable" ? "http://local.com:10000" : "http://localhost:10000",
|
|
||||||
}).then(() => console.log("Updated worker!"))
|
}).then(() => console.log("Updated worker!"))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const core = require("@budibase/backend-core")
|
import core from "@budibase/backend-core"
|
||||||
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
||||||
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
||||||
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
|
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
BulkUserRequest,
|
BulkUserRequest,
|
||||||
BulkUserResponse,
|
BulkUserResponse,
|
||||||
CloudAccount,
|
CloudAccount,
|
||||||
|
CreateAdminUserRequest,
|
||||||
InviteUserRequest,
|
InviteUserRequest,
|
||||||
InviteUsersRequest,
|
InviteUsersRequest,
|
||||||
SearchUsersRequest,
|
SearchUsersRequest,
|
||||||
|
@ -67,7 +68,8 @@ const parseBooleanParam = (param: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adminUser = async (ctx: 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 () => {
|
await tenancy.doInTenant(tenantId, async () => {
|
||||||
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
// account portal sends a pre-hashed password - honour param to prevent double hashing
|
||||||
const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
|
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 = {
|
ctx.body = {
|
||||||
multiTenancy: !!env.MULTI_TENANCY,
|
multiTenancy: !!env.MULTI_TENANCY,
|
||||||
cloud: !env.SELF_HOSTED,
|
cloud: !env.SELF_HOSTED,
|
|
@ -1,7 +1,8 @@
|
||||||
const accounts = require("@budibase/backend-core/accounts")
|
import { accounts } from "@budibase/backend-core"
|
||||||
const env = require("../../../environment")
|
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) {
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||||
const status = await accounts.getStatus()
|
const status = await accounts.getStatus()
|
||||||
ctx.body = status
|
ctx.body = status
|
|
@ -1,61 +1,18 @@
|
||||||
const { StaticDatabases, doWithDB } = require("@budibase/backend-core/db")
|
import { BBContext } from "@budibase/types"
|
||||||
const { getTenantId } = require("@budibase/backend-core/tenancy")
|
import { deprovisioning } from "@budibase/backend-core"
|
||||||
const { deleteTenant } = require("@budibase/backend-core/deprovision")
|
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
|
|
||||||
export const exists = async (ctx: any) => {
|
const _delete = async (ctx: BBContext) => {
|
||||||
const tenantId = ctx.request.params
|
const user = ctx.user!
|
||||||
ctx.body = {
|
const tenantId = ctx.params.tenantId
|
||||||
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
|
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetch = async (ctx: any) => {
|
if (tenantId !== user.tenantId) {
|
||||||
ctx.body = await doWithDB(
|
ctx.throw(403, "Tenant ID does not match current user")
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteTenant(tenantId)
|
|
||||||
await quotas.bustCache()
|
await quotas.bustCache()
|
||||||
|
await deprovisioning.deleteTenant(tenantId)
|
||||||
ctx.status = 204
|
ctx.status = 204
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.log.error(err)
|
ctx.log.error(err)
|
||||||
|
|
|
@ -7,11 +7,12 @@ import { errors, auth, middleware } from "@budibase/backend-core"
|
||||||
import { APIError } from "@budibase/types"
|
import { APIError } from "@budibase/types"
|
||||||
|
|
||||||
const PUBLIC_ENDPOINTS = [
|
const PUBLIC_ENDPOINTS = [
|
||||||
// old deprecated endpoints kept for backwards compat
|
// deprecated single tenant sso callback
|
||||||
{
|
{
|
||||||
route: "/api/admin/auth/google/callback",
|
route: "/api/admin/auth/google/callback",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
// deprecated single tenant sso callback
|
||||||
{
|
{
|
||||||
route: "/api/admin/auth/oidc/callback",
|
route: "/api/admin/auth/oidc/callback",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -44,17 +45,19 @@ const PUBLIC_ENDPOINTS = [
|
||||||
method: "POST",
|
method: "POST",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: "api/system/environment",
|
route: "/api/system/environment",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: "api/system/status",
|
route: "/api/system/status",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
// TODO: This should be an internal api
|
||||||
{
|
{
|
||||||
route: "/api/global/users/tenant/:id",
|
route: "/api/global/users/tenant/:id",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
// TODO: This should be an internal api
|
||||||
{
|
{
|
||||||
route: "/api/system/restored",
|
route: "/api/system/restored",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -62,17 +65,37 @@ const PUBLIC_ENDPOINTS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const NO_TENANCY_ENDPOINTS = [
|
const NO_TENANCY_ENDPOINTS = [
|
||||||
...PUBLIC_ENDPOINTS,
|
// system endpoints are not specific to any tenant
|
||||||
{
|
{
|
||||||
route: "/api/system",
|
route: "/api/system",
|
||||||
method: "ALL",
|
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",
|
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",
|
method: "GET",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,6 @@ const Router = require("@koa/router")
|
||||||
const authController = require("../../controllers/global/auth")
|
const authController = require("../../controllers/global/auth")
|
||||||
const { joiValidator } = require("@budibase/backend-core/auth")
|
const { joiValidator } = require("@budibase/backend-core/auth")
|
||||||
const Joi = require("joi")
|
const Joi = require("joi")
|
||||||
const { updateTenantId } = require("@budibase/backend-core/tenancy")
|
|
||||||
|
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
|
@ -29,77 +28,61 @@ function buildResetUpdateValidation() {
|
||||||
}).required().unknown(false))
|
}).required().unknown(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTenant(ctx, next) {
|
|
||||||
if (ctx.params) {
|
|
||||||
updateTenantId(ctx.params.tenantId)
|
|
||||||
}
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
router
|
router
|
||||||
|
// PASSWORD
|
||||||
.post(
|
.post(
|
||||||
"/api/global/auth/:tenantId/login",
|
"/api/global/auth/:tenantId/login",
|
||||||
buildAuthValidation(),
|
buildAuthValidation(),
|
||||||
updateTenant,
|
|
||||||
authController.authenticate
|
authController.authenticate
|
||||||
)
|
)
|
||||||
|
.post("/api/global/auth/logout", authController.logout)
|
||||||
.post(
|
.post(
|
||||||
"/api/global/auth/:tenantId/reset",
|
"/api/global/auth/:tenantId/reset",
|
||||||
buildResetValidation(),
|
buildResetValidation(),
|
||||||
updateTenant,
|
|
||||||
authController.reset
|
authController.reset
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/global/auth/:tenantId/reset/update",
|
"/api/global/auth/:tenantId/reset/update",
|
||||||
buildResetUpdateValidation(),
|
buildResetUpdateValidation(),
|
||||||
updateTenant,
|
|
||||||
authController.resetUpdate
|
authController.resetUpdate
|
||||||
)
|
)
|
||||||
.post("/api/global/auth/logout", authController.logout)
|
// INIT
|
||||||
.post("/api/global/auth/init", authController.setInitInfo)
|
.post("/api/global/auth/init", authController.setInitInfo)
|
||||||
.get("/api/global/auth/init", authController.getInitInfo)
|
.get("/api/global/auth/init", authController.getInitInfo)
|
||||||
.get(
|
|
||||||
"/api/global/auth/:tenantId/google",
|
// DATASOURCE - MULTI TENANT
|
||||||
updateTenant,
|
|
||||||
authController.googlePreAuth
|
|
||||||
)
|
|
||||||
.get(
|
.get(
|
||||||
"/api/global/auth/:tenantId/datasource/:provider",
|
"/api/global/auth/:tenantId/datasource/:provider",
|
||||||
updateTenant,
|
|
||||||
authController.datasourcePreAuth
|
authController.datasourcePreAuth
|
||||||
)
|
)
|
||||||
// single tenancy endpoint
|
.get(
|
||||||
.get("/api/global/auth/google/callback", authController.googleAuth)
|
"/api/global/auth/:tenantId/datasource/:provider/callback",
|
||||||
|
authController.datasourceAuth
|
||||||
|
)
|
||||||
|
|
||||||
|
// DATASOURCE - SINGLE TENANT - DEPRECATED
|
||||||
.get(
|
.get(
|
||||||
"/api/global/auth/datasource/:provider/callback",
|
"/api/global/auth/datasource/:provider/callback",
|
||||||
authController.datasourceAuth
|
authController.datasourceAuth
|
||||||
)
|
)
|
||||||
// multi-tenancy endpoint
|
|
||||||
.get(
|
// GOOGLE - MULTI TENANT
|
||||||
"/api/global/auth/:tenantId/google/callback",
|
.get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
|
||||||
updateTenant,
|
.get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
|
||||||
authController.googleAuth
|
|
||||||
)
|
// GOOGLE - SINGLE TENANT - DEPRECATED
|
||||||
.get(
|
.get("/api/global/auth/google/callback", authController.googleAuth)
|
||||||
"/api/global/auth/:tenantId/datasource/:provider/callback",
|
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||||
updateTenant,
|
|
||||||
authController.datasourceAuth
|
// OIDC - MULTI TENANT
|
||||||
)
|
|
||||||
.get(
|
.get(
|
||||||
"/api/global/auth/:tenantId/oidc/configs/:configId",
|
"/api/global/auth/:tenantId/oidc/configs/:configId",
|
||||||
updateTenant,
|
|
||||||
authController.oidcPreAuth
|
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)
|
.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)
|
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
jest.mock("nodemailer")
|
jest.mock("nodemailer")
|
||||||
import { TestConfiguration, mocks, API } from "../../../../tests"
|
import { TestConfiguration, mocks } from "../../../../tests"
|
||||||
const sendMailMock = mocks.email.mock()
|
const sendMailMock = mocks.email.mock()
|
||||||
import { events } from "@budibase/backend-core"
|
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", () => {
|
describe("/api/global/auth", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
const api = new API(config)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -19,90 +24,155 @@ describe("/api/global/auth", () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should logout", async () => {
|
describe("password", () => {
|
||||||
await api.auth.logout()
|
describe("POST /api/global/auth/:tenantId/login", () => {
|
||||||
expect(events.auth.logout).toBeCalledTimes(1)
|
it("should login", () => {})
|
||||||
})
|
|
||||||
|
|
||||||
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.",
|
|
||||||
})
|
})
|
||||||
expect(sendMailMock).toHaveBeenCalled()
|
|
||||||
|
|
||||||
expect(code).toBeDefined()
|
describe("POST /api/global/auth/logout", () => {
|
||||||
expect(events.user.passwordResetRequested).toBeCalledTimes(1)
|
it("should logout", async () => {
|
||||||
expect(events.user.passwordResetRequested).toBeCalledWith(user)
|
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 () => {
|
describe("init", () => {
|
||||||
const { code } = await api.auth.requestPasswordReset(sendMailMock)
|
describe("POST /api/global/auth/init", () => {})
|
||||||
const user = await config.getUser("test@test.com")
|
|
||||||
delete user.password
|
|
||||||
|
|
||||||
const res = await api.auth.updatePassword(code)
|
describe("GET /api/global/auth/init", () => {})
|
||||||
|
})
|
||||||
|
|
||||||
expect(res.body).toEqual({ message: "password reset successfully." })
|
describe("datasource", () => {
|
||||||
expect(events.user.passwordReset).toBeCalledTimes(1)
|
// MULTI TENANT
|
||||||
expect(events.user.passwordReset).toBeCalledWith(user)
|
|
||||||
|
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", () => {
|
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 () => {
|
beforeEach(async () => {
|
||||||
oidcConf = await config.saveOIDCConfig()
|
jest.clearAllMocks()
|
||||||
chosenConfig = oidcConf.config.configs[0]
|
mockGetWellKnownConfig()
|
||||||
configId = chosenConfig.uuid
|
|
||||||
mockStrategyConfig.mockReturnValue(chosenConfig)
|
// see: __mocks__/oauth
|
||||||
|
// for associated mocking inside passport
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
const generateOidcConfig = async () => {
|
||||||
expect(strategyFactory).toBeCalledWith(chosenConfig, expect.any(Function))
|
const oidcConf = await config.saveOIDCConfig()
|
||||||
})
|
const chosenConfig = oidcConf.config.configs[0]
|
||||||
|
return chosenConfig.uuid
|
||||||
|
}
|
||||||
|
|
||||||
describe("oidc configs", () => {
|
const mockGetWellKnownConfig = () => {
|
||||||
it("should load strategy and delegate to passport", async () => {
|
mocks.fetch.mockReturnValue({
|
||||||
await api.configs.getOIDCConfig(configId)
|
ok: true,
|
||||||
|
json: () => ({
|
||||||
|
issuer: "test",
|
||||||
|
authorization_endpoint: "http://localhost/auth",
|
||||||
|
token_endpoint: "http://localhost/token",
|
||||||
|
userinfo_endpoint: "http://localhost/userinfo",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
|
// MULTI TENANT
|
||||||
scope: ["profile", "email", "offline_access"],
|
describe("GET /api/global/auth/:tenantId/oidc/configs/:configId", () => {
|
||||||
})
|
it("redirects to auth provider", async () => {
|
||||||
expect(passportSpy.mock.calls.length).toBe(1)
|
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", () => {
|
describe("GET /api/global/auth/:tenantId/oidc/callback", () => {
|
||||||
it("should load strategy and delegate to passport", async () => {
|
it("logs in", async () => {
|
||||||
await api.configs.OIDCCallback(configId)
|
const configId = await generateOidcConfig()
|
||||||
|
const preAuthRes = await config.api.configs.getOIDCConfig(configId)
|
||||||
|
|
||||||
expect(passportSpy).toBeCalledWith(
|
const res = await config.api.configs.OIDCCallback(configId, preAuthRes)
|
||||||
mockStrategyReturn,
|
|
||||||
{
|
expect(events.auth.login).toBeCalledWith("oidc")
|
||||||
successRedirect: "/",
|
expect(events.auth.login).toBeCalledTimes(1)
|
||||||
failureRedirect: "/error",
|
expect(res.status).toBe(302)
|
||||||
},
|
const location: string = res.get("location")
|
||||||
expect.anything()
|
expect(location).toBe("/")
|
||||||
)
|
expectSetAuthCookie(res)
|
||||||
expect(passportSpy.mock.calls.length).toBe(1)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 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
|
// mock the email system
|
||||||
jest.mock("nodemailer")
|
jest.mock("nodemailer")
|
||||||
import { TestConfiguration, structures, mocks, API } from "../../../../tests"
|
import { TestConfiguration, structures, mocks } from "../../../../tests"
|
||||||
mocks.email.mock()
|
mocks.email.mock()
|
||||||
import { Configs, events } from "@budibase/backend-core"
|
import { Configs, events } from "@budibase/backend-core"
|
||||||
|
|
||||||
describe("configs", () => {
|
describe("configs", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
const api = new API(config)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -28,7 +27,7 @@ describe("configs", () => {
|
||||||
_rev,
|
_rev,
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api.configs.saveConfig(data)
|
const res = await config.api.configs.saveConfig(data)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
|
@ -235,7 +234,7 @@ describe("configs", () => {
|
||||||
expect(events.org.nameUpdated).toBeCalledTimes(1)
|
expect(events.org.nameUpdated).toBeCalledTimes(1)
|
||||||
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
||||||
expect(events.org.platformURLUpdated).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.nameUpdated).toBeCalledTimes(1)
|
||||||
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
||||||
expect(events.org.platformURLUpdated).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 () => {
|
it("should return the correct checklist status based on the state of the budibase installation", async () => {
|
||||||
await config.saveSmtpConfig()
|
await config.saveSmtpConfig()
|
||||||
|
|
||||||
const res = await api.configs.getConfigChecklist()
|
const res = await config.api.configs.getConfigChecklist()
|
||||||
const checklist = res.body
|
const checklist = res.body
|
||||||
|
|
||||||
expect(checklist.apps.checked).toBeFalsy()
|
expect(checklist.apps.checked).toBeFalsy()
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
jest.mock("nodemailer")
|
jest.mock("nodemailer")
|
||||||
import { TestConfiguration, mocks, API } from "../../../../tests"
|
import { TestConfiguration, mocks } from "../../../../tests"
|
||||||
const sendMailMock = mocks.email.mock()
|
const sendMailMock = mocks.email.mock()
|
||||||
import { EmailTemplatePurpose } from "../../../../constants"
|
import { EmailTemplatePurpose } from "../../../../constants"
|
||||||
|
|
||||||
describe("/api/global/email", () => {
|
describe("/api/global/email", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
const api = new API(config)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -20,7 +19,9 @@ describe("/api/global/email", () => {
|
||||||
await config.saveSmtpConfig()
|
await config.saveSmtpConfig()
|
||||||
await config.saveSettingsConfig()
|
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(res.body.message).toBeDefined()
|
||||||
expect(sendMailMock).toHaveBeenCalled()
|
expect(sendMailMock).toHaveBeenCalled()
|
||||||
|
|
31
packages/worker/src/api/routes/global/tests/license.spec.ts
Normal file
31
packages/worker/src/api/routes/global/tests/license.spec.ts
Normal file
|
@ -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"
|
import { EmailTemplatePurpose } from "../../../../constants"
|
||||||
const nodemailer = require("nodemailer")
|
const nodemailer = require("nodemailer")
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
|
@ -8,7 +8,6 @@ jest.setTimeout(30000)
|
||||||
|
|
||||||
describe("/api/global/email", () => {
|
describe("/api/global/email", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
const api = new API(config)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -35,7 +34,7 @@ describe("/api/global/email", () => {
|
||||||
await Promise.race([config.saveEtherealSmtpConfig(), timeout()])
|
await Promise.race([config.saveEtherealSmtpConfig(), timeout()])
|
||||||
await Promise.race([config.saveSettingsConfig(), 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
|
// ethereal hiccup, can't test right now
|
||||||
if (res.status >= 300) {
|
if (res.status >= 300) {
|
||||||
return
|
return
|
||||||
|
|
27
packages/worker/src/api/routes/global/tests/roles.spec.ts
Normal file
27
packages/worker/src/api/routes/global/tests/roles.spec.ts
Normal file
|
@ -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")
|
jest.mock("nodemailer")
|
||||||
import { TestConfiguration, API, mocks } from "../../../../tests"
|
import { TestConfiguration, mocks } from "../../../../tests"
|
||||||
import { events } from "@budibase/backend-core"
|
import { events } from "@budibase/backend-core"
|
||||||
|
|
||||||
describe("/api/global/self", () => {
|
describe("/api/global/self", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
const api = new API(config)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -24,7 +23,7 @@ describe("/api/global/self", () => {
|
||||||
await config.createSession(user)
|
await config.createSession(user)
|
||||||
|
|
||||||
delete user.password
|
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)
|
const dbUser = await config.getUser(user.email)
|
||||||
user._rev = dbUser._rev
|
user._rev = dbUser._rev
|
||||||
|
@ -40,7 +39,7 @@ describe("/api/global/self", () => {
|
||||||
await config.createSession(user)
|
await config.createSession(user)
|
||||||
|
|
||||||
user.password = "newPassword"
|
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)
|
const dbUser = await config.getUser(user.email)
|
||||||
user._rev = dbUser._rev
|
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,
|
mocks,
|
||||||
structures,
|
structures,
|
||||||
TENANT_1,
|
TENANT_1,
|
||||||
API,
|
|
||||||
} from "../../../../tests"
|
} from "../../../../tests"
|
||||||
const sendMailMock = mocks.email.mock()
|
const sendMailMock = mocks.email.mock()
|
||||||
import { events, tenancy } from "@budibase/backend-core"
|
import { events, tenancy } from "@budibase/backend-core"
|
||||||
|
|
||||||
describe("/api/global/users", () => {
|
describe("/api/global/users", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
const api = new API(config)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -30,7 +28,10 @@ describe("/api/global/users", () => {
|
||||||
describe("invite", () => {
|
describe("invite", () => {
|
||||||
it("should be able to generate an invitation", async () => {
|
it("should be able to generate an invitation", async () => {
|
||||||
const email = structures.users.newEmail()
|
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(res.body).toEqual({ message: "Invitation has been sent." })
|
||||||
expect(sendMailMock).toHaveBeenCalled()
|
expect(sendMailMock).toHaveBeenCalled()
|
||||||
|
@ -39,7 +40,7 @@ describe("/api/global/users", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not be able to generate an invitation for existing user", async () => {
|
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,
|
sendMailMock,
|
||||||
config.defaultUser!.email,
|
config.defaultUser!.email,
|
||||||
400
|
400
|
||||||
|
@ -53,9 +54,12 @@ describe("/api/global/users", () => {
|
||||||
|
|
||||||
it("should be able to create new user from invite", async () => {
|
it("should be able to create new user from invite", async () => {
|
||||||
const email = structures.users.newEmail()
|
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()
|
expect(res.body._id).toBeDefined()
|
||||||
const user = await config.getUser(email)
|
const user = await config.getUser(email)
|
||||||
|
@ -74,7 +78,7 @@ describe("/api/global/users", () => {
|
||||||
})
|
})
|
||||||
const request = [newUserInvite(), newUserInvite()]
|
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
|
const body = res.body as InviteUsersResponse
|
||||||
expect(body.successful.length).toBe(2)
|
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 () => {
|
it("should not be able to generate an invitation for existing user", async () => {
|
||||||
const request = [{ email: config.defaultUser!.email, userInfo: {} }]
|
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
|
const body = res.body as InviteUsersResponse
|
||||||
expect(body.successful.length).toBe(0)
|
expect(body.successful.length).toBe(0)
|
||||||
|
@ -102,7 +106,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser()
|
const user = await config.createUser()
|
||||||
jest.clearAllMocks()
|
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?.successful.length).toBe(0)
|
||||||
expect(response.created?.unsuccessful.length).toBe(1)
|
expect(response.created?.unsuccessful.length).toBe(1)
|
||||||
|
@ -115,7 +119,7 @@ describe("/api/global/users", () => {
|
||||||
jest.resetAllMocks()
|
jest.resetAllMocks()
|
||||||
|
|
||||||
await tenancy.doInTenant(TENANT_1, async () => {
|
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?.successful.length).toBe(0)
|
||||||
expect(response.created?.unsuccessful.length).toBe(1)
|
expect(response.created?.unsuccessful.length).toBe(1)
|
||||||
|
@ -126,11 +130,11 @@ describe("/api/global/users", () => {
|
||||||
|
|
||||||
it("should ignore accounts using the same email", async () => {
|
it("should ignore accounts using the same email", async () => {
|
||||||
const account = structures.accounts.account()
|
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 })
|
const user = structures.users.user({ email: resp.email })
|
||||||
jest.clearAllMocks()
|
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?.successful.length).toBe(0)
|
||||||
expect(response.created?.unsuccessful.length).toBe(1)
|
expect(response.created?.unsuccessful.length).toBe(1)
|
||||||
|
@ -143,7 +147,11 @@ describe("/api/global/users", () => {
|
||||||
const admin = structures.users.adminUser()
|
const admin = structures.users.adminUser()
|
||||||
const user = structures.users.user()
|
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.length).toBe(3)
|
||||||
expect(response.created?.successful[0].email).toBe(builder.email)
|
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 () => {
|
it("should be able to create a basic user", async () => {
|
||||||
const user = structures.users.user()
|
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.created).toBeCalledTimes(1)
|
||||||
expect(events.user.updated).not.toBeCalled()
|
expect(events.user.updated).not.toBeCalled()
|
||||||
|
@ -171,7 +179,7 @@ describe("/api/global/users", () => {
|
||||||
it("should be able to create an admin user", async () => {
|
it("should be able to create an admin user", async () => {
|
||||||
const user = structures.users.adminUser()
|
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.created).toBeCalledTimes(1)
|
||||||
expect(events.user.updated).not.toBeCalled()
|
expect(events.user.updated).not.toBeCalled()
|
||||||
|
@ -182,7 +190,7 @@ describe("/api/global/users", () => {
|
||||||
it("should be able to create a builder user", async () => {
|
it("should be able to create a builder user", async () => {
|
||||||
const user = structures.users.builderUser()
|
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.created).toBeCalledTimes(1)
|
||||||
expect(events.user.updated).not.toBeCalled()
|
expect(events.user.updated).not.toBeCalled()
|
||||||
|
@ -197,7 +205,7 @@ describe("/api/global/users", () => {
|
||||||
app_456: "role2",
|
app_456: "role2",
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
const savedUser = await config.getUser(user.email)
|
const savedUser = await config.getUser(user.email)
|
||||||
expect(events.user.created).toBeCalledTimes(1)
|
expect(events.user.created).toBeCalledTimes(1)
|
||||||
|
@ -213,7 +221,7 @@ describe("/api/global/users", () => {
|
||||||
delete user._id
|
delete user._id
|
||||||
delete user._rev
|
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(response.body.message).toBe(`Unavailable`)
|
||||||
expect(events.user.created).toBeCalledTimes(0)
|
expect(events.user.created).toBeCalledTimes(0)
|
||||||
|
@ -225,7 +233,7 @@ describe("/api/global/users", () => {
|
||||||
|
|
||||||
await tenancy.doInTenant(TENANT_1, async () => {
|
await tenancy.doInTenant(TENANT_1, async () => {
|
||||||
delete user._id
|
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(response.body.message).toBe(`Unavailable`)
|
||||||
expect(events.user.created).toBeCalledTimes(0)
|
expect(events.user.created).toBeCalledTimes(0)
|
||||||
|
@ -237,7 +245,7 @@ describe("/api/global/users", () => {
|
||||||
const account = structures.accounts.cloudAccount()
|
const account = structures.accounts.cloudAccount()
|
||||||
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
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(response.body.message).toBe(`Unavailable`)
|
||||||
expect(events.user.created).toBeCalledTimes(0)
|
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 () => {
|
it("should not be able to create a user with the same email and different casing", async () => {
|
||||||
const user = structures.users.user()
|
const user = structures.users.user()
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
user.email = user.email.toUpperCase()
|
user.email = user.email.toUpperCase()
|
||||||
await api.users.saveUser(user, 400)
|
await config.api.users.saveUser(user, 400)
|
||||||
|
|
||||||
expect(events.user.created).toBeCalledTimes(1)
|
expect(events.user.created).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not be able to bulk create a user with the same email and different casing", async () => {
|
it("should not be able to bulk create a user with the same email and different casing", async () => {
|
||||||
const user = structures.users.user()
|
const user = structures.users.user()
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
user.email = user.email.toUpperCase()
|
user.email = user.email.toUpperCase()
|
||||||
await api.users.bulkCreateUsers([user])
|
await config.api.users.bulkCreateUsers([user])
|
||||||
|
|
||||||
expect(events.user.created).toBeCalledTimes(1)
|
expect(events.user.created).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
@ -269,7 +277,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser()
|
const user = await config.createUser()
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
expect(events.user.created).not.toBeCalled()
|
expect(events.user.created).not.toBeCalled()
|
||||||
expect(events.user.updated).toBeCalledTimes(1)
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
@ -284,7 +292,7 @@ describe("/api/global/users", () => {
|
||||||
|
|
||||||
user.forceResetPassword = true
|
user.forceResetPassword = true
|
||||||
user.password = "tempPassword"
|
user.password = "tempPassword"
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
expect(events.user.created).not.toBeCalled()
|
expect(events.user.created).not.toBeCalled()
|
||||||
expect(events.user.updated).toBeCalledTimes(1)
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
@ -297,7 +305,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser()
|
const user = await config.createUser()
|
||||||
jest.clearAllMocks()
|
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.created).not.toBeCalled()
|
||||||
expect(events.user.updated).toBeCalledTimes(1)
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
@ -309,7 +317,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser()
|
const user = await config.createUser()
|
||||||
jest.clearAllMocks()
|
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.created).not.toBeCalled()
|
||||||
expect(events.user.updated).toBeCalledTimes(1)
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
@ -323,7 +331,7 @@ describe("/api/global/users", () => {
|
||||||
user.admin!.global = false
|
user.admin!.global = false
|
||||||
user.builder!.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.created).not.toBeCalled()
|
||||||
expect(events.user.updated).toBeCalledTimes(1)
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
@ -336,7 +344,7 @@ describe("/api/global/users", () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
user.builder!.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.created).not.toBeCalled()
|
||||||
expect(events.user.updated).toBeCalledTimes(1)
|
expect(events.user.updated).toBeCalledTimes(1)
|
||||||
|
@ -352,7 +360,7 @@ describe("/api/global/users", () => {
|
||||||
app_456: "role2",
|
app_456: "role2",
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
const savedUser = await config.getUser(user.email)
|
const savedUser = await config.getUser(user.email)
|
||||||
expect(events.user.created).not.toBeCalled()
|
expect(events.user.created).not.toBeCalled()
|
||||||
|
@ -372,7 +380,7 @@ describe("/api/global/users", () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
user.roles = {}
|
user.roles = {}
|
||||||
|
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
const savedUser = await config.getUser(user.email)
|
const savedUser = await config.getUser(user.email)
|
||||||
expect(events.user.created).not.toBeCalled()
|
expect(events.user.created).not.toBeCalled()
|
||||||
|
@ -395,7 +403,7 @@ describe("/api/global/users", () => {
|
||||||
app_456: "role2-edit",
|
app_456: "role2-edit",
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.users.saveUser(user)
|
await config.api.users.saveUser(user)
|
||||||
|
|
||||||
const savedUser = await config.getUser(user.email)
|
const savedUser = await config.getUser(user.email)
|
||||||
expect(events.user.created).not.toBeCalled()
|
expect(events.user.created).not.toBeCalled()
|
||||||
|
@ -411,7 +419,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser(structures.users.user({ email }))
|
const user = await config.createUser(structures.users.user({ email }))
|
||||||
user.email = "new@test.com"
|
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)
|
const dbUser = await config.getUser(email)
|
||||||
user.email = email
|
user.email = email
|
||||||
|
@ -424,7 +432,7 @@ describe("/api/global/users", () => {
|
||||||
it("should not be able to bulk delete current user", async () => {
|
it("should not be able to bulk delete current user", async () => {
|
||||||
const user = await config.defaultUser!
|
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(response.message).toBe("Unable to delete self.")
|
||||||
expect(events.user.deleted).not.toBeCalled()
|
expect(events.user.deleted).not.toBeCalled()
|
||||||
|
@ -436,7 +444,7 @@ describe("/api/global/users", () => {
|
||||||
account.budibaseUserId = user._id!
|
account.budibaseUserId = user._id!
|
||||||
mocks.accounts.getAccountByTenantId.mockReturnValue(account)
|
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?.successful.length).toBe(0)
|
||||||
expect(response.deleted?.unsuccessful.length).toBe(1)
|
expect(response.deleted?.unsuccessful.length).toBe(1)
|
||||||
|
@ -454,7 +462,7 @@ describe("/api/global/users", () => {
|
||||||
const builder = structures.users.builderUser()
|
const builder = structures.users.builderUser()
|
||||||
const admin = structures.users.adminUser()
|
const admin = structures.users.adminUser()
|
||||||
const user = structures.users.user()
|
const user = structures.users.user()
|
||||||
const createdUsers = await api.users.bulkCreateUsers([
|
const createdUsers = await config.api.users.bulkCreateUsers([
|
||||||
builder,
|
builder,
|
||||||
admin,
|
admin,
|
||||||
user,
|
user,
|
||||||
|
@ -463,7 +471,7 @@ describe("/api/global/users", () => {
|
||||||
const toDelete = createdUsers.created?.successful.map(
|
const toDelete = createdUsers.created?.successful.map(
|
||||||
u => u._id!
|
u => u._id!
|
||||||
) as string[]
|
) 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?.successful.length).toBe(3)
|
||||||
expect(response.deleted?.unsuccessful.length).toBe(0)
|
expect(response.deleted?.unsuccessful.length).toBe(0)
|
||||||
|
@ -478,7 +486,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser()
|
const user = await config.createUser()
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
await api.users.deleteUser(user._id!)
|
await config.api.users.deleteUser(user._id!)
|
||||||
|
|
||||||
expect(events.user.deleted).toBeCalledTimes(1)
|
expect(events.user.deleted).toBeCalledTimes(1)
|
||||||
expect(events.user.permissionBuilderRemoved).not.toBeCalled()
|
expect(events.user.permissionBuilderRemoved).not.toBeCalled()
|
||||||
|
@ -489,7 +497,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser(structures.users.adminUser())
|
const user = await config.createUser(structures.users.adminUser())
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
await api.users.deleteUser(user._id!)
|
await config.api.users.deleteUser(user._id!)
|
||||||
|
|
||||||
expect(events.user.deleted).toBeCalledTimes(1)
|
expect(events.user.deleted).toBeCalledTimes(1)
|
||||||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
||||||
|
@ -500,7 +508,7 @@ describe("/api/global/users", () => {
|
||||||
const user = await config.createUser(structures.users.builderUser())
|
const user = await config.createUser(structures.users.builderUser())
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
|
||||||
await api.users.deleteUser(user._id!)
|
await config.api.users.deleteUser(user._id!)
|
||||||
|
|
||||||
expect(events.user.deleted).toBeCalledTimes(1)
|
expect(events.user.deleted).toBeCalledTimes(1)
|
||||||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
|
||||||
|
@ -512,7 +520,7 @@ describe("/api/global/users", () => {
|
||||||
const account = structures.accounts.cloudAccount()
|
const account = structures.accounts.cloudAccount()
|
||||||
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
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")
|
expect(response.body.message).toBe("Account holder cannot be deleted")
|
||||||
})
|
})
|
||||||
|
@ -523,7 +531,7 @@ describe("/api/global/users", () => {
|
||||||
account.email = user.email
|
account.email = user.email
|
||||||
mocks.accounts.getAccount.mockReturnValueOnce(account)
|
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.")
|
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
|
|
8
packages/worker/src/api/routes/system/environment.ts
Normal file
8
packages/worker/src/api/routes/system/environment.ts
Normal file
|
@ -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
|
|
8
packages/worker/src/api/routes/system/status.ts
Normal file
8
packages/worker/src/api/routes/system/status.ts
Normal file
|
@ -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
|
|
13
packages/worker/src/api/routes/system/tenants.ts
Normal file
13
packages/worker/src/api/routes/system/tenants.ts
Normal file
|
@ -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 sdk from "../../../../sdk"
|
||||||
import { TestConfiguration, structures, API } from "../../../../tests"
|
import { TestConfiguration, structures } from "../../../../tests"
|
||||||
import { v4 as uuid } from "uuid"
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
describe("accounts", () => {
|
describe("accounts", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
const api = new API(config)
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
|
@ -23,7 +22,7 @@ describe("accounts", () => {
|
||||||
it("saves account metadata", async () => {
|
it("saves account metadata", async () => {
|
||||||
let account = structures.accounts.account()
|
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 id = sdk.accounts.formatAccountMetadataId(account.accountId)
|
||||||
const metadata = await sdk.accounts.getMetadata(id)
|
const metadata = await sdk.accounts.getMetadata(id)
|
||||||
|
@ -34,9 +33,9 @@ describe("accounts", () => {
|
||||||
describe("destroyMetadata", () => {
|
describe("destroyMetadata", () => {
|
||||||
it("destroys account metadata", async () => {
|
it("destroys account metadata", async () => {
|
||||||
const account = structures.accounts.account()
|
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)
|
const deleted = await sdk.accounts.getMetadata(account.accountId)
|
||||||
expect(deleted).toBe(undefined)
|
expect(deleted).toBe(undefined)
|
||||||
|
@ -45,7 +44,7 @@ describe("accounts", () => {
|
||||||
it("destroys account metadata that does not exist", async () => {
|
it("destroys account metadata that does not exist", async () => {
|
||||||
const id = uuid()
|
const id = uuid()
|
||||||
|
|
||||||
const response = await api.accounts.destroyMetadata(id)
|
const response = await config.api.accounts.destroyMetadata(id)
|
||||||
|
|
||||||
expect(response.status).toBe(204)
|
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",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
36
packages/worker/src/api/routes/system/tests/restore.spec.ts
Normal file
36
packages/worker/src/api/routes/system/tests/restore.spec.ts
Normal file
|
@ -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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
48
packages/worker/src/api/routes/system/tests/status.spec.ts
Normal file
48
packages/worker/src/api/routes/system/tests/status.spec.ts
Normal file
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
61
packages/worker/src/api/routes/system/tests/tenants.spec.ts
Normal file
61
packages/worker/src/api/routes/system/tests/tenants.spec.ts
Normal file
|
@ -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())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
73
packages/worker/src/middleware/tests/tenancy.spec.ts
Normal file
73
packages/worker/src/middleware/tests/tenancy.spec.ts
Normal file
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
|
@ -12,30 +12,33 @@ import {
|
||||||
Headers,
|
Headers,
|
||||||
sessions,
|
sessions,
|
||||||
auth,
|
auth,
|
||||||
|
constants,
|
||||||
|
env as coreEnv,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
import { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures"
|
import structures, { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures"
|
||||||
import structures from "./structures"
|
|
||||||
import { CreateUserResponse, User, AuthToken } from "@budibase/types"
|
import { CreateUserResponse, User, AuthToken } from "@budibase/types"
|
||||||
|
import API from "./api"
|
||||||
|
|
||||||
enum Mode {
|
enum Mode {
|
||||||
ACCOUNT = "account",
|
CLOUD = "cloud",
|
||||||
SELF = "self",
|
SELF = "self",
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
server: any
|
server: any
|
||||||
request: any
|
request: any
|
||||||
|
api: API
|
||||||
defaultUser?: User
|
defaultUser?: User
|
||||||
tenant1User?: User
|
tenant1User?: User
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
opts: { openServer: boolean; mode: Mode } = {
|
opts: { openServer: boolean; mode: Mode } = {
|
||||||
openServer: true,
|
openServer: true,
|
||||||
mode: Mode.ACCOUNT,
|
mode: Mode.CLOUD,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (opts.mode === Mode.ACCOUNT) {
|
if (opts.mode === Mode.CLOUD) {
|
||||||
this.modeAccount()
|
this.modeCloud()
|
||||||
} else if (opts.mode === Mode.SELF) {
|
} else if (opts.mode === Mode.SELF) {
|
||||||
this.modeSelf()
|
this.modeSelf()
|
||||||
}
|
}
|
||||||
|
@ -46,6 +49,8 @@ class TestConfiguration {
|
||||||
// we need the request for logging in, involves cookies, hard to fake
|
// we need the request for logging in, involves cookies, hard to fake
|
||||||
this.request = supertest(this.server)
|
this.request = supertest(this.server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.api = new API(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
getRequest() {
|
getRequest() {
|
||||||
|
@ -54,20 +59,24 @@ class TestConfiguration {
|
||||||
|
|
||||||
// MODES
|
// MODES
|
||||||
|
|
||||||
modeAccount = () => {
|
setMultiTenancy = (value: boolean) => {
|
||||||
env.SELF_HOSTED = false
|
env._set("MULTI_TENANCY", value)
|
||||||
// @ts-ignore
|
coreEnv._set("MULTI_TENANCY", value)
|
||||||
env.MULTI_TENANCY = true
|
}
|
||||||
// @ts-ignore
|
|
||||||
env.DISABLE_ACCOUNT_PORTAL = false
|
setSelfHosted = (value: boolean) => {
|
||||||
|
env._set("SELF_HOSTED", value)
|
||||||
|
coreEnv._set("SELF_HOSTED", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
modeCloud = () => {
|
||||||
|
this.setSelfHosted(false)
|
||||||
|
this.setMultiTenancy(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
modeSelf = () => {
|
modeSelf = () => {
|
||||||
env.SELF_HOSTED = true
|
this.setSelfHosted(true)
|
||||||
// @ts-ignore
|
this.setMultiTenancy(false)
|
||||||
env.MULTI_TENANCY = false
|
|
||||||
// @ts-ignore
|
|
||||||
env.DISABLE_ACCOUNT_PORTAL = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UTILS
|
// UTILS
|
||||||
|
@ -114,6 +123,25 @@ class TestConfiguration {
|
||||||
|
|
||||||
// TENANCY
|
// 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.Headers.TENANT_ID]: res.tenantId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// create a session for the new user
|
||||||
|
const user = userRes.body
|
||||||
|
await this.createSession(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
getTenantId() {
|
getTenantId() {
|
||||||
try {
|
try {
|
||||||
return tenancy.getTenantId()
|
return tenancy.getTenantId()
|
||||||
|
@ -122,35 +150,32 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// USER / AUTH
|
// AUTH
|
||||||
|
|
||||||
async createDefaultUser() {
|
async _createSession({
|
||||||
const user = structures.users.adminUser({
|
userId,
|
||||||
email: "test@test.com",
|
tenantId,
|
||||||
password: "test",
|
}: {
|
||||||
})
|
userId: string
|
||||||
this.defaultUser = await this.createUser(user)
|
tenantId: string
|
||||||
}
|
}) {
|
||||||
|
await sessions.createASession(userId!, {
|
||||||
async createTenant1User() {
|
|
||||||
const user = structures.users.adminUser({
|
|
||||||
email: "tenant1@test.com",
|
|
||||||
password: "test",
|
|
||||||
})
|
|
||||||
this.tenant1User = await this.createUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createSession(user: User) {
|
|
||||||
await sessions.createASession(user._id!, {
|
|
||||||
sessionId: "sessionid",
|
sessionId: "sessionid",
|
||||||
tenantId: user.tenantId,
|
tenantId: tenantId,
|
||||||
csrfToken: CSRF_TOKEN,
|
csrfToken: CSRF_TOKEN,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createSession(user: User) {
|
||||||
|
return this._createSession({ userId: user._id!, tenantId: user.tenantId })
|
||||||
|
}
|
||||||
|
|
||||||
cookieHeader(cookies: any) {
|
cookieHeader(cookies: any) {
|
||||||
|
if (!Array.isArray(cookies)) {
|
||||||
|
cookies = [cookies]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
Cookie: [cookies],
|
Cookie: cookies,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +204,32 @@ class TestConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internalAPIHeaders() {
|
||||||
|
return { [constants.Headers.API_KEY]: env.INTERNAL_API_KEY }
|
||||||
|
}
|
||||||
|
|
||||||
|
adminOnlyResponse = () => {
|
||||||
|
return { message: "Admin user only endpoint.", status: 403 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// USERS
|
||||||
|
|
||||||
|
async createDefaultUser() {
|
||||||
|
const user = structures.users.adminUser({
|
||||||
|
email: "test@test.com",
|
||||||
|
password: "test",
|
||||||
|
})
|
||||||
|
this.defaultUser = await this.createUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTenant1User() {
|
||||||
|
const user = structures.users.adminUser({
|
||||||
|
email: "tenant1@test.com",
|
||||||
|
password: "test",
|
||||||
|
})
|
||||||
|
this.tenant1User = await this.createUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
async getUser(email: string): Promise<User> {
|
async getUser(email: string): Promise<User> {
|
||||||
return tenancy.doInTenant(this.getTenantId(), () => {
|
return tenancy.doInTenant(this.getTenantId(), () => {
|
||||||
return users.getGlobalUserByEmail(email)
|
return users.getGlobalUserByEmail(email)
|
||||||
|
@ -240,11 +291,6 @@ class TestConfiguration {
|
||||||
|
|
||||||
// CONFIGS - OIDC
|
// CONFIGS - OIDC
|
||||||
|
|
||||||
getOIDConfigCookie(configId: string) {
|
|
||||||
const token = auth.jwt.sign(configId, env.JWT_SECRET)
|
|
||||||
return this.cookieHeader([[`${Cookies.OIDC_CONFIG}=${token}`]])
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveOIDCConfig() {
|
async saveOIDCConfig() {
|
||||||
await this.deleteConfig(Configs.OIDC)
|
await this.deleteConfig(Configs.OIDC)
|
||||||
const config = structures.configs.oidc()
|
const config = structures.configs.oidc()
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
import { Account, AccountMetadata } from "@budibase/types"
|
import { Account, AccountMetadata } from "@budibase/types"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class AccountAPI {
|
export class AccountAPI extends TestAPI {
|
||||||
config: TestConfiguration
|
|
||||||
request: any
|
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
super(config)
|
||||||
this.request = config.request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMetadata = async (account: Account) => {
|
saveMetadata = async (account: Account) => {
|
||||||
const res = await this.request
|
const res = await this.request
|
||||||
.put(`/api/system/accounts/${account.accountId}/metadata`)
|
.put(`/api/system/accounts/${account.accountId}/metadata`)
|
||||||
.send(account)
|
.send(account)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.internalAPIHeaders())
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
return res.body as AccountMetadata
|
return res.body as AccountMetadata
|
||||||
|
@ -23,6 +20,6 @@ export class AccountAPI {
|
||||||
destroyMetadata = (accountId: string) => {
|
destroyMetadata = (accountId: string) => {
|
||||||
return this.request
|
return this.request
|
||||||
.del(`/api/system/accounts/${accountId}/metadata`)
|
.del(`/api/system/accounts/${accountId}/metadata`)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.internalAPIHeaders())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class AuthAPI {
|
export class AuthAPI extends TestAPI {
|
||||||
config: TestConfiguration
|
|
||||||
request: any
|
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
super(config)
|
||||||
this.request = config.request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePassword = (code: string) => {
|
updatePassword = (code: string) => {
|
||||||
|
|
16
packages/worker/src/tests/api/base.ts
Normal file
16
packages/worker/src/tests/api/base.ts
Normal file
|
@ -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 TestConfiguration from "../TestConfiguration"
|
||||||
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class ConfigAPI {
|
export class ConfigAPI extends TestAPI {
|
||||||
config: TestConfiguration
|
|
||||||
request: any
|
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
super(config)
|
||||||
this.request = config.request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfigChecklist = () => {
|
getConfigChecklist = () => {
|
||||||
|
@ -26,10 +23,20 @@ export class ConfigAPI {
|
||||||
.expect(200)
|
.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
|
return this.request
|
||||||
.get(`/api/global/auth/${this.config.getTenantId()}/oidc/callback`)
|
.get(`/api/global/auth/${this.config.getTenantId()}/oidc/callback`)
|
||||||
.set(this.config.getOIDConfigCookie(configId))
|
.query({ code: "test", state: handle })
|
||||||
|
.set(cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
getOIDCConfig = (configId: string) => {
|
getOIDCConfig = (configId: string) => {
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class EmailAPI {
|
export class EmailAPI extends TestAPI {
|
||||||
config: TestConfiguration
|
|
||||||
request: any
|
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
super(config)
|
||||||
this.request = config.request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail = (purpose: string) => {
|
sendEmail = (purpose: string) => {
|
||||||
|
|
15
packages/worker/src/tests/api/environment.ts
Normal file
15
packages/worker/src/tests/api/environment.ts
Normal file
|
@ -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 { EmailAPI } from "./email"
|
||||||
import { SelfAPI } from "./self"
|
import { SelfAPI } from "./self"
|
||||||
import { UserAPI } from "./users"
|
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 {
|
export default class API {
|
||||||
accounts: AccountAPI
|
accounts: AccountAPI
|
||||||
|
@ -13,6 +18,11 @@ export default class API {
|
||||||
emails: EmailAPI
|
emails: EmailAPI
|
||||||
self: SelfAPI
|
self: SelfAPI
|
||||||
users: UserAPI
|
users: UserAPI
|
||||||
|
environment: EnvironmentAPI
|
||||||
|
migrations: MigrationAPI
|
||||||
|
status: StatusAPI
|
||||||
|
restore: RestoreAPI
|
||||||
|
tenants: TenantAPI
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.accounts = new AccountAPI(config)
|
this.accounts = new AccountAPI(config)
|
||||||
|
@ -21,5 +31,10 @@ export default class API {
|
||||||
this.emails = new EmailAPI(config)
|
this.emails = new EmailAPI(config)
|
||||||
this.self = new SelfAPI(config)
|
this.self = new SelfAPI(config)
|
||||||
this.users = new UserAPI(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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
22
packages/worker/src/tests/api/migrations.ts
Normal file
22
packages/worker/src/tests/api/migrations.ts
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
14
packages/worker/src/tests/api/restore.ts
Normal file
14
packages/worker/src/tests/api/restore.ts
Normal file
|
@ -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 TestConfiguration from "../TestConfiguration"
|
||||||
import { User } from "@budibase/types"
|
import { User } from "@budibase/types"
|
||||||
|
import { TestAPI } from "./base"
|
||||||
|
|
||||||
export class SelfAPI {
|
export class SelfAPI extends TestAPI {
|
||||||
config: TestConfiguration
|
|
||||||
request: any
|
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
super(config)
|
||||||
this.request = config.request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelf = (user: User) => {
|
updateSelf = (user: User) => {
|
||||||
|
@ -18,4 +15,12 @@ export class SelfAPI {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelf = (user: User) => {
|
||||||
|
return this.request
|
||||||
|
.get(`/api/global/self`)
|
||||||
|
.set(this.config.authHeaders(user))
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
12
packages/worker/src/tests/api/status.ts
Normal file
12
packages/worker/src/tests/api/status.ts
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
15
packages/worker/src/tests/api/tenants.ts
Normal file
15
packages/worker/src/tests/api/tenants.ts
Normal file
|
@ -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,
|
BulkUserRequest,
|
||||||
InviteUsersRequest,
|
InviteUsersRequest,
|
||||||
User,
|
User,
|
||||||
|
CreateAdminUserRequest,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import * as structures from "../structures"
|
||||||
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
import TestConfiguration from "../TestConfiguration"
|
import TestConfiguration from "../TestConfiguration"
|
||||||
|
import { TestAPI, TestAPIOpts } from "./base"
|
||||||
|
|
||||||
export class UserAPI {
|
export class UserAPI extends TestAPI {
|
||||||
config: TestConfiguration
|
|
||||||
request: any
|
|
||||||
|
|
||||||
constructor(config: TestConfiguration) {
|
constructor(config: TestConfiguration) {
|
||||||
this.config = config
|
super(config)
|
||||||
this.request = config.request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// INVITE
|
// INVITE
|
||||||
|
@ -91,6 +91,30 @@ export class UserAPI {
|
||||||
|
|
||||||
// USER
|
// 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) => {
|
saveUser = (user: User, status?: number) => {
|
||||||
return this.request
|
return this.request
|
||||||
.post(`/api/global/users`)
|
.post(`/api/global/users`)
|
||||||
|
@ -107,4 +131,12 @@ export class UserAPI {
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(status ? status : 200)
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import mocks from "./mocks"
|
import mocks from "./mocks"
|
||||||
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
import TestConfiguration from "./TestConfiguration"
|
import TestConfiguration from "./TestConfiguration"
|
||||||
import structures from "./structures"
|
import structures from "./structures"
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
structures,
|
structures,
|
||||||
|
generator,
|
||||||
|
uuid,
|
||||||
TENANT_1: structures.TENANT_1,
|
TENANT_1: structures.TENANT_1,
|
||||||
mocks,
|
mocks,
|
||||||
TestConfiguration,
|
TestConfiguration,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
|
||||||
env._set("SELF_HOSTED", "1")
|
env._set("SELF_HOSTED", "0")
|
||||||
env._set("NODE_ENV", "jest")
|
env._set("NODE_ENV", "jest")
|
||||||
env._set("JWT_SECRET", "test-jwtsecret")
|
env._set("JWT_SECRET", "test-jwtsecret")
|
||||||
env._set("LOG_LEVEL", "silent")
|
env._set("LOG_LEVEL", "silent")
|
||||||
|
@ -8,9 +8,15 @@ env._set("MULTI_TENANCY", true)
|
||||||
env._set("MINIO_URL", "http://localhost")
|
env._set("MINIO_URL", "http://localhost")
|
||||||
env._set("MINIO_ACCESS_KEY", "test")
|
env._set("MINIO_ACCESS_KEY", "test")
|
||||||
env._set("MINIO_SECRET_KEY", "test")
|
env._set("MINIO_SECRET_KEY", "test")
|
||||||
|
env._set("PLATFORM_URL", "http://localhost:10000")
|
||||||
|
env._set("INTERNAL_API_KEY", "test")
|
||||||
|
env._set("DISABLE_ACCOUNT_PORTAL", false)
|
||||||
|
|
||||||
import { mocks } from "@budibase/backend-core/tests"
|
import { mocks } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
|
// must explicitly enable fetch mock
|
||||||
|
mocks.fetch.enable()
|
||||||
|
|
||||||
// mock all dates to 2020-01-01T00:00:00.000Z
|
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||||
// use tk.reset() to use real dates in individual tests
|
// use tk.reset() to use real dates in individual tests
|
||||||
const tk = require("timekeeper")
|
const tk = require("timekeeper")
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { Account, AuthType, Hosting, CloudAccount } from "@budibase/types"
|
|
||||||
import { v4 as uuid } from "uuid"
|
|
||||||
import { utils } from "@budibase/backend-core"
|
|
||||||
|
|
||||||
export const account = (): Account => {
|
|
||||||
return {
|
|
||||||
email: `${uuid()}@test.com`,
|
|
||||||
tenantId: utils.newid(),
|
|
||||||
hosting: Hosting.SELF,
|
|
||||||
authType: AuthType.SSO,
|
|
||||||
accountId: uuid(),
|
|
||||||
createdAt: Date.now(),
|
|
||||||
verified: true,
|
|
||||||
verificationSent: true,
|
|
||||||
tier: "FREE",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cloudAccount = (): CloudAccount => {
|
|
||||||
return {
|
|
||||||
...account(),
|
|
||||||
budibaseUserId: uuid(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,18 @@
|
||||||
|
import { structures } from "@budibase/backend-core/tests"
|
||||||
import configs from "./configs"
|
import configs from "./configs"
|
||||||
import * as users from "./users"
|
import * as users from "./users"
|
||||||
import * as groups from "./groups"
|
import * as groups from "./groups"
|
||||||
import * as accounts from "./accounts"
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
const TENANT_ID = "default"
|
const TENANT_ID = "default"
|
||||||
const TENANT_1 = "tenant1"
|
const TENANT_1 = "tenant1"
|
||||||
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
|
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
|
||||||
|
|
||||||
const pkg = {
|
const pkg = {
|
||||||
|
...structures,
|
||||||
|
uuid,
|
||||||
configs,
|
configs,
|
||||||
users,
|
users,
|
||||||
accounts,
|
|
||||||
TENANT_ID,
|
TENANT_ID,
|
||||||
TENANT_1,
|
TENANT_1,
|
||||||
CSRF_TOKEN,
|
CSRF_TOKEN,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { v4 as uuid } from "uuid"
|
||||||
export const newEmail = () => {
|
export const newEmail = () => {
|
||||||
return `${uuid()}@test.com`
|
return `${uuid()}@test.com`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const user = (userProps?: any): User => {
|
export const user = (userProps?: any): User => {
|
||||||
return {
|
return {
|
||||||
email: newEmail(),
|
email: newEmail(),
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue