Update tenancy detection to honour any subdomain pattern according to platform url

This commit is contained in:
Rory Powell 2022-11-09 16:35:16 +00:00
parent e63afd560a
commit ada0eb79bc
18 changed files with 337 additions and 124 deletions

View File

@ -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/ {

View File

@ -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;
} }

View File

@ -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",

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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))
} }

View File

@ -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!"))

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -0,0 +1,2 @@
export * from "./matchers"
export * from "./tenancy"

View File

@ -0,0 +1,4 @@
export interface EndpointMatcher {
route: string
method: string
}

View File

@ -0,0 +1,12 @@
export interface GetTenantIdOptions {
allowNoTenant?: boolean
excludeStrategies?: TenantResolutionStrategy[]
includeStrategies?: TenantResolutionStrategy[]
}
export enum TenantResolutionStrategy {
USER = "user",
HEADER = "header",
QUERY = "query",
SUBDOMAIN = "subdomain",
}

View File

@ -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!"))

16
scripts/localdomain.sh Executable file
View File

@ -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