User context updates and misc fixes
This commit is contained in:
parent
22aa226ca9
commit
9d0b4ef45e
|
@ -5,6 +5,7 @@ const {
|
||||||
getAppId,
|
getAppId,
|
||||||
updateAppId,
|
updateAppId,
|
||||||
doInAppContext,
|
doInAppContext,
|
||||||
|
doInUserContext,
|
||||||
} = require("./src/context")
|
} = require("./src/context")
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -14,4 +15,5 @@ module.exports = {
|
||||||
getAppId,
|
getAppId,
|
||||||
updateAppId,
|
updateAppId,
|
||||||
doInAppContext,
|
doInAppContext,
|
||||||
|
doInUserContext,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { Headers } = require("../../constants")
|
|
||||||
const { SEPARATOR, DocumentTypes } = require("../db/constants")
|
const { SEPARATOR, DocumentTypes } = require("../db/constants")
|
||||||
const { DEFAULT_TENANT_ID } = require("../constants")
|
const { DEFAULT_TENANT_ID } = require("../constants")
|
||||||
const cls = require("./FunctionContext")
|
const cls = require("./FunctionContext")
|
||||||
|
@ -77,7 +76,11 @@ exports.isMultiTenant = () => {
|
||||||
exports.doInTenant = (tenantId, task) => {
|
exports.doInTenant = (tenantId, task) => {
|
||||||
// the internal function is so that we can re-use an existing
|
// the internal function is so that we can re-use an existing
|
||||||
// context - don't want to close DB on a parent context
|
// 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
|
// set the tenant id
|
||||||
if (!opts.existing) {
|
if (!opts.existing) {
|
||||||
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
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)
|
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||||
if (using && cls.getFromContext(ContextKeys.TENANT_ID) === tenantId) {
|
if (using && cls.getFromContext(ContextKeys.TENANT_ID) === tenantId) {
|
||||||
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
||||||
return internal({ existing: true })
|
return internal({ existing: true, user })
|
||||||
} else {
|
} else {
|
||||||
return cls.run(async () => {
|
return cls.run(async () => {
|
||||||
cls.setOnContext(ContextKeys.IN_USE, 1)
|
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
|
// the internal function is so that we can re-use an existing
|
||||||
// context - don't want to close DB on a parent context
|
// 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
|
// set the app tenant id
|
||||||
if (!opts.existing) {
|
if (!opts.existing) {
|
||||||
setAppTenantId(appId)
|
setAppTenantId(appId)
|
||||||
|
@ -162,28 +171,39 @@ exports.doInAppContext = (appId, task) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const user = cls.getFromContext(ContextKeys.USER)
|
||||||
const using = cls.getFromContext(ContextKeys.IN_USE)
|
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||||
if (using && cls.getFromContext(ContextKeys.APP_ID) === appId) {
|
if (using && cls.getFromContext(ContextKeys.APP_ID) === appId) {
|
||||||
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
||||||
return internal({ existing: true })
|
return internal({ existing: true, user })
|
||||||
} else {
|
} else {
|
||||||
return cls.run(async () => {
|
return cls.run(async () => {
|
||||||
cls.setOnContext(ContextKeys.IN_USE, 1)
|
cls.setOnContext(ContextKeys.IN_USE, 1)
|
||||||
return internal()
|
return internal({ existing: false, user })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.doInUserContext = (user, task) => {
|
exports.doInUserContext = (user, task) => {
|
||||||
return cls.run(async () => {
|
return cls.run(() => {
|
||||||
cls.setOnContext(ContextKeys.USER, user)
|
let tenantId = user.tenantId
|
||||||
|
if (!tenantId) {
|
||||||
|
tenantId = exports.getTenantId()
|
||||||
|
}
|
||||||
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
||||||
|
exports.setUser(user)
|
||||||
return task()
|
return task()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.setUser = user => {
|
||||||
|
cls.setOnContext(ContextKeys.USER, user)
|
||||||
|
}
|
||||||
|
|
||||||
exports.getUser = () => {
|
exports.getUser = () => {
|
||||||
try {
|
try {
|
||||||
return cls.getFromContext(ContextKeys.USER)
|
const user = cls.getFromContext(ContextKeys.USER)
|
||||||
|
return user
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing - user is not in context
|
// 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 => {
|
exports.setGlobalDB = tenantId => {
|
||||||
const dbName = baseGlobalDBName(tenantId)
|
const dbName = baseGlobalDBName(tenantId)
|
||||||
const db = dangerousGetDB(dbName)
|
const db = dangerousGetDB(dbName)
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
import {
|
|
||||||
isCloudAccount,
|
|
||||||
isSSOAccount,
|
|
||||||
} from "./../../../types/src/documents/account/account"
|
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import {
|
import {
|
||||||
|
@ -13,6 +9,8 @@ import {
|
||||||
Account,
|
Account,
|
||||||
AccountIdentity,
|
AccountIdentity,
|
||||||
BudibaseIdentity,
|
BudibaseIdentity,
|
||||||
|
isCloudAccount,
|
||||||
|
isSSOAccount,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { analyticsProcessor } from "./processors"
|
import { analyticsProcessor } from "./processors"
|
||||||
|
|
||||||
|
@ -26,7 +24,7 @@ export const getCurrentIdentity = (): Identity => {
|
||||||
} else if (env.SELF_HOSTED) {
|
} else if (env.SELF_HOSTED) {
|
||||||
id = "installationId" // TODO
|
id = "installationId" // TODO
|
||||||
} else {
|
} else {
|
||||||
id = context.getTenantId()
|
id = tenantId
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -137,7 +137,7 @@ module.exports = (
|
||||||
if (user && user.email) {
|
if (user && user.email) {
|
||||||
return context.doInUserContext(user, next)
|
return context.doInUserContext(user, next)
|
||||||
} else {
|
} else {
|
||||||
return next
|
return next()
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// invalid token, clear the cookie
|
// invalid token, clear the cookie
|
||||||
|
|
|
@ -1,6 +1,38 @@
|
||||||
const { setTenantId, setGlobalDB, closeTenancy } = require("../tenancy")
|
const { doInTenant, isMultiTenant, DEFAULT_TENANT_ID } = require("../tenancy")
|
||||||
const cls = require("../context/FunctionContext")
|
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
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 = (
|
module.exports = (
|
||||||
allowQueryStringPatterns,
|
allowQueryStringPatterns,
|
||||||
|
@ -11,15 +43,10 @@ module.exports = (
|
||||||
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
||||||
|
|
||||||
return async function (ctx, next) {
|
return async function (ctx, next) {
|
||||||
return cls.run(async () => {
|
const allowNoTenant =
|
||||||
const allowNoTenant =
|
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
||||||
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
const allowQs = !!matches(ctx, allowQsOptions)
|
||||||
const allowQs = !!matches(ctx, allowQsOptions)
|
const tenantId = getTenantID(ctx, { allowQs, allowNoTenant })
|
||||||
const tenantId = setTenantId(ctx, { allowQs, allowNoTenant })
|
return doInTenant(tenantId, next)
|
||||||
setGlobalDB(tenantId)
|
|
||||||
const res = await next()
|
|
||||||
await closeTenancy()
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"dist/**/*",
|
|
||||||
"**/*.json",
|
|
||||||
"**/*.spec.js",
|
"**/*.spec.js",
|
||||||
"**/*.spec.ts"
|
"**/*.spec.ts"
|
||||||
]
|
]
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"dist/**/*",
|
|
||||||
"**/*.spec.js",
|
"**/*.spec.js",
|
||||||
// "**/*.spec.ts" // don't exclude spec.ts files for editor support
|
// "**/*.spec.ts" // don't exclude spec.ts files for editor support
|
||||||
]
|
]
|
||||||
|
|
|
@ -131,5 +131,5 @@ exports.getBudibaseVersion = async ctx => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
version,
|
version,
|
||||||
}
|
}
|
||||||
events.org.versionChecked(version)
|
await events.org.versionChecked(version)
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,6 +315,6 @@ exports.bulkImport = async function (ctx) {
|
||||||
await handleRequest(DataSourceOperation.BULK_CREATE, table._id, {
|
await handleRequest(DataSourceOperation.BULK_CREATE, table._id, {
|
||||||
rows,
|
rows,
|
||||||
})
|
})
|
||||||
events.row.import(table, "csv", rows.length)
|
await events.rows.imported(table, "csv", rows.length)
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -36,7 +36,7 @@ export const backfill = async (globalDb: any) => {
|
||||||
if (isOIDCConfig(config)) {
|
if (isOIDCConfig(config)) {
|
||||||
await events.auth.SSOCreated("oidc")
|
await events.auth.SSOCreated("oidc")
|
||||||
if (config.config.configs[0].activated) {
|
if (config.config.configs[0].activated) {
|
||||||
events.auth.SSOActivated("oidc")
|
await events.auth.SSOActivated("oidc")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isSettingsConfig(config)) {
|
if (isSettingsConfig(config)) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export type LoginSource = "local" | "sso"
|
export type LoginSource = "local" | "google" | "oidc"
|
||||||
export type SSOType = "oidc" | "google"
|
export type SSOType = "oidc" | "google"
|
||||||
|
|
||||||
export interface LoginEvent {
|
export interface LoginEvent {
|
||||||
|
|
|
@ -14,8 +14,9 @@ const {
|
||||||
isMultiTenant,
|
isMultiTenant,
|
||||||
} = require("@budibase/backend-core/tenancy")
|
} = require("@budibase/backend-core/tenancy")
|
||||||
const env = require("../../../environment")
|
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 { users } from "../../../sdk"
|
||||||
|
import { User } from "@budibase/types"
|
||||||
|
|
||||||
const ssoCallbackUrl = async (config: any, type: any) => {
|
const ssoCallbackUrl = async (config: any, type: any) => {
|
||||||
// incase there is a callback URL from before
|
// incase there is a callback URL from before
|
||||||
|
@ -71,7 +72,9 @@ export const authenticate = async (ctx: any, next: any) => {
|
||||||
"local",
|
"local",
|
||||||
async (err: any, user: any, info: any) => {
|
async (err: any, user: any, info: any) => {
|
||||||
await authInternal(ctx, user, err, info)
|
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.status = 200
|
||||||
}
|
}
|
||||||
)(ctx, next)
|
)(ctx, next)
|
||||||
|
@ -105,7 +108,7 @@ export const reset = async (ctx: any) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
try {
|
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
|
// only if user exists, don't error though if they don't
|
||||||
if (user) {
|
if (user) {
|
||||||
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
|
||||||
|
@ -212,7 +215,9 @@ export const googleAuth = async (ctx: any, next: any) => {
|
||||||
{ successRedirect: "/", failureRedirect: "/error" },
|
{ successRedirect: "/", failureRedirect: "/error" },
|
||||||
async (err: any, user: any, info: any) => {
|
async (err: any, user: any, info: any) => {
|
||||||
await authInternal(ctx, user, err, info)
|
await authInternal(ctx, user, err, info)
|
||||||
await events.auth.login("google")
|
await context.doInUserContext(user, async () => {
|
||||||
|
await events.auth.login("google")
|
||||||
|
})
|
||||||
ctx.redirect("/")
|
ctx.redirect("/")
|
||||||
}
|
}
|
||||||
)(ctx, next)
|
)(ctx, next)
|
||||||
|
@ -256,7 +261,9 @@ export const oidcAuth = async (ctx: any, next: any) => {
|
||||||
{ successRedirect: "/", failureRedirect: "/error" },
|
{ successRedirect: "/", failureRedirect: "/error" },
|
||||||
async (err: any, user: any, info: any) => {
|
async (err: any, user: any, info: any) => {
|
||||||
await authInternal(ctx, user, err, info)
|
await authInternal(ctx, user, err, info)
|
||||||
await events.auth.login("oidc")
|
await context.doInUserContext(user, async () => {
|
||||||
|
await events.auth.login("oidc")
|
||||||
|
})
|
||||||
ctx.redirect("/")
|
ctx.redirect("/")
|
||||||
}
|
}
|
||||||
)(ctx, next)
|
)(ctx, next)
|
||||||
|
|
Loading…
Reference in New Issue