@budibase/server 2.5.6-alpha.1 → 2.5.6-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builder/assets/{index.1fe52b59.js → index.80d32454.js} +223 -223
- package/builder/index.html +1 -1
- package/dist/api/controllers/user.js +1 -83
- package/dist/api/routes/user.js +0 -1
- package/dist/events/docUpdates/index.js +17 -0
- package/dist/events/docUpdates/processors.js +18 -0
- package/dist/events/docUpdates/syncUsers.js +49 -0
- package/dist/events/index.js +3 -0
- package/dist/package.json +7 -7
- package/dist/sdk/app/applications/sync.js +117 -23
- package/dist/sdk/users/utils.js +21 -4
- package/dist/startup.js +2 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utilities/global.js +17 -12
- package/package.json +8 -8
- package/src/api/controllers/row/internal.ts +9 -10
- package/src/api/controllers/row/utils.ts +2 -2
- package/src/api/controllers/user.ts +10 -96
- package/src/api/routes/tests/user.spec.js +0 -37
- package/src/api/routes/user.ts +0 -5
- package/src/events/docUpdates/index.ts +1 -0
- package/src/events/docUpdates/processors.ts +14 -0
- package/src/events/docUpdates/syncUsers.ts +35 -0
- package/src/events/index.ts +1 -0
- package/src/sdk/app/applications/sync.ts +129 -22
- package/src/sdk/app/applications/tests/sync.spec.ts +137 -0
- package/src/sdk/users/tests/utils.spec.ts +1 -32
- package/src/sdk/users/utils.ts +23 -5
- package/src/startup.ts +2 -1
- package/src/tests/utilities/TestConfiguration.ts +28 -0
- package/src/utilities/global.ts +21 -16
package/dist/utilities/global.js
CHANGED
|
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.getGlobalUsersFromMetadata = exports.getGlobalUsers = exports.getGlobalUser = exports.getRawGlobalUser = exports.getCachedSelf = exports.updateAppRole = void 0;
|
|
15
|
+
exports.getGlobalUsersFromMetadata = exports.getGlobalUsers = exports.getGlobalUser = exports.getRawGlobalUser = exports.getCachedSelf = exports.processUser = exports.updateAppRole = void 0;
|
|
16
16
|
const utils_1 = require("../db/utils");
|
|
17
17
|
const backend_core_1 = require("@budibase/backend-core");
|
|
18
18
|
const environment_1 = __importDefault(require("../environment"));
|
|
@@ -20,7 +20,7 @@ const pro_1 = require("@budibase/pro");
|
|
|
20
20
|
function updateAppRole(user, { appId } = {}) {
|
|
21
21
|
var _a;
|
|
22
22
|
appId = appId || backend_core_1.context.getAppId();
|
|
23
|
-
if (!user || !user.roles) {
|
|
23
|
+
if (!user || (!user.roles && !user.userGroups)) {
|
|
24
24
|
return user;
|
|
25
25
|
}
|
|
26
26
|
// if in an multi-tenancy environment make sure roles are never updated
|
|
@@ -31,7 +31,7 @@ function updateAppRole(user, { appId } = {}) {
|
|
|
31
31
|
return user;
|
|
32
32
|
}
|
|
33
33
|
// always use the deployed app
|
|
34
|
-
if (appId) {
|
|
34
|
+
if (appId && user.roles) {
|
|
35
35
|
user.roleId = user.roles[backend_core_1.db.getProdAppID(appId)];
|
|
36
36
|
}
|
|
37
37
|
// if a role wasn't found then either set as admin (builder) or public (everyone else)
|
|
@@ -76,6 +76,7 @@ function processUser(user, opts = {}) {
|
|
|
76
76
|
return user;
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
+
exports.processUser = processUser;
|
|
79
80
|
function getCachedSelf(ctx, appId) {
|
|
80
81
|
var _a;
|
|
81
82
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -101,15 +102,13 @@ function getGlobalUser(userId) {
|
|
|
101
102
|
});
|
|
102
103
|
}
|
|
103
104
|
exports.getGlobalUser = getGlobalUser;
|
|
104
|
-
function getGlobalUsers(
|
|
105
|
+
function getGlobalUsers(userIds, opts) {
|
|
105
106
|
return __awaiter(this, void 0, void 0, function* () {
|
|
106
107
|
const appId = backend_core_1.context.getAppId();
|
|
107
108
|
const db = backend_core_1.tenancy.getGlobalDB();
|
|
108
|
-
const allGroups = yield pro_1.groups.fetch();
|
|
109
109
|
let globalUsers;
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
globalUsers = (yield db.allDocs((0, utils_1.getMultiIDParams)(globalIds))).rows.map(row => row.doc);
|
|
110
|
+
if (userIds) {
|
|
111
|
+
globalUsers = (yield db.allDocs((0, utils_1.getMultiIDParams)(userIds))).rows.map(row => row.doc);
|
|
113
112
|
}
|
|
114
113
|
else {
|
|
115
114
|
globalUsers = (yield db.allDocs(backend_core_1.db.getGlobalUserParams(null, {
|
|
@@ -126,15 +125,21 @@ function getGlobalUsers(users) {
|
|
|
126
125
|
if (!appId) {
|
|
127
126
|
return globalUsers;
|
|
128
127
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
if (opts === null || opts === void 0 ? void 0 : opts.noProcessing) {
|
|
129
|
+
return globalUsers;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// pass in the groups, meaning we don't actually need to retrieve them for
|
|
133
|
+
// each user individually
|
|
134
|
+
const allGroups = yield pro_1.groups.fetch();
|
|
135
|
+
return Promise.all(globalUsers.map(user => processUser(user, { groups: allGroups })));
|
|
136
|
+
}
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
exports.getGlobalUsers = getGlobalUsers;
|
|
135
140
|
function getGlobalUsersFromMetadata(users) {
|
|
136
141
|
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
-
const globalUsers = yield getGlobalUsers(users);
|
|
142
|
+
const globalUsers = yield getGlobalUsers(users.map(user => user._id));
|
|
138
143
|
return users.map(user => {
|
|
139
144
|
const globalUser = globalUsers.find(globalUser => { var _a; return globalUser && ((_a = user._id) === null || _a === void 0 ? void 0 : _a.includes(globalUser._id)); });
|
|
140
145
|
return Object.assign(Object.assign({}, globalUser), user);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.5.6-alpha.
|
|
4
|
+
"version": "2.5.6-alpha.2",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"license": "GPL-3.0",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@apidevtools/swagger-parser": "10.0.3",
|
|
48
|
-
"@budibase/backend-core": "2.5.6-alpha.
|
|
49
|
-
"@budibase/client": "2.5.6-alpha.
|
|
50
|
-
"@budibase/pro": "2.5.
|
|
51
|
-
"@budibase/shared-core": "2.5.6-alpha.
|
|
52
|
-
"@budibase/string-templates": "2.5.6-alpha.
|
|
53
|
-
"@budibase/types": "2.5.6-alpha.
|
|
48
|
+
"@budibase/backend-core": "2.5.6-alpha.2",
|
|
49
|
+
"@budibase/client": "2.5.6-alpha.2",
|
|
50
|
+
"@budibase/pro": "2.5.6-alpha.1",
|
|
51
|
+
"@budibase/shared-core": "2.5.6-alpha.2",
|
|
52
|
+
"@budibase/string-templates": "2.5.6-alpha.2",
|
|
53
|
+
"@budibase/types": "2.5.6-alpha.2",
|
|
54
54
|
"@bull-board/api": "3.7.0",
|
|
55
55
|
"@bull-board/koa": "3.9.4",
|
|
56
56
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -175,5 +175,5 @@
|
|
|
175
175
|
"optionalDependencies": {
|
|
176
176
|
"oracledb": "5.3.0"
|
|
177
177
|
},
|
|
178
|
-
"gitHead": "
|
|
178
|
+
"gitHead": "af0bc0890fbe45ca786366c72a754c2202277a54"
|
|
179
179
|
}
|
|
@@ -30,7 +30,6 @@ import { finaliseRow, updateRelatedFormula } from "./staticFormula"
|
|
|
30
30
|
import { csv, json, jsonWithSchema, Format } from "../view/exporters"
|
|
31
31
|
import { apiFileReturn } from "../../../utilities/fileSystem"
|
|
32
32
|
import {
|
|
33
|
-
Ctx,
|
|
34
33
|
UserCtx,
|
|
35
34
|
Database,
|
|
36
35
|
LinkDocumentValue,
|
|
@@ -72,7 +71,7 @@ async function getView(db: Database, viewName: string) {
|
|
|
72
71
|
return viewInfo
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
async function getRawTableData(ctx:
|
|
74
|
+
async function getRawTableData(ctx: UserCtx, db: Database, tableId: string) {
|
|
76
75
|
let rows
|
|
77
76
|
if (tableId === InternalTables.USER_METADATA) {
|
|
78
77
|
await userController.fetchMetadata(ctx)
|
|
@@ -188,7 +187,7 @@ export async function save(ctx: UserCtx) {
|
|
|
188
187
|
})
|
|
189
188
|
}
|
|
190
189
|
|
|
191
|
-
export async function fetchView(ctx:
|
|
190
|
+
export async function fetchView(ctx: UserCtx) {
|
|
192
191
|
const viewName = decodeURIComponent(ctx.params.viewName)
|
|
193
192
|
|
|
194
193
|
// if this is a table view being looked for just transfer to that
|
|
@@ -255,7 +254,7 @@ export async function fetchView(ctx: Ctx) {
|
|
|
255
254
|
return rows
|
|
256
255
|
}
|
|
257
256
|
|
|
258
|
-
export async function fetch(ctx:
|
|
257
|
+
export async function fetch(ctx: UserCtx) {
|
|
259
258
|
const db = context.getAppDB()
|
|
260
259
|
|
|
261
260
|
const tableId = ctx.params.tableId
|
|
@@ -264,7 +263,7 @@ export async function fetch(ctx: Ctx) {
|
|
|
264
263
|
return outputProcessing(table, rows)
|
|
265
264
|
}
|
|
266
265
|
|
|
267
|
-
export async function find(ctx:
|
|
266
|
+
export async function find(ctx: UserCtx) {
|
|
268
267
|
const db = dbCore.getDB(ctx.appId)
|
|
269
268
|
const table = await db.get(ctx.params.tableId)
|
|
270
269
|
let row = await utils.findRow(ctx, ctx.params.tableId, ctx.params.rowId)
|
|
@@ -272,7 +271,7 @@ export async function find(ctx: Ctx) {
|
|
|
272
271
|
return row
|
|
273
272
|
}
|
|
274
273
|
|
|
275
|
-
export async function destroy(ctx:
|
|
274
|
+
export async function destroy(ctx: UserCtx) {
|
|
276
275
|
const db = context.getAppDB()
|
|
277
276
|
const { _id } = ctx.request.body
|
|
278
277
|
let row = await db.get(_id)
|
|
@@ -308,7 +307,7 @@ export async function destroy(ctx: Ctx) {
|
|
|
308
307
|
return { response, row }
|
|
309
308
|
}
|
|
310
309
|
|
|
311
|
-
export async function bulkDestroy(ctx:
|
|
310
|
+
export async function bulkDestroy(ctx: UserCtx) {
|
|
312
311
|
const db = context.getAppDB()
|
|
313
312
|
const tableId = ctx.params.tableId
|
|
314
313
|
const table = await db.get(tableId)
|
|
@@ -347,7 +346,7 @@ export async function bulkDestroy(ctx: Ctx) {
|
|
|
347
346
|
return { response: { ok: true }, rows: processedRows }
|
|
348
347
|
}
|
|
349
348
|
|
|
350
|
-
export async function search(ctx:
|
|
349
|
+
export async function search(ctx: UserCtx) {
|
|
351
350
|
// Fetch the whole table when running in cypress, as search doesn't work
|
|
352
351
|
if (!env.COUCH_DB_URL && env.isCypress()) {
|
|
353
352
|
return { rows: await fetch(ctx) }
|
|
@@ -387,7 +386,7 @@ export async function search(ctx: Ctx) {
|
|
|
387
386
|
return response
|
|
388
387
|
}
|
|
389
388
|
|
|
390
|
-
export async function exportRows(ctx:
|
|
389
|
+
export async function exportRows(ctx: UserCtx) {
|
|
391
390
|
const db = context.getAppDB()
|
|
392
391
|
const table = await db.get(ctx.params.tableId)
|
|
393
392
|
const rowIds = ctx.request.body.rows
|
|
@@ -439,7 +438,7 @@ export async function exportRows(ctx: Ctx) {
|
|
|
439
438
|
}
|
|
440
439
|
}
|
|
441
440
|
|
|
442
|
-
export async function fetchEnrichedRow(ctx:
|
|
441
|
+
export async function fetchEnrichedRow(ctx: UserCtx) {
|
|
443
442
|
const db = context.getAppDB()
|
|
444
443
|
const tableId = ctx.params.tableId
|
|
445
444
|
const rowId = ctx.params.rowId
|
|
@@ -5,7 +5,7 @@ import { context } from "@budibase/backend-core"
|
|
|
5
5
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
|
6
6
|
import { Row, Table } from "@budibase/types"
|
|
7
7
|
import { Format } from "../view/exporters"
|
|
8
|
-
import {
|
|
8
|
+
import { UserCtx } from "@budibase/types"
|
|
9
9
|
import sdk from "../../../sdk"
|
|
10
10
|
const validateJs = require("validate.js")
|
|
11
11
|
const { cloneDeep } = require("lodash/fp")
|
|
@@ -26,7 +26,7 @@ export async function getDatasourceAndQuery(json: any) {
|
|
|
26
26
|
return makeExternalQuery(datasource, json)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export async function findRow(ctx:
|
|
29
|
+
export async function findRow(ctx: UserCtx, tableId: string, rowId: string) {
|
|
30
30
|
const db = context.getAppDB()
|
|
31
31
|
let row
|
|
32
32
|
// TODO remove special user case in future
|
|
@@ -1,98 +1,12 @@
|
|
|
1
1
|
import { generateUserMetadataID, generateUserFlagID } from "../../db/utils"
|
|
2
2
|
import { InternalTables } from "../../db/utils"
|
|
3
|
-
import { getGlobalUsers
|
|
3
|
+
import { getGlobalUsers } from "../../utilities/global"
|
|
4
4
|
import { getFullUser } from "../../utilities/users"
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
roles as rolesCore,
|
|
8
|
-
db as dbCore,
|
|
9
|
-
} from "@budibase/backend-core"
|
|
10
|
-
import { BBContext, Ctx, SyncUserRequest, User } from "@budibase/types"
|
|
5
|
+
import { context } from "@budibase/backend-core"
|
|
6
|
+
import { UserCtx } from "@budibase/types"
|
|
11
7
|
import sdk from "../../sdk"
|
|
12
8
|
|
|
13
|
-
export async function
|
|
14
|
-
let deleting = false,
|
|
15
|
-
user: User | any
|
|
16
|
-
const userId = ctx.params.id
|
|
17
|
-
|
|
18
|
-
const previousUser = ctx.request.body?.previousUser
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
user = (await getRawGlobalUser(userId)) as User
|
|
22
|
-
} catch (err: any) {
|
|
23
|
-
if (err && err.status === 404) {
|
|
24
|
-
user = {}
|
|
25
|
-
deleting = true
|
|
26
|
-
} else {
|
|
27
|
-
throw err
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let previousApps = previousUser
|
|
32
|
-
? Object.keys(previousUser.roles).map(appId => appId)
|
|
33
|
-
: []
|
|
34
|
-
|
|
35
|
-
const roles = deleting ? {} : user.roles
|
|
36
|
-
// remove props which aren't useful to metadata
|
|
37
|
-
delete user.password
|
|
38
|
-
delete user.forceResetPassword
|
|
39
|
-
delete user.roles
|
|
40
|
-
// run through all production appIDs in the users roles
|
|
41
|
-
let prodAppIds
|
|
42
|
-
// if they are a builder then get all production app IDs
|
|
43
|
-
if ((user.builder && user.builder.global) || deleting) {
|
|
44
|
-
prodAppIds = await dbCore.getProdAppIDs()
|
|
45
|
-
} else {
|
|
46
|
-
prodAppIds = Object.entries(roles)
|
|
47
|
-
.filter(entry => entry[1] !== rolesCore.BUILTIN_ROLE_IDS.PUBLIC)
|
|
48
|
-
.map(([appId]) => appId)
|
|
49
|
-
}
|
|
50
|
-
for (let prodAppId of new Set([...prodAppIds, ...previousApps])) {
|
|
51
|
-
const roleId = roles[prodAppId]
|
|
52
|
-
const deleteFromApp = !roleId
|
|
53
|
-
const devAppId = dbCore.getDevelopmentAppID(prodAppId)
|
|
54
|
-
for (let appId of [prodAppId, devAppId]) {
|
|
55
|
-
if (!(await dbCore.dbExists(appId))) {
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
await context.doInAppContext(appId, async () => {
|
|
59
|
-
const db = context.getAppDB()
|
|
60
|
-
const metadataId = generateUserMetadataID(userId)
|
|
61
|
-
let metadata
|
|
62
|
-
try {
|
|
63
|
-
metadata = await db.get(metadataId)
|
|
64
|
-
} catch (err) {
|
|
65
|
-
if (deleteFromApp) {
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
metadata = {
|
|
69
|
-
tableId: InternalTables.USER_METADATA,
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (deleteFromApp) {
|
|
74
|
-
await db.remove(metadata)
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// assign the roleId for the metadata doc
|
|
79
|
-
if (roleId) {
|
|
80
|
-
metadata.roleId = roleId
|
|
81
|
-
}
|
|
82
|
-
let combined = sdk.users.combineMetadataAndUser(user, metadata)
|
|
83
|
-
// if its null then there was no updates required
|
|
84
|
-
if (combined) {
|
|
85
|
-
await db.put(combined)
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
ctx.body = {
|
|
91
|
-
message: "User synced.",
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function fetchMetadata(ctx: BBContext) {
|
|
9
|
+
export async function fetchMetadata(ctx: UserCtx) {
|
|
96
10
|
const global = await getGlobalUsers()
|
|
97
11
|
const metadata = await sdk.users.rawUserMetadata()
|
|
98
12
|
const users = []
|
|
@@ -111,7 +25,7 @@ export async function fetchMetadata(ctx: BBContext) {
|
|
|
111
25
|
ctx.body = users
|
|
112
26
|
}
|
|
113
27
|
|
|
114
|
-
export async function updateSelfMetadata(ctx:
|
|
28
|
+
export async function updateSelfMetadata(ctx: UserCtx) {
|
|
115
29
|
// overwrite the ID with current users
|
|
116
30
|
ctx.request.body._id = ctx.user?._id
|
|
117
31
|
// make sure no stale rev
|
|
@@ -121,7 +35,7 @@ export async function updateSelfMetadata(ctx: BBContext) {
|
|
|
121
35
|
await updateMetadata(ctx)
|
|
122
36
|
}
|
|
123
37
|
|
|
124
|
-
export async function updateMetadata(ctx:
|
|
38
|
+
export async function updateMetadata(ctx: UserCtx) {
|
|
125
39
|
const db = context.getAppDB()
|
|
126
40
|
const user = ctx.request.body
|
|
127
41
|
// this isn't applicable to the user
|
|
@@ -133,7 +47,7 @@ export async function updateMetadata(ctx: BBContext) {
|
|
|
133
47
|
ctx.body = await db.put(metadata)
|
|
134
48
|
}
|
|
135
49
|
|
|
136
|
-
export async function destroyMetadata(ctx:
|
|
50
|
+
export async function destroyMetadata(ctx: UserCtx) {
|
|
137
51
|
const db = context.getAppDB()
|
|
138
52
|
try {
|
|
139
53
|
const dbUser = await db.get(ctx.params.id)
|
|
@@ -146,11 +60,11 @@ export async function destroyMetadata(ctx: BBContext) {
|
|
|
146
60
|
}
|
|
147
61
|
}
|
|
148
62
|
|
|
149
|
-
export async function findMetadata(ctx:
|
|
63
|
+
export async function findMetadata(ctx: UserCtx) {
|
|
150
64
|
ctx.body = await getFullUser(ctx, ctx.params.id)
|
|
151
65
|
}
|
|
152
66
|
|
|
153
|
-
export async function setFlag(ctx:
|
|
67
|
+
export async function setFlag(ctx: UserCtx) {
|
|
154
68
|
const userId = ctx.user?._id
|
|
155
69
|
const { flag, value } = ctx.request.body
|
|
156
70
|
if (!flag) {
|
|
@@ -169,7 +83,7 @@ export async function setFlag(ctx: BBContext) {
|
|
|
169
83
|
ctx.body = { message: "Flag set successfully" }
|
|
170
84
|
}
|
|
171
85
|
|
|
172
|
-
export async function getFlags(ctx:
|
|
86
|
+
export async function getFlags(ctx: UserCtx) {
|
|
173
87
|
const userId = ctx.user?._id
|
|
174
88
|
const docId = generateUserFlagID(userId!)
|
|
175
89
|
const db = context.getAppDB()
|
|
@@ -205,41 +205,4 @@ describe("/users", () => {
|
|
|
205
205
|
expect(res.body.message).toEqual("Flag set successfully")
|
|
206
206
|
})
|
|
207
207
|
})
|
|
208
|
-
|
|
209
|
-
describe("syncUser", () => {
|
|
210
|
-
it("should sync the user", async () => {
|
|
211
|
-
let user = await config.createUser()
|
|
212
|
-
await config.createApp("New App")
|
|
213
|
-
let res = await request
|
|
214
|
-
.post(`/api/users/metadata/sync/${user._id}`)
|
|
215
|
-
.set(config.defaultHeaders())
|
|
216
|
-
.expect(200)
|
|
217
|
-
.expect("Content-Type", /json/)
|
|
218
|
-
expect(res.body.message).toEqual("User synced.")
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it("should sync the user when a previous user is specified", async () => {
|
|
222
|
-
const app1 = await config.createApp("App 1")
|
|
223
|
-
const app2 = await config.createApp("App 2")
|
|
224
|
-
|
|
225
|
-
let user = await config.createUser({
|
|
226
|
-
builder: false,
|
|
227
|
-
admin: true,
|
|
228
|
-
roles: { [app1.appId]: "ADMIN" },
|
|
229
|
-
})
|
|
230
|
-
let res = await request
|
|
231
|
-
.post(`/api/users/metadata/sync/${user._id}`)
|
|
232
|
-
.set(config.defaultHeaders())
|
|
233
|
-
.send({
|
|
234
|
-
previousUser: {
|
|
235
|
-
...user,
|
|
236
|
-
roles: { ...user.roles, [app2.appId]: "BASIC" },
|
|
237
|
-
},
|
|
238
|
-
})
|
|
239
|
-
.expect(200)
|
|
240
|
-
.expect("Content-Type", /json/)
|
|
241
|
-
|
|
242
|
-
expect(res.body.message).toEqual("User synced.")
|
|
243
|
-
})
|
|
244
|
-
})
|
|
245
208
|
})
|
package/src/api/routes/user.ts
CHANGED
|
@@ -32,11 +32,6 @@ router
|
|
|
32
32
|
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
|
33
33
|
controller.destroyMetadata
|
|
34
34
|
)
|
|
35
|
-
.post(
|
|
36
|
-
"/api/users/metadata/sync/:id",
|
|
37
|
-
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
|
38
|
-
controller.syncUser
|
|
39
|
-
)
|
|
40
35
|
.post(
|
|
41
36
|
"/api/users/flags",
|
|
42
37
|
authorized(PermissionType.USER, PermissionLevel.WRITE),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./processors"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import userGroupProcessor from "./syncUsers"
|
|
2
|
+
import { docUpdates } from "@budibase/backend-core"
|
|
3
|
+
|
|
4
|
+
export type UpdateCallback = (docId: string) => void
|
|
5
|
+
let started = false
|
|
6
|
+
|
|
7
|
+
export function init(updateCb?: UpdateCallback) {
|
|
8
|
+
if (started) {
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
const processors = [userGroupProcessor(updateCb)]
|
|
12
|
+
docUpdates.init(processors)
|
|
13
|
+
started = true
|
|
14
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { constants, logging } from "@budibase/backend-core"
|
|
2
|
+
import { sdk as proSdk } from "@budibase/pro"
|
|
3
|
+
import { DocUpdateEvent, UserGroupSyncEvents } from "@budibase/types"
|
|
4
|
+
import { syncUsersToAllApps } from "../../sdk/app/applications/sync"
|
|
5
|
+
import { UpdateCallback } from "./processors"
|
|
6
|
+
|
|
7
|
+
export default function process(updateCb?: UpdateCallback) {
|
|
8
|
+
const processor = async (update: DocUpdateEvent) => {
|
|
9
|
+
try {
|
|
10
|
+
const docId = update.id
|
|
11
|
+
const isGroup = docId.startsWith(constants.DocumentType.GROUP)
|
|
12
|
+
let userIds: string[]
|
|
13
|
+
if (isGroup) {
|
|
14
|
+
const group = await proSdk.groups.get(docId)
|
|
15
|
+
userIds = group.users?.map(user => user._id) || []
|
|
16
|
+
} else {
|
|
17
|
+
userIds = [docId]
|
|
18
|
+
}
|
|
19
|
+
if (userIds.length > 0) {
|
|
20
|
+
await syncUsersToAllApps(userIds)
|
|
21
|
+
}
|
|
22
|
+
if (updateCb) {
|
|
23
|
+
updateCb(docId)
|
|
24
|
+
}
|
|
25
|
+
} catch (err: any) {
|
|
26
|
+
// if something not found - no changes to perform
|
|
27
|
+
if (err?.status === 404) {
|
|
28
|
+
return
|
|
29
|
+
} else {
|
|
30
|
+
logging.logAlert("Failed to perform user/group app sync", err)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { events: UserGroupSyncEvents, processor }
|
|
35
|
+
}
|
package/src/events/index.ts
CHANGED
|
@@ -1,6 +1,117 @@
|
|
|
1
1
|
import env from "../../../environment"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
db as dbCore,
|
|
4
|
+
context,
|
|
5
|
+
docUpdates,
|
|
6
|
+
constants,
|
|
7
|
+
logging,
|
|
8
|
+
roles,
|
|
9
|
+
} from "@budibase/backend-core"
|
|
10
|
+
import { User, ContextUser, UserGroup } from "@budibase/types"
|
|
11
|
+
import { sdk as proSdk } from "@budibase/pro"
|
|
3
12
|
import sdk from "../../"
|
|
13
|
+
import { getGlobalUsers, processUser } from "../../../utilities/global"
|
|
14
|
+
import { generateUserMetadataID, InternalTables } from "../../../db/utils"
|
|
15
|
+
|
|
16
|
+
type DeletedUser = { _id: string; deleted: boolean }
|
|
17
|
+
|
|
18
|
+
async function syncUsersToApp(
|
|
19
|
+
appId: string,
|
|
20
|
+
users: (User | DeletedUser)[],
|
|
21
|
+
groups: UserGroup[]
|
|
22
|
+
) {
|
|
23
|
+
if (!(await dbCore.dbExists(appId))) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
await context.doInAppContext(appId, async () => {
|
|
27
|
+
const db = context.getAppDB()
|
|
28
|
+
for (let user of users) {
|
|
29
|
+
let ctxUser = user as ContextUser
|
|
30
|
+
let deletedUser = false
|
|
31
|
+
const metadataId = generateUserMetadataID(user._id!)
|
|
32
|
+
if ((user as DeletedUser).deleted) {
|
|
33
|
+
deletedUser = true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// make sure role is correct
|
|
37
|
+
if (!deletedUser) {
|
|
38
|
+
ctxUser = await processUser(ctxUser, { appId, groups })
|
|
39
|
+
}
|
|
40
|
+
let roleId = ctxUser.roleId
|
|
41
|
+
if (roleId === roles.BUILTIN_ROLE_IDS.PUBLIC) {
|
|
42
|
+
roleId = undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let metadata
|
|
46
|
+
try {
|
|
47
|
+
metadata = await db.get(metadataId)
|
|
48
|
+
} catch (err: any) {
|
|
49
|
+
if (err.status !== 404) {
|
|
50
|
+
throw err
|
|
51
|
+
}
|
|
52
|
+
// no metadata and user is to be deleted, can skip
|
|
53
|
+
// no role - user isn't in app anyway
|
|
54
|
+
if (!roleId) {
|
|
55
|
+
continue
|
|
56
|
+
} else if (!deletedUser) {
|
|
57
|
+
// doesn't exist yet, creating it
|
|
58
|
+
metadata = {
|
|
59
|
+
tableId: InternalTables.USER_METADATA,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// the user doesn't exist, or doesn't have a role anymore
|
|
65
|
+
// get rid of their metadata
|
|
66
|
+
if (deletedUser || !roleId) {
|
|
67
|
+
await db.remove(metadata)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// assign the roleId for the metadata doc
|
|
72
|
+
if (roleId) {
|
|
73
|
+
metadata.roleId = roleId
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let combined = sdk.users.combineMetadataAndUser(ctxUser, metadata)
|
|
77
|
+
// if no combined returned, there are no updates to make
|
|
78
|
+
if (combined) {
|
|
79
|
+
await db.put(combined)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function syncUsersToAllApps(userIds: string[]) {
|
|
86
|
+
// list of users, if one has been deleted it will be undefined in array
|
|
87
|
+
const users = (await getGlobalUsers(userIds, {
|
|
88
|
+
noProcessing: true,
|
|
89
|
+
})) as User[]
|
|
90
|
+
const groups = await proSdk.groups.fetch()
|
|
91
|
+
const finalUsers: (User | DeletedUser)[] = []
|
|
92
|
+
for (let userId of userIds) {
|
|
93
|
+
const user = users.find(user => user._id === userId)
|
|
94
|
+
if (!user) {
|
|
95
|
+
finalUsers.push({ _id: userId, deleted: true })
|
|
96
|
+
} else {
|
|
97
|
+
finalUsers.push(user)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const devAppIds = await dbCore.getDevAppIDs()
|
|
101
|
+
let promises = []
|
|
102
|
+
for (let devAppId of devAppIds) {
|
|
103
|
+
const prodAppId = dbCore.getProdAppID(devAppId)
|
|
104
|
+
for (let appId of [prodAppId, devAppId]) {
|
|
105
|
+
promises.push(syncUsersToApp(appId, finalUsers, groups))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const resp = await Promise.allSettled(promises)
|
|
109
|
+
const failed = resp.filter(promise => promise.status === "rejected")
|
|
110
|
+
if (failed.length > 0) {
|
|
111
|
+
const reasons = failed.map(fail => (fail as PromiseRejectedResult).reason)
|
|
112
|
+
logging.logAlert("Failed to sync users to apps", reasons)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
4
115
|
|
|
5
116
|
export async function syncApp(
|
|
6
117
|
appId: string,
|
|
@@ -23,32 +134,28 @@ export async function syncApp(
|
|
|
23
134
|
// specific case, want to make sure setup is skipped
|
|
24
135
|
const prodDb = context.getProdAppDB({ skip_setup: true })
|
|
25
136
|
const exists = await prodDb.exists()
|
|
26
|
-
if (!exists) {
|
|
27
|
-
// the database doesn't exist. Don't replicate
|
|
28
|
-
return {
|
|
29
|
-
message: "App sync not required, app not deployed.",
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
137
|
|
|
33
|
-
const replication = new dbCore.Replication({
|
|
34
|
-
source: prodAppId,
|
|
35
|
-
target: appId,
|
|
36
|
-
})
|
|
37
138
|
let error
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
139
|
+
if (exists) {
|
|
140
|
+
const replication = new dbCore.Replication({
|
|
141
|
+
source: prodAppId,
|
|
142
|
+
target: appId,
|
|
143
|
+
})
|
|
144
|
+
try {
|
|
145
|
+
const replOpts = replication.appReplicateOpts()
|
|
146
|
+
if (opts?.automationOnly) {
|
|
147
|
+
replOpts.filter = (doc: any) =>
|
|
148
|
+
doc._id.startsWith(dbCore.DocumentType.AUTOMATION)
|
|
149
|
+
}
|
|
150
|
+
await replication.replicate(replOpts)
|
|
151
|
+
} catch (err) {
|
|
152
|
+
error = err
|
|
153
|
+
} finally {
|
|
154
|
+
await replication.close()
|
|
43
155
|
}
|
|
44
|
-
await replication.replicate(replOpts)
|
|
45
|
-
} catch (err) {
|
|
46
|
-
error = err
|
|
47
|
-
} finally {
|
|
48
|
-
await replication.close()
|
|
49
156
|
}
|
|
50
157
|
|
|
51
|
-
// sync the users
|
|
158
|
+
// sync the users - kept for safe keeping
|
|
52
159
|
await sdk.users.syncGlobalUsers()
|
|
53
160
|
|
|
54
161
|
if (error) {
|