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.

This commit is contained in:
mike12345567 2021-11-04 14:53:03 +00:00
parent 3a5e004f36
commit 94ee13ffc4
10 changed files with 106 additions and 15 deletions

View File

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

View File

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

View File

@ -8,8 +8,13 @@ const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global")
const { getFullUser } = require("../../utilities/users") const { getFullUser } = require("../../utilities/users")
const { isEqual } = require("lodash") const { isEqual } = require("lodash")
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") 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 { doesDatabaseExist } = require("../../utilities")
const { UserStatus } = require("@budibase/auth/constants")
async function rawMetadata(db) { async function rawMetadata(db) {
return ( return (
@ -21,7 +26,7 @@ async function rawMetadata(db) {
).rows.map(row => row.doc) ).rows.map(row => row.doc)
} }
async function combineMetadataAndUser(user, metadata) { function combineMetadataAndUser(user, metadata) {
// skip users with no access // skip users with no access
if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) { if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) {
return null return null
@ -67,22 +72,61 @@ exports.syncGlobalUsers = async appId => {
} }
exports.syncUser = async function (ctx) { exports.syncUser = async function (ctx) {
const user = await getRawGlobalUser(ctx.params.id) let deleting = false,
const roles = user.roles user
delete user.roles const userId = ctx.params.id
for (let [prodAppId, roleId] of Object.entries(roles)) { try {
if (roleId === BUILTIN_ROLE_IDS.PUBLIC) { user = await getRawGlobalUser(userId)
continue } 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
// 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) const devAppId = getDevelopmentAppID(prodAppId)
for (let appId of [prodAppId, devAppId]) { for (let appId of [prodAppId, devAppId]) {
if (!(await doesDatabaseExist(appId))) { if (!(await doesDatabaseExist(appId))) {
continue continue
} }
const db = new CouchDB(appId) const db = new CouchDB(appId)
const userId = generateUserMetadataID(user._id) const metadataId = generateUserMetadataID(userId)
const metadata = await db.get(userId) let metadata
const combined = combineMetadataAndUser(user, 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) await db.put(combined)
} }
} }

View File

@ -18,7 +18,8 @@ module.exports =
(permType, permLevel = null) => (permType, permLevel = null) =>
async (ctx, next) => { async (ctx, next) => {
// webhooks don't need authentication, each webhook unique // 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() return next()
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -42,6 +42,7 @@ module.exports = {
SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS, SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS,
PLATFORM_URL: process.env.PLATFORM_URL, PLATFORM_URL: process.env.PLATFORM_URL,
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
APPS_URL: process.env.APPS_URL,
_set(key, value) { _set(key, value) {
process.env[key] = value process.env[key] = value
module.exports[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 // clean up any environment variable edge cases
for (let [key, value] of Object.entries(module.exports)) { for (let [key, value] of Object.entries(module.exports)) {
// handle the edge case of "0" to disable an environment variable // handle the edge case of "0" to disable an environment variable

View File

@ -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."
}
}