@budibase/worker 1.4.17 → 1.4.18
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/Dockerfile +2 -0
- package/package.json +6 -6
- package/scripts/dev/manage.js +1 -0
- package/src/api/controllers/global/auth.ts +12 -4
- package/src/api/controllers/global/email.js +4 -0
- package/src/api/controllers/global/license.ts +5 -0
- package/src/api/controllers/global/{self.js → self.ts} +38 -39
- package/src/api/controllers/global/{templates.js → templates.ts} +19 -17
- package/src/api/controllers/global/users.ts +36 -46
- package/src/api/controllers/system/accounts.ts +5 -5
- package/src/api/routes/global/auth.js +1 -1
- package/src/api/routes/global/configs.js +1 -1
- package/src/api/routes/global/email.js +10 -2
- package/src/api/routes/global/license.ts +1 -0
- package/src/api/routes/global/roles.js +1 -1
- package/src/api/routes/global/self.ts +18 -0
- package/src/api/routes/global/{templates.js → templates.ts} +8 -8
- package/src/api/routes/global/tests/users.spec.ts +32 -33
- package/src/api/routes/global/users.js +4 -11
- package/src/api/routes/global/workspaces.js +1 -1
- package/src/api/routes/index.ts +34 -0
- package/src/api/routes/system/environment.js +1 -1
- package/src/api/routes/system/status.js +1 -1
- package/src/api/routes/system/tenants.js +1 -1
- package/src/api/routes/system/tests/accounts.spec.ts +4 -4
- package/src/api/routes/validation/users.ts +10 -5
- package/src/db/index.js +1 -1
- package/src/migrations/functions/globalInfoSyncUsers.ts +1 -1
- package/src/sdk/index.ts +7 -2
- package/src/sdk/users/users.ts +22 -38
- package/src/tests/api/users.ts +10 -10
- package/src/utilities/email.js +3 -1
- package/src/api/routes/global/self.js +0 -18
- package/src/api/routes/index.js +0 -34
package/Dockerfile
CHANGED
|
@@ -23,5 +23,7 @@ ENV NODE_ENV=production
|
|
|
23
23
|
ENV CLUSTER_MODE=${CLUSTER_MODE}
|
|
24
24
|
ENV SERVICE=worker-service
|
|
25
25
|
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
|
26
|
+
ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS
|
|
27
|
+
ENV ACCOUNT_PORTAL_URL=https://account.budibase.app
|
|
26
28
|
|
|
27
29
|
CMD ["./docker_run.sh"]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.18",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"author": "Budibase",
|
|
37
37
|
"license": "GPL-3.0",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@budibase/backend-core": "^1.4.
|
|
40
|
-
"@budibase/pro": "1.4.
|
|
41
|
-
"@budibase/string-templates": "^1.4.
|
|
42
|
-
"@budibase/types": "^1.4.
|
|
39
|
+
"@budibase/backend-core": "^1.4.18",
|
|
40
|
+
"@budibase/pro": "1.4.18-alpha.0",
|
|
41
|
+
"@budibase/string-templates": "^1.4.18",
|
|
42
|
+
"@budibase/types": "^1.4.18",
|
|
43
43
|
"@koa/router": "8.0.8",
|
|
44
44
|
"@sentry/node": "6.17.7",
|
|
45
45
|
"@techpass/passport-openidconnect": "0.3.2",
|
|
@@ -104,5 +104,5 @@
|
|
|
104
104
|
"./scripts/jestSetup.js"
|
|
105
105
|
]
|
|
106
106
|
},
|
|
107
|
-
"gitHead": "
|
|
107
|
+
"gitHead": "395b2a45e083fb38f6ee4d3ed2b4d71534d6f526"
|
|
108
108
|
}
|
package/scripts/dev/manage.js
CHANGED
|
@@ -8,7 +8,7 @@ const { checkResetPasswordCode } = require("../../../utilities/redis")
|
|
|
8
8
|
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
|
9
9
|
const env = require("../../../environment")
|
|
10
10
|
import { events, users as usersCore, context } from "@budibase/backend-core"
|
|
11
|
-
import
|
|
11
|
+
import sdk from "../../../sdk"
|
|
12
12
|
import { User } from "@budibase/types"
|
|
13
13
|
|
|
14
14
|
export const googleCallbackUrl = async (config: any) => {
|
|
@@ -167,7 +167,11 @@ export const googlePreAuth = async (ctx: any, next: any) => {
|
|
|
167
167
|
workspace: ctx.query.workspace,
|
|
168
168
|
})
|
|
169
169
|
let callbackUrl = await exports.googleCallbackUrl(config)
|
|
170
|
-
const strategy = await google.strategyFactory(
|
|
170
|
+
const strategy = await google.strategyFactory(
|
|
171
|
+
config,
|
|
172
|
+
callbackUrl,
|
|
173
|
+
sdk.users.save
|
|
174
|
+
)
|
|
171
175
|
|
|
172
176
|
return passport.authenticate(strategy, {
|
|
173
177
|
scope: ["profile", "email"],
|
|
@@ -184,7 +188,11 @@ export const googleAuth = async (ctx: any, next: any) => {
|
|
|
184
188
|
workspace: ctx.query.workspace,
|
|
185
189
|
})
|
|
186
190
|
const callbackUrl = await exports.googleCallbackUrl(config)
|
|
187
|
-
const strategy = await google.strategyFactory(
|
|
191
|
+
const strategy = await google.strategyFactory(
|
|
192
|
+
config,
|
|
193
|
+
callbackUrl,
|
|
194
|
+
sdk.users.save
|
|
195
|
+
)
|
|
188
196
|
|
|
189
197
|
return passport.authenticate(
|
|
190
198
|
strategy,
|
|
@@ -214,7 +222,7 @@ export const oidcStrategyFactory = async (ctx: any, configId: any) => {
|
|
|
214
222
|
chosenConfig,
|
|
215
223
|
callbackUrl
|
|
216
224
|
)
|
|
217
|
-
return oidc.strategyFactory(enrichedConfig, users.save)
|
|
225
|
+
return oidc.strategyFactory(enrichedConfig, sdk.users.save)
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
/**
|
|
@@ -10,6 +10,8 @@ exports.sendEmail = async ctx => {
|
|
|
10
10
|
contents,
|
|
11
11
|
from,
|
|
12
12
|
subject,
|
|
13
|
+
cc,
|
|
14
|
+
bcc,
|
|
13
15
|
automation,
|
|
14
16
|
} = ctx.request.body
|
|
15
17
|
let user
|
|
@@ -23,6 +25,8 @@ exports.sendEmail = async ctx => {
|
|
|
23
25
|
contents,
|
|
24
26
|
from,
|
|
25
27
|
subject,
|
|
28
|
+
cc,
|
|
29
|
+
bcc,
|
|
26
30
|
automation,
|
|
27
31
|
})
|
|
28
32
|
ctx.body = {
|
|
@@ -24,6 +24,11 @@ export const getInfo = async (ctx: any) => {
|
|
|
24
24
|
ctx.status = 200
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export const deleteInfo = async (ctx: any) => {
|
|
28
|
+
await licensing.deleteLicenseInfo()
|
|
29
|
+
ctx.status = 200
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
export const getQuotaUsage = async (ctx: any) => {
|
|
28
33
|
ctx.body = await quotas.getQuotaUsage()
|
|
29
34
|
}
|
|
@@ -1,39 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
const {
|
|
15
|
-
const {
|
|
16
|
-
const { users } = require("../../../sdk")
|
|
17
|
-
const { Cookies } = require("@budibase/backend-core/constants")
|
|
18
|
-
const { events, featureFlags } = require("@budibase/backend-core")
|
|
19
|
-
const env = require("../../../environment")
|
|
1
|
+
import sdk from "../../../sdk"
|
|
2
|
+
import {
|
|
3
|
+
events,
|
|
4
|
+
featureFlags,
|
|
5
|
+
tenancy,
|
|
6
|
+
constants,
|
|
7
|
+
db as dbCore,
|
|
8
|
+
utils,
|
|
9
|
+
cache,
|
|
10
|
+
encryption,
|
|
11
|
+
} from "@budibase/backend-core"
|
|
12
|
+
import env from "../../../environment"
|
|
13
|
+
import { groups } from "@budibase/pro"
|
|
14
|
+
const { hash, platformLogout, getCookie, clearCookie, newid } = utils
|
|
15
|
+
const { user: userCache } = cache
|
|
20
16
|
|
|
21
17
|
function newTestApiKey() {
|
|
22
18
|
return env.ENCRYPTED_TEST_PUBLIC_API_KEY
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
function newApiKey() {
|
|
26
|
-
return encrypt(
|
|
22
|
+
return encryption.encrypt(
|
|
23
|
+
`${tenancy.getTenantId()}${dbCore.SEPARATOR}${newid()}`
|
|
24
|
+
)
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
function cleanupDevInfo(info) {
|
|
27
|
+
function cleanupDevInfo(info: any) {
|
|
30
28
|
// user doesn't need to aware of dev doc info
|
|
31
29
|
delete info._id
|
|
32
30
|
delete info._rev
|
|
33
31
|
return info
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
export async function generateAPIKey(ctx: any) {
|
|
37
35
|
let userId
|
|
38
36
|
let apiKey
|
|
39
37
|
if (env.isTest() && ctx.request.body.userId) {
|
|
@@ -44,8 +42,8 @@ exports.generateAPIKey = async ctx => {
|
|
|
44
42
|
apiKey = newApiKey()
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
const db = getGlobalDB()
|
|
48
|
-
const id = generateDevInfoID(userId)
|
|
45
|
+
const db = tenancy.getGlobalDB()
|
|
46
|
+
const id = dbCore.generateDevInfoID(userId)
|
|
49
47
|
let devInfo
|
|
50
48
|
try {
|
|
51
49
|
devInfo = await db.get(id)
|
|
@@ -57,9 +55,9 @@ exports.generateAPIKey = async ctx => {
|
|
|
57
55
|
ctx.body = cleanupDevInfo(devInfo)
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
const db = getGlobalDB()
|
|
62
|
-
const id = generateDevInfoID(ctx.user._id)
|
|
58
|
+
export async function fetchAPIKey(ctx: any) {
|
|
59
|
+
const db = tenancy.getGlobalDB()
|
|
60
|
+
const id = dbCore.generateDevInfoID(ctx.user._id)
|
|
63
61
|
let devInfo
|
|
64
62
|
try {
|
|
65
63
|
devInfo = await db.get(id)
|
|
@@ -74,20 +72,20 @@ exports.fetchAPIKey = async ctx => {
|
|
|
74
72
|
ctx.body = cleanupDevInfo(devInfo)
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
const checkCurrentApp = ctx => {
|
|
78
|
-
const appCookie = getCookie(ctx, Cookies.CurrentApp)
|
|
79
|
-
if (appCookie && !isUserInAppTenant(appCookie.appId)) {
|
|
75
|
+
const checkCurrentApp = (ctx: any) => {
|
|
76
|
+
const appCookie = getCookie(ctx, constants.Cookies.CurrentApp)
|
|
77
|
+
if (appCookie && !tenancy.isUserInAppTenant(appCookie.appId)) {
|
|
80
78
|
// there is a currentapp cookie from another tenant
|
|
81
79
|
// remove the cookie as this is incompatible with the builder
|
|
82
80
|
// due to builder and admin permissions being removed
|
|
83
|
-
clearCookie(ctx, Cookies.CurrentApp)
|
|
81
|
+
clearCookie(ctx, constants.Cookies.CurrentApp)
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
/**
|
|
88
86
|
* Add the attributes that are session based to the current user.
|
|
89
87
|
*/
|
|
90
|
-
const addSessionAttributesToUser = ctx => {
|
|
88
|
+
const addSessionAttributesToUser = (ctx: any) => {
|
|
91
89
|
ctx.body.account = ctx.user.account
|
|
92
90
|
ctx.body.license = ctx.user.license
|
|
93
91
|
ctx.body.budibaseAccess = !!ctx.user.budibaseAccess
|
|
@@ -95,9 +93,9 @@ const addSessionAttributesToUser = ctx => {
|
|
|
95
93
|
ctx.body.csrfToken = ctx.user.csrfToken
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
const sanitiseUserUpdate = ctx => {
|
|
96
|
+
const sanitiseUserUpdate = (ctx: any) => {
|
|
99
97
|
const allowed = ["firstName", "lastName", "password", "forceResetPassword"]
|
|
100
|
-
const resp = {}
|
|
98
|
+
const resp: { [key: string]: any } = {}
|
|
101
99
|
for (let [key, value] of Object.entries(ctx.request.body)) {
|
|
102
100
|
if (allowed.includes(key)) {
|
|
103
101
|
resp[key] = value
|
|
@@ -106,7 +104,7 @@ const sanitiseUserUpdate = ctx => {
|
|
|
106
104
|
return resp
|
|
107
105
|
}
|
|
108
106
|
|
|
109
|
-
|
|
107
|
+
export async function getSelf(ctx: any) {
|
|
110
108
|
if (!ctx.user) {
|
|
111
109
|
ctx.throw(403, "User not logged in")
|
|
112
110
|
}
|
|
@@ -118,17 +116,18 @@ exports.getSelf = async ctx => {
|
|
|
118
116
|
checkCurrentApp(ctx)
|
|
119
117
|
|
|
120
118
|
// get the main body of the user
|
|
121
|
-
|
|
119
|
+
const user = await sdk.users.getUser(userId)
|
|
120
|
+
ctx.body = await groups.enrichUserRolesFromGroups(user)
|
|
122
121
|
|
|
123
122
|
// add the feature flags for this tenant
|
|
124
|
-
const tenantId = getTenantId()
|
|
123
|
+
const tenantId = tenancy.getTenantId()
|
|
125
124
|
ctx.body.featureFlags = featureFlags.getTenantFeatureFlags(tenantId)
|
|
126
125
|
|
|
127
126
|
addSessionAttributesToUser(ctx)
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
const db = getGlobalDB()
|
|
129
|
+
export async function updateSelf(ctx: any) {
|
|
130
|
+
const db = tenancy.getGlobalDB()
|
|
132
131
|
const user = await db.get(ctx.user._id)
|
|
133
132
|
let passwordChange = false
|
|
134
133
|
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
const {
|
|
1
|
+
import {
|
|
3
2
|
TemplateMetadata,
|
|
4
3
|
TemplateBindings,
|
|
5
4
|
GLOBAL_OWNER,
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
} from "../../../constants"
|
|
6
|
+
import { getTemplates } from "../../../constants/templates"
|
|
7
|
+
import { tenancy, db as dbCore } from "@budibase/backend-core"
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
const db = getGlobalDB()
|
|
9
|
+
export async function save(ctx: any) {
|
|
10
|
+
const db = tenancy.getGlobalDB()
|
|
12
11
|
let template = ctx.request.body
|
|
13
12
|
if (!template.ownerId) {
|
|
14
13
|
template.ownerId = GLOBAL_OWNER
|
|
15
14
|
}
|
|
16
15
|
if (!template._id) {
|
|
17
|
-
template._id = generateTemplateID(template.ownerId)
|
|
16
|
+
template._id = dbCore.generateTemplateID(template.ownerId)
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
const response = await db.put(template)
|
|
@@ -24,9 +23,9 @@ exports.save = async ctx => {
|
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
const bindings = {}
|
|
29
|
-
const info = {}
|
|
26
|
+
export async function definitions(ctx: any) {
|
|
27
|
+
const bindings: any = {}
|
|
28
|
+
const info: any = {}
|
|
30
29
|
for (let template of TemplateMetadata.email) {
|
|
31
30
|
bindings[template.purpose] = template.bindings
|
|
32
31
|
info[template.purpose] = {
|
|
@@ -45,30 +44,33 @@ exports.definitions = async ctx => {
|
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
export async function fetch(ctx: any) {
|
|
49
48
|
ctx.body = await getTemplates()
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
export async function fetchByType(ctx: any) {
|
|
52
|
+
// @ts-ignore
|
|
53
53
|
ctx.body = await getTemplates({
|
|
54
54
|
type: ctx.params.type,
|
|
55
55
|
})
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
export async function fetchByOwner(ctx: any) {
|
|
59
|
+
// @ts-ignore
|
|
59
60
|
ctx.body = await getTemplates({
|
|
60
61
|
ownerId: ctx.params.ownerId,
|
|
61
62
|
})
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
export async function find(ctx: any) {
|
|
66
|
+
// @ts-ignore
|
|
65
67
|
ctx.body = await getTemplates({
|
|
66
68
|
id: ctx.params.id,
|
|
67
69
|
})
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
const db = getGlobalDB()
|
|
72
|
+
export async function destroy(ctx: any) {
|
|
73
|
+
const db = tenancy.getGlobalDB()
|
|
72
74
|
await db.remove(ctx.params.id, ctx.params.rev)
|
|
73
75
|
ctx.message = `Template ${ctx.params.id} deleted.`
|
|
74
76
|
ctx.status = 200
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { checkInviteCode } from "../../../utilities/redis"
|
|
2
|
-
import
|
|
2
|
+
import sdk from "../../../sdk"
|
|
3
3
|
import env from "../../../environment"
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
BulkUserRequest,
|
|
6
|
+
BulkUserResponse,
|
|
6
7
|
CloudAccount,
|
|
7
8
|
InviteUserRequest,
|
|
8
9
|
InviteUsersRequest,
|
|
@@ -16,46 +17,48 @@ import {
|
|
|
16
17
|
tenancy,
|
|
17
18
|
} from "@budibase/backend-core"
|
|
18
19
|
import { checkAnyUserExists } from "../../../utilities/users"
|
|
19
|
-
import { groups as groupUtils } from "@budibase/pro"
|
|
20
20
|
|
|
21
21
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
|
22
22
|
|
|
23
23
|
export const save = async (ctx: any) => {
|
|
24
24
|
try {
|
|
25
|
-
ctx.body = await users.save(ctx.request.body)
|
|
25
|
+
ctx.body = await sdk.users.save(ctx.request.body)
|
|
26
26
|
} catch (err: any) {
|
|
27
27
|
ctx.throw(err.status || 400, err)
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const bulkDelete = async (userIds: string[], currentUserId: string) => {
|
|
32
|
+
if (userIds?.indexOf(currentUserId) !== -1) {
|
|
33
|
+
throw new Error("Unable to delete self.")
|
|
34
|
+
}
|
|
35
|
+
return await sdk.users.bulkDelete(userIds)
|
|
36
|
+
}
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
const bulkCreate = async (users: User[], groupIds: string[]) => {
|
|
39
|
+
if (!env.SELF_HOSTED && users.length > MAX_USERS_UPLOAD_LIMIT) {
|
|
40
|
+
throw new Error(
|
|
37
41
|
"Max limit for upload is 1000 users. Please reduce file size and try again."
|
|
38
42
|
)
|
|
39
43
|
}
|
|
44
|
+
return await sdk.users.bulkCreate(users, groupIds)
|
|
45
|
+
}
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
for (const groupId of groups) {
|
|
46
|
-
let oldGroup = await db.get(groupId)
|
|
47
|
-
groupsToSave.push(oldGroup)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
47
|
+
export const bulkUpdate = async (ctx: any) => {
|
|
48
|
+
const currentUserId = ctx.user._id
|
|
49
|
+
const input = ctx.request.body as BulkUserRequest
|
|
50
|
+
let created, deleted
|
|
51
51
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
if (input.create) {
|
|
53
|
+
created = await bulkCreate(input.create.users, input.create.groups)
|
|
54
|
+
}
|
|
55
|
+
if (input.delete) {
|
|
56
|
+
deleted = await bulkDelete(input.delete.userIds, currentUserId)
|
|
57
|
+
}
|
|
56
58
|
} catch (err: any) {
|
|
57
|
-
ctx.throw(err.status || 400, err)
|
|
59
|
+
ctx.throw(err.status || 400, err?.message || err)
|
|
58
60
|
}
|
|
61
|
+
ctx.body = { created, deleted } as BulkUserResponse
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
const parseBooleanParam = (param: any) => {
|
|
@@ -99,7 +102,7 @@ export const adminUser = async (ctx: any) => {
|
|
|
99
102
|
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
|
100
103
|
// stuck in a cycle
|
|
101
104
|
await cache.bustCache(cache.CacheKeys.CHECKLIST)
|
|
102
|
-
const finalUser = await users.save(user, {
|
|
105
|
+
const finalUser = await sdk.users.save(user, {
|
|
103
106
|
hashPassword,
|
|
104
107
|
requirePassword,
|
|
105
108
|
})
|
|
@@ -121,7 +124,7 @@ export const adminUser = async (ctx: any) => {
|
|
|
121
124
|
export const countByApp = async (ctx: any) => {
|
|
122
125
|
const appId = ctx.params.appId
|
|
123
126
|
try {
|
|
124
|
-
ctx.body = await users.countUsersByApp(appId)
|
|
127
|
+
ctx.body = await sdk.users.countUsersByApp(appId)
|
|
125
128
|
} catch (err: any) {
|
|
126
129
|
ctx.throw(err.status || 400, err)
|
|
127
130
|
}
|
|
@@ -133,28 +136,15 @@ export const destroy = async (ctx: any) => {
|
|
|
133
136
|
ctx.throw(400, "Unable to delete self.")
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
await users.destroy(id, ctx.user)
|
|
139
|
+
await sdk.users.destroy(id, ctx.user)
|
|
137
140
|
|
|
138
141
|
ctx.body = {
|
|
139
142
|
message: `User ${id} deleted.`,
|
|
140
143
|
}
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
export const bulkDelete = async (ctx: any) => {
|
|
144
|
-
const { userIds } = ctx.request.body as BulkDeleteUsersRequest
|
|
145
|
-
if (userIds?.indexOf(ctx.user._id) !== -1) {
|
|
146
|
-
ctx.throw(400, "Unable to delete self.")
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
ctx.body = await users.bulkDelete(userIds)
|
|
151
|
-
} catch (err) {
|
|
152
|
-
ctx.throw(err)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
146
|
export const search = async (ctx: any) => {
|
|
157
|
-
const paginated = await users.paginatedUsers(ctx.request.body)
|
|
147
|
+
const paginated = await sdk.users.paginatedUsers(ctx.request.body)
|
|
158
148
|
// user hashed password shouldn't ever be returned
|
|
159
149
|
for (let user of paginated.data) {
|
|
160
150
|
if (user) {
|
|
@@ -166,7 +156,7 @@ export const search = async (ctx: any) => {
|
|
|
166
156
|
|
|
167
157
|
// called internally by app server user fetch
|
|
168
158
|
export const fetch = async (ctx: any) => {
|
|
169
|
-
const all = await users.allUsers()
|
|
159
|
+
const all = await sdk.users.allUsers()
|
|
170
160
|
// user hashed password shouldn't ever be returned
|
|
171
161
|
for (let user of all) {
|
|
172
162
|
if (user) {
|
|
@@ -178,7 +168,7 @@ export const fetch = async (ctx: any) => {
|
|
|
178
168
|
|
|
179
169
|
// called internally by app server user find
|
|
180
170
|
export const find = async (ctx: any) => {
|
|
181
|
-
ctx.body = await users.getUser(ctx.params.id)
|
|
171
|
+
ctx.body = await sdk.users.getUser(ctx.params.id)
|
|
182
172
|
}
|
|
183
173
|
|
|
184
174
|
export const tenantUserLookup = async (ctx: any) => {
|
|
@@ -193,7 +183,7 @@ export const tenantUserLookup = async (ctx: any) => {
|
|
|
193
183
|
|
|
194
184
|
export const invite = async (ctx: any) => {
|
|
195
185
|
const request = ctx.request.body as InviteUserRequest
|
|
196
|
-
const response = await users.invite([request])
|
|
186
|
+
const response = await sdk.users.invite([request])
|
|
197
187
|
|
|
198
188
|
// explicitly throw for single user invite
|
|
199
189
|
if (response.unsuccessful.length) {
|
|
@@ -212,7 +202,7 @@ export const invite = async (ctx: any) => {
|
|
|
212
202
|
|
|
213
203
|
export const inviteMultiple = async (ctx: any) => {
|
|
214
204
|
const request = ctx.request.body as InviteUsersRequest
|
|
215
|
-
ctx.body = await users.invite(request)
|
|
205
|
+
ctx.body = await sdk.users.invite(request)
|
|
216
206
|
}
|
|
217
207
|
|
|
218
208
|
export const inviteAccept = async (ctx: any) => {
|
|
@@ -221,7 +211,7 @@ export const inviteAccept = async (ctx: any) => {
|
|
|
221
211
|
// info is an extension of the user object that was stored by global
|
|
222
212
|
const { email, info }: any = await checkInviteCode(inviteCode)
|
|
223
213
|
ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
|
|
224
|
-
const saved = await users.save({
|
|
214
|
+
const saved = await sdk.users.save({
|
|
225
215
|
firstName,
|
|
226
216
|
lastName,
|
|
227
217
|
password,
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { Account, AccountMetadata } from "@budibase/types"
|
|
2
|
-
import
|
|
2
|
+
import sdk from "../../../sdk"
|
|
3
3
|
|
|
4
4
|
export const save = async (ctx: any) => {
|
|
5
5
|
const account = ctx.request.body as Account
|
|
6
6
|
let metadata: AccountMetadata = {
|
|
7
|
-
_id: accounts.formatAccountMetadataId(account.accountId),
|
|
7
|
+
_id: sdk.accounts.formatAccountMetadataId(account.accountId),
|
|
8
8
|
email: account.email,
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
metadata = await accounts.saveMetadata(metadata)
|
|
11
|
+
metadata = await sdk.accounts.saveMetadata(metadata)
|
|
12
12
|
|
|
13
13
|
ctx.body = metadata
|
|
14
14
|
ctx.status = 200
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const destroy = async (ctx: any) => {
|
|
18
|
-
const accountId = accounts.formatAccountMetadataId(ctx.params.accountId)
|
|
19
|
-
await accounts.destroyMetadata(accountId)
|
|
18
|
+
const accountId = sdk.accounts.formatAccountMetadataId(ctx.params.accountId)
|
|
19
|
+
await sdk.accounts.destroyMetadata(accountId)
|
|
20
20
|
ctx.status = 204
|
|
21
21
|
}
|
|
@@ -4,7 +4,7 @@ const { joiValidator } = require("@budibase/backend-core/auth")
|
|
|
4
4
|
const Joi = require("joi")
|
|
5
5
|
const { updateTenantId } = require("@budibase/backend-core/tenancy")
|
|
6
6
|
|
|
7
|
-
const router = Router()
|
|
7
|
+
const router = new Router()
|
|
8
8
|
|
|
9
9
|
function buildAuthValidation() {
|
|
10
10
|
// prettier-ignore
|
|
@@ -5,12 +5,20 @@ const { joiValidator } = require("@budibase/backend-core/auth")
|
|
|
5
5
|
const { adminOnly } = require("@budibase/backend-core/auth")
|
|
6
6
|
const Joi = require("joi")
|
|
7
7
|
|
|
8
|
-
const router = Router()
|
|
8
|
+
const router = new Router()
|
|
9
9
|
|
|
10
10
|
function buildEmailSendValidation() {
|
|
11
11
|
// prettier-ignore
|
|
12
12
|
return joiValidator.body(Joi.object({
|
|
13
|
-
email: Joi.string().email(
|
|
13
|
+
email: Joi.string().email({
|
|
14
|
+
multiple: true,
|
|
15
|
+
}),
|
|
16
|
+
cc: Joi.string().email({
|
|
17
|
+
multiple: true,
|
|
18
|
+
}).allow("", null),
|
|
19
|
+
bcc: Joi.string().email({
|
|
20
|
+
multiple: true,
|
|
21
|
+
}).allow("", null),
|
|
14
22
|
purpose: Joi.string().valid(...Object.values(EmailTemplatePurpose)),
|
|
15
23
|
workspaceId: Joi.string().allow("", null),
|
|
16
24
|
from: Joi.string().allow("", null),
|
|
@@ -7,6 +7,7 @@ router
|
|
|
7
7
|
.post("/api/global/license/activate", controller.activate)
|
|
8
8
|
.post("/api/global/license/refresh", controller.refresh)
|
|
9
9
|
.get("/api/global/license/info", controller.getInfo)
|
|
10
|
+
.delete("/api/global/license/info", controller.deleteInfo)
|
|
10
11
|
.get("/api/global/license/usage", controller.getQuotaUsage)
|
|
11
12
|
|
|
12
13
|
export = router
|
|
@@ -2,7 +2,7 @@ const Router = require("@koa/router")
|
|
|
2
2
|
const controller = require("../../controllers/global/roles")
|
|
3
3
|
const { builderOrAdmin } = require("@budibase/backend-core/auth")
|
|
4
4
|
|
|
5
|
-
const router = Router()
|
|
5
|
+
const router = new Router()
|
|
6
6
|
|
|
7
7
|
router
|
|
8
8
|
.get("/api/global/roles", builderOrAdmin, controller.fetch)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Router from "@koa/router"
|
|
2
|
+
import * as controller from "../../controllers/global/self"
|
|
3
|
+
import { auth } from "@budibase/backend-core"
|
|
4
|
+
import { users } from "../validation"
|
|
5
|
+
|
|
6
|
+
const router = new Router()
|
|
7
|
+
|
|
8
|
+
router
|
|
9
|
+
.post("/api/global/self/api_key", auth.builderOnly, controller.generateAPIKey)
|
|
10
|
+
.get("/api/global/self/api_key", auth.builderOnly, controller.fetchAPIKey)
|
|
11
|
+
.get("/api/global/self", controller.getSelf)
|
|
12
|
+
.post(
|
|
13
|
+
"/api/global/self",
|
|
14
|
+
users.buildUserSaveValidation(true),
|
|
15
|
+
controller.updateSelf
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
export default router as any
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const { adminOnly } =
|
|
1
|
+
import Router from "@koa/router"
|
|
2
|
+
import * as controller from "../../controllers/global/templates"
|
|
3
|
+
import { TemplatePurpose, TemplateTypes } from "../../../constants"
|
|
4
|
+
import { auth as authCore } from "@budibase/backend-core"
|
|
5
|
+
import Joi from "joi"
|
|
6
|
+
const { adminOnly, joiValidator } = authCore
|
|
7
7
|
|
|
8
|
-
const router = Router()
|
|
8
|
+
const router = new Router()
|
|
9
9
|
|
|
10
10
|
function buildTemplateSaveValidation() {
|
|
11
11
|
// prettier-ignore
|
|
@@ -34,4 +34,4 @@ router
|
|
|
34
34
|
.get("/api/global/template/:id", controller.find)
|
|
35
35
|
.delete("/api/global/template/:id/:rev", adminOnly, controller.destroy)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
export default router
|
|
@@ -97,16 +97,16 @@ describe("/api/global/users", () => {
|
|
|
97
97
|
})
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
-
describe("
|
|
100
|
+
describe("bulk (create)", () => {
|
|
101
101
|
it("should ignore users existing in the same tenant", async () => {
|
|
102
102
|
const user = await config.createUser()
|
|
103
103
|
jest.clearAllMocks()
|
|
104
104
|
|
|
105
105
|
const response = await api.users.bulkCreateUsers([user])
|
|
106
106
|
|
|
107
|
-
expect(response.successful.length).toBe(0)
|
|
108
|
-
expect(response.unsuccessful.length).toBe(1)
|
|
109
|
-
expect(response.unsuccessful[0].email).toBe(user.email)
|
|
107
|
+
expect(response.created?.successful.length).toBe(0)
|
|
108
|
+
expect(response.created?.unsuccessful.length).toBe(1)
|
|
109
|
+
expect(response.created?.unsuccessful[0].email).toBe(user.email)
|
|
110
110
|
expect(events.user.created).toBeCalledTimes(0)
|
|
111
111
|
})
|
|
112
112
|
|
|
@@ -117,9 +117,9 @@ describe("/api/global/users", () => {
|
|
|
117
117
|
await tenancy.doInTenant(TENANT_1, async () => {
|
|
118
118
|
const response = await api.users.bulkCreateUsers([user])
|
|
119
119
|
|
|
120
|
-
expect(response.successful.length).toBe(0)
|
|
121
|
-
expect(response.unsuccessful.length).toBe(1)
|
|
122
|
-
expect(response.unsuccessful[0].email).toBe(user.email)
|
|
120
|
+
expect(response.created?.successful.length).toBe(0)
|
|
121
|
+
expect(response.created?.unsuccessful.length).toBe(1)
|
|
122
|
+
expect(response.created?.unsuccessful[0].email).toBe(user.email)
|
|
123
123
|
expect(events.user.created).toBeCalledTimes(0)
|
|
124
124
|
})
|
|
125
125
|
})
|
|
@@ -132,24 +132,24 @@ describe("/api/global/users", () => {
|
|
|
132
132
|
|
|
133
133
|
const response = await api.users.bulkCreateUsers([user])
|
|
134
134
|
|
|
135
|
-
expect(response.successful.length).toBe(0)
|
|
136
|
-
expect(response.unsuccessful.length).toBe(1)
|
|
137
|
-
expect(response.unsuccessful[0].email).toBe(user.email)
|
|
135
|
+
expect(response.created?.successful.length).toBe(0)
|
|
136
|
+
expect(response.created?.unsuccessful.length).toBe(1)
|
|
137
|
+
expect(response.created?.unsuccessful[0].email).toBe(user.email)
|
|
138
138
|
expect(events.user.created).toBeCalledTimes(0)
|
|
139
139
|
})
|
|
140
140
|
|
|
141
|
-
it("should be able to
|
|
141
|
+
it("should be able to bulk create users", async () => {
|
|
142
142
|
const builder = structures.users.builderUser()
|
|
143
143
|
const admin = structures.users.adminUser()
|
|
144
144
|
const user = structures.users.user()
|
|
145
145
|
|
|
146
146
|
const response = await api.users.bulkCreateUsers([builder, admin, user])
|
|
147
147
|
|
|
148
|
-
expect(response.successful.length).toBe(3)
|
|
149
|
-
expect(response.successful[0].email).toBe(builder.email)
|
|
150
|
-
expect(response.successful[1].email).toBe(admin.email)
|
|
151
|
-
expect(response.successful[2].email).toBe(user.email)
|
|
152
|
-
expect(response.unsuccessful.length).toBe(0)
|
|
148
|
+
expect(response.created?.successful.length).toBe(3)
|
|
149
|
+
expect(response.created?.successful[0].email).toBe(builder.email)
|
|
150
|
+
expect(response.created?.successful[1].email).toBe(admin.email)
|
|
151
|
+
expect(response.created?.successful[2].email).toBe(user.email)
|
|
152
|
+
expect(response.created?.unsuccessful.length).toBe(0)
|
|
153
153
|
expect(events.user.created).toBeCalledTimes(3)
|
|
154
154
|
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
|
|
155
155
|
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(2)
|
|
@@ -420,33 +420,30 @@ describe("/api/global/users", () => {
|
|
|
420
420
|
})
|
|
421
421
|
})
|
|
422
422
|
|
|
423
|
-
describe("
|
|
424
|
-
it("should not be able to
|
|
423
|
+
describe("bulk (delete)", () => {
|
|
424
|
+
it("should not be able to bulk delete current user", async () => {
|
|
425
425
|
const user = await config.defaultUser!
|
|
426
|
-
const request = { userIds: [user._id!] }
|
|
427
426
|
|
|
428
|
-
const response = await api.users.bulkDeleteUsers(
|
|
427
|
+
const response = await api.users.bulkDeleteUsers([user._id!], 400)
|
|
429
428
|
|
|
430
|
-
expect(response.
|
|
429
|
+
expect(response.message).toBe("Unable to delete self.")
|
|
431
430
|
expect(events.user.deleted).not.toBeCalled()
|
|
432
431
|
})
|
|
433
432
|
|
|
434
|
-
it("should not be able to
|
|
433
|
+
it("should not be able to bulk delete account owner", async () => {
|
|
435
434
|
const user = await config.createUser()
|
|
436
435
|
const account = structures.accounts.cloudAccount()
|
|
437
436
|
account.budibaseUserId = user._id!
|
|
438
437
|
mocks.accounts.getAccountByTenantId.mockReturnValue(account)
|
|
439
438
|
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
const response = await api.users.bulkDeleteUsers(request)
|
|
439
|
+
const response = await api.users.bulkDeleteUsers([user._id!])
|
|
443
440
|
|
|
444
|
-
expect(response.
|
|
445
|
-
expect(response.
|
|
446
|
-
expect(response.
|
|
441
|
+
expect(response.deleted?.successful.length).toBe(0)
|
|
442
|
+
expect(response.deleted?.unsuccessful.length).toBe(1)
|
|
443
|
+
expect(response.deleted?.unsuccessful[0].reason).toBe(
|
|
447
444
|
"Account holder cannot be deleted"
|
|
448
445
|
)
|
|
449
|
-
expect(response.
|
|
446
|
+
expect(response.deleted?.unsuccessful[0]._id).toBe(user._id)
|
|
450
447
|
expect(events.user.deleted).not.toBeCalled()
|
|
451
448
|
})
|
|
452
449
|
|
|
@@ -462,12 +459,14 @@ describe("/api/global/users", () => {
|
|
|
462
459
|
admin,
|
|
463
460
|
user,
|
|
464
461
|
])
|
|
465
|
-
const request = { userIds: createdUsers.successful.map(u => u._id!) }
|
|
466
462
|
|
|
467
|
-
const
|
|
463
|
+
const toDelete = createdUsers.created?.successful.map(
|
|
464
|
+
u => u._id!
|
|
465
|
+
) as string[]
|
|
466
|
+
const response = await api.users.bulkDeleteUsers(toDelete)
|
|
468
467
|
|
|
469
|
-
expect(response.
|
|
470
|
-
expect(response.
|
|
468
|
+
expect(response.deleted?.successful.length).toBe(3)
|
|
469
|
+
expect(response.deleted?.unsuccessful.length).toBe(0)
|
|
471
470
|
expect(events.user.deleted).toBeCalledTimes(3)
|
|
472
471
|
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
|
|
473
472
|
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(2)
|
|
@@ -8,7 +8,7 @@ const { users } = require("../validation")
|
|
|
8
8
|
const selfController = require("../../controllers/global/self")
|
|
9
9
|
const { builderOrAdmin } = require("@budibase/backend-core/auth")
|
|
10
10
|
|
|
11
|
-
const router = Router()
|
|
11
|
+
const router = new Router()
|
|
12
12
|
|
|
13
13
|
function buildAdminInitValidation() {
|
|
14
14
|
return joiValidator.body(
|
|
@@ -56,16 +56,15 @@ router
|
|
|
56
56
|
controller.save
|
|
57
57
|
)
|
|
58
58
|
.post(
|
|
59
|
-
"/api/global/users/
|
|
59
|
+
"/api/global/users/bulk",
|
|
60
60
|
adminOnly,
|
|
61
|
-
users.
|
|
62
|
-
controller.
|
|
61
|
+
users.buildUserBulkUserValidation(),
|
|
62
|
+
controller.bulkUpdate
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
.get("/api/global/users", builderOrAdmin, controller.fetch)
|
|
66
66
|
.post("/api/global/users/search", builderOrAdmin, controller.search)
|
|
67
67
|
.delete("/api/global/users/:id", adminOnly, controller.destroy)
|
|
68
|
-
.post("/api/global/users/bulkDelete", adminOnly, controller.bulkDelete)
|
|
69
68
|
.get("/api/global/users/count/:appId", builderOrAdmin, controller.countByApp)
|
|
70
69
|
.get("/api/global/roles/:appId")
|
|
71
70
|
.post(
|
|
@@ -74,12 +73,6 @@ router
|
|
|
74
73
|
buildInviteValidation(),
|
|
75
74
|
controller.invite
|
|
76
75
|
)
|
|
77
|
-
.post(
|
|
78
|
-
"/api/global/users/invite",
|
|
79
|
-
adminOnly,
|
|
80
|
-
buildInviteValidation(),
|
|
81
|
-
controller.invite
|
|
82
|
-
)
|
|
83
76
|
.post(
|
|
84
77
|
"/api/global/users/multi/invite",
|
|
85
78
|
adminOnly,
|
|
@@ -4,7 +4,7 @@ const { joiValidator } = require("@budibase/backend-core/auth")
|
|
|
4
4
|
const { adminOnly } = require("@budibase/backend-core/auth")
|
|
5
5
|
const Joi = require("joi")
|
|
6
6
|
|
|
7
|
-
const router = Router()
|
|
7
|
+
const router = new Router()
|
|
8
8
|
|
|
9
9
|
function buildWorkspaceSaveValidation() {
|
|
10
10
|
// prettier-ignore
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { api } from "@budibase/pro"
|
|
2
|
+
import userRoutes from "./global/users"
|
|
3
|
+
import configRoutes from "./global/configs"
|
|
4
|
+
import workspaceRoutes from "./global/workspaces"
|
|
5
|
+
import templateRoutes from "./global/templates"
|
|
6
|
+
import emailRoutes from "./global/email"
|
|
7
|
+
import authRoutes from "./global/auth"
|
|
8
|
+
import roleRoutes from "./global/roles"
|
|
9
|
+
import environmentRoutes from "./system/environment"
|
|
10
|
+
import tenantsRoutes from "./system/tenants"
|
|
11
|
+
import statusRoutes from "./system/status"
|
|
12
|
+
import selfRoutes from "./global/self"
|
|
13
|
+
import licenseRoutes from "./global/license"
|
|
14
|
+
import migrationRoutes from "./system/migrations"
|
|
15
|
+
import accountRoutes from "./system/accounts"
|
|
16
|
+
|
|
17
|
+
let userGroupRoutes = api.groups
|
|
18
|
+
export const routes = [
|
|
19
|
+
configRoutes,
|
|
20
|
+
userRoutes,
|
|
21
|
+
workspaceRoutes,
|
|
22
|
+
authRoutes,
|
|
23
|
+
templateRoutes,
|
|
24
|
+
tenantsRoutes,
|
|
25
|
+
emailRoutes,
|
|
26
|
+
roleRoutes,
|
|
27
|
+
environmentRoutes,
|
|
28
|
+
statusRoutes,
|
|
29
|
+
selfRoutes,
|
|
30
|
+
licenseRoutes,
|
|
31
|
+
userGroupRoutes,
|
|
32
|
+
migrationRoutes,
|
|
33
|
+
accountRoutes,
|
|
34
|
+
]
|
|
@@ -2,7 +2,7 @@ const Router = require("@koa/router")
|
|
|
2
2
|
const controller = require("../../controllers/system/tenants")
|
|
3
3
|
const { adminOnly } = require("@budibase/backend-core/auth")
|
|
4
4
|
|
|
5
|
-
const router = Router()
|
|
5
|
+
const router = new Router()
|
|
6
6
|
|
|
7
7
|
router
|
|
8
8
|
.get("/api/system/tenants/:tenantId/exists", controller.exists)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sdk from "../../../../sdk"
|
|
2
2
|
import { TestConfiguration, structures, API } from "../../../../tests"
|
|
3
3
|
import { v4 as uuid } from "uuid"
|
|
4
4
|
|
|
@@ -25,8 +25,8 @@ describe("accounts", () => {
|
|
|
25
25
|
|
|
26
26
|
const response = await api.accounts.saveMetadata(account)
|
|
27
27
|
|
|
28
|
-
const id = accounts.formatAccountMetadataId(account.accountId)
|
|
29
|
-
const metadata = await accounts.getMetadata(id)
|
|
28
|
+
const id = sdk.accounts.formatAccountMetadataId(account.accountId)
|
|
29
|
+
const metadata = await sdk.accounts.getMetadata(id)
|
|
30
30
|
expect(response).toStrictEqual(metadata)
|
|
31
31
|
})
|
|
32
32
|
})
|
|
@@ -38,7 +38,7 @@ describe("accounts", () => {
|
|
|
38
38
|
|
|
39
39
|
await api.accounts.destroyMetadata(account.accountId)
|
|
40
40
|
|
|
41
|
-
const deleted = await accounts.getMetadata(account.accountId)
|
|
41
|
+
const deleted = await sdk.accounts.getMetadata(account.accountId)
|
|
42
42
|
expect(deleted).toBe(undefined)
|
|
43
43
|
})
|
|
44
44
|
|
|
@@ -28,7 +28,7 @@ export const buildUserSaveValidation = (isSelf = false) => {
|
|
|
28
28
|
return joiValidator.body(Joi.object(schema).required().unknown(true))
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export const
|
|
31
|
+
export const buildUserBulkUserValidation = (isSelf = false) => {
|
|
32
32
|
if (!isSelf) {
|
|
33
33
|
schema = {
|
|
34
34
|
...schema,
|
|
@@ -36,10 +36,15 @@ export const buildUserBulkSaveValidation = (isSelf = false) => {
|
|
|
36
36
|
_rev: Joi.string(),
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
let bulkSchema = {
|
|
40
|
+
create: Joi.object({
|
|
41
|
+
groups: Joi.array().optional(),
|
|
42
|
+
users: Joi.array().items(Joi.object(schema).required().unknown(true)),
|
|
43
|
+
}),
|
|
44
|
+
delete: Joi.object({
|
|
45
|
+
userIds: Joi.array().items(Joi.string()),
|
|
46
|
+
}),
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
return joiValidator.body(Joi.object(
|
|
49
|
+
return joiValidator.body(Joi.object(bulkSchema).required().unknown(true))
|
|
45
50
|
}
|
package/src/db/index.js
CHANGED
package/src/sdk/index.ts
CHANGED
package/src/sdk/users/users.ts
CHANGED
|
@@ -19,9 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
AccountMetadata,
|
|
21
21
|
AllDocsResponse,
|
|
22
|
-
|
|
23
|
-
BulkDeleteUsersResponse,
|
|
24
|
-
BulkDocsResponse,
|
|
22
|
+
BulkUserResponse,
|
|
25
23
|
CloudAccount,
|
|
26
24
|
CreateUserResponse,
|
|
27
25
|
InviteUsersRequest,
|
|
@@ -31,9 +29,9 @@ import {
|
|
|
31
29
|
RowResponse,
|
|
32
30
|
User,
|
|
33
31
|
} from "@budibase/types"
|
|
34
|
-
import { groups as groupUtils } from "@budibase/pro"
|
|
35
32
|
import { sendEmail } from "../../utilities/email"
|
|
36
33
|
import { EmailTemplatePurpose } from "../../constants"
|
|
34
|
+
import { groups as groupsSdk } from "@budibase/pro"
|
|
37
35
|
|
|
38
36
|
const PAGE_LIMIT = 8
|
|
39
37
|
|
|
@@ -349,8 +347,7 @@ const searchExistingEmails = async (emails: string[]) => {
|
|
|
349
347
|
export const bulkCreate = async (
|
|
350
348
|
newUsersRequested: User[],
|
|
351
349
|
groups: string[]
|
|
352
|
-
): Promise<
|
|
353
|
-
const db = tenancy.getGlobalDB()
|
|
350
|
+
): Promise<BulkUserResponse["created"]> => {
|
|
354
351
|
const tenantId = tenancy.getTenantId()
|
|
355
352
|
|
|
356
353
|
let usersToSave: any[] = []
|
|
@@ -392,9 +389,9 @@ export const bulkCreate = async (
|
|
|
392
389
|
})
|
|
393
390
|
|
|
394
391
|
const usersToBulkSave = await Promise.all(usersToSave)
|
|
395
|
-
await
|
|
392
|
+
await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
|
|
396
393
|
|
|
397
|
-
// Post
|
|
394
|
+
// Post-processing of bulk added users, e.g. events and cache operations
|
|
398
395
|
for (const user of usersToBulkSave) {
|
|
399
396
|
// TODO: Refactor to bulk insert users into the info db
|
|
400
397
|
// instead of relying on looping tenant creation
|
|
@@ -410,6 +407,16 @@ export const bulkCreate = async (
|
|
|
410
407
|
}
|
|
411
408
|
})
|
|
412
409
|
|
|
410
|
+
// now update the groups
|
|
411
|
+
if (Array.isArray(saved) && groups) {
|
|
412
|
+
const groupPromises = []
|
|
413
|
+
const createdUserIds = saved.map(user => user._id)
|
|
414
|
+
for (let groupId of groups) {
|
|
415
|
+
groupPromises.push(groupsSdk.addUsers(groupId, createdUserIds))
|
|
416
|
+
}
|
|
417
|
+
await Promise.all(groupPromises)
|
|
418
|
+
}
|
|
419
|
+
|
|
413
420
|
return {
|
|
414
421
|
successful: saved,
|
|
415
422
|
unsuccessful,
|
|
@@ -438,10 +445,10 @@ const getAccountHolderFromUserIds = async (
|
|
|
438
445
|
|
|
439
446
|
export const bulkDelete = async (
|
|
440
447
|
userIds: string[]
|
|
441
|
-
): Promise<
|
|
448
|
+
): Promise<BulkUserResponse["deleted"]> => {
|
|
442
449
|
const db = tenancy.getGlobalDB()
|
|
443
450
|
|
|
444
|
-
const response:
|
|
451
|
+
const response: BulkUserResponse["deleted"] = {
|
|
445
452
|
successful: [],
|
|
446
453
|
unsuccessful: [],
|
|
447
454
|
}
|
|
@@ -458,7 +465,6 @@ export const bulkDelete = async (
|
|
|
458
465
|
})
|
|
459
466
|
}
|
|
460
467
|
|
|
461
|
-
let groupsToModify: any = {}
|
|
462
468
|
// Get users and delete
|
|
463
469
|
const allDocsResponse: AllDocsResponse<User> = await db.allDocs({
|
|
464
470
|
include_docs: true,
|
|
@@ -466,33 +472,16 @@ export const bulkDelete = async (
|
|
|
466
472
|
})
|
|
467
473
|
const usersToDelete: User[] = allDocsResponse.rows.map(
|
|
468
474
|
(user: RowResponse<User>) => {
|
|
469
|
-
// if we find a user that has an associated group, add it to
|
|
470
|
-
// an array so we can easily use allDocs on them later.
|
|
471
|
-
// This prevents us having to re-loop over all the users
|
|
472
|
-
if (user.doc.userGroups) {
|
|
473
|
-
for (let groupId of user.doc.userGroups) {
|
|
474
|
-
if (!Object.keys(groupsToModify).includes(groupId)) {
|
|
475
|
-
groupsToModify[groupId] = [user.id]
|
|
476
|
-
} else {
|
|
477
|
-
groupsToModify[groupId] = [...groupsToModify[groupId], user.id]
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
475
|
return user.doc
|
|
483
476
|
}
|
|
484
477
|
)
|
|
485
478
|
|
|
486
479
|
// Delete from DB
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
// Deletion post processing
|
|
495
|
-
await groupUtils.bulkDeleteGroupUsers(groupsToModify)
|
|
480
|
+
const toDelete = usersToDelete.map(user => ({
|
|
481
|
+
...user,
|
|
482
|
+
_deleted: true,
|
|
483
|
+
}))
|
|
484
|
+
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
|
|
496
485
|
for (let user of usersToDelete) {
|
|
497
486
|
await bulkDeleteProcessing(user)
|
|
498
487
|
}
|
|
@@ -526,7 +515,6 @@ export const destroy = async (id: string, currentUser: any) => {
|
|
|
526
515
|
const db = tenancy.getGlobalDB()
|
|
527
516
|
const dbUser = await db.get(id)
|
|
528
517
|
const userId = dbUser._id as string
|
|
529
|
-
let groups = dbUser.userGroups
|
|
530
518
|
|
|
531
519
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
|
532
520
|
// root account holder can't be deleted from inside budibase
|
|
@@ -545,10 +533,6 @@ export const destroy = async (id: string, currentUser: any) => {
|
|
|
545
533
|
|
|
546
534
|
await db.remove(userId, dbUser._rev)
|
|
547
535
|
|
|
548
|
-
if (groups) {
|
|
549
|
-
await groupUtils.deleteGroupUsers(groups, dbUser)
|
|
550
|
-
}
|
|
551
|
-
|
|
552
536
|
await eventHelpers.handleDeleteEvents(dbUser)
|
|
553
537
|
await cache.user.invalidateUser(userId)
|
|
554
538
|
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
package/src/tests/api/users.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
BulkDeleteUsersRequest,
|
|
5
|
-
BulkDeleteUsersResponse,
|
|
2
|
+
BulkUserResponse,
|
|
3
|
+
BulkUserRequest,
|
|
6
4
|
InviteUsersRequest,
|
|
7
5
|
User,
|
|
8
6
|
} from "@budibase/types"
|
|
@@ -69,24 +67,26 @@ export class UserAPI {
|
|
|
69
67
|
// BULK
|
|
70
68
|
|
|
71
69
|
bulkCreateUsers = async (users: User[], groups: any[] = []) => {
|
|
72
|
-
const body:
|
|
70
|
+
const body: BulkUserRequest = { create: { users, groups } }
|
|
73
71
|
const res = await this.request
|
|
74
|
-
.post(`/api/global/users/
|
|
72
|
+
.post(`/api/global/users/bulk`)
|
|
75
73
|
.send(body)
|
|
76
74
|
.set(this.config.defaultHeaders())
|
|
77
75
|
.expect("Content-Type", /json/)
|
|
78
76
|
.expect(200)
|
|
79
77
|
|
|
80
|
-
return res.body as
|
|
78
|
+
return res.body as BulkUserResponse
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
bulkDeleteUsers = (
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
bulkDeleteUsers = async (userIds: string[], status?: number) => {
|
|
82
|
+
const body: BulkUserRequest = { delete: { userIds } }
|
|
83
|
+
const res = await this.request
|
|
84
|
+
.post(`/api/global/users/bulk`)
|
|
86
85
|
.send(body)
|
|
87
86
|
.set(this.config.defaultHeaders())
|
|
88
87
|
.expect("Content-Type", /json/)
|
|
89
88
|
.expect(status ? status : 200)
|
|
89
|
+
return res.body as BulkUserResponse
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// USER
|
package/src/utilities/email.js
CHANGED
|
@@ -174,7 +174,7 @@ exports.isEmailConfigured = async (workspaceId = null) => {
|
|
|
174
174
|
exports.sendEmail = async (
|
|
175
175
|
email,
|
|
176
176
|
purpose,
|
|
177
|
-
{ workspaceId, user, from, contents, subject, info, automation } = {}
|
|
177
|
+
{ workspaceId, user, from, contents, subject, info, cc, bcc, automation } = {}
|
|
178
178
|
) => {
|
|
179
179
|
const db = getGlobalDB()
|
|
180
180
|
let config = (await getSmtpConfiguration(db, workspaceId, automation)) || {}
|
|
@@ -197,6 +197,8 @@ exports.sendEmail = async (
|
|
|
197
197
|
message = {
|
|
198
198
|
...message,
|
|
199
199
|
to: email,
|
|
200
|
+
cc: cc,
|
|
201
|
+
bcc: bcc,
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
if (subject || config.subject) {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const Router = require("@koa/router")
|
|
2
|
-
const controller = require("../../controllers/global/self")
|
|
3
|
-
const { builderOnly } = require("@budibase/backend-core/auth")
|
|
4
|
-
const { users } = require("../validation")
|
|
5
|
-
|
|
6
|
-
const router = Router()
|
|
7
|
-
|
|
8
|
-
router
|
|
9
|
-
.post("/api/global/self/api_key", builderOnly, controller.generateAPIKey)
|
|
10
|
-
.get("/api/global/self/api_key", builderOnly, controller.fetchAPIKey)
|
|
11
|
-
.get("/api/global/self", controller.getSelf)
|
|
12
|
-
.post(
|
|
13
|
-
"/api/global/self",
|
|
14
|
-
users.buildUserSaveValidation(true),
|
|
15
|
-
controller.updateSelf
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
module.exports = router
|
package/src/api/routes/index.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const { api } = require("@budibase/pro")
|
|
2
|
-
const userRoutes = require("./global/users")
|
|
3
|
-
const configRoutes = require("./global/configs")
|
|
4
|
-
const workspaceRoutes = require("./global/workspaces")
|
|
5
|
-
const templateRoutes = require("./global/templates")
|
|
6
|
-
const emailRoutes = require("./global/email")
|
|
7
|
-
const authRoutes = require("./global/auth")
|
|
8
|
-
const roleRoutes = require("./global/roles")
|
|
9
|
-
const environmentRoutes = require("./system/environment")
|
|
10
|
-
const tenantsRoutes = require("./system/tenants")
|
|
11
|
-
const statusRoutes = require("./system/status")
|
|
12
|
-
const selfRoutes = require("./global/self")
|
|
13
|
-
const licenseRoutes = require("./global/license")
|
|
14
|
-
const migrationRoutes = require("./system/migrations")
|
|
15
|
-
const accountRoutes = require("./system/accounts")
|
|
16
|
-
|
|
17
|
-
let userGroupRoutes = api.groups
|
|
18
|
-
exports.routes = [
|
|
19
|
-
configRoutes,
|
|
20
|
-
userRoutes,
|
|
21
|
-
workspaceRoutes,
|
|
22
|
-
authRoutes,
|
|
23
|
-
templateRoutes,
|
|
24
|
-
tenantsRoutes,
|
|
25
|
-
emailRoutes,
|
|
26
|
-
roleRoutes,
|
|
27
|
-
environmentRoutes,
|
|
28
|
-
statusRoutes,
|
|
29
|
-
selfRoutes,
|
|
30
|
-
licenseRoutes,
|
|
31
|
-
userGroupRoutes,
|
|
32
|
-
migrationRoutes,
|
|
33
|
-
accountRoutes,
|
|
34
|
-
]
|