Browse Source

Adding the sync call from the worker for creation, updating and deletion of users. Making sure that production and development apps are always up to date with user metadata.

pull/4089/head
mike12345567 5 years ago
parent
commit
94ee13ffc4
  1. 1
      hosting/docker-compose.yaml
  2. 2
      hosting/kubernetes/budibase/templates/worker-service-deployment.yaml
  3. 64
      packages/server/src/api/controllers/user.js
  4. 3
      packages/server/src/middleware/authorized.js
  5. 1
      packages/server/src/utilities/global.js
  6. 10
      packages/server/src/utilities/index.js
  7. 1
      packages/worker/scripts/dev/manage.js
  8. 8
      packages/worker/src/api/controllers/global/users.js
  9. 8
      packages/worker/src/environment.js
  10. 23
      packages/worker/src/utilities/appService.js

1
hosting/docker-compose.yaml

@ -45,6 +45,7 @@ services:
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
MINIO_URL: http://minio-service:9000
APPS_URL: http://app-service:4002
COUCH_DB_USERNAME: ${COUCH_DB_USER}
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984

2
hosting/kubernetes/budibase/templates/worker-service-deployment.yaml

@ -113,6 +113,8 @@ spec:
value: {{ .Values.globals.smtp.port | quote }}
- name: SMTP_FROM_ADDRESS
value: {{ .Values.globals.smtp.from | quote }}
- name: APPS_URL
value: http://app-service:{{ .Values.services.apps.port }}
image: budibase/worker
imagePullPolicy: Always
name: bbworker

64
packages/server/src/api/controllers/user.js

