@budibase/backend-core 2.7.36-alpha.2 → 2.7.36

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/jest.config.ts CHANGED
@@ -31,6 +31,4 @@ const config: Config.InitialOptions = {
31
31
  coverageReporters: ["lcov", "json", "clover"],
32
32
  }
33
33
 
34
- process.env.DISABLE_PINO_LOGGER = "1"
35
-
36
34
  export default config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/backend-core",
3
- "version": "2.7.36-alpha.2",
3
+ "version": "2.7.36",
4
4
  "description": "Budibase backend core libraries used in server and worker",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -22,12 +22,12 @@
22
22
  "dependencies": {
23
23
  "@budibase/nano": "10.1.2",
24
24
  "@budibase/pouchdb-replication-stream": "1.2.10",
25
- "@budibase/types": "2.7.36-alpha.2",
25
+ "@budibase/types": "2.7.36",
26
26
  "@shopify/jest-koa-mocks": "5.0.1",
27
27
  "@techpass/passport-openidconnect": "0.3.2",
28
28
  "aws-cloudfront-sign": "2.2.0",
29
29
  "aws-sdk": "2.1030.0",
30
- "bcrypt": "5.1.0",
30
+ "bcrypt": "5.0.1",
31
31
  "bcryptjs": "2.4.3",
32
32
  "bull": "4.10.1",
33
33
  "correlation-id": "4.0.0",
@@ -101,5 +101,5 @@
101
101
  }
102
102
  }
103
103
  },
104
- "gitHead": "9e73358c0b5d945e052a08f6a9277a4d263eec01"
104
+ "gitHead": "66929c1a9739fa64aae8cbb1b9af50919d9a306d"
105
105
  }
