User context updates and misc fixes

This commit is contained in:
Rory Powell 2022-05-24 09:54:36 +01:00
parent 28b48a88fe
commit ff48aaec6e
13 changed files with 91 additions and 95 deletions

View File

@ -5,6 +5,7 @@ const {
getAppId,
updateAppId,
doInAppContext,
doInUserContext,
} = require("./src/context")
module.exports = {
@ -14,4 +15,5 @@ module.exports = {
getAppId,
updateAppId,
doInAppContext,
doInUserContext,
}

View File

@ -1,5 +1,4 @@
const env = require("../environment")
const { Headers } = require("../../constants")
const { SEPARATOR, DocumentTypes } = require("../db/constants")
const { DEFAULT_TENANT_ID } = require("../constants")
const cls = require("./FunctionContext")
@ -77,7 +76,11 @@ exports.isMultiTenant = () => {
exports.doInTenant = (tenantId, task) => {
// the internal function is so that we can re-use an existing
// context - don't want to close DB on a parent context
async function internal(opts = { existing: false }) {
async function internal(opts = { existing: false, user: undefined }) {
// preserve the user
if (user) {
exports.setUser(user)
}
// set the tenant id
if (!opts.existing) {
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
@ -98,14 +101,16 @@ exports.doInTenant = (tenantId, task) => {
}
}
}
const user = cls.getFromContext(ContextKeys.USER)
const using = cls.getFromContext(ContextKeys.IN_USE)
if (using && cls.getFromContext(ContextKeys.TENANT_ID) === tenantId) {
cls.setOnContext(ContextKeys.IN_USE, using + 1)
return internal({ existing: true })
return internal({ existing: true, user })
} else {
return cls.run(async () => {
cls.setOnContext(ContextKeys.IN_USE, 1)
return internal()
return internal({ existing: false, user })
})
}
}
@ -143,7 +148,11 @@ exports.doInAppContext = (appId, task) => {
// the internal function is so that we can re-use an existing
// context - don't want to close DB on a parent context
async function internal(opts = { existing: false }) {
async function internal(opts = { existing: false, user: undefined }) {
// preserve the user
if (user) {
exports.setUser(user)
}
// set the app tenant id
if (!opts.existing) {
setAppTenantId(appId)
@ -162,28 +171,39 @@ exports.doInAppContext = (appId, task) => {
}
}
}
const user = cls.getFromContext(ContextKeys.USER)
const using = cls.getFromContext(ContextKeys.IN_USE)
if (using && cls.getFromContext(ContextKeys.APP_ID) === appId) {
cls.setOnContext(ContextKeys.IN_USE, using + 1)
return internal({ existing: true })
return internal({ existing: true, user })
} else {
return cls.run(async () => {
cls.setOnContext(ContextKeys.IN_USE, 1)
return internal()
return internal({ existing: false, user })
})
}
}
exports.doInUserContext = (user, task) => {
return cls.run(async () => {
cls.setOnContext(ContextKeys.USER, user)
return cls.run(() => {
let tenantId = user.tenantId
if (!tenantId) {
tenantId = exports.getTenantId()
}
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
exports.setUser(user)
return task()
})
}
exports.setUser = user => {
cls.setOnContext(ContextKeys.USER, user)
}
exports.getUser = () => {
try {
return cls.getFromContext(ContextKeys.USER)
const user = cls.getFromContext(ContextKeys.USER)
return user
} catch (e) {
// do nothing - user is not in context
}
@ -208,45 +228,6 @@ exports.updateAppId = async appId => {
}
}
exports.setTenantId = (
ctx,
opts = { allowQs: false, allowNoTenant: false }
) => {
let tenantId
// exit early if not multi-tenant
if (!exports.isMultiTenant()) {
cls.setOnContext(ContextKeys.TENANT_ID, exports.DEFAULT_TENANT_ID)
return exports.DEFAULT_TENANT_ID
}
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")
}
// check tenant ID just incase no tenant was allowed
if (tenantId) {
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
}
return tenantId
}
exports.setGlobalDB = tenantId => {
const dbName = baseGlobalDBName(tenantId)
const db = dangerousGetDB(dbName)

View File

@ -1,7 +1,3 @@
import {
isCloudAccount,
isSSOAccount,
} from "./../../../types/src/documents/account/account"
import * as context from "../context"
import env from "../environment"
import {
@ -13,6 +9,8 @@ import {
Account,
AccountIdentity,
BudibaseIdentity,
isCloudAccount,
isSSOAccount,
} from "@budibase/types"
import { analyticsProcessor } from "./processors"
@ -26,7 +24,7 @@ export const getCurrentIdentity = (): Identity => {
} else if (env.SELF_HOSTED) {
id = "installationId" // TODO
} else {
id = context.getTenantId()
id = tenantId
}
return {

View File

@ -137,7 +137,7 @@ module.exports = (
if (user && user.email) {
return context.doInUserContext(user, next)
} else {
return next
return next()
}
} catch (err) {
// invalid token, clear the cookie

View File

@ -1,6 +1,38 @@
const { setTenantId, setGlobalDB, closeTenancy } = require("../tenancy")
const cls = require("../context/FunctionContext")
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,
@ -11,15 +43,10 @@ module.exports = (
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
return async function (ctx, next) {
return cls.run(async () => {
const allowNoTenant =
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
const allowQs = !!matches(ctx, allowQsOptions)
const tenantId = setTenantId(ctx, { allowQs, allowNoTenant })
setGlobalDB(tenantId)
const res = await next()
await closeTenancy()
return res
})
const allowNoTenant =
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
const allowQs = !!matches(ctx, allowQsOptions)
const tenantId = getTenantID(ctx, { allowQs, allowNoTenant })
return doInTenant(tenantId, next)
}
}

View File

@ -3,8 +3,6 @@
"extends": "./tsconfig.json",
"exclude": [
"node_modules",
"dist/**/*",
"**/*.json",
"**/*.spec.js",
"**/*.spec.ts"
]

View File

@ -29,7 +29,6 @@
],
"exclude": [
"node_modules",
"dist/**/*",
"**/*.spec.js",
// "**/*.spec.ts" // don't exclude spec.ts files for editor support
]

View File

@ -131,5 +131,5 @@ exports.getBudibaseVersion = async ctx => {
ctx.body = {
version,
}
events.org.versionChecked(version)
await events.org.versionChecked(version)
}

View File

@ -315,6 +315,6 @@ exports.bulkImport = async function (ctx) {
await handleRequest(DataSourceOperation.BULK_CREATE, table._id, {
rows,
})
events.row.import(table, "csv", rows.length)
await events.rows.imported(table, "csv", rows.length)
return table
}

View File

@ -1,16 +0,0 @@
// TODO: Add migrations to account portal
import { events, db } from "@budibase/backend-core"
import { Account } from "@budibase/types"
export const backfill = async (appDb: any) => {
const accounts: Account[] = []
for (const account of accounts) {
events.account.created(account)
if (account.verified) {
events.account.verified(account)
}
}
}

View File

@ -36,7 +36,7 @@ export const backfill = async (globalDb: any) => {
if (isOIDCConfig(config)) {
await events.auth.SSOCreated("oidc")
if (config.config.configs[0].activated) {
events.auth.SSOActivated("oidc")
await events.auth.SSOActivated("oidc")
}
}
if (isSettingsConfig(config)) {

View File

@ -1,4 +1,4 @@
export type LoginSource = "local" | "sso"
export type LoginSource = "local" | "google" | "oidc"
export type SSOType = "oidc" | "google"
export interface LoginEvent {

View File

@ -14,8 +14,9 @@ const {
isMultiTenant,
} = require("@budibase/backend-core/tenancy")
const env = require("../../../environment")
const { events, users: usersCore } = require("@budibase/backend-core")
import { events, users as usersCore, context } from "@budibase/backend-core"
import { users } from "../../../sdk"
import { User } from "@budibase/types"
const ssoCallbackUrl = async (config: any, type: any) => {
// incase there is a callback URL from before
@ -71,7 +72,9 @@ export const authenticate = async (ctx: any, next: any) => {
"local",
async (err: any, user: any, info: any) => {
await authInternal(ctx, user, err, info)
await events.auth.login("local")
await context.doInUserContext(user, async () => {
await events.auth.login("local")
})
ctx.status = 200
}
)(ctx, next)
@ -105,7 +108,7 @@ export const reset = async (ctx: any) => {
)
}
try {
const user = await usersCore.getGlobalUserByEmail(email)
const user = (await usersCore.getGlobalUserByEmail(email)) as User
// only if user exists, don't error though if they don't
if (user) {
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
@ -212,7 +215,9 @@ export const googleAuth = async (ctx: any, next: any) => {
{ successRedirect: "/", failureRedirect: "/error" },
async (err: any, user: any, info: any) => {
await authInternal(ctx, user, err, info)
await events.auth.login("google")
await context.doInUserContext(user, async () => {
await events.auth.login("google")
})
ctx.redirect("/")
}
)(ctx, next)
@ -256,7 +261,9 @@ export const oidcAuth = async (ctx: any, next: any) => {
{ successRedirect: "/", failureRedirect: "/error" },
async (err: any, user: any, info: any) => {
await authInternal(ctx, user, err, info)
await events.auth.login("oidc")
await context.doInUserContext(user, async () => {
await events.auth.login("oidc")
})
ctx.redirect("/")
}
)(ctx, next)