|
|
|
@ -1,128 +1,141 @@ |
|
|
|
import * as context from "../context" |
|
|
|
import * as identityCtx from "../context/identity" |
|
|
|
import env from "../environment" |
|
|
|
import { |
|
|
|
Hosting, |
|
|
|
User, |
|
|
|
SessionUser, |
|
|
|
Identity, |
|
|
|
IdentityType, |
|
|
|
Account, |
|
|
|
BudibaseIdentity, |
|
|
|
isCloudAccount, |
|
|
|
isSSOAccount, |
|
|
|
TenantIdentity, |
|
|
|
TenantGroup, |
|
|
|
SettingsConfig, |
|
|
|
CloudAccount, |
|
|
|
UserIdentity, |
|
|
|
InstallationIdentity, |
|
|
|
Installation, |
|
|
|
isInstallation, |
|
|
|
InstallationGroup, |
|
|
|
isSelfHostAccount, |
|
|
|
UserContext, |
|
|
|
Group, |
|
|
|
} from "@budibase/types" |
|
|
|
import { processors } from "./processors" |
|
|
|
import * as dbUtils from "../db/utils" |
|
|
|
import { Configs } from "../constants" |
|
|
|
import * as hashing from "../hashing" |
|
|
|
import * as installation from "../installation" |
|
|
|
|
|
|
|
const pkg = require("../../package.json") |
|
|
|
|
|
|
|
/** |
|
|
|
* An identity can be: |
|
|
|
* - account user (Self host) |
|
|
|
* - budibase user |
|
|
|
* - tenant |
|
|
|
* - installation |
|
|
|
*/ |
|
|
|
export const getCurrentIdentity = async (): Promise<Identity> => { |
|
|
|
const user: SessionUser | undefined = context.getUser() |
|
|
|
let identityContext = identityCtx.getIdentity() |
|
|
|
|
|
|
|
const tenantId = await getGlobalTenantId(context.getTenantId()) |
|
|
|
let id: string |
|
|
|
let type: IdentityType |
|
|
|
let identityType |
|
|
|
|
|
|
|
if (user) { |
|
|
|
id = user._id |
|
|
|
type = IdentityType.USER |
|
|
|
if (!identityContext) { |
|
|
|
identityType = IdentityType.TENANT |
|
|
|
} else { |
|
|
|
id = tenantId |
|
|
|
type = IdentityType.TENANT |
|
|
|
identityType = identityContext.type |
|
|
|
} |
|
|
|
|
|
|
|
if (user && isInstallation(user)) { |
|
|
|
type = IdentityType.INSTALLATION |
|
|
|
} |
|
|
|
if (identityType === 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 { |
|
|
|
id, |
|
|
|
tenantId, |
|
|
|
type, |
|
|
|
} |
|
|
|
} |
|
|
|
return { |
|
|
|
id: formatDistinctId(tenantId, identityType), |
|
|
|
type: identityType, |
|
|
|
installationId, |
|
|
|
tenantId, |
|
|
|
} |
|
|
|
} else if (identityType === IdentityType.USER) { |
|
|
|
const userContext = identityContext as UserContext |
|
|
|
const tenantId = await getCurrentTenantId() |
|
|
|
let installationId: string | undefined |
|
|
|
|
|
|
|
const getGlobalId = async (tenantId: string): Promise<string> => { |
|
|
|
const db = context.getGlobalDB() |
|
|
|
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, { |
|
|
|
type: Configs.SETTINGS, |
|
|
|
}) |
|
|
|
// self host account users won't have installation
|
|
|
|
if (!userContext.account || !isSelfHostAccount(userContext.account)) { |
|
|
|
installationId = await getInstallationId() |
|
|
|
} |
|
|
|
|
|
|
|
let globalId: string |
|
|
|
if (config.config.globalId) { |
|
|
|
return config.config.globalId |
|
|
|
return { |
|
|
|
id: userContext._id, |
|
|
|
type: identityType, |
|
|
|
installationId, |
|
|
|
tenantId, |
|
|
|
} |
|
|
|
} else { |
|
|
|
globalId = `${hashing.newid()}_${tenantId}` |
|
|
|
config.config.globalId = globalId |
|
|
|
await db.put(config) |
|
|
|
return globalId |
|
|
|
throw new Error("Unknown identity type") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
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 identity: InstallationIdentity = { |
|
|
|
const group: InstallationGroup = { |
|
|
|
id, |
|
|
|
tenantId, |
|
|
|
type, |
|
|
|
version, |
|
|
|
hosting, |
|
|
|
version, |
|
|
|
} |
|
|
|
await identify(identity, timestamp) |
|
|
|
|
|
|
|
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 identifyTenant = async ( |
|
|
|
export const identifyTenantGroup = async ( |
|
|
|
tenantId: string, |
|
|
|
account: CloudAccount | undefined, |
|
|
|
account: Account | undefined, |
|
|
|
timestamp?: string | number |
|
|
|
) => { |
|
|
|
const globalTenantId = await getGlobalTenantId(tenantId) |
|
|
|
const id = globalTenantId |
|
|
|
const hosting = getHostingFromEnv() |
|
|
|
): Promise<void> => { |
|
|
|
const id = await getGlobalTenantId(tenantId) |
|
|
|
const type = IdentityType.TENANT |
|
|
|
const profession = account?.profession |
|
|
|
const companySize = account?.size |
|
|
|
|
|
|
|
const identity: TenantIdentity = { |
|
|
|
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, |
|
|
|
tenantId: globalTenantId, |
|
|
|
hosting, |
|
|
|
type, |
|
|
|
hosting, |
|
|
|
profession, |
|
|
|
companySize, |
|
|
|
} |
|
|
|
await identify(identity, timestamp) |
|
|
|
|
|
|
|
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 ( |
|
|
|
@ -131,30 +144,26 @@ export const identifyUser = async ( |
|
|
|
timestamp?: string | number |
|
|
|
) => { |
|
|
|
const id = user._id as string |
|
|
|
const tenantId = user.tenantId |
|
|
|
const hosting = env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD |
|
|
|
const tenantId = await getGlobalTenantId(user.tenantId) |
|
|
|
const type = IdentityType.USER |
|
|
|
let builder = user.builder?.global |
|
|
|
let admin = user.admin?.global |
|
|
|
let builder = user.builder?.global || false |
|
|
|
let admin = user.admin?.global || false |
|
|
|
let providerType = user.providerType |
|
|
|
const accountHolder = account?.budibaseUserId === user._id |
|
|
|
const accountHolder = account?.budibaseUserId === user._id || false |
|
|
|
const verified = |
|
|
|
account && account?.budibaseUserId === user._id ? account.verified : false |
|
|
|
const profession = account?.profession |
|
|
|
const companySize = account?.size |
|
|
|
const installationId = await getInstallationId() |
|
|
|
|
|
|
|
const identity: BudibaseIdentity = { |
|
|
|
const identity: UserIdentity = { |
|
|
|
id, |
|
|
|
tenantId, |
|
|
|
hosting, |
|
|
|
type, |
|
|
|
installationId, |
|
|
|
tenantId, |
|
|
|
verified, |
|
|
|
accountHolder, |
|
|
|
providerType, |
|
|
|
builder, |
|
|
|
admin, |
|
|
|
providerType, |
|
|
|
accountHolder, |
|
|
|
verified, |
|
|
|
profession, |
|
|
|
companySize, |
|
|
|
} |
|
|
|
|
|
|
|
await identify(identity, timestamp) |
|
|
|
@ -163,12 +172,9 @@ export const identifyUser = async ( |
|
|
|
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)) { |
|
|
|
@ -180,13 +186,10 @@ export const identifyAccount = async (account: Account) => { |
|
|
|
|
|
|
|
const identity: UserIdentity = { |
|
|
|
id, |
|
|
|
tenantId, |
|
|
|
hosting, |
|
|
|
type, |
|
|
|
tenantId, |
|
|
|
providerType, |
|
|
|
verified, |
|
|
|
profession, |
|
|
|
companySize, |
|
|
|
accountHolder, |
|
|
|
} |
|
|
|
|
|
|
|
@ -199,3 +202,63 @@ export const identify = async ( |
|
|
|
) => { |
|
|
|
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 db = context.getGlobalDB() |
|
|
|
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, { |
|
|
|
type: Configs.SETTINGS, |
|
|
|
}) |
|
|
|
|
|
|
|
let globalId: string |
|
|
|
if (config.config.globalId) { |
|
|
|
return config.config.globalId |
|
|
|
} else { |
|
|
|
globalId = `${hashing.newid()}_${tenantId}` |
|
|
|
config.config.globalId = globalId |
|
|
|
await db.put(config) |
|
|
|
return globalId |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const isAccountPortal = () => { |
|
|
|
return env.SERVICE === "account-portal" |
|
|
|
} |
|
|
|
|
|
|
|
const formatDistinctId = (id: string, type: IdentityType) => { |
|
|
|
if (type === IdentityType.INSTALLATION || type === IdentityType.TENANT) { |
|
|
|
return `$${type}_${id}` |
|
|
|
} else { |
|
|
|
return id |
|
|
|
} |
|
|
|
} |
|
|
|
|