@@ -57,9 +57,6 @@ class Replication {
57
57
  appReplicateOpts() {
58
58
  return {
59
59
  filter: (doc: any) => {
60
- if (doc._id && doc._id.startsWith(DocumentType.AUTOMATION_LOG)) {
61
- return false
62
- }
63
60
  return doc._id !== DocumentType.APP_METADATA
64
61
  },
65
62
  }
package/src/docIds/ids.ts CHANGED
@@ -81,19 +81,8 @@ export function generateAppUserID(prodAppId: string, userId: string) {
81
81
  * Generates a new role ID.
82
82
  * @returns {string} The new role ID which the role doc can be stored under.
83
83
  */
84
- export function generateRoleID(name: string) {
85
- const prefix = `${DocumentType.ROLE}${SEPARATOR}`
86
- if (name.startsWith(prefix)) {
87
- return name
88
- }
89
- return `${prefix}${name}`
90
- }
91
-
92
- /**
93
- * Utility function to be more verbose.
94
- */
95
- export function prefixRoleID(name: string) {
96
- return generateRoleID(name)
84
+ export function generateRoleID(id?: any) {
85
+ return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}`
97
86
  }
98
87
 
99
88
  /**
@@ -14,15 +14,10 @@ async function servedBuilder(timezone: string) {
14
14
  await publishEvent(Event.SERVED_BUILDER, properties)
15
15
  }
16
16
 
17
- async function servedApp(
18
- app: App,
19
- timezone: string,
20
- embed?: boolean | undefined
21
- ) {
17
+ async function servedApp(app: App, timezone: string) {
22
18
  const properties: AppServedEvent = {
23
19
  appVersion: app.version,
24
20
  timezone,
25
- embed: embed === true,
26
21
  }
27
22
  await publishEvent(Event.SERVED_APP, properties)
28
23
  }
@@ -1,11 +1,10 @@
1
1
  import * as google from "../sso/google"
2
2
  import { Cookie } from "../../../constants"
3
+ import { clearCookie, getCookie } from "../../../utils"
4
+ import { doWithDB } from "../../../db"
3
5
  import * as configs from "../../../configs"
4
- import * as cache from "../../../cache"
5
- import * as utils from "../../../utils"
6
- import { UserCtx, SSOProfile } from "@budibase/types"
6
+ import { BBContext, Database, SSOProfile } from "@budibase/types"
7
7
  import { ssoSaveUserNoOp } from "../sso/sso"
8
-
9
8
  const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
10
9
 
11
10
  type Passport = {
@@ -23,7 +22,7 @@ async function fetchGoogleCreds() {
23
22
 
24
23
  export async function preAuth(
25
24
  passport: Passport,
26
- ctx: UserCtx,
25
+ ctx: BBContext,
27
26
  next: Function
28
27
  ) {
29
28
  // get the relevant config
@@ -37,8 +36,8 @@ export async function preAuth(
37
36
  ssoSaveUserNoOp
38
37
  )
39
38
 
40
- if (!ctx.query.appId) {
41
- ctx.throw(400, "appId query param not present.")
39
+ if (!ctx.query.appId || !ctx.query.datasourceId) {
40
+ ctx.throw(400, "appId and datasourceId query params not present.")
42
41
  }
43
42
 
44
43
  return passport.authenticate(strategy, {
@@ -50,7 +49,7 @@ export async function preAuth(
50
49
 
51
50
  export async function postAuth(
52
51
  passport: Passport,
53
- ctx: UserCtx,
52
+ ctx: BBContext,
54
53
  next: Function
55
54
  ) {
56
55
  // get the relevant config
@@ -58,7 +57,7 @@ export async function postAuth(
58
57
  const platformUrl = await configs.getPlatformUrl({ tenantAware: false })
59
58
 
60
59
  let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
61
- const authStateCookie = utils.getCookie(ctx, Cookie.DatasourceAuth)
60
+ const authStateCookie = getCookie(ctx, Cookie.DatasourceAuth)
62
61
 
63
62
  return passport.authenticate(
64
63
  new GoogleStrategy(
@@ -70,26 +69,33 @@ export async function postAuth(
70
69
  (
71
70
  accessToken: string,
72
71
  refreshToken: string,
73
- _profile: SSOProfile,
72
+ profile: SSOProfile,
74
73
  done: Function
75
74
  ) => {
76
- utils.clearCookie(ctx, Cookie.DatasourceAuth)
75
+ clearCookie(ctx, Cookie.DatasourceAuth)
77
76
  done(null, { accessToken, refreshToken })
78
77
  }
79
78
  ),
80
79
  { successRedirect: "/", failureRedirect: "/error" },
81
80
  async (err: any, tokens: string[]) => {
82
81
  const baseUrl = `/builder/app/${authStateCookie.appId}/data`
83
-
84
- const id = utils.newid()
85
- await cache.store(
86
- `datasource:creation:${authStateCookie.appId}:google:${id}`,
87
- {
88
- tokens,
82
+ // update the DB for the datasource with all the user info
83
+ await doWithDB(authStateCookie.appId, async (db: Database) => {
84
+ let datasource
85
+ try {
86
+ datasource = await db.get(authStateCookie.datasourceId)
87
+ } catch (err: any) {
88
+ if (err.status === 404) {
89
+ ctx.redirect(baseUrl)
90
+ }
89
91
  }
90
- )
91
-
92
- ctx.redirect(`${baseUrl}/new?continue_google_setup=${id}`)
92
+ if (!datasource.config) {
93
+ datasource.config = {}
94
+ }
95
+ datasource.config.auth = { type: "google", ...tokens }
96
+ await db.put(datasource)
97
+ ctx.redirect(`${baseUrl}/datasource/${authStateCookie.datasourceId}`)
98
+ })
93
99
  }
94
100
  )(ctx, next)
95
101
  }
@@ -1,17 +1,12 @@
1
1
  import crypto from "crypto"
2
- import fs from "fs"
3
- import zlib from "zlib"
4
2
  import env from "../environment"
5
- import { join } from "path"
6
3
 
7
4
  const ALGO = "aes-256-ctr"
8
5
  const SEPARATOR = "-"
9
6
  const ITERATIONS = 10000
7
+ const RANDOM_BYTES = 16
10
8
  const STRETCH_LENGTH = 32
11
9
 
12
- const SALT_LENGTH = 16
13
- const IV_LENGTH = 16
14
-
15
10
  export enum SecretOption {
16
11
  API = "api",
17
12
  ENCRYPTION = "encryption",
@@ -36,15 +31,15 @@ export function getSecret(secretOption: SecretOption): string {
36
31
  return secret
37
32
  }
38
33
 
39
- function stretchString(secret: string, salt: Buffer) {
40
- return crypto.pbkdf2Sync(secret, salt, ITERATIONS, STRETCH_LENGTH, "sha512")
34
+ function stretchString(string: string, salt: Buffer) {
35
+ return crypto.pbkdf2Sync(string, salt, ITERATIONS, STRETCH_LENGTH, "sha512")
41
36
  }
42
37
 
43
38
  export function encrypt(
44
39
  input: string,
45
40
  secretOption: SecretOption = SecretOption.API
46
41
  ) {
47
- const salt = crypto.randomBytes(SALT_LENGTH)
42
+ const salt = crypto.randomBytes(RANDOM_BYTES)
48
43
  const stretched = stretchString(getSecret(secretOption), salt)
49
44
  const cipher = crypto.createCipheriv(ALGO, stretched, salt)
50
45
  const base = cipher.update(input)
@@ -65,115 +60,3 @@ export function decrypt(
65
60
  const final = decipher.final()
66
61
  return Buffer.concat([base, final]).toString()
67
62
  }
68
-
69
- export async function encryptFile(
70
- { dir, filename }: { dir: string; filename: string },
71
- secret: string
72
- ) {
73
- const outputFileName = `${filename}.enc`
74
-
75
- const filePath = join(dir, filename)
76
- const inputFile = fs.createReadStream(filePath)
77
- const outputFile = fs.createWriteStream(join(dir, outputFileName))
78
-
79
- const salt = crypto.randomBytes(SALT_LENGTH)
80
- const iv = crypto.randomBytes(IV_LENGTH)
81
- const stretched = stretchString(secret, salt)
82
- const cipher = crypto.createCipheriv(ALGO, stretched, iv)
83
-
84
- outputFile.write(salt)
85
- outputFile.write(iv)
86
-
87
- inputFile.pipe(zlib.createGzip()).pipe(cipher).pipe(outputFile)
88
-
89
- return new Promise<{ filename: string; dir: string }>(r => {
90
- outputFile.on("finish", () => {
91
- r({
92
- filename: outputFileName,
93
- dir,
94
- })
95
- })
96
- })
97
- }
98
-
99
- async function getSaltAndIV(path: string) {
100
- const fileStream = fs.createReadStream(path)
101
-
102
- const salt = await readBytes(fileStream, SALT_LENGTH)
103
- const iv = await readBytes(fileStream, IV_LENGTH)
104
- fileStream.close()
105
- return { salt, iv }
106
- }
107
-
108
- export async function decryptFile(
109
- inputPath: string,
110
- outputPath: string,
111
- secret: string
112
- ) {
113
- const { salt, iv } = await getSaltAndIV(inputPath)
114
- const inputFile = fs.createReadStream(inputPath, {
115
- start: SALT_LENGTH + IV_LENGTH,
116
- })
117
-
118
- const outputFile = fs.createWriteStream(outputPath)
119
-
120
- const stretched = stretchString(secret, salt)
121
- const decipher = crypto.createDecipheriv(ALGO, stretched, iv)
122
-
123
- const unzip = zlib.createGunzip()
124
-
125
- inputFile.pipe(decipher).pipe(unzip).pipe(outputFile)
126
-
127
- return new Promise<void>((res, rej) => {
128
- outputFile.on("finish", () => {
129
- outputFile.close()
130
- res()
131
- })
132
-
133
- inputFile.on("error", e => {
134
- outputFile.close()
135
- rej(e)
136
- })
137
-
138
- decipher.on("error", e => {
139
- outputFile.close()
140
- rej(e)
141
- })
142
-
143
- unzip.on("error", e => {
144
- outputFile.close()
145
- rej(e)
146
- })
147
-
148
- outputFile.on("error", e => {
149
- outputFile.close()
150
- rej(e)
151
- })
152
- })
153
- }
154
-
155
- function readBytes(stream: fs.ReadStream, length: number) {
156
- return new Promise<Buffer>((resolve, reject) => {
157
- let bytesRead = 0
158
- const data: Buffer[] = []
159
-
160
- stream.on("readable", () => {
161
- let chunk
162
-
163
- while ((chunk = stream.read(length - bytesRead)) !== null) {
164
- data.push(chunk)
165
- bytesRead += chunk.length
166
- }
167
-
168
- resolve(Buffer.concat(data))
169
- })
170
-
171
- stream.on("end", () => {
172
- reject(new Error("Insufficient data in the stream."))
173
- })
174
-
175
- stream.on("error", error => {
176
- reject(error)
177
- })
178
- })
179
- }
@@ -1,5 +1,5 @@
1
1
  import { BuiltinPermissionID, PermissionLevel } from "./permissions"
2
- import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
2
+ import { generateRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
3
3
  import { getAppDB } from "../context"
4
4
  import { doWithDB } from "../db"
5
5
  import { Screen, Role as RoleDoc } from "@budibase/types"
@@ -25,28 +25,18 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [
25
25
  BUILTIN_IDS.PUBLIC,
26
26
  ]
27
27
 
28
- export const RoleIDVersion = {
29
- // original version, with a UUID based ID
30
- UUID: undefined,
31
- // new version - with name based ID
32
- NAME: "name",
33
- }
34
-
35
28
  export class Role implements RoleDoc {
36
29
  _id: string
37
30
  _rev?: string
38
31
  name: string
39
32
  permissionId: string
40
33
  inherits?: string
41
- version?: string
42
34
  permissions = {}
43
35
 
44
36
  constructor(id: string, name: string, permissionId: string) {
45
37
  this._id = id
46
38
  this.name = name
47
39
  this.permissionId = permissionId
48
- // version for managing the ID - removing the role_ when responding
49
- this.version = RoleIDVersion.NAME
50
40
  }
51
41
 
52
42
  addInheritance(inherits: string) {
@@ -150,13 +140,9 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string): string {
150
140
  * Gets the role object, this is mainly useful for two purposes, to check if the level exists and
151
141
  * to check if the role inherits any others.
152
142
  * @param {string|null} roleId The level ID to lookup.
153
- * @param {object|null} opts options for the function, like whether to halt errors, instead return public.
154
143
  * @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
155
144
  */
156
- export async function getRole(
157
- roleId?: string,
158
- opts?: { defaultPublic?: boolean }
159
- ): Promise<RoleDoc | undefined> {
145
+ export async function getRole(roleId?: string): Promise<RoleDoc | undefined> {
160
146
  if (!roleId) {
161
147
  return undefined
162
148
  }
@@ -167,20 +153,14 @@ export async function getRole(
167
153
  role = cloneDeep(
168
154
  Object.values(BUILTIN_ROLES).find(role => role._id === roleId)
169
155
  )
170
- } else {
171
- // make sure has the prefix (if it has it then it won't be added)
172
- roleId = prefixRoleID(roleId)
173
156
  }
174
157
  try {
175
158
  const db = getAppDB()
176
159
  const dbRole = await db.get(getDBRoleID(roleId))
177
160
  role = Object.assign(role, dbRole)
178
161
  // finalise the ID
179
- role._id = getExternalRoleID(role._id, role.version)
162
+ role._id = getExternalRoleID(role._id)
180
163
  } catch (err) {
181
- if (!isBuiltin(roleId) && opts?.defaultPublic) {
182
- return cloneDeep(BUILTIN_ROLES.PUBLIC)
183
- }
184
164
  // only throw an error if there is no role at all
185
165
  if (Object.keys(role).length === 0) {
186
166
  throw err
@@ -274,9 +254,6 @@ export async function getAllRoles(appId?: string) {
274
254
  })
275
255
  )
276
256
  roles = body.rows.map((row: any) => row.doc)
277
- roles.forEach(
278
- role => (role._id = getExternalRoleID(role._id!, role.version))
279
- )
280
257
  }
281
258
  const builtinRoles = getBuiltinRoles()
282
259
 
@@ -284,15 +261,14 @@ export async function getAllRoles(appId?: string) {
284
261
  for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
285
262
  const builtinRole = builtinRoles[builtinRoleId]
286
263
  const dbBuiltin = roles.filter(
287
- dbRole =>
288
- getExternalRoleID(dbRole._id!, dbRole.version) === builtinRoleId
264
+ dbRole => getExternalRoleID(dbRole._id) === builtinRoleId
289
265
  )[0]
290
266
  if (dbBuiltin == null) {
291
267
  roles.push(builtinRole || builtinRoles.BASIC)
292
268
  } else {
293
269
  // remove role and all back after combining with the builtin
294
270
  roles = roles.filter(role => role._id !== dbBuiltin._id)
295
- dbBuiltin._id = getExternalRoleID(dbBuiltin._id!, dbBuiltin.version)
271
+ dbBuiltin._id = getExternalRoleID(dbBuiltin._id)
296
272
  roles.push(Object.assign(builtinRole, dbBuiltin))
297
273
  }
298
274
  }
@@ -398,22 +374,19 @@ export class AccessController {
398
374
  /**
399
375
  * Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions).
400
376
  */
401
- export function getDBRoleID(roleName: string) {
402
- if (roleName?.startsWith(DocumentType.ROLE)) {
403
- return roleName
377
+ export function getDBRoleID(roleId?: string) {
378
+ if (roleId?.startsWith(DocumentType.ROLE)) {
379
+ return roleId
404
380
  }
405
- return prefixRoleID(roleName)
381
+ return generateRoleID(roleId)
406
382
  }
407
383
 
408
384
  /**
409
385
  * Remove the "role_" from builtin role IDs that have been written to the DB (for permissions).
410
386
  */
411
- export function getExternalRoleID(roleId: string, version?: string) {
387
+ export function getExternalRoleID(roleId?: string) {
412
388
  // for built-in roles we want to remove the DB role ID element (role_)
413
- if (
414
- (roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) ||
415
- version === RoleIDVersion.NAME
416
- ) {
389
+ if (roleId?.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) {
417
390
  return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1]
418
391
  }
419
392
  return roleId