@budibase/server 2.7.15 → 2.7.16-alpha.1

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.
@@ -1,4 +1,4 @@
1
- import { db as dbCore, objectStore } from "@budibase/backend-core"
1
+ import { db as dbCore, encryption, objectStore } from "@budibase/backend-core"
2
2
  import { budibaseTempDir } from "../../../utilities/budibaseDir"
3
3
  import { streamFile, createTempFolder } from "../../../utilities/fileSystem"
4
4
  import { ObjectStoreBuckets } from "../../../constants"
@@ -18,7 +18,8 @@ import { join } from "path"
18
18
  import env from "../../../environment"
19
19
 
20
20
  const uuid = require("uuid/v4")
21
- const tar = require("tar")
21
+ import tar from "tar"
22
+
22
23
  const MemoryStream = require("memorystream")
23
24
 
24
25
  interface DBDumpOpts {
@@ -30,16 +31,18 @@ interface ExportOpts extends DBDumpOpts {
30
31
  tar?: boolean
31
32
  excludeRows?: boolean
32
33
  excludeLogs?: boolean
34
+ encryptPassword?: string
33
35
  }
34
36
 
35
37
  function tarFilesToTmp(tmpDir: string, files: string[]) {
36
- const exportFile = join(budibaseTempDir(), `${uuid()}.tar.gz`)
38
+ const fileName = `${uuid()}.tar.gz`
39
+ const exportFile = join(budibaseTempDir(), fileName)
37
40
  tar.create(
38
41
  {
39
42
  sync: true,
40
43
  gzip: true,
41
44
  file: exportFile,
42
- recursive: true,
45
+ noDirRecurse: false,
43
46
  cwd: tmpDir,
44
47
  },
45
48
  files
@@ -124,6 +127,7 @@ export async function exportApp(appId: string, config?: ExportOpts) {
124
127
  )
125
128
  }
126
129
  }
130
+
127
131
  const downloadedPath = join(tmpPath, appPath)
128
132
  if (fs.existsSync(downloadedPath)) {
129
133
  const allFiles = fs.readdirSync(downloadedPath)
@@ -141,12 +145,27 @@ export async function exportApp(appId: string, config?: ExportOpts) {
141
145
  filter: defineFilter(config?.excludeRows, config?.excludeLogs),
142
146
  exportPath: dbPath,
143
147
  })
148
+
149
+ if (config?.encryptPassword) {
150
+ for (let file of fs.readdirSync(tmpPath)) {
151
+ const path = join(tmpPath, file)
152
+
153
+ await encryption.encryptFile(
154
+ { dir: tmpPath, filename: file },
155
+ config.encryptPassword
156
+ )
157
+
158
+ fs.rmSync(path)
159
+ }
160
+ }
161
+
144
162
  // if tar requested, return where the tarball is
145
163
  if (config?.tar) {
146
164
  // now the tmpPath contains both the DB export and attachments, tar this
147
165
  const tarPath = tarFilesToTmp(tmpPath, fs.readdirSync(tmpPath))
148
166
  // cleanup the tmp export files as tarball returned
149
167
  fs.rmSync(tmpPath, { recursive: true, force: true })
168
+
150
169
  return tarPath
151
170
  }
152
171
  // tar not requested, turn the directory where export is
@@ -161,11 +180,20 @@ export async function exportApp(appId: string, config?: ExportOpts) {
161
180
  * @param {boolean} excludeRows Flag to state whether the export should include data.
162
181
  * @returns {*} a readable stream of the backup which is written in real time
163
182
  */
164
- export async function streamExportApp(appId: string, excludeRows: boolean) {
183
+ export async function streamExportApp({
184
+ appId,
185
+ excludeRows,
186
+ encryptPassword,
187
+ }: {
188
+ appId: string
189
+ excludeRows: boolean
190
+ encryptPassword?: string
191
+ }) {
165
192
  const tmpPath = await exportApp(appId, {
166
193
  excludeRows,
167
194
  excludeLogs: true,
168
195
  tar: true,
196
+ encryptPassword,
169
197
  })
170
198
  return streamFile(tmpPath)
171
199
  }
@@ -1,4 +1,4 @@
1
- import { db as dbCore, objectStore } from "@budibase/backend-core"
1
+ import { db as dbCore, encryption, objectStore } from "@budibase/backend-core"
2
2
  import { Database, Row } from "@budibase/types"
3
3
  import { getAutomationParams, TABLE_ROW_PREFIX } from "../../../db/utils"
4
4
  import { budibaseTempDir } from "../../../utilities/budibaseDir"
@@ -20,6 +20,7 @@ type TemplateType = {
20
20
  file?: {
21
21
  type: string
22
22
  path: string
23
+ password?: string
23
24
  }
24
25
  key?: string
25
26
  }
@@ -123,6 +124,22 @@ export function untarFile(file: { path: string }) {
123
124
  return tmpPath
124
125
  }
125
126
 
127
+ async function decryptFiles(path: string, password: string) {
128
+ try {
129
+ for (let file of fs.readdirSync(path)) {
130
+ const inputPath = join(path, file)
131
+ const outputPath = inputPath.replace(/\.enc$/, "")
132
+ await encryption.decryptFile(inputPath, outputPath, password)
133
+ fs.rmSync(inputPath)
134
+ }
135
+ } catch (err: any) {
136
+ if (err.message === "incorrect header check") {
137
+ throw new Error("File cannot be imported")
138
+ }
139
+ throw err
140
+ }
141
+ }
142
+
126
143
  export function getGlobalDBFile(tmpPath: string) {
127
144
  return fs.readFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), "utf8")
128
145
  }
@@ -143,6 +160,9 @@ export async function importApp(
143
160
  template.file && fs.lstatSync(template.file.path).isDirectory()
144
161
  if (template.file && (isTar || isDirectory)) {
145
162
  const tmpPath = isTar ? untarFile(template.file) : template.file.path
163
+ if (isTar && template.file.password) {
164
+ await decryptFiles(tmpPath, template.file.password)
165
+ }
146
166
  const contents = fs.readdirSync(tmpPath)
147
167
  // have to handle object import
148
168
  if (contents.length) {
@@ -164,5 +164,6 @@ export function mergeConfigs(update: Datasource, old: Datasource) {
164
164
  delete update.config[key]
165
165
  }
166
166
  }
167
+
167
168
  return update
168
169
  }
@@ -9,7 +9,7 @@ import {
9
9
  env as coreEnv,
10
10
  } from "@budibase/backend-core"
11
11
  import { updateAppRole } from "./global"
12
- import { BBContext, User } from "@budibase/types"
12
+ import { BBContext, User, EmailInvite } from "@budibase/types"
13
13
 
14
14
  export function request(ctx?: BBContext, request?: any) {
15
15
  if (!request.headers) {
@@ -65,15 +65,25 @@ async function checkResponse(
65
65
  }
66
66
 
67
67
  // have to pass in the tenant ID as this could be coming from an automation
68
- export async function sendSmtpEmail(
69
- to: string,
70
- from: string,
71
- subject: string,
72
- contents: string,
73
- cc: string,
74
- bcc: string,
68
+ export async function sendSmtpEmail({
69
+ to,
70
+ from,
71
+ subject,
72
+ contents,
73
+ cc,
74
+ bcc,
75
+ automation,
76
+ invite,
77
+ }: {
78
+ to: string
79
+ from: string
80
+ subject: string
81
+ contents: string
82
+ cc: string
83
+ bcc: string
75
84
  automation: boolean
76
- ) {
85
+ invite?: EmailInvite
86
+ }) {
77
87
  // tenant ID will be set in header
78
88
  const response = await fetch(
79
89
  checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`),
@@ -88,6 +98,7 @@ export async function sendSmtpEmail(
88
98
  bcc,
89
99
  purpose: "custom",
90
100
  automation,
101
+ invite,
91
102
  },
92
103
  })
93
104
  )
@@ -1,71 +0,0 @@
1
-
2
- function generateResponse(to, from) {
3
- return {
4
- "success": true,
5
- "response": {
6
- "accepted": [
7
- to
8
- ],
9
- "envelope": {
10
- "from": from,
11
- "to": [
12
- to
13
- ]
14
- },
15
- "message": `Email sent to ${to}.`
16
- }
17
-
18
- }
19
- }
20
-
21
- const mockFetch = jest.fn(() => ({
22
- headers: {
23
- raw: () => {
24
- return { "content-type": ["application/json"] }
25
- },
26
- get: () => ["application/json"],
27
- },
28
- json: jest.fn(() => response),
29
- status: 200,
30
- text: jest.fn(),
31
- }))
32
- jest.mock("node-fetch", () => mockFetch)
33
- const setup = require("./utilities")
34
-
35
-
36
- describe("test the outgoing webhook action", () => {
37
- let inputs
38
- let config = setup.getConfig()
39
- beforeAll(async () => {
40
- await config.init()
41
- })
42
-
43
- afterAll(setup.afterAll)
44
-
45
- it("should be able to run the action", async () => {
46
- inputs = {
47
- to: "user1@test.com",
48
- from: "admin@test.com",
49
- subject: "hello",
50
- contents: "testing",
51
- }
52
- let resp = generateResponse(inputs.to, inputs.from)
53
- mockFetch.mockImplementationOnce(() => ({
54
- headers: {
55
- raw: () => {
56
- return { "content-type": ["application/json"] }
57
- },
58
- get: () => ["application/json"],
59
- },
60
- json: jest.fn(() => resp),
61
- status: 200,
62
- text: jest.fn(),
63
- }))
64
- const res = await setup.runStep(setup.actions.SEND_EMAIL_SMTP.stepId, inputs)
65
- expect(res.response).toEqual(resp)
66
- expect(res.success).toEqual(true)
67
-
68
- })
69
-
70
-
71
- })