1
0
Fork 0
mirror of synced 2024-06-27 18:40:42 +12:00

Typescript conversions, as well as updating context to just use an object map.

This commit is contained in:
mike12345567 2022-11-10 17:38:26 +00:00
parent c356e0d152
commit fd624e097a
7 changed files with 106 additions and 93 deletions

View file

@ -1,9 +1,11 @@
import { IdentityContext } from "@budibase/types"
export enum ContextKey { export enum ContextKey {
MAIN = "main", MAIN = "main",
} }
export enum ContextElement { export type ContextMap = {
TENANT_ID = "tenantId", tenantId?: string
APP_ID = "appId", appId?: string
IDENTITY = "identity", identity?: IdentityContext
} }

View file

@ -4,12 +4,10 @@ import cls from "./FunctionContext"
import { baseGlobalDBName } from "../db/tenancy" import { baseGlobalDBName } from "../db/tenancy"
import { IdentityContext } from "@budibase/types" import { IdentityContext } from "@budibase/types"
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants" import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
import { ContextElement, ContextKey } from "./constants" import { ContextMap, ContextKey } from "./constants"
import { PouchLike } from "../couch" import { PouchLike } from "../couch"
import { getDevelopmentAppID, getProdAppID } from "../db/conversions" import { getDevelopmentAppID, getProdAppID } from "../db/conversions"
type ContextMap = { [key in ContextElement]?: any }
export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID
// some test cases call functions directly, need to // some test cases call functions directly, need to
@ -22,7 +20,7 @@ export function isMultiTenant() {
export function isTenantIdSet() { export function isTenantIdSet() {
const context = cls.getFromContext(ContextKey.MAIN) as ContextMap const context = cls.getFromContext(ContextKey.MAIN) as ContextMap
return !!context?.[ContextElement.TENANT_ID] return !!context?.tenantId
} }
export function isTenancyEnabled() { export function isTenancyEnabled() {
@ -35,7 +33,7 @@ export function isTenancyEnabled() {
*/ */
export function getTenantIDFromAppID(appId: string) { export function getTenantIDFromAppID(appId: string) {
if (!appId) { if (!appId) {
return null return undefined
} }
if (!isMultiTenant()) { if (!isMultiTenant()) {
return DEFAULT_TENANT_ID return DEFAULT_TENANT_ID
@ -43,7 +41,7 @@ export function getTenantIDFromAppID(appId: string) {
const split = appId.split(SEPARATOR) const split = appId.split(SEPARATOR)
const hasDev = split[1] === DocumentType.DEV const hasDev = split[1] === DocumentType.DEV
if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) { if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) {
return null return undefined
} }
if (hasDev) { if (hasDev) {
return split[2] return split[2]
@ -80,8 +78,8 @@ export async function doInContext(appId: string, task: any): Promise<any> {
const tenantId = getTenantIDFromAppID(appId) const tenantId = getTenantIDFromAppID(appId)
return newContext( return newContext(
{ {
[ContextElement.TENANT_ID]: tenantId, tenantId,
[ContextElement.APP_ID]: appId, appId,
}, },
task task
) )
@ -96,12 +94,8 @@ export async function doInTenant(
tenantId = tenantId || DEFAULT_TENANT_ID tenantId = tenantId || DEFAULT_TENANT_ID
} }
return newContext( const updates = tenantId ? { tenantId } : {}
{ return newContext(updates, task)
[ContextElement.TENANT_ID]: tenantId,
},
task
)
} }
export async function doInAppContext(appId: string, task: any): Promise<any> { export async function doInAppContext(appId: string, task: any): Promise<any> {
@ -112,8 +106,8 @@ export async function doInAppContext(appId: string, task: any): Promise<any> {
const tenantId = getTenantIDFromAppID(appId) const tenantId = getTenantIDFromAppID(appId)
return newContext( return newContext(
{ {
[ContextElement.TENANT_ID]: tenantId, tenantId,
[ContextElement.APP_ID]: appId, appId,
}, },
task task
) )
@ -128,10 +122,10 @@ export async function doInIdentityContext(
} }
const context: ContextMap = { const context: ContextMap = {
[ContextElement.IDENTITY]: identity, identity,
} }
if (identity.tenantId) { if (identity.tenantId) {
context[ContextElement.TENANT_ID] = identity.tenantId context.tenantId = identity.tenantId
} }
return newContext(context, task) return newContext(context, task)
} }
@ -139,7 +133,7 @@ export async function doInIdentityContext(
export function getIdentity(): IdentityContext | undefined { export function getIdentity(): IdentityContext | undefined {
try { try {
const context = cls.getFromContext(ContextKey.MAIN) as ContextMap const context = cls.getFromContext(ContextKey.MAIN) as ContextMap
return context?.[ContextElement.IDENTITY] return context?.identity
} catch (e) { } catch (e) {
// do nothing - identity is not in context // do nothing - identity is not in context
} }
@ -150,7 +144,7 @@ export function getTenantId(): string {
return DEFAULT_TENANT_ID return DEFAULT_TENANT_ID
} }
const context = cls.getFromContext(ContextKey.MAIN) as ContextMap const context = cls.getFromContext(ContextKey.MAIN) as ContextMap
const tenantId = context?.[ContextElement.TENANT_ID] const tenantId = context?.tenantId
if (!tenantId) { if (!tenantId) {
throw new Error("Tenant id not found") throw new Error("Tenant id not found")
} }
@ -159,7 +153,7 @@ export function getTenantId(): string {
export function getAppId(): string | undefined { export function getAppId(): string | undefined {
const context = cls.getFromContext(ContextKey.MAIN) as ContextMap const context = cls.getFromContext(ContextKey.MAIN) as ContextMap
const foundId = context?.[ContextElement.APP_ID] const foundId = context?.appId
if (!foundId && env.isTest() && TEST_APP_ID) { if (!foundId && env.isTest() && TEST_APP_ID) {
return TEST_APP_ID return TEST_APP_ID
} else { } else {
@ -167,16 +161,16 @@ export function getAppId(): string | undefined {
} }
} }
export function updateTenantId(tenantId: string | null) { export function updateTenantId(tenantId?: string) {
let context: ContextMap = updateContext({ let context: ContextMap = updateContext({
[ContextElement.TENANT_ID]: tenantId, tenantId,
}) })
cls.setOnContext(ContextKey.MAIN, context) cls.setOnContext(ContextKey.MAIN, context)
} }
export function updateAppId(appId: string) { export function updateAppId(appId: string) {
let context: ContextMap = updateContext({ let context: ContextMap = updateContext({
[ContextElement.APP_ID]: appId, appId,
}) })
try { try {
cls.setOnContext(ContextKey.MAIN, context) cls.setOnContext(ContextKey.MAIN, context)
@ -191,7 +185,7 @@ export function updateAppId(appId: string) {
export function getGlobalDB(): PouchLike { export function getGlobalDB(): PouchLike {
const context = cls.getFromContext(ContextKey.MAIN) as ContextMap const context = cls.getFromContext(ContextKey.MAIN) as ContextMap
return new PouchLike(baseGlobalDBName(context?.[ContextElement.TENANT_ID])) return new PouchLike(baseGlobalDBName(context?.tenantId))
} }
/** /**

View file

@ -147,9 +147,9 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string) {
* @param {string|null} roleId The level ID to lookup. * @param {string|null} roleId The level ID to lookup.
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property. * @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
*/ */
export async function getRole(roleId?: string) { export async function getRole(roleId?: string): Promise<RoleDoc | undefined> {
if (!roleId) { if (!roleId) {
return null return undefined
} }
let role: any = {} let role: any = {}
// built in roles mostly come from the in-code implementation, // built in roles mostly come from the in-code implementation,
@ -193,7 +193,9 @@ async function getAllUserRoles(userRoleId?: string): Promise<RoleDoc[]> {
) { ) {
roleIds.push(currentRole.inherits) roleIds.push(currentRole.inherits)
currentRole = await getRole(currentRole.inherits) currentRole = await getRole(currentRole.inherits)
roles.push(currentRole) if (currentRole) {
roles.push(currentRole)
}
} }
return roles return roles
} }

View file

@ -3,10 +3,10 @@ import { queryPlatformView } from "../db/views"
import { StaticDatabases, ViewName } from "../db/constants" import { StaticDatabases, ViewName } from "../db/constants"
import { getGlobalDBName } from "../db/tenancy" import { getGlobalDBName } from "../db/tenancy"
import { import {
getTenantId,
DEFAULT_TENANT_ID, DEFAULT_TENANT_ID,
isMultiTenant, getTenantId,
getTenantIDFromAppID, getTenantIDFromAppID,
isMultiTenant,
} from "../context" } from "../context"
import env from "../environment" import env from "../environment"
import { PlatformUser } from "@budibase/types" import { PlatformUser } from "@budibase/types"
@ -14,7 +14,7 @@ import { PlatformUser } 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
export const addTenantToUrl = (url: string) => { export function addTenantToUrl(url: string) {
const tenantId = getTenantId() const tenantId = getTenantId()
if (isMultiTenant()) { if (isMultiTenant()) {
@ -25,7 +25,7 @@ export const addTenantToUrl = (url: string) => {
return url return url
} }
export const doesTenantExist = async (tenantId: string) => { export async function doesTenantExist(tenantId: string) {
return doWithDB(PLATFORM_INFO_DB, async (db: any) => { return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
let tenants let tenants
try { try {
@ -42,12 +42,12 @@ export const doesTenantExist = async (tenantId: string) => {
}) })
} }
export const tryAddTenant = async ( export async function tryAddTenant(
tenantId: string, tenantId: string,
userId: string, userId: string,
email: string, email: string,
afterCreateTenant: () => Promise<void> afterCreateTenant: () => Promise<void>
) => { ) {
return doWithDB(PLATFORM_INFO_DB, async (db: any) => { return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
const getDoc = async (id: string) => { const getDoc = async (id: string) => {
if (!id) { if (!id) {
@ -89,11 +89,11 @@ export const tryAddTenant = async (
}) })
} }
export const doWithGlobalDB = (tenantId: string, cb: any) => { export function doWithGlobalDB(tenantId: string, cb: any) {
return doWithDB(getGlobalDBName(tenantId), cb) return doWithDB(getGlobalDBName(tenantId), cb)
} }
export const lookupTenantId = async (userId: string) => { export async function lookupTenantId(userId: string) {
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => { return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => {
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null
try { try {
@ -109,19 +109,26 @@ export const lookupTenantId = async (userId: string) => {
} }
// lookup, could be email or userId, either will return a doc // lookup, could be email or userId, either will return a doc
export const getTenantUser = async ( export async function getTenantUser(
identifier: string identifier: string
): Promise<PlatformUser | null> => { ): Promise<PlatformUser | undefined> {
// use the view here and allow to find anyone regardless of casing // use the view here and allow to find anyone regardless of casing
// Use lowercase to ensure email login is case insensitive // Use lowercase to ensure email login is case-insensitive
const response = queryPlatformView(ViewName.PLATFORM_USERS_LOWERCASE, { const users = await queryPlatformView<PlatformUser>(
keys: [identifier.toLowerCase()], ViewName.PLATFORM_USERS_LOWERCASE,
include_docs: true, {
}) as Promise<PlatformUser> keys: [identifier.toLowerCase()],
return response include_docs: true,
}
)
if (Array.isArray(users)) {
return users[0]
} else {
return users
}
} }
export const isUserInAppTenant = (appId: string, user?: any) => { export function isUserInAppTenant(appId: string, user?: any) {
let userTenantId let userTenantId
if (user) { if (user) {
userTenantId = user.tenantId || DEFAULT_TENANT_ID userTenantId = user.tenantId || DEFAULT_TENANT_ID
@ -132,7 +139,7 @@ export const isUserInAppTenant = (appId: string, user?: any) => {
return tenantId === userTenantId return tenantId === userTenantId
} }
export const getTenantIds = async () => { export async function getTenantIds() {
return doWithDB(PLATFORM_INFO_DB, async (db: any) => { return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
let tenants let tenants
try { try {

View file

@ -1,29 +1,26 @@
const { import {
getAppIdFromCtx, utils,
setCookie, constants,
getCookie, roles,
clearCookie, db as dbCore,
} = require("@budibase/backend-core/utils") tenancy,
const { Cookies, Headers } = require("@budibase/backend-core/constants") context,
const { getRole } = require("@budibase/backend-core/roles") } from "@budibase/backend-core"
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles") import { generateUserMetadataID, isDevAppID } from "../db/utils"
const { generateUserMetadataID, isDevAppID } = require("../db/utils") import { getCachedSelf } from "../utilities/global"
const { dbExists } = require("@budibase/backend-core/db") import env from "../environment"
const { isUserInAppTenant } = require("@budibase/backend-core/tenancy") import { isWebhookEndpoint } from "./utils"
const { getCachedSelf } = require("../utilities/global") import { BBContext } from "@budibase/types"
const env = require("../environment")
const { isWebhookEndpoint } = require("./utils")
const { doInAppContext } = require("@budibase/backend-core/context")
module.exports = async (ctx, next) => { export = async (ctx: BBContext, next: any) => {
// try to get the appID from the request // try to get the appID from the request
let requestAppId = await getAppIdFromCtx(ctx) let requestAppId = await utils.getAppIdFromCtx(ctx)
// get app cookie if it exists // get app cookie if it exists
let appCookie = null let appCookie: { appId?: string } | undefined
try { try {
appCookie = getCookie(ctx, Cookies.CurrentApp) appCookie = utils.getCookie(ctx, constants.Cookies.CurrentApp)
} catch (err) { } catch (err) {
clearCookie(ctx, Cookies.CurrentApp) utils.clearCookie(ctx, constants.Cookies.CurrentApp)
} }
if (!appCookie && !requestAppId) { if (!appCookie && !requestAppId) {
return next() return next()
@ -31,9 +28,9 @@ module.exports = async (ctx, next) => {
// check the app exists referenced in cookie // check the app exists referenced in cookie
if (appCookie) { if (appCookie) {
const appId = appCookie.appId const appId = appCookie.appId
const exists = await dbExists(appId) const exists = await dbCore.dbExists(appId)
if (!exists) { if (!exists) {
clearCookie(ctx, Cookies.CurrentApp) utils.clearCookie(ctx, constants.Cookies.CurrentApp)
return next() return next()
} }
// if the request app ID wasn't set, update it with the cookie // if the request app ID wasn't set, update it with the cookie
@ -47,13 +44,13 @@ module.exports = async (ctx, next) => {
!isWebhookEndpoint(ctx) && !isWebhookEndpoint(ctx) &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) (!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
) { ) {
clearCookie(ctx, Cookies.CurrentApp) utils.clearCookie(ctx, constants.Cookies.CurrentApp)
return ctx.redirect("/") return ctx.redirect("/")
} }
} }
let appId, let appId: string | undefined,
roleId = BUILTIN_ROLE_IDS.PUBLIC roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
if (!ctx.user) { if (!ctx.user) {
// not logged in, try to set a cookie for public apps // not logged in, try to set a cookie for public apps
appId = requestAppId appId = requestAppId
@ -68,16 +65,20 @@ module.exports = async (ctx, next) => {
const isBuilder = const isBuilder =
globalUser && globalUser.builder && globalUser.builder.global globalUser && globalUser.builder && globalUser.builder.global
const isDevApp = appId && isDevAppID(appId) const isDevApp = appId && isDevAppID(appId)
const roleHeader = ctx.request && ctx.request.headers[Headers.PREVIEW_ROLE] const roleHeader =
ctx.request &&
(ctx.request.headers[constants.Headers.PREVIEW_ROLE] as string)
if (isBuilder && isDevApp && roleHeader) { if (isBuilder && isDevApp && roleHeader) {
// Ensure the role is valid by ensuring a definition exists // Ensure the role is valid by ensuring a definition exists
try { try {
await getRole(roleHeader) if (roleHeader) {
roleId = roleHeader await roles.getRole(roleHeader)
roleId = roleHeader
// Delete admin and builder flags so that the specified role is honoured // Delete admin and builder flags so that the specified role is honoured
delete ctx.user.builder delete ctx.user.builder
delete ctx.user.admin delete ctx.user.admin
}
} catch (error) { } catch (error) {
// Swallow error and do nothing // Swallow error and do nothing
} }
@ -89,21 +90,22 @@ module.exports = async (ctx, next) => {
return next() return next()
} }
return doInAppContext(appId, async () => { return context.doInAppContext(appId, async () => {
let skipCookie = false let skipCookie = false
// if the user not in the right tenant then make sure they have no permissions // if the user not in the right tenant then make sure they have no permissions
// need to judge this only based on the request app ID, // need to judge this only based on the request app ID,
if ( if (
env.MULTI_TENANCY && env.MULTI_TENANCY &&
ctx.user & requestAppId && ctx.user &&
!isUserInAppTenant(requestAppId, ctx.user) requestAppId &&
!tenancy.isUserInAppTenant(requestAppId, ctx.user)
) { ) {
// don't error, simply remove the users rights (they are a public user) // don't error, simply remove the users rights (they are a public user)
delete ctx.user.builder delete ctx.user.builder
delete ctx.user.admin delete ctx.user.admin
delete ctx.user.roles delete ctx.user.roles
ctx.isAuthenticated = false ctx.isAuthenticated = false
roleId = BUILTIN_ROLE_IDS.PUBLIC roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
skipCookie = true skipCookie = true
} }
@ -111,15 +113,17 @@ module.exports = async (ctx, next) => {
if (roleId) { if (roleId) {
ctx.roleId = roleId ctx.roleId = roleId
const globalId = ctx.user ? ctx.user._id : undefined const globalId = ctx.user ? ctx.user._id : undefined
const userId = ctx.user ? generateUserMetadataID(ctx.user._id) : null const userId = ctx.user
? generateUserMetadataID(ctx.user._id!)
: undefined
ctx.user = { ctx.user = {
...ctx.user, ...ctx.user!,
// override userID with metadata one // override userID with metadata one
_id: userId, _id: userId,
userId, userId,
globalId, globalId,
roleId, roleId,
role: await getRole(roleId), role: await roles.getRole(roleId),
} }
} }
if ( if (
@ -128,7 +132,7 @@ module.exports = async (ctx, next) => {
appCookie.appId !== requestAppId) && appCookie.appId !== requestAppId) &&
!skipCookie !skipCookie
) { ) {
setCookie(ctx, { appId }, Cookies.CurrentApp) utils.setCookie(ctx, { appId }, constants.Cookies.CurrentApp)
} }
return next() return next()

View file

@ -7,7 +7,7 @@ jest.mock("@budibase/backend-core/db", () => {
coreDb.init() coreDb.init()
return { return {
...coreDb, ...coreDb,
dbExists: () => true, dbExists: async () => true,
} }
}) })

View file

@ -1,10 +1,14 @@
import { Context, Request } from "koa" import { Context, Request } from "koa"
import { User } from "../documents" import { User, Role, UserRoles } from "../documents"
import { License } from "../sdk" import { License } from "../sdk"
export interface ContextUser extends User { export interface ContextUser extends Omit<User, "roles"> {
globalId?: string globalId?: string
license: License license: License
userId?: string
roleId?: string
role?: Role
roles?: UserRoles
} }
export interface BBRequest extends Request { export interface BBRequest extends Request {