@ -8,8 +8,13 @@ const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global")
const { getFullUser } = require("../../utilities/users")
const { isEqual } = require("lodash")
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
const { getDevelopmentAppID } = require("@budibase/auth/db")
const {
getDevelopmentAppID,
getAllApps,
isDevAppID,
} = require("@budibase/auth/db")
const { doesDatabaseExist } = require("../../utilities")
const { UserStatus } = require("@budibase/auth/constants")
async function rawMetadata(db) {
return (
@ -21,7 +26,7 @@ async function rawMetadata(db) {
).rows.map(row => row.doc)
}
async function combineMetadataAndUser(user, metadata) {
function combineMetadataAndUser(user, metadata) {
// skip users with no access
if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) {
return null
@ -67,22 +72,61 @@ exports.syncGlobalUsers = async appId => {
}
exports.syncUser = async function (ctx) {
const user = await getRawGlobalUser(ctx.params.id)
let deleting = false,
user
const userId = ctx.params.id
try {
user = await getRawGlobalUser(userId)
} catch (err) {
user = {}
deleting = true
}
const roles = user.roles
// remove props which aren't useful to metadata
delete user.password
delete user.forceResetPassword
delete user.roles
for (let [prodAppId, roleId] of Object.entries(roles)) {
if (roleId === BUILTIN_ROLE_IDS.PUBLIC) {
continue
}
// run through all production appIDs in the users roles
let prodAppIds
// if they are a builder then get all production app IDs
if ((user.builder && user.builder.global) || deleting) {
prodAppIds = (await getAllApps(CouchDB, { idsOnly: true })).filter(
id => !isDevAppID(id)
)
} else {
prodAppIds = Object.entries(roles)
.filter(entry => entry[1] !== BUILTIN_ROLE_IDS.PUBLIC)
.map(([appId]) => appId)
}
for (let prodAppId of prodAppIds) {
const devAppId = getDevelopmentAppID(prodAppId)
for (let appId of [prodAppId, devAppId]) {
if (!(await doesDatabaseExist(appId))) {
continue
}
const db = new CouchDB(appId)
const userId = generateUserMetadataID(user._id)
const metadata = await db.get(userId)
const combined = combineMetadataAndUser(user, metadata)
const metadataId = generateUserMetadataID(userId)
let metadata
try {
metadata = await db.get(metadataId)
} catch (err) {
if (deleting) {
continue
}
metadata = {
tableId: InternalTables.USER_METADATA,
}
}
let combined
if (deleting) {
combined = {
...metadata,
status: UserStatus.INACTIVE,
metadata: BUILTIN_ROLE_IDS.PUBLIC,
}
} else {
combined = combineMetadataAndUser(user, metadata)
}
await db.put(combined)
}
}

3
packages/server/src/middleware/authorized.js

@ -18,7 +18,8 @@ module.exports =
(permType, permLevel = null) =>
async (ctx, next) => {
// webhooks don't need authentication, each webhook unique
if (WEBHOOK_ENDPOINTS.test(ctx.request.url)) {
// also internal requests (between services) don't need authorized
if (WEBHOOK_ENDPOINTS.test(ctx.request.url) || ctx.internal) {
return next()
}

1
packages/server/src/utilities/global.js

@ -77,6 +77,7 @@ exports.getGlobalUsers = async (appId = null, users = null) => {
.filter(user => user != null)
.map(user => {
delete user.password
delete user.forceResetPassword
return user
})
if (!appId) {

10
packages/server/src/utilities/index.js

@ -136,7 +136,11 @@ exports.stringToReadStream = string => {
}
exports.doesDatabaseExist = async dbName => {
const db = new CouchDB(dbName, { skip_setup: true })
const info = await db.info()
return info && !info.error
try {
const db = new CouchDB(dbName, { skip_setup: true })
const info = await db.info()
return info && !info.error
} catch (err) {
return false
}
}

1
packages/worker/scripts/dev/manage.js

@ -25,6 +25,7 @@ async function init() {
ACCOUNT_PORTAL_URL: "http://localhost:10001",
ACCOUNT_PORTAL_API_KEY: "budibase",
PLATFORM_URL: "http://localhost:10000",
APPS_URL: "http://localhost:4001",
}
let envFile = ""
Object.keys(envFileJson).forEach(key => {

8
packages/worker/src/api/controllers/global/users.js

@ -19,6 +19,7 @@ const {
} = require("@budibase/auth/tenancy")
const { removeUserFromInfoDB } = require("@budibase/auth/deprovision")
const env = require("../../../environment")
const { syncUserInApps } = require("../../../utilities/appService")
async function allUsers() {
const db = getGlobalDB()
@ -32,7 +33,10 @@ async function allUsers() {
exports.save = async ctx => {
try {
ctx.body = await saveUser(ctx.request.body, getTenantId())
const user = await saveUser(ctx.request.body, getTenantId())
// let server know to sync user
await syncUserInApps(user._id)
ctx.body = user
} catch (err) {
ctx.throw(err.status || 400, err)
}
@ -129,6 +133,8 @@ exports.destroy = async ctx => {
await db.remove(dbUser._id, dbUser._rev)
await userCache.invalidateUser(dbUser._id)
await invalidateSessions(dbUser._id)
// let server know to sync user
await syncUserInApps(dbUser._id)
ctx.body = {
message: `User ${ctx.params.id} deleted.`,
}

8
packages/worker/src/environment.js

@ -42,6 +42,7 @@ module.exports = {
SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS,
PLATFORM_URL: process.env.PLATFORM_URL,
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
APPS_URL: process.env.APPS_URL,
_set(key, value) {
process.env[key] = value
module.exports[key] = value
@ -53,6 +54,13 @@ module.exports = {
},
}
// if some var haven't been set, define them
if (!module.exports.APPS_URL) {
module.exports.APPS_URL = isDev()
? "http://localhost:4001"
: "http://app-service:4002"
}
// clean up any environment variable edge cases
for (let [key, value] of Object.entries(module.exports)) {
// handle the edge case of "0" to disable an environment variable

23
packages/worker/src/utilities/appService.js

@ -0,0 +1,23 @@
const fetch = require("node-fetch")
const { Headers } = require("@budibase/auth/constants")
const { getTenantId, isTenantIdSet } = require("@budibase/auth/tenancy")
const { checkSlashesInUrl } = require("../utilities")
const env = require("../environment")
exports.syncUserInApps = async userId => {
const request = { headers: {} }
request.headers[Headers.API_KEY] = env.INTERNAL_API_KEY
if (isTenantIdSet()) {
request.headers[Headers.TENANT_ID] = getTenantId()
}
request.headers["Content-Type"] = "application/json"
request.body = JSON.stringify({})
request.method = "POST"
const response = await fetch(
checkSlashesInUrl(env.APPS_URL + `/api/users/sync/${userId}`),
request
)
if (response.status !== 200) {
throw "Unable to sync user."
}
}
Loading…
Cancel
Save