@budibase/worker 3.12.4 → 3.12.6

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/worker",
3
3
  "email": "hi@budibase.com",
4
- "version": "3.12.4",
4
+ "version": "3.12.6",
5
5
  "description": "Budibase background service",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -123,5 +123,5 @@
123
123
  }
124
124
  }
125
125
  },
126
- "gitHead": "e267231b6e4eff719290f0d07b6a25ff56f4371d"
126
+ "gitHead": "a9dabfd71bc85ee7bff3ad4c1421e1478987d0a2"
127
127
  }
@@ -13,7 +13,6 @@ import {
13
13
  } from "@budibase/backend-core"
14
14
  import { checkAnyUserExists } from "../../../utilities/users"
15
15
  import {
16
- AIConfig,
17
16
  AIInnerConfig,
18
17
  Config,
19
18
  ConfigChecklistResponse,
@@ -36,6 +35,7 @@ import {
36
35
  SaveConfigResponse,
37
36
  SettingsBrandingConfig,
38
37
  SettingsInnerConfig,
38
+ SMTPInnerConfig,
39
39
  SSOConfig,
40
40
  SSOConfigType,
41
41
  UploadConfigFileResponse,
@@ -158,7 +158,23 @@ async function hasActivatedConfig(ssoConfigs?: SSOConfigs) {
158
158
  return !!Object.values(ssoConfigs).find(c => c?.activated)
159
159
  }
160
160
 
161
- async function verifySettingsConfig(
161
+ async function processSMTPConfig(
162
+ config: SMTPInnerConfig,
163
+ existingConfig?: SMTPInnerConfig
164
+ ) {
165
+ await email.verifyConfig(config)
166
+ if (config.auth?.pass === PASSWORD_REPLACEMENT) {
167
+ // if the password is being replaced, use the existing password
168
+ if (existingConfig && existingConfig.auth?.pass) {
169
+ config.auth.pass = existingConfig.auth.pass
170
+ } else {
171
+ // otherwise, throw an error
172
+ throw new BadRequestError("SMTP password is required")
173
+ }
174
+ }
175
+ }
176
+
177
+ async function processSettingsConfig(
162
178
  config: SettingsInnerConfig & SettingsBrandingConfig,
163
179
  existingConfig?: SettingsInnerConfig & SettingsBrandingConfig
164
180
  ) {
@@ -203,19 +219,37 @@ async function verifySSOConfig(type: SSOConfigType, config: SSOConfig) {
203
219
  }
204
220
  }
205
221
 
206
- async function verifyGoogleConfig(config: GoogleInnerConfig) {
222
+ async function processGoogleConfig(
223
+ config: GoogleInnerConfig,
224
+ existing?: GoogleInnerConfig
225
+ ) {
207
226
  await verifySSOConfig(ConfigType.GOOGLE, config)
227
+
228
+ if (existing && config.clientSecret === PASSWORD_REPLACEMENT) {
229
+ config.clientSecret = existing.clientSecret
230
+ }
208
231
  }
209
232
 
210
- async function verifyOIDCConfig(config: OIDCConfigs) {
233
+ async function processOIDCConfig(config: OIDCConfigs, existing?: OIDCConfigs) {
211
234
  await verifySSOConfig(ConfigType.OIDC, config.configs[0])
235
+
236
+ if (existing) {
237
+ for (const c of config.configs) {
238
+ const existingConfig = existing.configs.find(e => e.uuid === c.uuid)
239
+ if (!existingConfig) {
240
+ continue
241
+ }
242
+ if (c.clientSecret === PASSWORD_REPLACEMENT) {
243
+ c.clientSecret = existingConfig.clientSecret
244
+ }
245
+ }
246
+ }
212
247
  }
213
248
 
214
249
  export async function processAIConfig(
215
250
  newConfig: AIInnerConfig,
216
251
  existingConfig: AIInnerConfig
217
252
  ) {
218
- // ensure that the redacted API keys are not overwritten in the DB
219
253
  for (const key in existingConfig) {
220
254
  if (newConfig[key]?.apiKey === PASSWORD_REPLACEMENT) {
221
255
  newConfig[key].apiKey = existingConfig[key].apiKey
@@ -256,16 +290,16 @@ export async function save(
256
290
  try {
257
291
  switch (type) {
258
292
  case ConfigType.SMTP:
259
- await email.verifyConfig(config)
293
+ await processSMTPConfig(config, existingConfig?.config)
260
294
  break
261
295
  case ConfigType.SETTINGS:
262
- await verifySettingsConfig(config, existingConfig?.config)
296
+ await processSettingsConfig(config, existingConfig?.config)
263
297
  break
264
298
  case ConfigType.GOOGLE:
265
- await verifyGoogleConfig(config)
299
+ await processGoogleConfig(config, existingConfig?.config)
266
300
  break
267
301
  case ConfigType.OIDC:
268
- await verifyOIDCConfig(config)
302
+ await processOIDCConfig(config, existingConfig?.config)
269
303
  break
270
304
  case ConfigType.AI:
271
305
  if (existingConfig) {
@@ -353,45 +387,48 @@ async function enrichOIDCLogos(oidcLogos: OIDCLogosConfig) {
353
387
  }
354
388
 
355
389
  export async function find(ctx: UserCtx<void, FindConfigResponse>) {
356
- try {
357
- // Find the config with the most granular scope based on context
358
- const type = ctx.params.type
359
- let scopedConfig = await configs.getConfig(type)
360
-
361
- if (scopedConfig) {
362
- await handleConfigType(type, scopedConfig)
363
- } else if (type === ConfigType.AI) {
364
- scopedConfig = { type: ConfigType.AI, config: {} }
365
- await handleAIConfig(scopedConfig)
366
- } else {
367
- // If no config found and not AI type, just return an empty body
368
- ctx.body = {}
369
- return
370
- }
390
+ // Find the config with the most granular scope based on context
391
+ const type = ctx.params.type
392
+ let config = await configs.getConfig(type)
371
393
 
372
- ctx.body = scopedConfig
373
- } catch (err: any) {
374
- ctx.throw(err?.status || 400, err)
394
+ if (!config && type === ConfigType.AI) {
395
+ config = { type: ConfigType.AI, config: {} }
396
+ }
397
+
398
+ if (!config) {
399
+ ctx.body = {}
400
+ return
375
401
  }
376
- }
377
402
 
378
- async function handleConfigType(type: ConfigType, config: Config) {
379
- if (type === ConfigType.OIDC_LOGOS) {
380
- await enrichOIDCLogos(config)
381
- } else if (type === ConfigType.AI) {
382
- await handleAIConfig(config)
403
+ switch (type) {
404
+ case ConfigType.OIDC_LOGOS:
405
+ await enrichOIDCLogos(config)
406
+ break
407
+ case ConfigType.AI:
408
+ await pro.sdk.ai.enrichAIConfig(config)
409
+ break
383
410
  }
384
- }
385
411
 
386
- async function handleAIConfig(config: AIConfig) {
387
- await pro.sdk.ai.enrichAIConfig(config)
388
- stripApiKeys(config)
412
+ stripSecrets(config)
413
+ ctx.body = config
389
414
  }
390
415
 
391
- function stripApiKeys(config: AIConfig) {
392
- for (const key in config?.config) {
393
- if (config.config[key].apiKey) {
394
- config.config[key].apiKey = PASSWORD_REPLACEMENT
416
+ function stripSecrets(config: Config) {
417
+ if (isAIConfig(config)) {
418
+ for (const key in config.config) {
419
+ if (config.config[key].apiKey) {
420
+ config.config[key].apiKey = PASSWORD_REPLACEMENT
421
+ }
422
+ }
423
+ } else if (isSMTPConfig(config)) {
424
+ if (config.config.auth?.pass) {
425
+ config.config.auth.pass = PASSWORD_REPLACEMENT
426
+ }
427
+ } else if (isGoogleConfig(config)) {
428
+ config.config.clientSecret = PASSWORD_REPLACEMENT
429
+ } else if (isOIDCConfig(config)) {
430
+ for (const c of config.config.configs) {
431
+ c.clientSecret = PASSWORD_REPLACEMENT
395
432
  }
396
433
  }
397
434
  }
@@ -33,6 +33,7 @@ import {
33
33
  SaveUserResponse,
34
34
  SearchUsersRequest,
35
35
  SearchUsersResponse,
36
+ StrippedUser,
36
37
  UnsavedUser,
37
38
  UpdateInviteRequest,
38
39
  UpdateInviteResponse,
@@ -66,6 +67,15 @@ const generatePassword = (length: number) => {
66
67
  .slice(0, length)
67
68
  }
68
69
 
70
+ const stripUsers = (users: (User | StrippedUser)[]): StrippedUser[] => {
71
+ return users.map(user => ({
72
+ _id: user._id,
73
+ email: user.email,
74
+ tenantId: user.tenantId,
75
+ userId: user.userId,
76
+ }))
77
+ }
78
+
69
79
  export const save = async (ctx: UserCtx<UnsavedUser, SaveUserResponse>) => {
70
80
  try {
71
81
  const currentUserId = ctx.user?._id
@@ -249,18 +259,8 @@ export const destroy = async (ctx: UserCtx<void, DeleteUserResponse>) => {
249
259
  }
250
260
  }
251
261
 
252
- export const getAppUsers = async (ctx: Ctx<SearchUsersRequest>) => {
253
- const body = ctx.request.body
254
- const users = await userSdk.db.getUsersByAppAccess({
255
- appId: body.appId,
256
- limit: body.limit,
257
- })
258
-
259
- ctx.body = { data: users }
260
- }
261
-
262
262
  export const search = async (
263
- ctx: Ctx<SearchUsersRequest, SearchUsersResponse>
263
+ ctx: UserCtx<SearchUsersRequest, SearchUsersResponse>
264
264
  ) => {
265
265
  const body = ctx.request.body
266
266
 
@@ -287,8 +287,13 @@ export const search = async (
287
287
  }
288
288
  }
289
289
 
290
+ let response: SearchUsersResponse = { data: [] }
291
+
290
292
  if (body.paginate === false) {
291
- await getAppUsers(ctx)
293
+ response.data = await userSdk.db.getUsersByAppAccess({
294
+ appId: body.appId,
295
+ limit: body.limit,
296
+ })
292
297
  } else {
293
298
  const paginated = await userSdk.core.paginatedUsers(body)
294
299
  // user hashed password shouldn't ever be returned
@@ -297,8 +302,18 @@ export const search = async (
297
302
  delete user.password
298
303
  }
299
304
  }
300
- ctx.body = paginated
305
+ response = {
306
+ data: paginated.data,
307
+ hasNextPage: paginated.hasNextPage,
308
+ nextPage: paginated.nextPage,
309
+ }
301
310
  }
311
+
312
+ if (!users.hasBuilderPermissions(ctx.user)) {
313
+ response.data = stripUsers(response.data)
314
+ }
315
+
316
+ ctx.body = response
302
317
  }
303
318
 
304
319
  // called internally by app server user fetch
@@ -1,19 +1,17 @@
1
- // mock the email system
2
1
  jest.mock("nodemailer")
3
2
  import { TestConfiguration, structures, mocks } from "../../../../tests"
4
3
 
5
4
  mocks.email.mock()
6
- import { events } from "@budibase/backend-core"
5
+ import { configs, events } from "@budibase/backend-core"
7
6
  import { GetPublicSettingsResponse, Config, ConfigType } from "@budibase/types"
8
7
 
8
+ const { google, smtp, settings, oidc } = structures.configs
9
+
9
10
  describe("configs", () => {
10
11
  const config = new TestConfiguration()
11
12
 
12
13
  beforeEach(async () => {
13
14
  await config.beforeAll()
14
- })
15
-
16
- beforeEach(() => {
17
15
  jest.clearAllMocks()
18
16
  })
19
17
 
@@ -22,38 +20,20 @@ describe("configs", () => {
22
20
  })
23
21
 
24
22
  const saveConfig = async (conf: Config, _id?: string, _rev?: string) => {
25
- const data = {
26
- ...conf,
27
- _id,
28
- _rev,
29
- }
23
+ const data = { ...conf, _id, _rev }
30
24
  const res = await config.api.configs.saveConfig(data)
31
25
  return { ...data, ...res }
32
26
  }
33
27
 
34
- const saveSettingsConfig = async (
35
- conf?: any,
36
- _id?: string,
37
- _rev?: string
38
- ) => {
39
- const settingsConfig = structures.configs.settings(conf)
40
- return saveConfig(settingsConfig, _id, _rev)
41
- }
42
-
43
28
  describe("POST /api/global/configs", () => {
44
29
  describe("google", () => {
45
- const saveGoogleConfig = async (
46
- conf?: any,
47
- _id?: string,
48
- _rev?: string
49
- ) => {
50
- const googleConfig = structures.configs.google(conf)
51
- return saveConfig(googleConfig, _id, _rev)
52
- }
30
+ afterEach(async () => {
31
+ await config.deleteConfig(ConfigType.GOOGLE)
32
+ })
53
33
 
54
34
  describe("create", () => {
55
35
  it("should create activated google config", async () => {
56
- await saveGoogleConfig()
36
+ await saveConfig(google())
57
37
  expect(events.auth.SSOCreated).toHaveBeenCalledTimes(1)
58
38
  expect(events.auth.SSOCreated).toHaveBeenCalledWith(ConfigType.GOOGLE)
59
39
  expect(events.auth.SSODeactivated).not.toHaveBeenCalled()
@@ -61,25 +41,23 @@ describe("configs", () => {
61
41
  expect(events.auth.SSOActivated).toHaveBeenCalledWith(
62
42
  ConfigType.GOOGLE
63
43
  )
64
- await config.deleteConfig(ConfigType.GOOGLE)
65
44
  })
66
45
 
67
46
  it("should create deactivated google config", async () => {
68
- await saveGoogleConfig({ activated: false })
47
+ await saveConfig(google({ activated: false }))
69
48
  expect(events.auth.SSOCreated).toHaveBeenCalledTimes(1)
70
49
  expect(events.auth.SSOCreated).toHaveBeenCalledWith(ConfigType.GOOGLE)
71
50
  expect(events.auth.SSOActivated).not.toHaveBeenCalled()
72
51
  expect(events.auth.SSODeactivated).not.toHaveBeenCalled()
73
- await config.deleteConfig(ConfigType.GOOGLE)
74
52
  })
75
53
  })
76
54
 
77
55
  describe("update", () => {
78
56
  it("should update google config to deactivated", async () => {
79
- const googleConf = await saveGoogleConfig()
57
+ const googleConf = await saveConfig(google())
80
58
  jest.clearAllMocks()
81
- await saveGoogleConfig(
82
- { ...googleConf.config, activated: false },
59
+ await saveConfig(
60
+ google({ activated: false }),
83
61
  googleConf._id,
84
62
  googleConf._rev
85
63
  )
@@ -90,14 +68,13 @@ describe("configs", () => {
90
68
  expect(events.auth.SSODeactivated).toHaveBeenCalledWith(
91
69
  ConfigType.GOOGLE
92
70
  )
93
- await config.deleteConfig(ConfigType.GOOGLE)
94
71
  })
95
72
 
96
73
  it("should update google config to activated", async () => {
97
- const googleConf = await saveGoogleConfig({ activated: false })
74
+ const googleConf = await saveConfig(google({ activated: false }))
98
75
  jest.clearAllMocks()
99
- await saveGoogleConfig(
100
- { ...googleConf.config, activated: true },
76
+ await saveConfig(
77
+ google({ activated: true }),
101
78
  googleConf._id,
102
79
  googleConf._rev
103
80
  )
@@ -108,48 +85,64 @@ describe("configs", () => {
108
85
  expect(events.auth.SSOActivated).toHaveBeenCalledWith(
109
86
  ConfigType.GOOGLE
110
87
  )
111
- await config.deleteConfig(ConfigType.GOOGLE)
88
+ })
89
+
90
+ it("should not overwrite secret when updating google config", async () => {
91
+ await saveConfig(google({ clientSecret: "spooky" }))
92
+
93
+ const conf = await config.api.configs.getConfig(ConfigType.GOOGLE)
94
+ await saveConfig(conf)
95
+
96
+ await config.doInTenant(async () => {
97
+ const rawConf = await configs.getGoogleConfig()
98
+ expect(rawConf!.clientSecret).toEqual("spooky")
99
+ })
100
+ })
101
+ })
102
+
103
+ describe("get", () => {
104
+ it("should not leak credentials", async () => {
105
+ await saveConfig(google())
106
+ const conf = await config.api.configs.getConfig(ConfigType.GOOGLE)
107
+ expect(conf.config.clientSecret).toEqual("--secret-value--")
112
108
  })
113
109
  })
114
110
  })
115
111
 
116
112
  describe("oidc", () => {
117
- const saveOIDCConfig = async (
118
- conf?: any,
119
- _id?: string,
120
- _rev?: string
121
- ) => {
122
- const oidcConfig = structures.configs.oidc(conf)
123
- return saveConfig(oidcConfig, _id, _rev)
124
- }
113
+ beforeEach(async () => {
114
+ await config.deleteConfig(ConfigType.OIDC)
115
+ })
116
+
117
+ afterEach(async () => {
118
+ await config.deleteConfig(ConfigType.OIDC)
119
+ })
125
120
 
126
121
  describe("create", () => {
127
122
  it("should create activated OIDC config", async () => {
128
- await saveOIDCConfig()
123
+ await saveConfig(oidc())
129
124
  expect(events.auth.SSOCreated).toHaveBeenCalledTimes(1)
130
125
  expect(events.auth.SSOCreated).toHaveBeenCalledWith(ConfigType.OIDC)
131
126
  expect(events.auth.SSODeactivated).not.toHaveBeenCalled()
132
127
  expect(events.auth.SSOActivated).toHaveBeenCalledTimes(1)
133
128
  expect(events.auth.SSOActivated).toHaveBeenCalledWith(ConfigType.OIDC)
134
- await config.deleteConfig(ConfigType.OIDC)
135
129
  })
136
130
 
137
131
  it("should create deactivated OIDC config", async () => {
138
- await saveOIDCConfig({ activated: false })
132
+ await saveConfig(oidc({ activated: false }))
139
133
  expect(events.auth.SSOCreated).toHaveBeenCalledTimes(1)
140
134
  expect(events.auth.SSOCreated).toHaveBeenCalledWith(ConfigType.OIDC)
141
135
  expect(events.auth.SSOActivated).not.toHaveBeenCalled()
142
136
  expect(events.auth.SSODeactivated).not.toHaveBeenCalled()
143
- await config.deleteConfig(ConfigType.OIDC)
144
137
  })
145
138
  })
146
139
 
147
140
  describe("update", () => {
148
141
  it("should update OIDC config to deactivated", async () => {
149
- const oidcConf = await saveOIDCConfig()
142
+ const oidcConf = await saveConfig(oidc())
150
143
  jest.clearAllMocks()
151
- await saveOIDCConfig(
152
- { ...oidcConf.config.configs[0], activated: false },
144
+ await saveConfig(
145
+ oidc({ activated: false }),
153
146
  oidcConf._id,
154
147
  oidcConf._rev
155
148
  )
@@ -160,14 +153,13 @@ describe("configs", () => {
160
153
  expect(events.auth.SSODeactivated).toHaveBeenCalledWith(
161
154
  ConfigType.OIDC
162
155
  )
163
- await config.deleteConfig(ConfigType.OIDC)
164
156
  })
165
157
 
166
158
  it("should update OIDC config to activated", async () => {
167
- const oidcConf = await saveOIDCConfig({ activated: false })
159
+ const oidcConf = await saveConfig(oidc({ activated: false }))
168
160
  jest.clearAllMocks()
169
- await saveOIDCConfig(
170
- { ...oidcConf.config.configs[0], activated: true },
161
+ await saveConfig(
162
+ oidc({ activated: true }),
171
163
  oidcConf._id,
172
164
  oidcConf._rev
173
165
  )
@@ -176,50 +168,88 @@ describe("configs", () => {
176
168
  expect(events.auth.SSODeactivated).not.toHaveBeenCalled()
177
169
  expect(events.auth.SSOActivated).toHaveBeenCalledTimes(1)
178
170
  expect(events.auth.SSOActivated).toHaveBeenCalledWith(ConfigType.OIDC)
179
- await config.deleteConfig(ConfigType.OIDC)
171
+ })
172
+
173
+ it("should not overwrite secret when updating OIDC config", async () => {
174
+ await saveConfig(oidc({ clientSecret: "spooky" }))
175
+ const conf = await config.api.configs.getConfig(ConfigType.OIDC)
176
+ await saveConfig(conf)
177
+ await config.doInTenant(async () => {
178
+ const rawConf = await configs.getOIDCConfig()
179
+ expect(rawConf!.clientSecret).toEqual("spooky")
180
+ })
181
+ })
182
+ })
183
+
184
+ describe("get", () => {
185
+ it("should not leak credentials", async () => {
186
+ await saveConfig(oidc({ clientSecret: "spooky" }))
187
+ const conf = await config.api.configs.getConfig(ConfigType.OIDC)
188
+ expect(conf.config.configs[0].clientSecret).toEqual(
189
+ "--secret-value--"
190
+ )
180
191
  })
181
192
  })
182
193
  })
183
194
 
184
195
  describe("smtp", () => {
185
- const saveSMTPConfig = async (
186
- conf?: any,
187
- _id?: string,
188
- _rev?: string
189
- ) => {
190
- const smtpConfig = structures.configs.smtp(conf)
191
- return saveConfig(smtpConfig, _id, _rev)
192
- }
196
+ beforeEach(async () => {
197
+ await config.deleteConfig(ConfigType.SMTP)
198
+ })
199
+
200
+ afterEach(async () => {
201
+ await config.deleteConfig(ConfigType.SMTP)
202
+ })
193
203
 
194
204
  describe("create", () => {
195
205
  it("should create SMTP config", async () => {
196
- await config.deleteConfig(ConfigType.SMTP)
197
- await saveSMTPConfig()
206
+ await saveConfig(smtp())
198
207
  expect(events.email.SMTPUpdated).not.toHaveBeenCalled()
199
208
  expect(events.email.SMTPCreated).toHaveBeenCalledTimes(1)
200
- await config.deleteConfig(ConfigType.SMTP)
201
209
  })
202
210
  })
203
211
 
204
212
  describe("update", () => {
205
213
  it("should update SMTP config", async () => {
206
- const smtpConf = await saveSMTPConfig()
214
+ const smtpConf = await saveConfig(smtp())
207
215
  jest.clearAllMocks()
208
- await saveSMTPConfig(smtpConf.config, smtpConf._id, smtpConf._rev)
216
+ await saveConfig(smtp({ secure: true }), smtpConf._id, smtpConf._rev)
209
217
  expect(events.email.SMTPCreated).not.toHaveBeenCalled()
210
218
  expect(events.email.SMTPUpdated).toHaveBeenCalledTimes(1)
211
- await config.deleteConfig(ConfigType.SMTP)
219
+ })
220
+
221
+ it("should not overwrite secret when updating SMTP config", async () => {
222
+ await saveConfig(smtp({ auth: { user: "jeff", pass: "spooky" } }))
223
+ const conf = await config.api.configs.getConfig(ConfigType.SMTP)
224
+ await saveConfig(conf)
225
+ await config.doInTenant(async () => {
226
+ const rawConf = await configs.getSMTPConfig()
227
+ expect(rawConf!.auth!.pass).toEqual("spooky")
228
+ })
229
+ })
230
+ })
231
+
232
+ describe("get", () => {
233
+ it("should not leak credentials", async () => {
234
+ await saveConfig(smtp({ auth: { user: "jeff", pass: "spooky" } }))
235
+ const conf = await config.api.configs.getConfig(ConfigType.SMTP)
236
+ expect(conf.config.auth!.pass).toEqual("--secret-value--")
212
237
  })
213
238
  })
214
239
  })
215
240
 
216
241
  describe("settings", () => {
217
- describe("create", () => {
218
- it("should create settings config with default settings", async () => {
219
- await config.deleteConfig(ConfigType.SETTINGS)
242
+ beforeEach(async () => {
243
+ await config.deleteConfig(ConfigType.SETTINGS)
244
+ })
220
245
 
221
- await saveSettingsConfig()
246
+ afterEach(async () => {
247
+ await config.deleteConfig(ConfigType.SETTINGS)
248
+ })
222
249
 
250
+ describe("create", () => {
251
+ it("should create settings config with default settings", async () => {
252
+ await saveConfig(settings())
223
253
  expect(events.org.nameUpdated).not.toHaveBeenCalled()
224
254
  expect(events.org.logoUpdated).not.toHaveBeenCalled()
225
255
  expect(events.org.platformURLUpdated).not.toHaveBeenCalled()
@@ -234,7 +264,7 @@ describe("configs", () => {
234
264
  platformUrl: "http://example.com",
235
265
  }
236
266
 
237
- await saveSettingsConfig(conf)
267
+ await saveConfig(settings(conf))
238
268
 
239
269
  expect(events.org.nameUpdated).toHaveBeenCalledTimes(1)
240
270
  expect(events.org.logoUpdated).toHaveBeenCalledTimes(1)
@@ -247,13 +277,13 @@ describe("configs", () => {
247
277
  it("should update settings config", async () => {
248
278
  config.selfHosted()
249
279
  await config.deleteConfig(ConfigType.SETTINGS)
250
- const settingsConfig = await saveSettingsConfig()
280
+ const settingsConfig = await saveConfig(settings())
251
281
  settingsConfig.config.company = "acme"
252
282
  settingsConfig.config.logoUrl = "http://example.com"
253
283
  settingsConfig.config.platformUrl = "http://example.com"
254
284
 
255
- await saveSettingsConfig(
256
- settingsConfig.config,
285
+ await saveConfig(
286
+ settingsConfig,
257
287
  settingsConfig._id,
258
288
  settingsConfig._rev
259
289
  )
@@ -282,16 +312,24 @@ describe("configs", () => {
282
312
  })
283
313
 
284
314
  describe("GET /api/global/configs/public", () => {
315
+ beforeEach(async () => {
316
+ await config.deleteConfig(ConfigType.SETTINGS)
317
+ })
318
+
319
+ afterEach(async () => {
320
+ await config.deleteConfig(ConfigType.SETTINGS)
321
+ })
322
+
285
323
  it("should return the expected public settings", async () => {
286
- await saveSettingsConfig()
324
+ await saveConfig(settings())
287
325
  mocks.pro.features.isSSOEnforced.mockResolvedValue(false)
288
326
 
289
327
  const res = await config.api.configs.getPublicSettings()
290
328
  const body = res.body as GetPublicSettingsResponse
291
329
 
292
330
  const expected = {
293
- _id: "config_settings",
294
- type: "settings",
331
+ _id: `config_${ConfigType.SETTINGS}`,
332
+ type: ConfigType.SETTINGS,
295
333
  config: {
296
334
  company: "Budibase",
297
335
  emailBrandingEnabled: true,
@@ -704,6 +704,24 @@ describe("/api/global/users", () => {
704
704
  expect(response.body.data.length).toBe(1)
705
705
  expect(response.body.data[0].email).toBe(user.email)
706
706
  })
707
+
708
+ it("should strip users if accessing as an end user", async () => {
709
+ const user = await config.createUser({
710
+ admin: { global: false },
711
+ builder: { global: false },
712
+ })
713
+ const response = await config.api.users.searchUsers(
714
+ {
715
+ query: {},
716
+ },
717
+ { useHeaders: await config.login(user) }
718
+ )
719
+ for (let user of response.body.data) {
720
+ expect(user.roles).toBeUndefined()
721
+ expect(user.builder).toBeUndefined()
722
+ expect(user.admin).toBeUndefined()
723
+ }
724
+ })
707
725
  })
708
726
 
709
727
  describe("DELETE /api/global/users/:userId", () => {
@@ -46,6 +46,7 @@ class TestConfiguration {
46
46
  user?: User
47
47
  apiKey?: string
48
48
  userPassword = "password123!"
49
+ sessions: string[] = []
49
50
 
50
51
  constructor(opts: { openServer: boolean } = { openServer: true }) {
51
52
  // default to cloud hosting
@@ -185,12 +186,19 @@ class TestConfiguration {
185
186
  })
186
187
  }
187
188
 
189
+ hasSession(user: User) {
190
+ return this.sessions.includes(user._id!)
191
+ }
192
+
188
193
  async createSession(user: User) {
189
- return this._createSession({
190
- userId: user._id!,
191
- tenantId: user.tenantId,
192
- email: user.email,
193
- })
194
+ if (!this.hasSession(user)) {
195
+ this.sessions.push(user._id!)
196
+ return this._createSession({
197
+ userId: user._id!,
198
+ tenantId: user.tenantId,
199
+ email: user.email,
200
+ })
201
+ }
194
202
  }
195
203
 
196
204
  cookieHeader(cookies: any) {
@@ -212,6 +220,11 @@ class TestConfiguration {
212
220
  }
213
221
  }
214
222
 
223
+ async login(user: User) {
224
+ await this.createSession(user)
225
+ return this.authHeaders(user)
226
+ }
227
+
215
228
  authHeaders(user: User) {
216
229
  const authToken: AuthToken = {
217
230
  userId: user._id!,
@@ -279,10 +292,10 @@ class TestConfiguration {
279
292
  })
280
293
  }
281
294
 
282
- async createUser(opts?: Partial<User>) {
295
+ async createUser(userCfg?: Partial<User>) {
283
296
  let user = structures.users.user()
284
297
  if (user) {
285
- user = { ...user, ...opts }
298
+ user = { ...user, ...userCfg }
286
299
  }
287
300
  const response = await this._req(user, null, controllers.users.save)
288
301
  const body = response as SaveUserResponse
@@ -1,5 +1,6 @@
1
1
  import {
2
- AIConfig,
2
+ ConfigType,
3
+ ConfigTypeToConfig,
3
4
  SaveConfigRequest,
4
5
  SaveConfigResponse,
5
6
  } from "@budibase/types"
@@ -23,12 +24,16 @@ export class ConfigAPI extends TestAPI {
23
24
  }
24
25
 
25
26
  getAIConfig = async () => {
27
+ return await this.getConfig(ConfigType.AI)
28
+ }
29
+
30
+ getConfig = async <T extends ConfigType>(type: T) => {
26
31
  const resp = await this.request
27
- .get(`/api/global/configs/ai`)
32
+ .get(`/api/global/configs/${type}`)
28
33
  .set(this.config.defaultHeaders())
29
34
  .expect(200)
30
35
  .expect("Content-Type", /json/)
31
- return resp.body as AIConfig
36
+ return resp.body as ConfigTypeToConfig<T>
32
37
  }
33
38
 
34
39
  saveConfig = async (
@@ -152,14 +152,20 @@ export class UserAPI extends TestAPI {
152
152
 
153
153
  searchUsers = (
154
154
  { query }: { query?: SearchFilters },
155
- opts?: { status?: number; noHeaders?: boolean }
155
+ opts?: {
156
+ status?: number
157
+ noHeaders?: boolean
158
+ useHeaders?: Record<string, string>
159
+ }
156
160
  ) => {
157
161
  const req = this.request
158
162
  .post("/api/global/users/search")
159
163
  .send({ query })
160
164
  .expect("Content-Type", /json/)
161
165
  .expect(opts?.status ? opts.status : 200)
162
- if (!opts?.noHeaders) {
166
+ if (opts?.useHeaders) {
167
+ req.set(opts.useHeaders)
168
+ } else if (!opts?.noHeaders) {
163
169
  req.set(this.config.defaultHeaders())
164
170
  }
165
171
  return req
@@ -5,9 +5,13 @@ import {
5
5
  SMTPConfig,
6
6
  GoogleConfig,
7
7
  OIDCConfig,
8
+ GoogleInnerConfig,
9
+ OIDCInnerConfig,
10
+ SMTPInnerConfig,
11
+ SettingsInnerConfig,
8
12
  } from "@budibase/types"
9
13
 
10
- export function oidc(conf?: any): OIDCConfig {
14
+ export function oidc(conf?: Partial<OIDCInnerConfig>): OIDCConfig {
11
15
  return {
12
16
  type: ConfigType.OIDC,
13
17
  config: {
@@ -20,6 +24,7 @@ export function oidc(conf?: any): OIDCConfig {
20
24
  name: "Active Directory",
21
25
  uuid: utils.newid(),
22
26
  activated: true,
27
+ scopes: [],
23
28
  ...conf,
24
29
  },
25
30
  ],
@@ -27,7 +32,7 @@ export function oidc(conf?: any): OIDCConfig {
27
32
  }
28
33
  }
29
34
 
30
- export function google(conf?: any): GoogleConfig {
35
+ export function google(conf?: Partial<GoogleInnerConfig>): GoogleConfig {
31
36
  return {
32
37
  type: ConfigType.GOOGLE,
33
38
  config: {
@@ -39,7 +44,7 @@ export function google(conf?: any): GoogleConfig {
39
44
  }
40
45
  }
41
46
 
42
- export function smtp(conf?: any): SMTPConfig {
47
+ export function smtp(conf?: Partial<SMTPInnerConfig>): SMTPConfig {
43
48
  return {
44
49
  type: ConfigType.SMTP,
45
50
  config: {
@@ -70,7 +75,7 @@ export function smtpEthereal(): SMTPConfig {
70
75
  }
71
76
  }
72
77
 
73
- export function settings(conf?: any): SettingsConfig {
78
+ export function settings(conf?: Partial<SettingsInnerConfig>): SettingsConfig {
74
79
  return {
75
80
  type: ConfigType.SETTINGS,
76
81
  config: {