Identity tenant and installation groups, property updates

This commit is contained in:
Rory Powell 2022-05-28 21:38:22 +01:00
parent 398a4e7034
commit 9610d8f1e7
40 changed files with 529 additions and 348 deletions

View File

@ -5,10 +5,11 @@ const {
getAppId, getAppId,
updateAppId, updateAppId,
doInAppContext, doInAppContext,
doInUserContext,
doInTenant, doInTenant,
} = require("./src/context") } = require("./src/context")
const identity = require("./src/context/identity")
module.exports = { module.exports = {
getAppDB, getAppDB,
getDevAppDB, getDevAppDB,
@ -16,6 +17,6 @@ module.exports = {
getAppId, getAppId,
updateAppId, updateAppId,
doInAppContext, doInAppContext,
doInUserContext,
doInTenant, doInTenant,
identity,
} }

View File

@ -32,6 +32,7 @@
"pouchdb-find": "^7.2.2", "pouchdb-find": "^7.2.2",
"pouchdb-replication-stream": "^1.2.9", "pouchdb-replication-stream": "^1.2.9",
"sanitize-s3-objectkey": "^0.0.1", "sanitize-s3-objectkey": "^0.0.1",
"semver": "^7.0.0",
"tar-fs": "^2.1.1", "tar-fs": "^2.1.1",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"zlib": "^1.0.5" "zlib": "^1.0.5"
@ -52,6 +53,7 @@
"@types/node-fetch": "^2.6.1", "@types/node-fetch": "^2.6.1",
"@types/tar-fs": "^2.0.1", "@types/tar-fs": "^2.0.1",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@types/semver": "^7.0.0",
"ioredis-mock": "^5.5.5", "ioredis-mock": "^5.5.5",
"jest": "^27.0.3", "jest": "^27.0.3",
"koa": "2.7.0", "koa": "2.7.0",

View File

@ -0,0 +1,50 @@
import {
IdentityContext,
IdentityType,
User,
UserContext,
isCloudAccount,
Account,
AccountUserContext,
} from "@budibase/types"
import * as context from "."
export const getIdentity = (): IdentityContext | undefined => {
return context.getIdentity()
}
export const doInIdentityContext = (identity: IdentityContext, task: any) => {
return context.doInIdentityContext(identity, task)
}
export const doInUserContext = (user: User, task: any) => {
const userContext: UserContext = {
...user,
_id: user._id as string,
type: IdentityType.USER,
}
return doInIdentityContext(userContext, task)
}
export const doInAccountContext = (account: Account, task: any) => {
const _id = getAccountUserId(account)
const tenantId = account.tenantId
const accountContext: AccountUserContext = {
_id,
type: IdentityType.USER,
tenantId,
account,
}
return doInIdentityContext(accountContext, task)
}
export const getAccountUserId = (account: Account) => {
let userId: string
if (isCloudAccount(account)) {
userId = account.budibaseUserId
} else {
// use account id as user id for self hosting
userId = account.accountId
}
return userId
}

View File

@ -15,7 +15,7 @@ const ContextKeys = {
TENANT_ID: "tenantId", TENANT_ID: "tenantId",
GLOBAL_DB: "globalDb", GLOBAL_DB: "globalDb",
APP_ID: "appId", APP_ID: "appId",
USER: "user", IDENTITY: "identity",
// whatever the request app DB was // whatever the request app DB was
CURRENT_DB: "currentDb", CURRENT_DB: "currentDb",
// get the prod app DB from the request // get the prod app DB from the request
@ -138,19 +138,19 @@ exports.doInAppContext = (appId, task) => {
throw new Error("appId is required") throw new Error("appId is required")
} }
const user = exports.getUser() const identity = exports.getIdentity()
// 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, user: undefined }) { async function internal(opts = { existing: false }) {
// set the app tenant id // set the app tenant id
if (!opts.existing) { if (!opts.existing) {
setAppTenantId(appId) setAppTenantId(appId)
} }
// set the app ID // set the app ID
cls.setOnContext(ContextKeys.APP_ID, appId) cls.setOnContext(ContextKeys.APP_ID, appId)
// preserve the user // preserve the identity
exports.setUser(user) exports.setIdentity(identity)
try { try {
// invoke the task // invoke the task
return await task() return await task()
@ -175,17 +175,17 @@ exports.doInAppContext = (appId, task) => {
} }
} }
exports.doInUserContext = (user, task) => { exports.doInIdentityContext = (identity, task) => {
if (!user) { if (!identity) {
throw new Error("user is required") throw new Error("identity is required")
} }
async function internal(opts = { existing: false }) { async function internal(opts = { existing: false }) {
if (!opts.existing) { if (!opts.existing) {
cls.setOnContext(ContextKeys.USER, user) cls.setOnContext(ContextKeys.IDENTITY, identity)
// set the tenant so that doInTenant will preserve user // set the tenant so that doInTenant will preserve identity
if (user.tenantId) { if (identity.tenantId) {
exports.updateTenantId(user.tenantId) exports.updateTenantId(identity.tenantId)
} }
} }
@ -195,16 +195,16 @@ exports.doInUserContext = (user, task) => {
} finally { } finally {
const using = cls.getFromContext(ContextKeys.IN_USE) const using = cls.getFromContext(ContextKeys.IN_USE)
if (!using || using <= 1) { if (!using || using <= 1) {
exports.setUser(null) exports.setIdentity(null)
} else { } else {
cls.setOnContext(using - 1) cls.setOnContext(using - 1)
} }
} }
} }
const existing = cls.getFromContext(ContextKeys.USER) const existing = cls.getFromContext(ContextKeys.IDENTITY)
const using = cls.getFromContext(ContextKeys.IN_USE) const using = cls.getFromContext(ContextKeys.IN_USE)
if (using && existing && existing._id === user._id) { if (using && existing && existing._id === identity._id) {
cls.setOnContext(ContextKeys.IN_USE, using + 1) cls.setOnContext(ContextKeys.IN_USE, using + 1)
return internal({ existing: true }) return internal({ existing: true })
} else { } else {
@ -215,15 +215,15 @@ exports.doInUserContext = (user, task) => {
} }
} }
exports.setUser = user => { exports.setIdentity = identity => {
cls.setOnContext(ContextKeys.USER, user) cls.setOnContext(ContextKeys.IDENTITY, identity)
} }
exports.getUser = () => { exports.getIdentity = () => {
try { try {
return cls.getFromContext(ContextKeys.USER) return cls.getFromContext(ContextKeys.IDENTITY)
} catch (e) { } catch (e) {
// do nothing - user is not in context // do nothing - identity is not in context
} }
} }

View File

@ -52,6 +52,7 @@ const env: any = {
process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads", process.env.GLOBAL_CLOUD_BUCKET_NAME || "prod-budi-tenant-uploads",
USE_COUCH: process.env.USE_COUCH || true, USE_COUCH: process.env.USE_COUCH || true,
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE, DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
SERVICE: process.env.SERVICE || "budibase",
_set(key: any, value: any) { _set(key: any, value: any) {
process.env[key] = value process.env[key] = value
module.exports[key] = value module.exports[key] = value

View File

@ -3,6 +3,7 @@ import * as tenancy from "../tenancy"
import * as dbUtils from "../db/utils" import * as dbUtils from "../db/utils"
import { Configs } from "../constants" import { Configs } from "../constants"
// TODO: cache in redis
export const enabled = async () => { export const enabled = async () => {
// cloud - always use the environment variable // cloud - always use the environment variable
if (!env.SELF_HOSTED) { if (!env.SELF_HOSTED) {

View File

@ -1,56 +1,239 @@
import * as context from "../context" import * as context from "../context"
import * as identityCtx from "../context/identity"
import env from "../environment" import env from "../environment"
import { import {
Hosting, Hosting,
User, User,
SessionUser,
Identity, Identity,
IdentityType, IdentityType,
Account, Account,
BudibaseIdentity,
isCloudAccount, isCloudAccount,
isSSOAccount, isSSOAccount,
TenantIdentity, TenantGroup,
SettingsConfig, SettingsConfig,
CloudAccount, CloudAccount,
UserIdentity, UserIdentity,
InstallationIdentity, InstallationGroup,
Installation, isSelfHostAccount,
isInstallation, UserContext,
Group,
} from "@budibase/types" } from "@budibase/types"
import { processors } from "./processors" import { processors } from "./processors"
import * as dbUtils from "../db/utils" import * as dbUtils from "../db/utils"
import { Configs } from "../constants" import { Configs } from "../constants"
import * as hashing from "../hashing" import * as hashing from "../hashing"
import * as installation from "../installation"
const pkg = require("../../package.json") const pkg = require("../../package.json")
/**
* An identity can be:
* - account user (Self host)
* - budibase user
* - tenant
* - installation
*/
export const getCurrentIdentity = async (): Promise<Identity> => { export const getCurrentIdentity = async (): Promise<Identity> => {
const user: SessionUser | undefined = context.getUser() let identityContext = identityCtx.getIdentity()
const tenantId = await getGlobalTenantId(context.getTenantId()) let identityType
let id: string
let type: IdentityType
if (user) { if (!identityContext) {
id = user._id identityType = IdentityType.TENANT
type = IdentityType.USER
} else { } else {
id = tenantId identityType = identityContext.type
type = IdentityType.TENANT
} }
if (user && isInstallation(user)) { if (identityType === IdentityType.INSTALLATION) {
type = IdentityType.INSTALLATION const installationId = await getInstallationId()
} return {
id: formatDistinctId(installationId, identityType),
type: identityType,
installationId,
}
} else if (identityType === IdentityType.TENANT) {
const installationId = await getInstallationId()
const tenantId = await getCurrentTenantId()
return { return {
id, id: formatDistinctId(tenantId, identityType),
tenantId, type: identityType,
type, installationId,
tenantId,
}
} else if (identityType === IdentityType.USER) {
const userContext = identityContext as UserContext
const tenantId = await getCurrentTenantId()
let installationId: string | undefined
// self host account users won't have installation
if (!userContext.account || !isSelfHostAccount(userContext.account)) {
installationId = await getInstallationId()
}
return {
id: userContext._id,
type: identityType,
installationId,
tenantId,
}
} else {
throw new Error("Unknown identity type")
} }
} }
export const identifyInstallationGroup = async (
installId: string,
timestamp?: string | number
): Promise<void> => {
const id = installId
const type = IdentityType.INSTALLATION
const hosting = getHostingFromEnv()
const version = pkg.version
const group: InstallationGroup = {
id,
type,
hosting,
version,
}
await identifyGroup(group, timestamp)
// need to create a normal identity for the group to be able to query it globally
// match the posthog syntax to link this identity to the empty auto generated one
await identify({ ...group, id: `$${type}_${id}` }, timestamp)
}
export const identifyTenantGroup = async (
tenantId: string,
account: Account | undefined,
timestamp?: string | number
): Promise<void> => {
const id = await getGlobalTenantId(tenantId)
const type = IdentityType.TENANT
let hosting: Hosting
let profession: string | undefined
let companySize: string | undefined
if (account) {
profession = account.profession
companySize = account.size
hosting = account.hosting
} else {
hosting = getHostingFromEnv()
}
const group: TenantGroup = {
id,
type,
hosting,
profession,
companySize,
}
await identifyGroup(group, timestamp)
// need to create a normal identity for the group to be able to query it globally
// match the posthog syntax to link this identity to the auto generated one
await identify({ ...group, id: `$${type}_${id}` }, timestamp)
}
export const identifyUser = async (
user: User,
account: CloudAccount | undefined,
timestamp?: string | number
) => {
const id = user._id as string
const tenantId = await getGlobalTenantId(user.tenantId)
const type = IdentityType.USER
let builder = user.builder?.global || false
let admin = user.admin?.global || false
let providerType = user.providerType
const accountHolder = account?.budibaseUserId === user._id || false
const verified =
account && account?.budibaseUserId === user._id ? account.verified : false
const installationId = await getInstallationId()
const identity: UserIdentity = {
id,
type,
installationId,
tenantId,
verified,
accountHolder,
providerType,
builder,
admin,
}
await identify(identity, timestamp)
}
export const identifyAccount = async (account: Account) => {
let id = account.accountId
const tenantId = account.tenantId
let type = IdentityType.USER
let providerType = isSSOAccount(account) ? account.providerType : undefined
const verified = account.verified
const accountHolder = true
if (isCloudAccount(account)) {
if (account.budibaseUserId) {
// use the budibase user as the id if set
id = account.budibaseUserId
}
}
const identity: UserIdentity = {
id,
type,
tenantId,
providerType,
verified,
accountHolder,
}
await identify(identity)
}
export const identify = async (
identity: Identity,
timestamp?: string | number
) => {
await processors.identify(identity, timestamp)
}
export const identifyGroup = async (
group: Group,
timestamp?: string | number
) => {
await processors.identifyGroup(group, timestamp)
}
const getHostingFromEnv = () => {
return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD
}
export const getCurrentTenantId = () => getGlobalTenantId(context.getTenantId())
export const getInstallationId = async () => {
if (isAccountPortal()) {
return "account-portal"
}
const install = await installation.getInstall()
return install.installId
}
const getGlobalTenantId = async (tenantId: string): Promise<string> => {
if (env.SELF_HOSTED) {
return getGlobalId(tenantId)
} else {
// tenant id's in the cloud are already unique
return tenantId
}
}
// TODO: cache in redis
const getGlobalId = async (tenantId: string): Promise<string> => { const getGlobalId = async (tenantId: string): Promise<string> => {
const db = context.getGlobalDB() const db = context.getGlobalDB()
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, { const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, {
@ -68,134 +251,14 @@ const getGlobalId = async (tenantId: string): Promise<string> => {
} }
} }
const getGlobalTenantId = async (tenantId: string): Promise<string> => { const isAccountPortal = () => {
if (env.SELF_HOSTED) { return env.SERVICE === "account-portal"
return getGlobalId(tenantId) }
const formatDistinctId = (id: string, type: IdentityType) => {
if (type === IdentityType.INSTALLATION || type === IdentityType.TENANT) {
return `$${type}_${id}`
} else { } else {
// tenant id's in the cloud are already unique return id
return tenantId
} }
} }
const getHostingFromEnv = () => {
return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD
}
export const identifyInstallation = async (
install: Installation,
timestamp: string | number
) => {
const id = install.installId
// the default tenant id, so we can match installations to other events
const tenantId = await getGlobalTenantId(context.getTenantId())
const version: string = pkg.version as string
const type = IdentityType.INSTALLATION
const hosting = getHostingFromEnv()
const identity: InstallationIdentity = {
id,
tenantId,
type,
version,
hosting,
}
await identify(identity, timestamp)
}
export const identifyTenant = async (
tenantId: string,
account: CloudAccount | undefined,
timestamp?: string | number
) => {
const globalTenantId = await getGlobalTenantId(tenantId)
const id = globalTenantId
const hosting = getHostingFromEnv()
const type = IdentityType.TENANT
const profession = account?.profession
const companySize = account?.size
const identity: TenantIdentity = {
id,
tenantId: globalTenantId,
hosting,
type,
profession,
companySize,
}
await identify(identity, timestamp)
}
export const identifyUser = async (
user: User,
account: CloudAccount | undefined,
timestamp?: string | number
) => {
const id = user._id as string
const tenantId = user.tenantId
const hosting = env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD
const type = IdentityType.USER
let builder = user.builder?.global
let admin = user.admin?.global
let providerType = user.providerType
const accountHolder = account?.budibaseUserId === user._id
const verified =
account && account?.budibaseUserId === user._id ? account.verified : false
const profession = account?.profession
const companySize = account?.size
const identity: BudibaseIdentity = {
id,
tenantId,
hosting,
type,
builder,
admin,
providerType,
accountHolder,
verified,
profession,
companySize,
}
await identify(identity, timestamp)
}
export const identifyAccount = async (account: Account) => {
let id = account.accountId
const tenantId = account.tenantId
const hosting = account.hosting
let type = IdentityType.USER
let providerType = isSSOAccount(account) ? account.providerType : undefined
const verified = account.verified
const profession = account.profession
const companySize = account.size
const accountHolder = true
if (isCloudAccount(account)) {
if (account.budibaseUserId) {
// use the budibase user as the id if set
id = account.budibaseUserId
}
}
const identity: UserIdentity = {
id,
tenantId,
hosting,
type,
providerType,
verified,
profession,
companySize,
accountHolder,
}
await identify(identity)
}
export const identify = async (
identity: Identity,
timestamp?: string | number
) => {
await processors.identify(identity, timestamp)
}

View File

@ -1,9 +1,15 @@
import { Event, Identity } from "@budibase/types" import { Event, Identity, Group, IdentityType } from "@budibase/types"
import { EventProcessor } from "./types" import { EventProcessor } from "./types"
import env from "../../environment" import env from "../../environment"
import * as analytics from "../analytics" import * as analytics from "../analytics"
import PosthogProcessor from "./PosthogProcessor" import PosthogProcessor from "./PosthogProcessor"
/**
* Events that are always captured.
*/
const EVENT_WHITELIST = [Event.VERSION_UPGRADED, Event.VERSION_DOWNGRADED]
const IDENTITY_WHITELIST = [IdentityType.INSTALLATION, IdentityType.TENANT]
export default class AnalyticsProcessor implements EventProcessor { export default class AnalyticsProcessor implements EventProcessor {
posthog: PosthogProcessor | undefined posthog: PosthogProcessor | undefined
@ -19,7 +25,7 @@ export default class AnalyticsProcessor implements EventProcessor {
properties: any, properties: any,
timestamp?: string | number timestamp?: string | number
): Promise<void> { ): Promise<void> {
if (!(await analytics.enabled())) { if (!EVENT_WHITELIST.includes(event) && !(await analytics.enabled())) {
return return
} }
if (this.posthog) { if (this.posthog) {
@ -28,7 +34,11 @@ export default class AnalyticsProcessor implements EventProcessor {
} }
async identify(identity: Identity, timestamp?: string | number) { async identify(identity: Identity, timestamp?: string | number) {
if (!(await analytics.enabled())) { // Group indentifications (tenant and installation) always on
if (
!IDENTITY_WHITELIST.includes(identity.type) &&
!(await analytics.enabled())
) {
return return
} }
if (this.posthog) { if (this.posthog) {
@ -36,6 +46,13 @@ export default class AnalyticsProcessor implements EventProcessor {
} }
} }
async identifyGroup(group: Group, timestamp?: string | number) {
// Group indentifications (tenant and installation) always on
if (this.posthog) {
this.posthog.identifyGroup(group, timestamp)
}
}
shutdown() { shutdown() {
if (this.posthog) { if (this.posthog) {
this.posthog.shutdown() this.posthog.shutdown()

View File

@ -1,7 +1,17 @@
import { Event, Identity } from "@budibase/types" import { Event, Identity, Group } from "@budibase/types"
import { EventProcessor } from "./types" import { EventProcessor } from "./types"
import env from "../../environment" import env from "../../environment"
const getTimestampString = (timestamp?: string | number) => {
let timestampString = ""
if (timestamp) {
timestampString = `[timestamp=${new Date(timestamp).toISOString()}]`
}
return timestampString
}
const skipLogging = env.SELF_HOSTED && !env.isDev()
export default class LoggingProcessor implements EventProcessor { export default class LoggingProcessor implements EventProcessor {
async processEvent( async processEvent(
event: Event, event: Event,
@ -9,34 +19,35 @@ export default class LoggingProcessor implements EventProcessor {
properties: any, properties: any,
timestamp?: string timestamp?: string
): Promise<void> { ): Promise<void> {
if (env.SELF_HOSTED && !env.isDev()) { if (skipLogging) {
return return
} }
let timestampString = "" let timestampString = getTimestampString(timestamp)
if (timestamp) {
timestampString = `[timestamp=${new Date(timestamp).toISOString()}]`
}
console.log( console.log(
`[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} ` `[audit] [tenant=${identity.tenantId}] [identityType=${identity.type}] [identity=${identity.id}] ${timestampString} ${event} `
) )
} }
async identify(identity: Identity, timestamp?: string | number) { async identify(identity: Identity, timestamp?: string | number) {
if (env.SELF_HOSTED && !env.isDev()) { if (skipLogging) {
return return
} }
let timestampString = getTimestampString(timestamp)
let timestampString = ""
if (timestamp) {
timestampString = `[timestamp=${new Date(timestamp).toISOString()}]`
}
console.log( console.log(
`[audit] [${JSON.stringify(identity)}] ${timestampString} identified` `[audit] [${JSON.stringify(identity)}] ${timestampString} identified`
) )
} }
async identifyGroup(group: Group, timestamp?: string | number) {
if (skipLogging) {
return
}
let timestampString = getTimestampString(timestamp)
console.log(
`[audit] [${JSON.stringify(group)}] ${timestampString} group identified`
)
}
shutdown(): void { shutdown(): void {
// no-op // no-op
} }

View File

@ -1,6 +1,8 @@
import PostHog from "posthog-node" import PostHog from "posthog-node"
import { Event, Identity } from "@budibase/types" import { Event, Identity, Group } from "@budibase/types"
import { EventProcessor } from "./types" import { EventProcessor } from "./types"
import env from "../../environment"
const pkg = require("../../../package.json")
export default class PosthogProcessor implements EventProcessor { export default class PosthogProcessor implements EventProcessor {
posthog: PostHog posthog: PostHog
@ -18,10 +20,24 @@ export default class PosthogProcessor implements EventProcessor {
properties: any, properties: any,
timestamp?: string | number timestamp?: string | number
): Promise<void> { ): Promise<void> {
properties.version = pkg.version
properties.service = env.SERVICE
const payload: any = { distinctId: identity.id, event, properties } const payload: any = { distinctId: identity.id, event, properties }
if (timestamp) { if (timestamp) {
payload.timestamp = new Date(timestamp) payload.timestamp = new Date(timestamp)
} }
// add groups to the event
if (identity.installationId || identity.tenantId) {
payload.groups = {}
if (identity.installationId) {
payload.groups.installation = identity.installationId
}
if (identity.tenantId) {
payload.groups.tenant = identity.tenantId
}
}
this.posthog.capture(payload) this.posthog.capture(payload)
} }
@ -33,6 +49,20 @@ export default class PosthogProcessor implements EventProcessor {
this.posthog.identify(payload) this.posthog.identify(payload)
} }
async identifyGroup(group: Group, timestamp?: string | number) {
const payload: any = {
distinctId: group.id,
groupType: group.type,
groupKey: group.id,
properties: group,
}
if (timestamp) {
payload.timestamp = new Date(timestamp)
}
this.posthog.groupIdentify(payload)
}
shutdown() { shutdown() {
this.posthog.shutdown() this.posthog.shutdown()
} }

View File

@ -1,4 +1,4 @@
import { Event, Identity } from "@budibase/types" import { Event, Identity, Group } from "@budibase/types"
import { EventProcessor } from "./types" import { EventProcessor } from "./types"
export default class Processor implements EventProcessor { export default class Processor implements EventProcessor {
@ -29,6 +29,15 @@ export default class Processor implements EventProcessor {
} }
} }
async identifyGroup(
identity: Group,
timestamp?: string | number
): Promise<void> {
for (const eventProcessor of this.processors) {
await eventProcessor.identifyGroup(identity, timestamp)
}
}
shutdown() { shutdown() {
for (const eventProcessor of this.processors) { for (const eventProcessor of this.processors) {
eventProcessor.shutdown() eventProcessor.shutdown()

View File

@ -1,4 +1,4 @@
import { Event, Identity } from "@budibase/types" import { Event, Identity, Group } from "@budibase/types"
export enum EventProcessorType { export enum EventProcessorType {
POSTHOG = "posthog", POSTHOG = "posthog",
@ -13,5 +13,6 @@ export interface EventProcessor {
timestamp?: string | number timestamp?: string | number
): Promise<void> ): Promise<void>
identify(identity: Identity, timestamp?: string | number): Promise<void> identify(identity: Identity, timestamp?: string | number): Promise<void>
identifyGroup(group: Group, timestamp?: string | number): Promise<void>
shutdown(): void shutdown(): void
} }

View File

@ -13,9 +13,9 @@ export async function created(layout: Layout, timestamp?: string) {
await publishEvent(Event.LAYOUT_CREATED, properties, timestamp) await publishEvent(Event.LAYOUT_CREATED, properties, timestamp)
} }
export async function deleted(layout: Layout) { export async function deleted(layoutId: string) {
const properties: LayoutDeletedEvent = { const properties: LayoutDeletedEvent = {
layoutId: layout._id as string, layoutId,
} }
await publishEvent(Event.LAYOUT_DELETED, properties) await publishEvent(Event.LAYOUT_DELETED, properties)
} }

View File

@ -9,17 +9,23 @@ import {
/* eslint-disable */ /* eslint-disable */
export async function servedBuilder(version: number) { export async function servedBuilder() {
const properties: BuilderServedEvent = {} const properties: BuilderServedEvent = {}
await publishEvent(Event.SERVED_BUILDER, properties) await publishEvent(Event.SERVED_BUILDER, properties)
} }
export async function servedApp(app: App) { export async function servedApp(app: App) {
const properties: AppServedEvent = {} const properties: AppServedEvent = {
appId: app.appId,
appVersion: app.version,
}
await publishEvent(Event.SERVED_APP, properties) await publishEvent(Event.SERVED_APP, properties)
} }
export async function servedAppPreview(app: App) { export async function servedAppPreview(app: App) {
const properties: AppPreviewServedEvent = {} const properties: AppPreviewServedEvent = {
appId: app.appId,
appVersion: app.version,
}
await publishEvent(Event.SERVED_APP_PREVIEW, properties) await publishEvent(Event.SERVED_APP_PREVIEW, properties)
} }

View File

@ -4,6 +4,7 @@ import * as events from "./events"
import * as migrations from "./migrations" import * as migrations from "./migrations"
import * as users from "./users" import * as users from "./users"
import * as accounts from "./cloud/accounts" import * as accounts from "./cloud/accounts"
import * as installation from "./installation"
import env from "./environment" import env from "./environment"
import tenancy from "./tenancy" import tenancy from "./tenancy"
import featureFlags from "./featureFlags" import featureFlags from "./featureFlags"
@ -46,4 +47,5 @@ export = {
events, events,
sessions, sessions,
deprovisioning, deprovisioning,
installation,
} }

View File

@ -1,29 +1,27 @@
import { import * as hashing from "./hashing"
db as dbUtils, import * as events from "./events"
events, import { StaticDatabases } from "./db/constants"
utils, import { doWithDB } from "./db"
context, import { Installation, IdentityType } from "@budibase/types"
tenancy, import * as context from "./context"
} from "@budibase/backend-core"
import { Installation } from "@budibase/types"
import semver from "semver" import semver from "semver"
const pkg = require("../package.json") const pkg = require("../package.json")
export const getInstall = async (): Promise<Installation> => { export const getInstall = async (): Promise<Installation> => {
return dbUtils.doWithDB( return doWithDB(
dbUtils.StaticDatabases.PLATFORM_INFO.name, StaticDatabases.PLATFORM_INFO.name,
async (platformDb: any) => { async (platformDb: any) => {
let install: Installation let install: Installation
try { try {
install = await platformDb.get( install = await platformDb.get(
dbUtils.StaticDatabases.PLATFORM_INFO.docs.install StaticDatabases.PLATFORM_INFO.docs.install
) )
} catch (e: any) { } catch (e: any) {
if (e.status === 404) { if (e.status === 404) {
install = { install = {
_id: dbUtils.StaticDatabases.PLATFORM_INFO.docs.install, _id: StaticDatabases.PLATFORM_INFO.docs.install,
installId: utils.newid(), installId: hashing.newid(),
version: pkg.version, version: pkg.version,
} }
const resp = await platformDb.put(install) const resp = await platformDb.put(install)
@ -40,8 +38,8 @@ export const getInstall = async (): Promise<Installation> => {
const updateVersion = async (version: string): Promise<boolean> => { const updateVersion = async (version: string): Promise<boolean> => {
try { try {
await dbUtils.doWithDB( await doWithDB(
dbUtils.StaticDatabases.PLATFORM_INFO.name, StaticDatabases.PLATFORM_INFO.name,
async (platformDb: any) => { async (platformDb: any) => {
const install = await getInstall() const install = await getInstall()
install.version = version install.version = version
@ -73,11 +71,10 @@ export const checkInstallVersion = async (): Promise<void> => {
const success = await updateVersion(newVersion) const success = await updateVersion(newVersion)
if (success) { if (success) {
await context.doInUserContext( await context.doInIdentityContext(
{ {
_id: install.installId, _id: install.installId,
isInstall: true, type: IdentityType.INSTALLATION,
tenantId: tenancy.DEFAULT_TENANT_ID,
}, },
async () => { async () => {
if (isUpgrade) { if (isUpgrade) {
@ -87,6 +84,7 @@ export const checkInstallVersion = async (): Promise<void> => {
} }
} }
) )
await events.identification.identifyInstallationGroup(install.installId)
} }
} }
} }

View File

@ -7,7 +7,7 @@ const env = require("../environment")
const { SEPARATOR, ViewNames, queryGlobalView } = require("../../db") const { SEPARATOR, ViewNames, queryGlobalView } = require("../../db")
const { getGlobalDB, doInTenant } = require("../tenancy") const { getGlobalDB, doInTenant } = require("../tenancy")
const { decrypt } = require("../security/encryption") const { decrypt } = require("../security/encryption")
const context = require("../context") const identity = require("../context/identity")
function finalise( function finalise(
ctx, ctx,
@ -135,7 +135,7 @@ module.exports = (
finalise(ctx, { authenticated, user, internal, version, publicEndpoint }) finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
if (user && user.email) { if (user && user.email) {
return context.doInUserContext(user, next) return identity.doInUserContext(user, next)
} else { } else {
return next() return next()
} }
@ -147,7 +147,7 @@ module.exports = (
// allow configuring for public access // allow configuring for public access
if ((opts && opts.publicAllowed) || publicEndpoint) { if ((opts && opts.publicAllowed) || publicEndpoint) {
finalise(ctx, { authenticated: false, version, publicEndpoint }) finalise(ctx, { authenticated: false, version, publicEndpoint })
return context.doInUserContext({ _id: "public_user" }, next) return next()
} else { } else {
ctx.throw(err.status || 403, err) ctx.throw(err.status || 403, err)
} }

View File

@ -56,9 +56,11 @@ jest.mock("../../../events", () => {
nameUpdated: jest.fn(), nameUpdated: jest.fn(),
logoUpdated: jest.fn(), logoUpdated: jest.fn(),
platformURLUpdated: jest.fn(), platformURLUpdated: jest.fn(),
versionChecked: jest.fn(),
analyticsOptOut: jest.fn(), analyticsOptOut: jest.fn(),
}, },
version: {
checked: jest.fn(),
},
query: { query: {
created: jest.fn(), created: jest.fn(),
updated: jest.fn(), updated: jest.fn(),

View File

@ -971,6 +971,11 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/semver@^7.0.0":
version "7.3.9"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
"@types/serve-static@*": "@types/serve-static@*":
version "1.13.10" version "1.13.10"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
@ -5012,7 +5017,7 @@ semver-diff@^3.1.1:
dependencies: dependencies:
semver "^6.3.0" semver "^6.3.0"
semver@7.x, semver@^7.3.4: semver@7.x, semver@^7.0.0, semver@^7.3.4:
version "7.3.7" version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==

View File

@ -129,7 +129,6 @@
"pouchdb-find": "^7.2.2", "pouchdb-find": "^7.2.2",
"pouchdb-replication-stream": "1.2.9", "pouchdb-replication-stream": "1.2.9",
"redis": "4", "redis": "4",
"semver": "^7.0.0",
"server-destroy": "1.0.1", "server-destroy": "1.0.1",
"svelte": "^3.38.2", "svelte": "^3.38.2",
"swagger-parser": "^10.0.3", "swagger-parser": "^10.0.3",
@ -160,7 +159,6 @@
"@types/node": "^15.12.4", "@types/node": "^15.12.4",
"@types/oracledb": "^5.2.1", "@types/oracledb": "^5.2.1",
"@types/redis": "^4.0.11", "@types/redis": "^4.0.11",
"@types/semver": "^7.0.0",
"@typescript-eslint/parser": "5.12.0", "@typescript-eslint/parser": "5.12.0",
"apidoc": "^0.50.2", "apidoc": "^0.50.2",
"babel-jest": "^27.0.2", "babel-jest": "^27.0.2",

View File

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

View File

@ -20,7 +20,7 @@ exports.save = async function (ctx) {
layout._id = layout._id || generateLayoutID() layout._id = layout._id || generateLayoutID()
const response = await db.put(layout) const response = await db.put(layout)
await events.layout.created() await events.layout.created(layout)
layout._rev = response.rev layout._rev = response.rev
ctx.body = layout ctx.body = layout
@ -48,7 +48,7 @@ exports.destroy = async function (ctx) {
} }
await db.remove(layoutId, layoutRev) await db.remove(layoutId, layoutRev)
await events.layout.deleted() await events.layout.deleted(layoutId)
ctx.body = { message: "Layout deleted successfully" } ctx.body = { message: "Layout deleted successfully" }
ctx.status = 200 ctx.status = 200
} }

View File

@ -19,7 +19,6 @@ const { getAppDB, getAppId } = require("@budibase/backend-core/context")
const AWS = require("aws-sdk") const AWS = require("aws-sdk")
const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1" const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1"
const { events } = require("@budibase/backend-core") const { events } = require("@budibase/backend-core")
const version = require("../../../../package.json").version
async function prepareUpload({ s3Key, bucket, metadata, file }) { async function prepareUpload({ s3Key, bucket, metadata, file }) {
const response = await upload({ const response = await upload({
@ -43,7 +42,7 @@ async function prepareUpload({ s3Key, bucket, metadata, file }) {
exports.serveBuilder = async function (ctx) { exports.serveBuilder = async function (ctx) {
let builderPath = resolve(TOP_LEVEL_PATH, "builder") let builderPath = resolve(TOP_LEVEL_PATH, "builder")
await send(ctx, ctx.file, { root: builderPath }) await send(ctx, ctx.file, { root: builderPath })
await events.serve.servedBuilder(version) await events.serve.servedBuilder()
} }
exports.uploadFile = async function (ctx) { exports.uploadFile = async function (ctx) {

View File

@ -33,8 +33,8 @@ describe("/dev", () => {
.expect(200) .expect(200)
expect(res.body.version).toBe(version) expect(res.body.version).toBe(version)
expect(events.org.versionChecked).toBeCalledTimes(1) expect(events.version.checked).toBeCalledTimes(1)
expect(events.org.versionChecked).toBeCalledWith(version) expect(events.version.checked).toBeCalledWith(version)
}) })
}) })
}) })

View File

@ -16,8 +16,7 @@ const fileSystem = require("./utilities/fileSystem")
const bullboard = require("./automations/bullboard") const bullboard = require("./automations/bullboard")
import redis from "./utilities/redis" import redis from "./utilities/redis"
import * as migrations from "./migrations" import * as migrations from "./migrations"
import { events } from "@budibase/backend-core" import { events, installation } from "@budibase/backend-core"
import * as installation from "./installation"
const app = new Koa() const app = new Koa()

View File

@ -2,9 +2,8 @@ import * as users from "./global/users"
import * as rows from "./global/rows" import * as rows from "./global/rows"
import * as configs from "./global/configs" import * as configs from "./global/configs"
import { tenancy, events, migrations, accounts } from "@budibase/backend-core" import { tenancy, events, migrations, accounts } from "@budibase/backend-core"
import { CloudAccount, Installation } from "@budibase/types" import { CloudAccount } from "@budibase/types"
import env from "../../../environment" import env from "../../../environment"
import * as installation from "../../../installation"
/** /**
* Date: * Date:
@ -22,7 +21,7 @@ export const run = async (db: any) => {
account = await accounts.getAccountByTenantId(tenantId) account = await accounts.getAccountByTenantId(tenantId)
} }
await events.identification.identifyTenant( await events.identification.identifyTenantGroup(
tenantId, tenantId,
account, account,
installTimestamp installTimestamp

View File

@ -1,6 +1,5 @@
import { events, tenancy } from "@budibase/backend-core" import { events, tenancy, installation } from "@budibase/backend-core"
import { Installation } from "@budibase/types" import { Installation } from "@budibase/types"
import * as installation from "../../../installation"
import * as global from "./global" import * as global from "./global"
/** /**
@ -17,6 +16,9 @@ export const run = async () => {
const db = tenancy.getGlobalDB() const db = tenancy.getGlobalDB()
const installTimestamp = (await global.getInstallTimestamp(db)) as number const installTimestamp = (await global.getInstallTimestamp(db)) as number
const install: Installation = await installation.getInstall() const install: Installation = await installation.getInstall()
await events.identification.identifyInstallation(install, installTimestamp) await events.identification.identifyInstallationGroup(
install.installId,
installTimestamp
)
}) })
} }

View File

@ -2754,11 +2754,6 @@
"@types/tough-cookie" "*" "@types/tough-cookie" "*"
form-data "^2.5.0" form-data "^2.5.0"
"@types/semver@^7.0.0":
version "7.3.9"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
"@types/serve-static@*": "@types/serve-static@*":
version "1.13.10" version "1.13.10"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
@ -11650,13 +11645,6 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.0.0:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
seq-queue@^0.0.5: seq-queue@^0.0.5:
version "0.0.5" version "0.0.5"
resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"

View File

@ -0,0 +1,19 @@
import { User, Account } from "../documents"
import { IdentityType } from "./identification"
export interface BaseContext {
_id: string
type: IdentityType
}
export interface AccountUserContext extends BaseContext {
tenantId: string
account: Account
}
export interface UserContext extends BaseContext, User {
_id: string
account?: Account
}
export type IdentityContext = BaseContext | AccountUserContext | UserContext

View File

@ -0,0 +1,49 @@
import { Hosting } from "."
// GROUPS
export enum GroupType {
TENANT = "tenant",
INSTALLATION = "installation",
}
export interface Group {
id: string
type: IdentityType
}
export interface TenantGroup extends Group {
// account level information is associated with the tenant group
// as we don't have this at the user level
profession?: string // only available in cloud
companySize?: string // only available in cloud
hosting: Hosting // need hosting at the tenant level for cloud self host accounts
}
export interface InstallationGroup extends Group {
version: string
hosting: Hosting
}
// IDENTITIES
export enum IdentityType {
USER = "user",
TENANT = "tenant",
INSTALLATION = "installation",
}
export interface Identity {
id: string
type: IdentityType
installationId?: string
tenantId?: string
}
export interface UserIdentity extends Identity {
verified: boolean
accountHolder: boolean
providerType?: string
builder?: boolean
admin?: boolean
}

View File

@ -1,2 +1,3 @@
export * from "./hosting" export * from "./hosting"
export * from "./sessions" export * from "./context"
export * from "./identification"

View File

@ -1,49 +0,0 @@
import { User, Account } from "../documents"
import { Hosting } from "./hosting"
/**
* Account portal user session. Used for self hosted accounts only.
*/
export interface AccountUserSession {
_id: string
email: string
tenantId: string
accountPortalAccess: boolean
account: Account
}
/**
* Budibase user session.
*/
export interface BudibaseUserSession extends User {
_id: string // overwrite potentially undefined
account?: Account
accountPortalAccess?: boolean
}
export const isAccountSession = (
user: AccountUserSession | BudibaseUserSession
): user is AccountUserSession => {
return user.account?.hosting === Hosting.SELF
}
export const isUserSession = (
user: AccountUserSession | BudibaseUserSession
): user is BudibaseUserSession => {
return !user.account || user.account?.hosting === Hosting.CLOUD
}
// not technically a session, but used to identify the installation
export interface InstallationSession {
_id: string
isInstallation: boolean
}
export const isInstallation = (user: any): user is InstallationSession => {
return !!user.isInstallation
}
export type SessionUser =
| AccountUserSession
| BudibaseUserSession
| InstallationSession

View File

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

View File

@ -8,7 +8,7 @@ export enum Event {
USER_PERMISSION_ADMIN_ASSIGNED = "user:admin:assigned", USER_PERMISSION_ADMIN_ASSIGNED = "user:admin:assigned",
USER_PERMISSION_ADMIN_REMOVED = "user:admin:removed", USER_PERMISSION_ADMIN_REMOVED = "user:admin:removed",
USER_PERMISSION_BUILDER_ASSIGNED = "user:builder:assigned", USER_PERMISSION_BUILDER_ASSIGNED = "user:builder:assigned",
USER_PERMISSION_BUILDER_REMOVED = "userbuilder:removed", USER_PERMISSION_BUILDER_REMOVED = "user:builder:removed",
// USER / INVITE // USER / INVITE
USER_INVITED = "user:invited", USER_INVITED = "user:invited",

View File

@ -1,37 +0,0 @@
import { Hosting } from "../core"
export enum IdentityType {
USER = "user",
TENANT = "tenant",
INSTALLATION = "installation",
}
export interface Identity {
id: string
tenantId: string
type: IdentityType
}
export interface InstallationIdentity extends Identity {
version: string
hosting: Hosting
}
export interface TenantIdentity extends Identity {
hosting: Hosting
profession?: string
companySize?: string
}
export interface UserIdentity extends TenantIdentity {
hosting: Hosting
type: IdentityType
verified: boolean
accountHolder: boolean
providerType?: string
}
export interface BudibaseIdentity extends UserIdentity {
builder?: boolean
admin?: boolean
}

View File

@ -15,5 +15,4 @@ export * from "./serve"
export * from "./table" export * from "./table"
export * from "./user" export * from "./user"
export * from "./view" export * from "./view"
export * from "./identification"
export * from "./account" export * from "./account"

View File

@ -1,5 +1,11 @@
export interface BuilderServedEvent {} export interface BuilderServedEvent {}
export interface AppServedEvent {} export interface AppServedEvent {
appId: string
appVersion: string
}
export interface AppPreviewServedEvent {} export interface AppPreviewServedEvent {
appId: string
appVersion: string
}

View File

@ -70,9 +70,9 @@ async function authInternal(ctx: any, user: any, err = null, info = null) {
export const authenticate = async (ctx: any, next: any) => { export const authenticate = async (ctx: any, next: any) => {
return passport.authenticate( return passport.authenticate(
"local", "local",
async (err: any, user: any, info: any) => { async (err: any, user: User, info: any) => {
await authInternal(ctx, user, err, info) await authInternal(ctx, user, err, info)
await context.doInUserContext(user, async () => { await context.identity.doInUserContext(user, async () => {
await events.auth.login("local") await events.auth.login("local")
}) })
ctx.status = 200 ctx.status = 200
@ -213,10 +213,10 @@ export const googleAuth = async (ctx: any, next: any) => {
return passport.authenticate( return passport.authenticate(
strategy, strategy,
{ successRedirect: "/", failureRedirect: "/error" }, { successRedirect: "/", failureRedirect: "/error" },
async (err: any, user: any, info: any) => { async (err: any, user: User, info: any) => {
await authInternal(ctx, user, err, info) await authInternal(ctx, user, err, info)
await context.doInUserContext(user, async () => { await context.identity.doInUserContext(user, async () => {
await events.auth.login("google") await events.auth.login("google-internal")
}) })
ctx.redirect("/") ctx.redirect("/")
} }
@ -261,7 +261,7 @@ 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 context.doInUserContext(user, async () => { await context.identity.doInUserContext(user, async () => {
await events.auth.login("oidc") await events.auth.login("oidc")
}) })
ctx.redirect("/") ctx.redirect("/")

View File

@ -69,7 +69,7 @@ export const adminUser = async (ctx: any) => {
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
account = await accounts.getAccountByTenantId(tenantId) account = await accounts.getAccountByTenantId(tenantId)
} }
await events.identification.identifyTenant(tenantId, account) await events.identification.identifyTenantGroup(tenantId, account)
} catch (err: any) { } catch (err: any) {
ctx.throw(err.status || 400, err) ctx.throw(err.status || 400, err)
} }

View File

@ -8,6 +8,12 @@ cd packages/string-templates
yarn link yarn link
cd - cd -
echo "Linking types"
cd packages/types
yarn link
cd -
if [ -d "../budibase-pro" ]; then if [ -d "../budibase-pro" ]; then
cd ../budibase-pro cd ../budibase-pro
yarn bootstrap yarn bootstrap
@ -38,6 +44,9 @@ if [ -d "../account-portal" ]; then
echo "Linking string-templates to account-portal" echo "Linking string-templates to account-portal"
yarn link "@budibase/string-templates" yarn link "@budibase/string-templates"
echo "Linking types to account-portal"
yarn link "@budibase/types"
if [ -d "../../../budibase-pro" ]; then if [ -d "../../../budibase-pro" ]; then
echo "Linking pro to account-portal" echo "Linking pro to account-portal"
yarn link "@budibase/pro" yarn link "@budibase/pro"