Identity updates
This commit is contained in:
parent
ff48aaec6e
commit
b3f04e7e8f
|
@ -76,17 +76,10 @@ 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, user: undefined }) {
|
||||
// preserve the user
|
||||
if (user) {
|
||||
exports.setUser(user)
|
||||
}
|
||||
async function internal(opts = { existing: false }) {
|
||||
// set the tenant id
|
||||
if (!opts.existing) {
|
||||
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
||||
if (env.USE_COUCH) {
|
||||
exports.setGlobalDB(tenantId)
|
||||
}
|
||||
exports.updateTenantId(tenantId)
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -102,15 +95,14 @@ 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, user })
|
||||
return internal({ existing: true })
|
||||
} else {
|
||||
return cls.run(async () => {
|
||||
cls.setOnContext(ContextKeys.IN_USE, 1)
|
||||
return internal({ existing: false, user })
|
||||
return internal()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -146,19 +138,19 @@ exports.doInAppContext = (appId, task) => {
|
|||
throw new Error("appId is required")
|
||||
}
|
||||
|
||||
const user = exports.getUser()
|
||||
|
||||
// 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, user: undefined }) {
|
||||
// preserve the user
|
||||
if (user) {
|
||||
exports.setUser(user)
|
||||
}
|
||||
// set the app tenant id
|
||||
if (!opts.existing) {
|
||||
setAppTenantId(appId)
|
||||
}
|
||||
// set the app ID
|
||||
cls.setOnContext(ContextKeys.APP_ID, appId)
|
||||
// preserve the user
|
||||
exports.setUser(user)
|
||||
try {
|
||||
// invoke the task
|
||||
return await task()
|
||||
|
@ -171,29 +163,56 @@ 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, user })
|
||||
return internal({ existing: true })
|
||||
} else {
|
||||
return cls.run(async () => {
|
||||
cls.setOnContext(ContextKeys.IN_USE, 1)
|
||||
return internal({ existing: false, user })
|
||||
return internal()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.doInUserContext = (user, task) => {
|
||||
return cls.run(() => {
|
||||
let tenantId = user.tenantId
|
||||
if (!tenantId) {
|
||||
tenantId = exports.getTenantId()
|
||||
if (!user) {
|
||||
throw new Error("user is required")
|
||||
}
|
||||
|
||||
async function internal(opts = { existing: false }) {
|
||||
if (!opts.existing) {
|
||||
cls.setOnContext(ContextKeys.USER, user)
|
||||
// set the tenant so that doInTenant will preserve user
|
||||
if (user.tenantId) {
|
||||
exports.updateTenantId(user.tenantId)
|
||||
}
|
||||
}
|
||||
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
||||
exports.setUser(user)
|
||||
return task()
|
||||
})
|
||||
|
||||
try {
|
||||
// invoke the task
|
||||
return await task()
|
||||
} finally {
|
||||
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||
if (!using || using <= 1) {
|
||||
exports.setUser(null)
|
||||
} else {
|
||||
cls.setOnContext(using - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const existing = cls.getFromContext(ContextKeys.USER)
|
||||
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||
if (using && existing && existing._id === user._id) {
|
||||
cls.setOnContext(ContextKeys.IN_USE, using + 1)
|
||||
return internal({ existing: true })
|
||||
} else {
|
||||
return cls.run(async () => {
|
||||
cls.setOnContext(ContextKeys.IN_USE, 1)
|
||||
return internal({ existing: false })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.setUser = user => {
|
||||
|
@ -202,8 +221,7 @@ exports.setUser = user => {
|
|||
|
||||
exports.getUser = () => {
|
||||
try {
|
||||
const user = cls.getFromContext(ContextKeys.USER)
|
||||
return user
|
||||
return cls.getFromContext(ContextKeys.USER)
|
||||
} catch (e) {
|
||||
// do nothing - user is not in context
|
||||
}
|
||||
|
@ -211,7 +229,9 @@ exports.getUser = () => {
|
|||
|
||||
exports.updateTenantId = tenantId => {
|
||||
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
||||
exports.setGlobalDB(tenantId)
|
||||
if (env.USE_COUCH) {
|
||||
exports.setGlobalDB(tenantId)
|
||||
}
|
||||
}
|
||||
|
||||
exports.updateAppId = async appId => {
|
||||
|
|
|
@ -4,6 +4,6 @@ import * as identification from "./identification"
|
|||
|
||||
export const publishEvent = async (event: Event, properties: any) => {
|
||||
// in future this should use async events via a distributed queue.
|
||||
const identity = identification.getCurrentIdentity()
|
||||
const identity = await identification.getCurrentIdentity()
|
||||
await processors.processEvent(event, identity, properties)
|
||||
}
|
||||
|
|
|
@ -11,20 +11,25 @@ import {
|
|||
BudibaseIdentity,
|
||||
isCloudAccount,
|
||||
isSSOAccount,
|
||||
TenantIdentity,
|
||||
SettingsConfig,
|
||||
} from "@budibase/types"
|
||||
import { analyticsProcessor } from "./processors"
|
||||
import * as dbUtils from "../db/utils"
|
||||
import { Configs } from "../constants"
|
||||
import * as hashing from "../hashing"
|
||||
|
||||
export const getCurrentIdentity = (): Identity => {
|
||||
export const getCurrentIdentity = async (): Promise<Identity> => {
|
||||
const user: SessionUser | undefined = context.getUser()
|
||||
const tenantId = context.getTenantId()
|
||||
let tenantId = context.getTenantId()
|
||||
let id: string
|
||||
|
||||
if (user) {
|
||||
id = user._id
|
||||
} else if (env.SELF_HOSTED) {
|
||||
id = "installationId" // TODO
|
||||
} else {
|
||||
id = tenantId
|
||||
const global = await getGlobalIdentifiers(tenantId)
|
||||
id = global.id
|
||||
tenantId = global.tenantId
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -33,6 +38,55 @@ export const getCurrentIdentity = (): Identity => {
|
|||
}
|
||||
}
|
||||
|
||||
const getGlobalId = async (): Promise<string> => {
|
||||
const db = context.getGlobalDB()
|
||||
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, {
|
||||
type: Configs.SETTINGS,
|
||||
})
|
||||
if (config.config.globalId) {
|
||||
return config.config.globalId
|
||||
} else {
|
||||
const globalId = `global_${hashing.newid()}`
|
||||
config.config.globalId = globalId
|
||||
await db.put(config)
|
||||
return globalId
|
||||
}
|
||||
}
|
||||
|
||||
const getGlobalIdentifiers = async (
|
||||
tenantId: string
|
||||
): Promise<{ id: string; tenantId: string }> => {
|
||||
if (env.SELF_HOSTED) {
|
||||
const globalId = await getGlobalId()
|
||||
return {
|
||||
id: globalId,
|
||||
tenantId: `${globalId}-${tenantId}`,
|
||||
}
|
||||
} else {
|
||||
// tenant id's in the cloud are already unique
|
||||
return {
|
||||
id: tenantId,
|
||||
tenantId: tenantId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getHostingFromEnv = () => {
|
||||
return env.SELF_HOSTED ? Hosting.SELF : Hosting.CLOUD
|
||||
}
|
||||
|
||||
export const identifyTenant = async (tenantId: string) => {
|
||||
const global = await getGlobalIdentifiers(tenantId)
|
||||
|
||||
const identity: TenantIdentity = {
|
||||
id: global.id,
|
||||
tenantId: global.tenantId,
|
||||
hosting: getHostingFromEnv(),
|
||||
type: IdentityType.TENANT,
|
||||
}
|
||||
await identify(identity)
|
||||
}
|
||||
|
||||
export const identifyUser = async (user: User) => {
|
||||
const id = user._id as string
|
||||
const tenantId = user.tenantId
|
||||
|
@ -40,7 +94,7 @@ export const identifyUser = async (user: User) => {
|
|||
const type = IdentityType.USER
|
||||
let builder = user.builder?.global
|
||||
let admin = user.admin?.global
|
||||
let authType = user.providerType ? user.providerType : "password"
|
||||
let providerType = user.providerType
|
||||
|
||||
const identity: BudibaseIdentity = {
|
||||
id,
|
||||
|
@ -49,7 +103,7 @@ export const identifyUser = async (user: User) => {
|
|||
type,
|
||||
builder,
|
||||
admin,
|
||||
authType,
|
||||
providerType,
|
||||
}
|
||||
|
||||
await identify(identity)
|
||||
|
@ -60,9 +114,7 @@ export const identifyAccount = async (account: Account) => {
|
|||
const tenantId = account.tenantId
|
||||
const hosting = account.hosting
|
||||
let type = IdentityType.ACCOUNT
|
||||
let authType = isSSOAccount(account)
|
||||
? (account.providerType as string)
|
||||
: "password"
|
||||
let providerType = isSSOAccount(account) ? account.providerType : undefined
|
||||
|
||||
if (isCloudAccount(account)) {
|
||||
if (account.budibaseUserId) {
|
||||
|
@ -77,7 +129,7 @@ export const identifyAccount = async (account: Account) => {
|
|||
tenantId,
|
||||
hosting,
|
||||
type,
|
||||
authType,
|
||||
providerType,
|
||||
verified: account.verified,
|
||||
profession: account.profession,
|
||||
companySize: account.size,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { processors } from "./processors"
|
||||
export * from "./publishers"
|
||||
export * as analytics from "./analytics"
|
||||
export * as identification from "./identification"
|
||||
|
||||
export const shutdown = () => {
|
||||
processors.shutdown()
|
||||
|
|
|
@ -79,14 +79,14 @@ exports.authenticateThirdParty = async function (
|
|||
dbUser.forceResetPassword = false
|
||||
|
||||
// create or sync the user
|
||||
let response
|
||||
try {
|
||||
response = await saveUserFn(dbUser, false, false)
|
||||
await saveUserFn(dbUser, false, false)
|
||||
} catch (err) {
|
||||
return authError(done, err)
|
||||
}
|
||||
|
||||
dbUser._rev = response.rev
|
||||
// now that we're sure user exists, load them from the db
|
||||
dbUser = await users.getGlobalUserByEmail(thirdPartyUser.email)
|
||||
|
||||
// authenticate
|
||||
const sessionId = newid()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"extends": "./tsconfig.json",
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist/**/*",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist/**/*",
|
||||
"**/*.spec.js",
|
||||
// "**/*.spec.ts" // don't exclude spec.ts files for editor support
|
||||
]
|
||||
|
|
|
@ -19,6 +19,7 @@ export interface SettingsConfig extends Config {
|
|||
company: string
|
||||
logoUrl: string
|
||||
platformUrl: string
|
||||
globalId?: string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ export interface User extends Document {
|
|||
}
|
||||
providerType?: string
|
||||
tenantId: string
|
||||
email: string
|
||||
password?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export interface UserRoles {
|
||||
|
|
|
@ -11,10 +11,15 @@ export interface Identity {
|
|||
tenantId: string
|
||||
}
|
||||
|
||||
export interface UserIdentity extends Identity {
|
||||
export interface TenantIdentity extends Identity {
|
||||
hosting: Hosting
|
||||
type: IdentityType
|
||||
authType: string
|
||||
}
|
||||
|
||||
export interface UserIdentity extends TenantIdentity {
|
||||
hosting: Hosting
|
||||
type: IdentityType
|
||||
providerType?: string
|
||||
}
|
||||
|
||||
export interface BudibaseIdentity extends UserIdentity {
|
||||
|
|
|
@ -2,13 +2,15 @@ import { EmailTemplatePurpose } from "../../../constants"
|
|||
import { checkInviteCode } from "../../../utilities/redis"
|
||||
import { sendEmail } from "../../../utilities/email"
|
||||
import { users } from "../../../sdk"
|
||||
import { User } from "@budibase/types"
|
||||
import { events } from "@budibase/backend-core"
|
||||
import { getGlobalDB } from "@budibase/backend-core/dist/src/context"
|
||||
|
||||
const {
|
||||
errors,
|
||||
users: usersCore,
|
||||
tenancy,
|
||||
db: dbUtils,
|
||||
events,
|
||||
} = require("@budibase/backend-core")
|
||||
|
||||
export const save = async (ctx: any) => {
|
||||
|
@ -48,10 +50,9 @@ export const adminUser = async (ctx: any) => {
|
|||
ctx.throw(403, "You cannot initialise once a global user has been created.")
|
||||
}
|
||||
|
||||
const user = {
|
||||
const user: User = {
|
||||
email: email,
|
||||
password: password,
|
||||
createdAt: Date.now(),
|
||||
roles: {},
|
||||
builder: {
|
||||
global: true,
|
||||
|
@ -65,6 +66,7 @@ export const adminUser = async (ctx: any) => {
|
|||
ctx.body = await tenancy.doInTenant(tenantId, async () => {
|
||||
return users.save(user, hashPassword, requirePassword)
|
||||
})
|
||||
await events.identification.identifyTenant(tenantId)
|
||||
} catch (err: any) {
|
||||
ctx.throw(err.status || 400, err)
|
||||
}
|
||||
|
@ -132,15 +134,17 @@ export const inviteAccept = async (ctx: any) => {
|
|||
// info is an extension of the user object that was stored by global
|
||||
const { email, info }: any = await checkInviteCode(inviteCode)
|
||||
ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
|
||||
const user = await users.save({
|
||||
const saved = await users.save({
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
email,
|
||||
...info,
|
||||
})
|
||||
const db = getGlobalDB()
|
||||
const user = await db.get(saved._id)
|
||||
await events.user.inviteAccepted(user)
|
||||
return user
|
||||
return saved
|
||||
})
|
||||
} catch (err: any) {
|
||||
if (err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import env from "../../environment"
|
|||
import { quotas } from "@budibase/pro"
|
||||
import * as apps from "../../utilities/appService"
|
||||
import * as eventHelpers from "./events"
|
||||
import { User } from "@budibase/types"
|
||||
|
||||
const {
|
||||
tenancy,
|
||||
|
@ -16,6 +17,8 @@ const {
|
|||
HTTPError,
|
||||
} = require("@budibase/backend-core")
|
||||
|
||||
import { events } from "@budibase/backend-core"
|
||||
|
||||
/**
|
||||
* Retrieves all users from the current tenancy.
|
||||
*/
|
||||
|
@ -51,7 +54,7 @@ export const getUser = async (userId: string) => {
|
|||
}
|
||||
|
||||
export const save = async (
|
||||
user: any,
|
||||
user: User,
|
||||
hashPassword = true,
|
||||
requirePassword = true
|
||||
) => {
|
||||
|
@ -97,7 +100,7 @@ export const save = async (
|
|||
}
|
||||
|
||||
if (!_id) {
|
||||
_id = dbUtils.generateGlobalUserID(email)
|
||||
_id = dbUtils.generateGlobalUserID()
|
||||
}
|
||||
|
||||
user = {
|
||||
|
@ -130,7 +133,7 @@ export const save = async (
|
|||
user._rev = response.rev
|
||||
|
||||
await eventHelpers.handleSaveEvents(user, dbUser)
|
||||
|
||||
await events.identification.identifyUser(user)
|
||||
await tenancy.tryAddTenant(tenantId, _id, email)
|
||||
await cache.user.invalidateUser(response.id)
|
||||
// let server know to sync user
|
||||
|
|
Loading…
Reference in New Issue