@budibase/backend-core 2.13.14 → 2.13.16

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.
Files changed (71) hide show
  1. package/dist/index.js +302 -242
  2. package/dist/index.js.map +3 -3
  3. package/dist/index.js.meta.json +1 -1
  4. package/dist/package.json +4 -4
  5. package/dist/plugins.js.meta.json +1 -1
  6. package/dist/src/auth/auth.js.map +1 -1
  7. package/dist/src/configs/configs.js +3 -4
  8. package/dist/src/configs/configs.js.map +1 -1
  9. package/dist/src/constants/db.js.map +1 -1
  10. package/dist/src/db/utils.js.map +1 -1
  11. package/dist/src/docIds/conversions.js.map +1 -1
  12. package/dist/src/events/processors/posthog/index.js.map +1 -1
  13. package/dist/src/features/index.js.map +1 -1
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/installation.js +2 -3
  16. package/dist/src/installation.js.map +1 -1
  17. package/dist/src/logging/correlation/correlation.js.map +1 -1
  18. package/dist/src/logging/correlation/middleware.js.map +1 -1
  19. package/dist/src/logging/pino/middleware.js.map +1 -1
  20. package/dist/src/middleware/index.js.map +1 -1
  21. package/dist/src/middleware/passport/sso/google.js.map +1 -1
  22. package/dist/src/objectStore/objectStore.js.map +1 -1
  23. package/dist/src/security/permissions.d.ts +1 -0
  24. package/dist/src/security/permissions.js +2 -1
  25. package/dist/src/security/permissions.js.map +1 -1
  26. package/dist/src/security/roles.js +1 -2
  27. package/dist/src/security/roles.js.map +1 -1
  28. package/dist/src/security/sessions.js.map +1 -1
  29. package/dist/src/users/db.d.ts +6 -1
  30. package/dist/src/users/db.js +29 -8
  31. package/dist/src/users/db.js.map +1 -1
  32. package/dist/src/users/users.d.ts +14 -11
  33. package/dist/src/users/users.js +200 -144
  34. package/dist/src/users/users.js.map +1 -1
  35. package/dist/src/utils/hashing.js.map +1 -1
  36. package/dist/src/utils/utils.js.map +1 -1
  37. package/dist/tests/core/utilities/mocks/alerts.js.map +1 -1
  38. package/dist/tests/core/utilities/mocks/index.js.map +1 -1
  39. package/dist/tests/core/utilities/structures/generator.js.map +1 -1
  40. package/dist/tests/jestSetup.js.map +1 -1
  41. package/package.json +4 -4
  42. package/src/auth/auth.ts +2 -0
  43. package/src/configs/configs.ts +3 -4
  44. package/src/constants/db.ts +1 -0
  45. package/src/db/utils.ts +1 -0
  46. package/src/docIds/conversions.ts +1 -0
  47. package/src/events/processors/posthog/index.ts +1 -0
  48. package/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +2 -0
  49. package/src/features/index.ts +1 -0
  50. package/src/index.ts +1 -0
  51. package/src/installation.ts +1 -2
  52. package/src/logging/correlation/correlation.ts +1 -0
  53. package/src/logging/correlation/middleware.ts +1 -0
  54. package/src/logging/pino/middleware.ts +3 -0
  55. package/src/middleware/index.ts +1 -0
  56. package/src/middleware/passport/sso/google.ts +1 -0
  57. package/src/middleware/passport/sso/tests/google.spec.ts +1 -0
  58. package/src/middleware/passport/sso/tests/sso.spec.ts +1 -0
  59. package/src/middleware/tests/builder.spec.ts +1 -0
  60. package/src/objectStore/objectStore.ts +1 -0
  61. package/src/security/permissions.ts +1 -0
  62. package/src/security/roles.ts +7 -2
  63. package/src/security/sessions.ts +1 -0
  64. package/src/users/db.ts +35 -14
  65. package/src/users/users.ts +46 -16
  66. package/src/utils/hashing.ts +1 -0
  67. package/src/utils/utils.ts +1 -0
  68. package/tests/core/utilities/mocks/alerts.ts +1 -0
  69. package/tests/core/utilities/mocks/index.ts +1 -0
  70. package/tests/core/utilities/structures/generator.ts +1 -0
  71. package/tests/jestSetup.ts +1 -0
@@ -1,7 +1,9 @@
1
1
  import { testEnv } from "../../../../../tests/extra"
2
2
  import PosthogProcessor from "../PosthogProcessor"
3
3
  import { Event, IdentityType, Hosting } from "@budibase/types"
4
+
4
5
  const tk = require("timekeeper")
6
+
5
7
  import * as cache from "../../../../cache/generic"
6
8
  import { CacheKey } from "../../../../cache/generic"
7
9
  import * as context from "../../../../context"
@@ -1,5 +1,6 @@
1
1
  import env from "../environment"
2
2
  import * as context from "../context"
3
+
3
4
  export * from "./installation"
4
5
 
5
6
  /**
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ export * as docIds from "./docIds"
38
38
  // circular dependencies
39
39
  import * as context from "./context"
40
40
  import * as _tenancy from "./tenancy"
41
+
41
42
  export const tenancy = {
42
43
  ..._tenancy,
43
44
  ...context,
@@ -1,7 +1,6 @@
1
1
  import { newid } from "./utils"
2
2
  import * as events from "./events"
3
- import { StaticDatabases } from "./db"
4
- import { doWithDB } from "./db"
3
+ import { StaticDatabases, doWithDB } from "./db"
5
4
  import { Installation, IdentityType, Database } from "@budibase/types"
6
5
  import * as context from "./context"
7
6
  import semver from "semver"
@@ -1,4 +1,5 @@
1
1
  import { Header } from "../../constants"
2
+
2
3
  const correlator = require("correlation-id")
3
4
 
4
5
  export const setHeader = (headers: any) => {
@@ -1,5 +1,6 @@
1
1
  import { Header } from "../../constants"
2
2
  import { v4 as uuid } from "uuid"
3
+
3
4
  const correlator = require("correlation-id")
4
5
 
5
6
  const correlation = (ctx: any, next: any) => {
@@ -1,9 +1,12 @@
1
1
  import env from "../../environment"
2
2
  import { logger } from "./logger"
3
3
  import { IncomingMessage } from "http"
4
+
4
5
  const pino = require("koa-pino-logger")
6
+
5
7
  import { Options } from "pino-http"
6
8
  import { Ctx } from "@budibase/types"
9
+
7
10
  const correlator = require("correlation-id")
8
11
 
9
12
  export function pinoSettings(): Options {
@@ -2,6 +2,7 @@ export * as local from "./passport/local"
2
2
  export * as google from "./passport/sso/google"
3
3
  export * as oidc from "./passport/sso/oidc"
4
4
  import * as datasourceGoogle from "./passport/datasource/google"
5
+
5
6
  export const datasource = {
6
7
  google: datasourceGoogle,
7
8
  }
@@ -8,6 +8,7 @@ import {
8
8
  SaveSSOUserFunction,
9
9
  GoogleInnerConfig,
10
10
  } from "@budibase/types"
11
+
11
12
  const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
12
13
 
13
14
  export function buildVerifyFn(saveUserFn: SaveSSOUserFunction) {
@@ -6,6 +6,7 @@ const mockStrategy = require("passport-google-oauth").OAuth2Strategy
6
6
 
7
7
  jest.mock("../sso")
8
8
  import * as _sso from "../sso"
9
+
9
10
  const sso = jest.mocked(_sso)
10
11
 
11
12
  const mockSaveUserFn = jest.fn()
@@ -11,6 +11,7 @@ const mockSaveUser = jest.fn()
11
11
 
12
12
  jest.mock("../../../../users")
13
13
  import * as _users from "../../../../users"
14
+
14
15
  const users = jest.mocked(_users)
15
16
 
16
17
  const getErrorMessage = () => {
@@ -5,6 +5,7 @@ import { structures } from "../../../tests"
5
5
  import { ContextUser, ServiceType } from "@budibase/types"
6
6
  import { doInAppContext } from "../../context"
7
7
  import env from "../../environment"
8
+
8
9
  env._set("SERVICE_TYPE", ServiceType.APPS)
9
10
 
10
11
  const appId = "app_aaa"
@@ -1,4 +1,5 @@
1
1
  const sanitize = require("sanitize-s3-objectkey")
2
+
2
3
  import AWS from "aws-sdk"
3
4
  import stream, { Readable } from "stream"
4
5
  import fetch from "node-fetch"
@@ -160,4 +160,5 @@ export function isPermissionLevelHigherThanRead(level: PermissionLevel) {
160
160
 
161
161
  // utility as a lot of things need simply the builder permission
162
162
  export const BUILDER = PermissionType.BUILDER
163
+ export const CREATOR = PermissionType.CREATOR
163
164
  export const GLOBAL_BUILDER = PermissionType.GLOBAL_BUILDER
@@ -1,7 +1,12 @@
1
1
  import { BuiltinPermissionID, PermissionLevel } from "./permissions"
2
- import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
2
+ import {
3
+ prefixRoleID,
4
+ getRoleParams,
5
+ DocumentType,
6
+ SEPARATOR,
7
+ doWithDB,
8
+ } from "../db"
3
9
  import { getAppDB } from "../context"
4
- import { doWithDB } from "../db"
5
10
  import { Screen, Role as RoleDoc } from "@budibase/types"
6
11
  import cloneDeep from "lodash/fp/cloneDeep"
7
12
 
@@ -1,6 +1,7 @@
1
1
  const redis = require("../redis/init")
2
2
  const { v4: uuidv4 } = require("uuid")
3
3
  const { logWarn } = require("../logging")
4
+
4
5
  import env from "../environment"
5
6
  import {
6
7
  Session,
package/src/users/db.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import env from "../environment"
2
2
  import * as eventHelpers from "./events"
3
- import * as accounts from "../accounts"
4
3
  import * as accountSdk from "../accounts"
5
4
  import * as cache from "../cache"
6
- import { getGlobalDB, getIdentity, getTenantId } from "../context"
5
+ import { doInTenant, getGlobalDB, getIdentity, getTenantId } from "../context"
7
6
  import * as dbUtils from "../db"
8
7
  import { EmailUnavailableError, HTTPError } from "../errors"
9
8
  import * as platform from "../platform"
@@ -11,12 +10,10 @@ import * as sessions from "../security/sessions"
11
10
  import * as usersCore from "./users"
12
11
  import {
13
12
  Account,
14
- AllDocsResponse,
15
13
  BulkUserCreated,
16
14
  BulkUserDeleted,
17
15
  isSSOAccount,
18
16
  isSSOUser,
19
- RowResponse,
20
17
  SaveUserOpts,
21
18
  User,
22
19
  UserStatus,
@@ -149,12 +146,12 @@ export class UserDB {
149
146
 
150
147
  static async allUsers() {
151
148
  const db = getGlobalDB()
152
- const response = await db.allDocs(
149
+ const response = await db.allDocs<User>(
153
150
  dbUtils.getGlobalUserParams(null, {
154
151
  include_docs: true,
155
152
  })
156
153
  )
157
- return response.rows.map((row: any) => row.doc)
154
+ return response.rows.map(row => row.doc!)
158
155
  }
159
156
 
160
157
  static async countUsersByApp(appId: string) {
@@ -212,13 +209,6 @@ export class UserDB {
212
209
  throw new Error("_id or email is required")
213
210
  }
214
211
 
215
- if (
216
- user.builder?.apps?.length &&
217
- !(await UserDB.features.isAppBuildersEnabled())
218
- ) {
219
- throw new Error("Unable to update app builders, please check license")
220
- }
221
-
222
212
  let dbUser: User | undefined
223
213
  if (_id) {
224
214
  // try to get existing user from db
@@ -467,7 +457,7 @@ export class UserDB {
467
457
  if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
468
458
  // root account holder can't be deleted from inside budibase
469
459
  const email = dbUser.email
470
- const account = await accounts.getAccount(email)
460
+ const account = await accountSdk.getAccount(email)
471
461
  if (account) {
472
462
  if (dbUser.userId === getIdentity()!._id) {
473
463
  throw new HTTPError('Please visit "Account" to delete this user', 400)
@@ -488,6 +478,37 @@ export class UserDB {
488
478
  await sessions.invalidateSessions(userId, { reason: "deletion" })
489
479
  }
490
480
 
481
+ static async createAdminUser(
482
+ email: string,
483
+ password: string,
484
+ tenantId: string,
485
+ opts?: { ssoId?: string; hashPassword?: boolean; requirePassword?: boolean }
486
+ ) {
487
+ const user: User = {
488
+ email: email,
489
+ password: password,
490
+ createdAt: Date.now(),
491
+ roles: {},
492
+ builder: {
493
+ global: true,
494
+ },
495
+ admin: {
496
+ global: true,
497
+ },
498
+ tenantId,
499
+ }
500
+ if (opts?.ssoId) {
501
+ user.ssoId = opts.ssoId
502
+ }
503
+ // always bust checklist beforehand, if an error occurs but can proceed, don't get
504
+ // stuck in a cycle
505
+ await cache.bustCache(cache.CacheKey.CHECKLIST)
506
+ return await UserDB.save(user, {
507
+ hashPassword: opts?.hashPassword,
508
+ requirePassword: opts?.requirePassword,
509
+ })
510
+ }
511
+
491
512
  static async getGroups(groupIds: string[]) {
492
513
  return await this.groups.getBulk(groupIds)
493
514
  }
@@ -25,6 +25,7 @@ import {
25
25
  import { getGlobalDB } from "../context"
26
26
  import * as context from "../context"
27
27
  import { isCreator } from "./utils"
28
+ import { UserDB } from "./db"
28
29
 
29
30
  type GetOpts = { cleanup?: boolean }
30
31
 
@@ -43,7 +44,7 @@ function removeUserPassword(users: User | User[]) {
43
44
  return users
44
45
  }
45
46
 
46
- export const isSupportedUserSearch = (query: SearchQuery) => {
47
+ export function isSupportedUserSearch(query: SearchQuery) {
47
48
  const allowed = [
48
49
  { op: SearchQueryOperators.STRING, key: "email" },
49
50
  { op: SearchQueryOperators.EQUAL, key: "_id" },
@@ -68,10 +69,10 @@ export const isSupportedUserSearch = (query: SearchQuery) => {
68
69
  return true
69
70
  }
70
71
 
71
- export const bulkGetGlobalUsersById = async (
72
+ export async function bulkGetGlobalUsersById(
72
73
  userIds: string[],
73
74
  opts?: GetOpts
74
- ) => {
75
+ ) {
75
76
  const db = getGlobalDB()
76
77
  let users = (
77
78
  await db.allDocs({
@@ -85,7 +86,7 @@ export const bulkGetGlobalUsersById = async (
85
86
  return users
86
87
  }
87
88
 
88
- export const getAllUserIds = async () => {
89
+ export async function getAllUserIds() {
89
90
  const db = getGlobalDB()
90
91
  const startKey = `${DocumentType.USER}${SEPARATOR}`
91
92
  const response = await db.allDocs({
@@ -95,7 +96,7 @@ export const getAllUserIds = async () => {
95
96
  return response.rows.map(row => row.id)
96
97
  }
97
98
 
98
- export const bulkUpdateGlobalUsers = async (users: User[]) => {
99
+ export async function bulkUpdateGlobalUsers(users: User[]) {
99
100
  const db = getGlobalDB()
100
101
  return (await db.bulkDocs(users)) as BulkDocsResponse
101
102
  }
@@ -113,10 +114,10 @@ export async function getById(id: string, opts?: GetOpts): Promise<User> {
113
114
  * Given an email address this will use a view to search through
114
115
  * all the users to find one with this email address.
115
116
  */
116
- export const getGlobalUserByEmail = async (
117
+ export async function getGlobalUserByEmail(
117
118
  email: String,
118
119
  opts?: GetOpts
119
- ): Promise<User | undefined> => {
120
+ ): Promise<User | undefined> {
120
121
  if (email == null) {
121
122
  throw "Must supply an email address to view"
122
123
  }
@@ -139,11 +140,23 @@ export const getGlobalUserByEmail = async (
139
140
  return user
140
141
  }
141
142
 
142
- export const searchGlobalUsersByApp = async (
143
+ export async function doesUserExist(email: string) {
144
+ try {
145
+ const user = await getGlobalUserByEmail(email)
146
+ if (Array.isArray(user) || user != null) {
147
+ return true
148
+ }
149
+ } catch (err) {
150
+ return false
151
+ }
152
+ return false
153
+ }
154
+
155
+ export async function searchGlobalUsersByApp(
143
156
  appId: any,
144
157
  opts: DatabaseQueryOpts,
145
158
  getOpts?: GetOpts
146
- ) => {
159
+ ) {
147
160
  if (typeof appId !== "string") {
148
161
  throw new Error("Must provide a string based app ID")
149
162
  }
@@ -167,10 +180,10 @@ export const searchGlobalUsersByApp = async (
167
180
  Return any user who potentially has access to the application
168
181
  Admins, developers and app users with the explicitly role.
169
182
  */
170
- export const searchGlobalUsersByAppAccess = async (
183
+ export async function searchGlobalUsersByAppAccess(
171
184
  appId: any,
172
185
  opts?: { limit?: number }
173
- ) => {
186
+ ) {
174
187
  const roleSelector = `roles.${appId}`
175
188
 
176
189
  let orQuery: any[] = [
@@ -205,7 +218,7 @@ export const searchGlobalUsersByAppAccess = async (
205
218
  return resp.rows
206
219
  }
207
220
 
208
- export const getGlobalUserByAppPage = (appId: string, user: User) => {
221
+ export function getGlobalUserByAppPage(appId: string, user: User) {
209
222
  if (!user) {
210
223
  return
211
224
  }
@@ -215,11 +228,11 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
215
228
  /**
216
229
  * Performs a starts with search on the global email view.
217
230
  */
218
- export const searchGlobalUsersByEmail = async (
231
+ export async function searchGlobalUsersByEmail(
219
232
  email: string | unknown,
220
233
  opts: any,
221
234
  getOpts?: GetOpts
222
- ) => {
235
+ ) {
223
236
  if (typeof email !== "string") {
224
237
  throw new Error("Must provide a string to search by")
225
238
  }
@@ -242,12 +255,12 @@ export const searchGlobalUsersByEmail = async (
242
255
  }
243
256
 
244
257
  const PAGE_LIMIT = 8
245
- export const paginatedUsers = async ({
258
+ export async function paginatedUsers({
246
259
  bookmark,
247
260
  query,
248
261
  appId,
249
262
  limit,
250
- }: SearchUsersRequest = {}) => {
263
+ }: SearchUsersRequest = {}) {
251
264
  const db = getGlobalDB()
252
265
  const pageSize = limit ?? PAGE_LIMIT
253
266
  const pageLimit = pageSize + 1
@@ -324,3 +337,20 @@ export function cleanseUserObject(user: User | ContextUser, base?: User) {
324
337
  }
325
338
  return user
326
339
  }
340
+
341
+ export async function addAppBuilder(user: User, appId: string) {
342
+ const prodAppId = getProdAppID(appId)
343
+ user.builder ??= {}
344
+ user.builder.creator = true
345
+ user.builder.apps ??= []
346
+ user.builder.apps.push(prodAppId)
347
+ await UserDB.save(user, { hashPassword: false })
348
+ }
349
+
350
+ export async function removeAppBuilder(user: User, appId: string) {
351
+ const prodAppId = getProdAppID(appId)
352
+ if (user.builder && user.builder.apps?.includes(prodAppId)) {
353
+ user.builder.apps = user.builder.apps.filter(id => id !== prodAppId)
354
+ }
355
+ await UserDB.save(user, { hashPassword: false })
356
+ }
@@ -1,4 +1,5 @@
1
1
  import env from "../environment"
2
+
2
3
  export * from "../docIds/newid"
3
4
  const bcrypt = env.JS_BCRYPT ? require("bcryptjs") : require("bcrypt")
4
5
 
@@ -11,6 +11,7 @@ import {
11
11
  TenantResolutionStrategy,
12
12
  } from "@budibase/types"
13
13
  import type { SetOption } from "cookies"
14
+
14
15
  const jwt = require("jsonwebtoken")
15
16
 
16
17
  const APP_PREFIX = DocumentType.APP + SEPARATOR
@@ -1,3 +1,4 @@
1
1
  jest.mock("../../../../src/logging/alerts")
2
2
  import * as _alerts from "../../../../src/logging/alerts"
3
+
3
4
  export const alerts = jest.mocked(_alerts)
@@ -1,5 +1,6 @@
1
1
  jest.mock("../../../../src/accounts")
2
2
  import * as _accounts from "../../../../src/accounts"
3
+
3
4
  export const accounts = jest.mocked(_accounts)
4
5
 
5
6
  export * as date from "./date"
@@ -1,2 +1,3 @@
1
1
  import Chance from "./Chance"
2
+
2
3
  export const generator = new Chance()
@@ -9,6 +9,7 @@ mocks.fetch.enable()
9
9
  // mock all dates to 2020-01-01T00:00:00.000Z
10
10
  // use tk.reset() to use real dates in individual tests
11
11
  import tk from "timekeeper"
12
+
12
13
  tk.freeze(mocks.date.MOCK_DATE)
13
14
 
14
15
  if (!process.env.DEBUG) {