Update tenancy detection to honour any subdomain pattern according to platform url
This commit is contained in:
parent
e63afd560a
commit
ada0eb79bc
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { doInTenant, getTenantIDFromCtx } from "../tenancy"
|
||||||
|
import { buildMatcherRegex, matches } from "./matchers"
|
||||||
|
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)
|
||||||
|
return doInTenant(tenantId, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export = tenancy
|
|
@ -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,82 @@ 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
|
||||||
|
const tenantId = requestHost.substring(
|
||||||
|
0,
|
||||||
|
requestHost.indexOf(`.${platformHost}`)
|
||||||
|
)
|
||||||
|
if (tenantId) {
|
||||||
|
return tenantId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opts.allowNoTenant) {
|
||||||
|
ctx.throw(403, "Tenant id not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
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 { App, BBContext, TenantResolutionStrategy } from "@budibase/types"
|
||||||
const events = require("./events")
|
import { SetOption } from "cookies"
|
||||||
const tenancy = require("./tenancy")
|
|
||||||
|
|
||||||
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.SELF_HOSTED) {
|
||||||
// always use the tenant id from the url in cloud
|
// always use the tenant id from the subdomain in cloud
|
||||||
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 +45,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 +62,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 +98,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 +110,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 +127,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 +154,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 +164,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,18 +184,28 @@ const getBuilders = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getBuildersCount = async () => {
|
export const getBuildersCount = async () => {
|
||||||
const builders = await getBuilders()
|
const builders = await getBuilders()
|
||||||
return builders.length
|
return builders.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PlatformLogoutOpts {
|
||||||
|
ctx: BBContext
|
||||||
|
userId: string
|
||||||
|
keepActiveSession: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 +214,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 +224,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))
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./matchers"
|
||||||
|
export * from "./tenancy"
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface EndpointMatcher {
|
||||||
|
route: string
|
||||||
|
method: string
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
export interface GetTenantIdOptions {
|
||||||
|
allowNoTenant?: boolean
|
||||||
|
excludeStrategies?: TenantResolutionStrategy[]
|
||||||
|
includeStrategies?: TenantResolutionStrategy[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TenantResolutionStrategy {
|
||||||
|
USER = "user",
|
||||||
|
HEADER = "header",
|
||||||
|
QUERY = "query",
|
||||||
|
SUBDOMAIN = "subdomain",
|
||||||
|
}
|
|
@ -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!"))
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
enable=$1
|
||||||
|
domain=$2
|
||||||
|
|
||||||
|
if [ "$enable" = "enable" ]; then
|
||||||
|
lerna run env:localdomain:enable -- "$domain"
|
||||||
|
cd ../account-portal
|
||||||
|
yarn env:localdomain:enable "$domain"
|
||||||
|
cd -
|
||||||
|
else
|
||||||
|
lerna run env:localdomain:disable
|
||||||
|
cd ../account-portal
|
||||||
|
yarn env:localdomain:disable
|
||||||
|
cd -
|
||||||
|
fi
|
Loading…
Reference in New Issue