forked from tsai/budibase
41 changed files with 861 additions and 340 deletions
@ -0,0 +1,11 @@ |
|||||
|
const { BudibaseError } = require("./base") |
||||
|
|
||||
|
class GenericError extends BudibaseError { |
||||
|
constructor(message, code, type) { |
||||
|
super(message, code, type ? type : "generic") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
GenericError, |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
const { GenericError } = require("./generic") |
||||
|
|
||||
|
class HTTPError extends GenericError { |
||||
|
constructor(message, httpStatus, code, type) { |
||||
|
super(message, code ? code : "http", type) |
||||
|
this.status = httpStatus |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
HTTPError, |
||||
|
} |
||||
@ -1,19 +1,29 @@ |
|||||
const events = require("../events") |
const events = require("../events") |
||||
const { Events } = require("../constants") |
const { Events } = require("../constants") |
||||
|
|
||||
exports.created = () => { |
/* eslint-disable */ |
||||
|
|
||||
|
exports.created = role => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.ROLE_CREATED, properties) |
events.processEvent(Events.ROLE_CREATED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
exports.updated = role => { |
||||
exports.deleted = () => { |
const properties = {} |
||||
|
events.processEvent(Events.ROLE_UPDATED, properties) |
||||
|
} |
||||
|
|
||||
|
exports.deleted = role => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.ROLE_DELETED, properties) |
events.processEvent(Events.ROLE_DELETED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
exports.assigned = (user, role) => { |
||||
exports.assigned = () => { |
|
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.ROLE_ASSIGNED, properties) |
events.processEvent(Events.ROLE_ASSIGNED, properties) |
||||
} |
} |
||||
|
|
||||
|
exports.unassigned = (user, role) => { |
||||
|
const properties = {} |
||||
|
events.processEvent(Events.ROLE_UNASSIGNED, properties) |
||||
|
} |
||||
|
|||||
@ -1,85 +1,87 @@ |
|||||
const events = require("../events") |
const events = require("../events") |
||||
const { Events } = require("../constants") |
const { Events } = require("../constants") |
||||
|
|
||||
// TODO
|
/* eslint-disable */ |
||||
exports.created = () => { |
|
||||
|
exports.created = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_CREATED, properties) |
events.processEvent(Events.USER_CREATED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
exports.updated = user => { |
||||
exports.updated = () => { |
|
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_UPDATED, properties) |
events.processEvent(Events.USER_UPDATED, properties) |
||||
} |
} |
||||
|
|
||||
exports.deleted = () => { |
exports.deleted = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_DELETED, properties) |
events.processEvent(Events.USER_DELETED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
// TODO
|
||||
exports.passwordForceReset = () => { |
exports.passwordForceReset = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_PASSWORD_FORCE_RESET, properties) |
events.processEvent(Events.USER_PASSWORD_FORCE_RESET, properties) |
||||
} |
} |
||||
|
|
||||
// PERMISSIONS
|
// PERMISSIONS
|
||||
|
|
||||
// TODO
|
exports.permissionAdminAssigned = user => { |
||||
exports.permissionAdminAssigned = () => { |
|
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_PERMISSION_ADMIN_ASSIGNED, properties) |
events.processEvent(Events.USER_PERMISSION_ADMIN_ASSIGNED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
exports.permissionAdminRemoved = user => { |
||||
exports.permissionAdminRemoved = () => { |
|
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_PERMISSION_ADMIN_REMOVED, properties) |
events.processEvent(Events.USER_PERMISSION_ADMIN_REMOVED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
exports.permissionBuilderAssigned = user => { |
||||
exports.permissionBuilderAssigned = () => { |
|
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_PERMISSION_BUILDER_ASSIGNED, properties) |
events.processEvent(Events.USER_PERMISSION_BUILDER_ASSIGNED, properties) |
||||
} |
} |
||||
|
|
||||
// TODO
|
exports.permissionBuilderRemoved = user => { |
||||
exports.permissionBuilderRemoved = () => { |
|
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_PERMISSION_BUILDER_REMOVED, properties) |
events.processEvent(Events.USER_PERMISSION_BUILDER_REMOVED, properties) |
||||
} |
} |
||||
|
|
||||
// INVITE
|
// INVITE
|
||||
|
|
||||
exports.invited = () => { |
// TODO
|
||||
|
exports.invited = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_INVITED, properties) |
events.processEvent(Events.USER_INVITED, properties) |
||||
} |
} |
||||
|
|
||||
exports.inviteAccepted = () => { |
// TODO
|
||||
|
exports.inviteAccepted = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_INVITED_ACCEPTED, properties) |
events.processEvent(Events.USER_INVITED_ACCEPTED, properties) |
||||
} |
} |
||||
|
|
||||
// SELF
|
// SELF
|
||||
|
|
||||
exports.selfUpdated = () => { |
// TODO
|
||||
|
exports.selfUpdated = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_SELF_UPDATED, properties) |
events.processEvent(Events.USER_SELF_UPDATED, properties) |
||||
} |
} |
||||
|
|
||||
exports.selfPasswordUpdated = () => { |
// TODO
|
||||
|
exports.selfPasswordUpdated = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_SELF_PASSWORD_UPDATED, properties) |
events.processEvent(Events.USER_SELF_PASSWORD_UPDATED, properties) |
||||
} |
} |
||||
|
|
||||
exports.passwordResetRequested = () => { |
// TODO
|
||||
|
exports.passwordResetRequested = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_PASSWORD_RESET_REQUESTED, properties) |
events.processEvent(Events.USER_PASSWORD_RESET_REQUESTED, properties) |
||||
} |
} |
||||
|
|
||||
exports.passwordReset = () => { |
// TODO
|
||||
|
exports.passwordReset = user => { |
||||
const properties = {} |
const properties = {} |
||||
events.processEvent(Events.USER_PASSWORD_RESET, properties) |
events.processEvent(Events.USER_PASSWORD_RESET, properties) |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,19 @@ |
|||||
|
const { ViewNames } = require("./db/utils") |
||||
|
const { queryGlobalView } = require("./db/views") |
||||
|
|
||||
|
/** |
||||
|
* Given an email address this will use a view to search through |
||||
|
* all the users to find one with this email address. |
||||
|
* @param {string} email the email to lookup the user by. |
||||
|
* @return {Promise<object|null>} |
||||
|
*/ |
||||
|
exports.getGlobalUserByEmail = async email => { |
||||
|
if (email == null) { |
||||
|
throw "Must supply an email address to view" |
||||
|
} |
||||
|
|
||||
|
return queryGlobalView(ViewNames.USER_BY_EMAIL, { |
||||
|
key: email.toLowerCase(), |
||||
|
include_docs: true, |
||||
|
}) |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
export * as users from "./users" |
||||
@ -1,8 +1,8 @@ |
|||||
const joiValidator = require("../../middleware/joi-validator") |
import joiValidator from "../../../middleware/joi-validator" |
||||
const Joi = require("joi") |
import Joi from "joi" |
||||
|
|
||||
exports.buildUserSaveValidation = (isSelf = false) => { |
export const buildUserSaveValidation = (isSelf = false) => { |
||||
let schema = { |
let schema: any = { |
||||
email: Joi.string().allow(null, ""), |
email: Joi.string().allow(null, ""), |
||||
password: Joi.string().allow(null, ""), |
password: Joi.string().allow(null, ""), |
||||
forceResetPassword: Joi.boolean().optional(), |
forceResetPassword: Joi.boolean().optional(), |
||||
@ -1,33 +0,0 @@ |
|||||
const { getGlobalDB } = require("@budibase/backend-core/tenancy") |
|
||||
const { getGlobalUserParams } = require("@budibase/backend-core/db") |
|
||||
|
|
||||
/** |
|
||||
* Retrieves all users from the current tenancy. |
|
||||
*/ |
|
||||
exports.allUsers = async () => { |
|
||||
const db = getGlobalDB() |
|
||||
const response = await db.allDocs( |
|
||||
getGlobalUserParams(null, { |
|
||||
include_docs: true, |
|
||||
}) |
|
||||
) |
|
||||
return response.rows.map(row => row.doc) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Gets a user by ID from the global database, based on the current tenancy. |
|
||||
*/ |
|
||||
exports.getUser = async userId => { |
|
||||
const db = getGlobalDB() |
|
||||
let user |
|
||||
try { |
|
||||
user = await db.get(userId) |
|
||||
} catch (err) { |
|
||||
// no user found, just return nothing
|
|
||||
user = {} |
|
||||
} |
|
||||
if (user) { |
|
||||
delete user.password |
|
||||
} |
|
||||
return user |
|
||||
} |
|
||||
@ -0,0 +1 @@ |
|||||
|
export * as users from "./users" |
||||
@ -0,0 +1,136 @@ |
|||||
|
const { events } = require("@budibase/backend-core") |
||||
|
|
||||
|
export const handleDeleteEvents = (user: any) => { |
||||
|
events.user.deleted(user) |
||||
|
|
||||
|
if (isBuilder(user)) { |
||||
|
events.user.permissionBuilderRemoved(user) |
||||
|
} |
||||
|
|
||||
|
if (isAdmin(user)) { |
||||
|
events.user.permissionAdminRemoved(user) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const assignAppRoleEvents = (roles: any, existingRoles: any) => { |
||||
|
for (const [appId, role] of Object.entries(roles)) { |
||||
|
// app role in existing is not same as new
|
||||
|
if (!existingRoles || existingRoles[appId] !== role) { |
||||
|
events.role.assigned(role) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const unassignAppRoleEvents = (roles: any, existingRoles: any) => { |
||||
|
if (!existingRoles) { |
||||
|
return |
||||
|
} |
||||
|
for (const [appId, role] of Object.entries(existingRoles)) { |
||||
|
// app role in new is not same as existing
|
||||
|
if (!roles || roles[appId] !== role) { |
||||
|
events.role.unassigned(role) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const handleAppRoleEvents = (user: any, existingUser: any) => { |
||||
|
const roles = user.roles |
||||
|
const existingRoles = existingUser?.roles |
||||
|
|
||||
|
assignAppRoleEvents(roles, existingRoles) |
||||
|
unassignAppRoleEvents(roles, existingRoles) |
||||
|
} |
||||
|
|
||||
|
export const handleSaveEvents = (user: any, existingUser: any) => { |
||||
|
if (existingUser) { |
||||
|
events.user.updated(user) |
||||
|
|
||||
|
if (isRemovingBuilder(user, existingUser)) { |
||||
|
events.user.permissionBuilderRemoved(user) |
||||
|
} |
||||
|
|
||||
|
if (isRemovingAdmin(user, existingUser)) { |
||||
|
events.user.permissionAdminRemoved(user) |
||||
|
} |
||||
|
} else { |
||||
|
events.user.created(user) |
||||
|
} |
||||
|
|
||||
|
if (isAddingBuilder(user, existingUser)) { |
||||
|
events.user.permissionBuilderAssigned(user) |
||||
|
} |
||||
|
|
||||
|
if (isAddingAdmin(user, existingUser)) { |
||||
|
events.user.permissionAdminAssigned(user) |
||||
|
} |
||||
|
|
||||
|
handleAppRoleEvents(user, existingUser) |
||||
|
} |
||||
|
|
||||
|
const isBuilder = (user: any) => user.builder && user.builder.global |
||||
|
const isAdmin = (user: any) => user.admin && user.admin.global |
||||
|
|
||||
|
export const isAddingBuilder = (user: any, existingUser: any) => { |
||||
|
return isAddingPermission(user, existingUser, isBuilder) |
||||
|
} |
||||
|
|
||||
|
export const isRemovingBuilder = (user: any, existingUser: any) => { |
||||
|
return isRemovingPermission(user, existingUser, isBuilder) |
||||
|
} |
||||
|
|
||||
|
const isAddingAdmin = (user: any, existingUser: any) => { |
||||
|
return isAddingPermission(user, existingUser, isAdmin) |
||||
|
} |
||||
|
|
||||
|
const isRemovingAdmin = (user: any, existingUser: any) => { |
||||
|
return isRemovingPermission(user, existingUser, isAdmin) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if a permission is being added to a new or existing user. |
||||
|
*/ |
||||
|
const isAddingPermission = ( |
||||
|
user: any, |
||||
|
existingUser: any, |
||||
|
hasPermission: any |
||||
|
) => { |
||||
|
// new user doesn't have the permission
|
||||
|
if (!hasPermission(user)) { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// existing user has the permission
|
||||
|
if (existingUser && hasPermission(existingUser)) { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// permission is being added
|
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if a permission is being removed from an existing user. |
||||
|
*/ |
||||
|
const isRemovingPermission = ( |
||||
|
user: any, |
||||
|
existingUser: any, |
||||
|
hasPermission: any |
||||
|
) => { |
||||
|
// new user has the permission
|
||||
|
if (hasPermission(user)) { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// no existing user or existing user doesn't have the permission
|
||||
|
if (!existingUser) { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// existing user doesn't have the permission
|
||||
|
if (!hasPermission(existingUser)) { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// permission is being removed
|
||||
|
return true |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
export * from "./users" |
||||
@ -0,0 +1,180 @@ |
|||||
|
import env from "../../environment" |
||||
|
import { quotas } from "@budibase/pro" |
||||
|
import * as apps from "../../utilities/appService" |
||||
|
const { events } = require("@budibase/backend-core") |
||||
|
import * as eventHelpers from "./events" |
||||
|
|
||||
|
const { |
||||
|
tenancy, |
||||
|
accounts, |
||||
|
utils, |
||||
|
db: dbUtils, |
||||
|
constants, |
||||
|
cache, |
||||
|
users: usersCore, |
||||
|
deprovisioning, |
||||
|
sessions, |
||||
|
HTTPError, |
||||
|
} = require("@budibase/backend-core") |
||||
|
|
||||
|
/** |
||||
|
* Retrieves all users from the current tenancy. |
||||
|
*/ |
||||
|
export const allUsers = async () => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
const response = await db.allDocs( |
||||
|
dbUtils.getGlobalUserParams(null, { |
||||
|
include_docs: true, |
||||
|
}) |
||||
|
) |
||||
|
return response.rows.map((row: any) => row.doc) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets a user by ID from the global database, based on the current tenancy. |
||||
|
*/ |
||||
|
export const getUser = async (userId: string) => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
let user |
||||
|
try { |
||||
|
user = await db.get(userId) |
||||
|
} catch (err: any) { |
||||
|
// no user found, just return nothing
|
||||
|
if (err.status === 404) { |
||||
|
return {} |
||||
|
} |
||||
|
throw err |
||||
|
} |
||||
|
if (user) { |
||||
|
delete user.password |
||||
|
} |
||||
|
return user |
||||
|
} |
||||
|
|
||||
|
export const save = async ( |
||||
|
user: any, |
||||
|
hashPassword = true, |
||||
|
requirePassword = true |
||||
|
) => { |
||||
|
const tenantId = tenancy.getTenantId() |
||||
|
|
||||
|
// specify the tenancy incase we're making a new admin user (public)
|
||||
|
const db = tenancy.getGlobalDB(tenantId) |
||||
|
let { email, password, _id } = user |
||||
|
// make sure another user isn't using the same email
|
||||
|
let dbUser |
||||
|
if (email) { |
||||
|
// check budibase users inside the tenant
|
||||
|
dbUser = await usersCore.getGlobalUserByEmail(email) |
||||
|
if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) { |
||||
|
throw `Email address ${email} already in use.` |
||||
|
} |
||||
|
|
||||
|
// check budibase users in other tenants
|
||||
|
if (env.MULTI_TENANCY) { |
||||
|
const tenantUser = await tenancy.getTenantUser(email) |
||||
|
if (tenantUser != null && tenantUser.tenantId !== tenantId) { |
||||
|
throw `Email address ${email} already in use.` |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// check root account users in account portal
|
||||
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { |
||||
|
const account = await accounts.getAccount(email) |
||||
|
if (account && account.verified && account.tenantId !== tenantId) { |
||||
|
throw `Email address ${email} already in use.` |
||||
|
} |
||||
|
} |
||||
|
} else if (_id) { |
||||
|
dbUser = await db.get(_id) |
||||
|
} |
||||
|
|
||||
|
// get the password, make sure one is defined
|
||||
|
let hashedPassword |
||||
|
if (password) { |
||||
|
hashedPassword = hashPassword ? await utils.hash(password) : password |
||||
|
} else if (dbUser) { |
||||
|
hashedPassword = dbUser.password |
||||
|
} else if (requirePassword) { |
||||
|
throw "Password must be specified." |
||||
|
} |
||||
|
|
||||
|
if (!_id) { |
||||
|
_id = dbUtils.generateGlobalUserID(email) |
||||
|
} |
||||
|
|
||||
|
user = { |
||||
|
createdAt: Date.now(), |
||||
|
...dbUser, |
||||
|
...user, |
||||
|
_id, |
||||
|
password: hashedPassword, |
||||
|
tenantId, |
||||
|
} |
||||
|
// make sure the roles object is always present
|
||||
|
if (!user.roles) { |
||||
|
user.roles = {} |
||||
|
} |
||||
|
// add the active status to a user if its not provided
|
||||
|
if (user.status == null) { |
||||
|
user.status = constants.UserStatus.ACTIVE |
||||
|
} |
||||
|
try { |
||||
|
// save the user to db
|
||||
|
let response |
||||
|
const putUserFn = () => { |
||||
|
return db.put(user) |
||||
|
} |
||||
|
if (await eventHelpers.isAddingBuilder(user, dbUser)) { |
||||
|
response = await quotas.addDeveloper(putUserFn) |
||||
|
} else { |
||||
|
response = await putUserFn() |
||||
|
} |
||||
|
|
||||
|
eventHelpers.handleSaveEvents(user, dbUser) |
||||
|
|
||||
|
await tenancy.tryAddTenant(tenantId, _id, email) |
||||
|
await cache.user.invalidateUser(response.id) |
||||
|
// let server know to sync user
|
||||
|
await apps.syncUserInApps(user._id) |
||||
|
|
||||
|
return { |
||||
|
_id: response.id, |
||||
|
_rev: response.rev, |
||||
|
email, |
||||
|
} |
||||
|
} catch (err: any) { |
||||
|
if (err.status === 409) { |
||||
|
throw "User exists already" |
||||
|
} else { |
||||
|
throw err |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const destroy = async (id: string, currentUser: any) => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
const dbUser = await db.get(id) |
||||
|
|
||||
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { |
||||
|
// root account holder can't be deleted from inside budibase
|
||||
|
const email = dbUser.email |
||||
|
const account = await accounts.getAccount(email) |
||||
|
if (account) { |
||||
|
if (email === currentUser.email) { |
||||
|
throw new HTTPError('Please visit "Account" to delete this user', 400) |
||||
|
} else { |
||||
|
throw new HTTPError("Account holder cannot be deleted", 400) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await deprovisioning.removeUserFromInfoDB(dbUser) |
||||
|
await db.remove(dbUser._id, dbUser._rev) |
||||
|
eventHelpers.handleDeleteEvents(dbUser) |
||||
|
await quotas.removeUser(dbUser) |
||||
|
await cache.user.invalidateUser(dbUser._id) |
||||
|
await sessions.invalidateSessions(dbUser._id) |
||||
|
// let server know to sync user
|
||||
|
await apps.syncUserInApps(dbUser._id) |
||||
|
} |
||||
@ -1,10 +1,12 @@ |
|||||
const configs = require("./configs") |
const configs = require("./configs") |
||||
|
const users = require("./users") |
||||
|
|
||||
const TENANT_ID = "default" |
const TENANT_ID = "default" |
||||
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306" |
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306" |
||||
|
|
||||
module.exports = { |
module.exports = { |
||||
configs, |
configs, |
||||
|
users, |
||||
TENANT_ID, |
TENANT_ID, |
||||
CSRF_TOKEN, |
CSRF_TOKEN, |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,28 @@ |
|||||
|
export const email = "test@test.com" |
||||
|
|
||||
|
export const user = (userProps: any) => { |
||||
|
return { |
||||
|
email: "test@test.com", |
||||
|
password: "test", |
||||
|
roles: {}, |
||||
|
...userProps, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const adminUser = (userProps: any) => { |
||||
|
return { |
||||
|
...user(userProps), |
||||
|
admin: { |
||||
|
global: true, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const builderUser = (userProps: any) => { |
||||
|
return { |
||||
|
...user(userProps), |
||||
|
builder: { |
||||
|
global: true, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue