@budibase/server 1.0.46-alpha.4 → 1.0.46-alpha.8

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 (226) hide show
  1. package/builder/assets/{index.c21ee1fb.js → index.51efc215.js} +243 -243
  2. package/builder/assets/{index.243ca52c.css → index.dd9e386f.css} +1 -1
  3. package/builder/assets/{vendor.10e2ad1b.js → vendor.3bd2f661.js} +1 -1
  4. package/builder/index.html +3 -3
  5. package/coverage/clover.xml +1794 -1793
  6. package/coverage/coverage-final.json +136 -138
  7. package/coverage/lcov-report/api/controllers/analytics.js.html +6 -6
  8. package/coverage/lcov-report/api/controllers/apikeys.js.html +6 -6
  9. package/coverage/lcov-report/api/controllers/application.js.html +209 -125
  10. package/coverage/lcov-report/api/controllers/auth.js.html +23 -11
  11. package/coverage/lcov-report/api/controllers/automation.js.html +21 -21
  12. package/coverage/lcov-report/api/controllers/backup.js.html +3 -3
  13. package/coverage/lcov-report/api/controllers/cloud.js.html +12 -12
  14. package/coverage/lcov-report/api/controllers/component.js.html +5 -5
  15. package/coverage/lcov-report/api/controllers/datasource.js.html +58 -22
  16. package/coverage/lcov-report/api/controllers/dev.js.html +19 -19
  17. package/coverage/lcov-report/api/controllers/index.html +38 -53
  18. package/coverage/lcov-report/api/controllers/integration.js.html +4 -4
  19. package/coverage/lcov-report/api/controllers/layout.js.html +6 -6
  20. package/coverage/lcov-report/api/controllers/metadata.js.html +9 -9
  21. package/coverage/lcov-report/api/controllers/permission.js.html +15 -15
  22. package/coverage/lcov-report/api/controllers/query/index.html +15 -15
  23. package/coverage/lcov-report/api/controllers/query/index.js.html +61 -28
  24. package/coverage/lcov-report/api/controllers/query/validation.js.html +9 -9
  25. package/coverage/lcov-report/api/controllers/role.js.html +9 -9
  26. package/coverage/lcov-report/api/controllers/routing.js.html +9 -9
  27. package/coverage/lcov-report/api/controllers/row/external.js.html +16 -16
  28. package/coverage/lcov-report/api/controllers/row/index.html +5 -5
  29. package/coverage/lcov-report/api/controllers/row/index.js.html +13 -13
  30. package/coverage/lcov-report/api/controllers/row/internal.js.html +26 -26
  31. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +8 -8
  32. package/coverage/lcov-report/api/controllers/row/utils.js.html +17 -26
  33. package/coverage/lcov-report/api/controllers/screen.js.html +7 -7
  34. package/coverage/lcov-report/api/controllers/script.js.html +4 -4
  35. package/coverage/lcov-report/api/controllers/table/external.js.html +12 -12
  36. package/coverage/lcov-report/api/controllers/table/index.html +1 -1
  37. package/coverage/lcov-report/api/controllers/table/index.js.html +14 -14
  38. package/coverage/lcov-report/api/controllers/table/internal.js.html +10 -10
  39. package/coverage/lcov-report/api/controllers/table/utils.js.html +26 -26
  40. package/coverage/lcov-report/api/controllers/templates.js.html +5 -5
  41. package/coverage/lcov-report/api/controllers/user.js.html +30 -24
  42. package/coverage/lcov-report/api/controllers/view/exporters.js.html +4 -4
  43. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  44. package/coverage/lcov-report/api/controllers/view/index.js.html +14 -14
  45. package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
  46. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +7 -7
  47. package/coverage/lcov-report/api/controllers/webhook.js.html +15 -15
  48. package/coverage/lcov-report/api/index.html +1 -1
  49. package/coverage/lcov-report/api/index.js.html +26 -26
  50. package/coverage/lcov-report/api/routes/analytics.js.html +6 -6
  51. package/coverage/lcov-report/api/routes/apikeys.js.html +8 -8
  52. package/coverage/lcov-report/api/routes/application.js.html +9 -9
  53. package/coverage/lcov-report/api/routes/auth.js.html +6 -6
  54. package/coverage/lcov-report/api/routes/automation.js.html +14 -14
  55. package/coverage/lcov-report/api/routes/backup.js.html +8 -8
  56. package/coverage/lcov-report/api/routes/cloud.js.html +8 -8
  57. package/coverage/lcov-report/api/routes/component.js.html +8 -8
  58. package/coverage/lcov-report/api/routes/datasource.js.html +13 -13
  59. package/coverage/lcov-report/api/routes/deploy.js.html +8 -8
  60. package/coverage/lcov-report/api/routes/dev.js.html +13 -13
  61. package/coverage/lcov-report/api/routes/index.html +7 -22
  62. package/coverage/lcov-report/api/routes/index.js.html +31 -37
  63. package/coverage/lcov-report/api/routes/integration.js.html +8 -8
  64. package/coverage/lcov-report/api/routes/layout.js.html +8 -8
  65. package/coverage/lcov-report/api/routes/metadata.js.html +9 -9
  66. package/coverage/lcov-report/api/routes/permission.js.html +12 -12
  67. package/coverage/lcov-report/api/routes/query.js.html +10 -10
  68. package/coverage/lcov-report/api/routes/role.js.html +12 -12
  69. package/coverage/lcov-report/api/routes/routing.js.html +8 -8
  70. package/coverage/lcov-report/api/routes/row.js.html +10 -10
  71. package/coverage/lcov-report/api/routes/screen.js.html +11 -11
  72. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/static.js.html +13 -13
  74. package/coverage/lcov-report/api/routes/table.js.html +12 -12
  75. package/coverage/lcov-report/api/routes/templates.js.html +8 -8
  76. package/coverage/lcov-report/api/routes/user.js.html +8 -8
  77. package/coverage/lcov-report/api/routes/view.js.html +10 -10
  78. package/coverage/lcov-report/api/routes/webhook.js.html +11 -11
  79. package/coverage/lcov-report/automations/actions.js.html +24 -24
  80. package/coverage/lcov-report/automations/automationUtils.js.html +5 -5
  81. package/coverage/lcov-report/automations/bullboard.js.html +16 -16
  82. package/coverage/lcov-report/automations/index.html +1 -1
  83. package/coverage/lcov-report/automations/index.js.html +7 -7
  84. package/coverage/lcov-report/automations/steps/bash.js.html +6 -6
  85. package/coverage/lcov-report/automations/steps/createRow.js.html +7 -7
  86. package/coverage/lcov-report/automations/steps/delay.js.html +4 -4
  87. package/coverage/lcov-report/automations/steps/deleteRow.js.html +7 -7
  88. package/coverage/lcov-report/automations/steps/discord.js.html +7 -7
  89. package/coverage/lcov-report/automations/steps/executeQuery.js.html +6 -6
  90. package/coverage/lcov-report/automations/steps/executeScript.js.html +6 -6
  91. package/coverage/lcov-report/automations/steps/filter.js.html +7 -7
  92. package/coverage/lcov-report/automations/steps/index.html +1 -1
  93. package/coverage/lcov-report/automations/steps/integromat.js.html +5 -5
  94. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +8 -8
  95. package/coverage/lcov-report/automations/steps/queryRows.js.html +10 -10
  96. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +5 -5
  97. package/coverage/lcov-report/automations/steps/serverLog.js.html +3 -3
  98. package/coverage/lcov-report/automations/steps/slack.js.html +5 -5
  99. package/coverage/lcov-report/automations/steps/updateRow.js.html +6 -6
  100. package/coverage/lcov-report/automations/steps/utils.js.html +3 -3
  101. package/coverage/lcov-report/automations/steps/zapier.js.html +5 -5
  102. package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
  103. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
  104. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  105. package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
  106. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
  107. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
  108. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
  109. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
  110. package/coverage/lcov-report/automations/triggers.js.html +18 -18
  111. package/coverage/lcov-report/automations/utils.js.html +32 -32
  112. package/coverage/lcov-report/constants/index.html +5 -5
  113. package/coverage/lcov-report/constants/index.js.html +36 -24
  114. package/coverage/lcov-report/constants/layouts.js.html +5 -5
  115. package/coverage/lcov-report/constants/screens.js.html +4 -4
  116. package/coverage/lcov-report/db/client.js.html +18 -18
  117. package/coverage/lcov-report/db/inMemoryView.js.html +7 -7
  118. package/coverage/lcov-report/db/index.html +1 -1
  119. package/coverage/lcov-report/db/index.js.html +3 -3
  120. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +7 -7
  121. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  122. package/coverage/lcov-report/db/linkedRows/index.js.html +18 -18
  123. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +13 -13
  124. package/coverage/lcov-report/db/newid.js.html +4 -4
  125. package/coverage/lcov-report/db/utils.js.html +73 -73
  126. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  127. package/coverage/lcov-report/events/index.html +1 -1
  128. package/coverage/lcov-report/events/index.js.html +6 -6
  129. package/coverage/lcov-report/events/utils.js.html +3 -3
  130. package/coverage/lcov-report/index.html +62 -62
  131. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  132. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  133. package/coverage/lcov-report/middleware/appInfo.js.html +5 -5
  134. package/coverage/lcov-report/middleware/authorized.js.html +171 -69
  135. package/coverage/lcov-report/middleware/builder.js.html +16 -16
  136. package/coverage/lcov-report/middleware/currentapp.js.html +46 -73
  137. package/coverage/lcov-report/middleware/index.html +24 -24
  138. package/coverage/lcov-report/middleware/joi-validator.js.html +6 -6
  139. package/coverage/lcov-report/middleware/resourceId.js.html +22 -22
  140. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  141. package/coverage/lcov-report/middleware/usageQuota.js.html +17 -17
  142. package/coverage/lcov-report/middleware/utils.js.html +4 -4
  143. package/coverage/lcov-report/migrations/usageQuotas/index.html +1 -1
  144. package/coverage/lcov-report/migrations/usageQuotas/index.js.html +6 -6
  145. package/coverage/lcov-report/migrations/usageQuotas/syncApps.js.html +6 -6
  146. package/coverage/lcov-report/migrations/usageQuotas/syncRows.js.html +7 -7
  147. package/coverage/lcov-report/threads/automation.js.html +1 -1
  148. package/coverage/lcov-report/threads/index.html +15 -15
  149. package/coverage/lcov-report/threads/index.js.html +10 -10
  150. package/coverage/lcov-report/threads/query.js.html +23 -8
  151. package/coverage/lcov-report/threads/utils.js.html +14 -14
  152. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  153. package/coverage/lcov-report/utilities/budibaseDir.js.html +7 -7
  154. package/coverage/lcov-report/utilities/centralPath.js.html +4 -4
  155. package/coverage/lcov-report/utilities/csvParser.js.html +6 -6
  156. package/coverage/lcov-report/utilities/global.js.html +26 -26
  157. package/coverage/lcov-report/utilities/index.html +19 -19
  158. package/coverage/lcov-report/utilities/index.js.html +25 -25
  159. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +9 -9
  160. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  161. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  162. package/coverage/lcov-report/utilities/routing/index.js.html +6 -6
  163. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  164. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +17 -17
  165. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +4 -4
  166. package/coverage/lcov-report/utilities/scriptRunner.js.html +5 -5
  167. package/coverage/lcov-report/utilities/security.js.html +8 -8
  168. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  169. package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
  170. package/coverage/lcov-report/utilities/usageQuota/index.js.html +8 -8
  171. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +8 -8
  172. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
  173. package/coverage/lcov-report/utilities/users.js.html +5 -5
  174. package/coverage/lcov-report/utilities/workerRequests.js.html +39 -108
  175. package/coverage/lcov.info +2292 -2272
  176. package/dist/api/controllers/application.js +49 -20
  177. package/dist/api/controllers/auth.js +4 -0
  178. package/dist/api/controllers/datasource.js +11 -0
  179. package/dist/api/controllers/query/index.js +11 -3
  180. package/dist/api/controllers/row/utils.js +1 -2
  181. package/dist/api/controllers/static/index.js +9 -10
  182. package/dist/api/controllers/user.js +2 -0
  183. package/dist/api/routes/index.js +0 -2
  184. package/dist/constants/index.js +3 -0
  185. package/dist/definitions/datasource.js +1 -0
  186. package/dist/integrations/googlesheets.js +340 -0
  187. package/dist/integrations/index.js +3 -0
  188. package/dist/integrations/utils.js +6 -3
  189. package/dist/middleware/authorized.js +55 -30
  190. package/dist/middleware/currentapp.js +0 -9
  191. package/dist/package.json +8 -5
  192. package/dist/tests/utilities/TestConfiguration.js +10 -6
  193. package/dist/threads/query.js +4 -0
  194. package/dist/tsconfig.tsbuildinfo +1 -1
  195. package/dist/utilities/workerRequests.js +0 -20
  196. package/nodemon.json +1 -1
  197. package/package.json +9 -6
  198. package/src/api/controllers/application.js +52 -24
  199. package/src/api/controllers/auth.js +4 -0
  200. package/src/api/controllers/datasource.js +12 -0
  201. package/src/api/controllers/query/index.js +14 -3
  202. package/src/api/controllers/row/utils.js +1 -4
  203. package/src/api/controllers/static/index.js +13 -10
  204. package/src/api/controllers/user.js +2 -0
  205. package/src/api/routes/index.js +0 -2
  206. package/src/api/routes/tests/application.spec.js +2 -2
  207. package/src/api/routes/tests/auth.spec.js +1 -2
  208. package/src/constants/index.js +4 -0
  209. package/src/definitions/datasource.ts +3 -0
  210. package/src/integrations/googlesheets.ts +353 -0
  211. package/src/integrations/index.ts +3 -0
  212. package/src/integrations/utils.ts +18 -5
  213. package/src/middleware/authorized.js +64 -30
  214. package/src/middleware/currentapp.js +0 -9
  215. package/src/middleware/tests/authorized.spec.js +6 -4
  216. package/src/migrations/tests/usageQuotas/syncRows.spec.js +1 -1
  217. package/src/tests/utilities/TestConfiguration.js +10 -6
  218. package/src/threads/query.js +5 -0
  219. package/src/utilities/workerRequests.js +0 -23
  220. package/coverage/lcov-report/api/controllers/hosting.js.html +0 -146
  221. package/coverage/lcov-report/api/routes/hosting.js.html +0 -119
  222. package/dist/api/controllers/hosting.js +0 -30
  223. package/dist/api/routes/hosting.js +0 -11
  224. package/src/api/controllers/hosting.js +0 -22
  225. package/src/api/routes/hosting.js +0 -13
  226. package/src/api/routes/tests/hosting.spec.js +0 -36
@@ -0,0 +1,353 @@
1
+ import {
2
+ DatasourceFieldTypes,
3
+ Integration,
4
+ QueryJson,
5
+ QueryTypes,
6
+ } from "../definitions/datasource"
7
+ import { OAuth2Client } from "google-auth-library"
8
+ import { DatasourcePlus } from "./base/datasourcePlus"
9
+ import { Row, Table, TableSchema } from "../definitions/common"
10
+ import { buildExternalTableId } from "./utils"
11
+ import { DataSourceOperation, FieldTypes } from "../constants"
12
+ import { GoogleSpreadsheet } from "google-spreadsheet"
13
+ import { table } from "console"
14
+
15
+ module GoogleSheetsModule {
16
+ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
17
+ const { getScopedConfig } = require("@budibase/backend-core/db")
18
+ const { Configs } = require("@budibase/backend-core/constants")
19
+
20
+ interface GoogleSheetsConfig {
21
+ spreadsheetId: string
22
+ auth: OAuthClientConfig
23
+ }
24
+
25
+ interface OAuthClientConfig {
26
+ appId: string
27
+ accessToken: string
28
+ refreshToken: string
29
+ }
30
+
31
+ const SCHEMA: Integration = {
32
+ plus: true,
33
+ auth: {
34
+ type: "google",
35
+ },
36
+ relationships: false,
37
+ docs: "https://developers.google.com/sheets/api/quickstart/nodejs",
38
+ description:
39
+ "Create and collaborate on online spreadsheets in real-time and from any device. ",
40
+ friendlyName: "Google Sheets",
41
+ datasource: {
42
+ spreadsheetId: {
43
+ type: DatasourceFieldTypes.STRING,
44
+ required: true,
45
+ },
46
+ },
47
+ query: {
48
+ create: {
49
+ type: QueryTypes.FIELDS,
50
+ fields: {
51
+ sheet: {
52
+ type: DatasourceFieldTypes.STRING,
53
+ required: true,
54
+ },
55
+ row: {
56
+ type: QueryTypes.JSON,
57
+ required: true,
58
+ },
59
+ },
60
+ },
61
+ read: {
62
+ type: QueryTypes.FIELDS,
63
+ fields: {
64
+ sheet: {
65
+ type: DatasourceFieldTypes.STRING,
66
+ required: true,
67
+ },
68
+ },
69
+ },
70
+ update: {
71
+ type: QueryTypes.FIELDS,
72
+ fields: {
73
+ sheet: {
74
+ type: DatasourceFieldTypes.STRING,
75
+ required: true,
76
+ },
77
+ rowIndex: {
78
+ type: DatasourceFieldTypes.STRING,
79
+ required: true,
80
+ },
81
+ row: {
82
+ type: QueryTypes.JSON,
83
+ required: true,
84
+ },
85
+ },
86
+ },
87
+ delete: {
88
+ type: QueryTypes.FIELDS,
89
+ fields: {
90
+ sheet: {
91
+ type: DatasourceFieldTypes.STRING,
92
+ required: true,
93
+ },
94
+ rowIndex: {
95
+ type: DatasourceFieldTypes.NUMBER,
96
+ required: true,
97
+ },
98
+ },
99
+ },
100
+ },
101
+ }
102
+
103
+ class GoogleSheetsIntegration implements DatasourcePlus {
104
+ private readonly config: GoogleSheetsConfig
105
+ private client: any
106
+ public tables: Record<string, Table> = {}
107
+ public schemaErrors: Record<string, string> = {}
108
+
109
+ constructor(config: GoogleSheetsConfig) {
110
+ this.config = config
111
+ const spreadsheetId = this.cleanSpreadsheetUrl(this.config.spreadsheetId)
112
+ this.client = new GoogleSpreadsheet(spreadsheetId)
113
+ }
114
+
115
+ /**
116
+ * Pull the spreadsheet ID out from a valid google sheets URL
117
+ * @param spreadsheetId - the URL or standard spreadsheetId of the google sheet
118
+ * @returns spreadsheet Id of the google sheet
119
+ */
120
+ cleanSpreadsheetUrl(spreadsheetId: string) {
121
+ if (!spreadsheetId) {
122
+ throw new Error(
123
+ "You must set a spreadsheet ID in your configuration to fetch tables."
124
+ )
125
+ }
126
+ const parts = spreadsheetId.split("/")
127
+ return parts.length > 5 ? parts[5] : spreadsheetId
128
+ }
129
+
130
+ async connect() {
131
+ try {
132
+ // Initialise oAuth client
133
+ const db = getGlobalDB()
134
+ const googleConfig = await getScopedConfig(db, {
135
+ type: Configs.GOOGLE,
136
+ })
137
+ const oauthClient = new OAuth2Client({
138
+ clientId: googleConfig.clientID,
139
+ clientSecret: googleConfig.clientSecret,
140
+ })
141
+ oauthClient.credentials.access_token = this.config.auth.accessToken
142
+ oauthClient.credentials.refresh_token = this.config.auth.refreshToken
143
+ this.client.useOAuth2Client(oauthClient)
144
+ await this.client.loadInfo()
145
+ } catch (err) {
146
+ console.error("Error connecting to google sheets", err)
147
+ throw err
148
+ }
149
+ }
150
+
151
+ async buildSchema(datasourceId: string) {
152
+ await this.connect()
153
+ const sheets = await this.client.sheetsByIndex
154
+ const tables: Record<string, Table> = {}
155
+ for (let sheet of sheets) {
156
+ // must fetch rows to determine schema
157
+ await sheet.getRows()
158
+ // build schema
159
+ const schema: TableSchema = {}
160
+
161
+ // build schema from headers
162
+ for (let header of sheet.headerValues) {
163
+ schema[header] = {
164
+ name: header,
165
+ type: FieldTypes.STRING,
166
+ }
167
+ }
168
+
169
+ // create tables
170
+ tables[sheet.title] = {
171
+ _id: buildExternalTableId(datasourceId, sheet.title),
172
+ name: sheet.title,
173
+ primary: ["rowNumber"],
174
+ schema,
175
+ }
176
+ }
177
+
178
+ this.tables = tables
179
+ }
180
+
181
+ async query(json: QueryJson) {
182
+ const sheet = json.endpoint.entityId
183
+
184
+ const handlers = {
185
+ [DataSourceOperation.CREATE]: () =>
186
+ this.create({ sheet, row: json.body }),
187
+ [DataSourceOperation.READ]: () => this.read({ sheet }),
188
+ [DataSourceOperation.UPDATE]: () =>
189
+ this.update({
190
+ // exclude the header row and zero index
191
+ rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
192
+ sheet,
193
+ row: json.body,
194
+ }),
195
+ [DataSourceOperation.DELETE]: () =>
196
+ this.delete({
197
+ // exclude the header row and zero index
198
+ rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
199
+ sheet,
200
+ }),
201
+ [DataSourceOperation.CREATE_TABLE]: () =>
202
+ this.createTable(json?.table?.name),
203
+ [DataSourceOperation.UPDATE_TABLE]: () => this.updateTable(json.table),
204
+ [DataSourceOperation.DELETE_TABLE]: () =>
205
+ this.deleteTable(json?.table?.name),
206
+ }
207
+
208
+ const internalQueryMethod = handlers[json.endpoint.operation]
209
+
210
+ return await internalQueryMethod()
211
+ }
212
+
213
+ buildRowObject(headers: string[], values: string[], rowNumber: number) {
214
+ const rowObject: { rowNumber: number; [key: string]: any } = { rowNumber }
215
+ for (let i = 0; i < headers.length; i++) {
216
+ rowObject._id = rowNumber
217
+ rowObject[headers[i]] = values[i]
218
+ }
219
+ return rowObject
220
+ }
221
+
222
+ async createTable(name?: string) {
223
+ try {
224
+ await this.connect()
225
+ const sheet = await this.client.addSheet({ title: name })
226
+ return sheet
227
+ } catch (err) {
228
+ console.error("Error creating new table in google sheets", err)
229
+ throw err
230
+ }
231
+ }
232
+
233
+ async updateTable(table?: any) {
234
+ try {
235
+ await this.connect()
236
+ const sheet = await this.client.sheetsByTitle[table.name]
237
+ await sheet.loadHeaderRow()
238
+
239
+ if (table._rename) {
240
+ const headers = []
241
+ for (let header of sheet.headerValues) {
242
+ if (header === table._rename.old) {
243
+ headers.push(table._rename.updated)
244
+ } else {
245
+ headers.push(header)
246
+ }
247
+ }
248
+ await sheet.setHeaderRow(headers)
249
+ } else {
250
+ let newField = Object.keys(table.schema).find(
251
+ key => !sheet.headerValues.includes(key)
252
+ )
253
+ await sheet.setHeaderRow([...sheet.headerValues, newField])
254
+ }
255
+ } catch (err) {
256
+ console.error("Error updating table in google sheets", err)
257
+ throw err
258
+ }
259
+ }
260
+
261
+ async deleteTable(sheet: any) {
262
+ try {
263
+ await this.connect()
264
+ const sheetToDelete = await this.client.sheetsByTitle[sheet]
265
+ return await sheetToDelete.delete()
266
+ } catch (err) {
267
+ console.error("Error deleting table in google sheets", err)
268
+ throw err
269
+ }
270
+ }
271
+
272
+ async create(query: { sheet: string; row: any }) {
273
+ try {
274
+ await this.connect()
275
+ const sheet = await this.client.sheetsByTitle[query.sheet]
276
+ const rowToInsert =
277
+ typeof query.row === "string" ? JSON.parse(query.row) : query.row
278
+ const row = await sheet.addRow(rowToInsert)
279
+ return [
280
+ this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
281
+ ]
282
+ } catch (err) {
283
+ console.error("Error writing to google sheets", err)
284
+ throw err
285
+ }
286
+ }
287
+
288
+ async read(query: { sheet: string }) {
289
+ try {
290
+ await this.connect()
291
+ const sheet = await this.client.sheetsByTitle[query.sheet]
292
+ const rows = await sheet.getRows()
293
+ const headerValues = sheet.headerValues
294
+ const response = []
295
+ for (let row of rows) {
296
+ response.push(
297
+ this.buildRowObject(headerValues, row._rawData, row._rowNumber)
298
+ )
299
+ }
300
+ return response
301
+ } catch (err) {
302
+ console.error("Error reading from google sheets", err)
303
+ throw err
304
+ }
305
+ }
306
+
307
+ async update(query: { sheet: string; rowIndex: number; row: any }) {
308
+ try {
309
+ await this.connect()
310
+ const sheet = await this.client.sheetsByTitle[query.sheet]
311
+ const rows = await sheet.getRows()
312
+ const row = rows[query.rowIndex]
313
+ if (row) {
314
+ const updateValues = query.row
315
+ for (let key in updateValues) {
316
+ row[key] = updateValues[key]
317
+ }
318
+ await row.save()
319
+ return [
320
+ this.buildRowObject(
321
+ sheet.headerValues,
322
+ row._rawData,
323
+ row._rowNumber
324
+ ),
325
+ ]
326
+ } else {
327
+ throw new Error("Row does not exist.")
328
+ }
329
+ } catch (err) {
330
+ console.error("Error reading from google sheets", err)
331
+ throw err
332
+ }
333
+ }
334
+
335
+ async delete(query: { sheet: string; rowIndex: number }) {
336
+ await this.connect()
337
+ const sheet = await this.client.sheetsByTitle[query.sheet]
338
+ const rows = await sheet.getRows()
339
+ const row = rows[query.rowIndex]
340
+ if (row) {
341
+ await row.delete()
342
+ return [{ deleted: query.rowIndex }]
343
+ } else {
344
+ throw new Error("Row does not exist.")
345
+ }
346
+ }
347
+ }
348
+
349
+ module.exports = {
350
+ schema: SCHEMA,
351
+ integration: GoogleSheetsIntegration,
352
+ }
353
+ }
@@ -9,6 +9,7 @@ const airtable = require("./airtable")
9
9
  const mysql = require("./mysql")
10
10
  const arangodb = require("./arangodb")
11
11
  const rest = require("./rest")
12
+ const googlesheets = require("./googlesheets")
12
13
  const { SourceNames } = require("../definitions/datasource")
13
14
 
14
15
  const DEFINITIONS = {
@@ -23,6 +24,7 @@ const DEFINITIONS = {
23
24
  [SourceNames.MYSQL]: mysql.schema,
24
25
  [SourceNames.ARANGODB]: arangodb.schema,
25
26
  [SourceNames.REST]: rest.schema,
27
+ [SourceNames.GOOGLE_SHEETS]: googlesheets.schema,
26
28
  }
27
29
 
28
30
  const INTEGRATIONS = {
@@ -37,6 +39,7 @@ const INTEGRATIONS = {
37
39
  [SourceNames.MYSQL]: mysql.integration,
38
40
  [SourceNames.ARANGODB]: arangodb.integration,
39
41
  [SourceNames.REST]: rest.integration,
42
+ [SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
40
43
  }
41
44
 
42
45
  // optionally add oracle integration if the oracle binary can be installed
@@ -153,8 +153,15 @@ export function isIsoDateString(str: string) {
153
153
  * @param column The column to check, to see if it is a valid relationship.
154
154
  * @param tableIds The IDs of the tables which currently exist.
155
155
  */
156
- function shouldCopyRelationship(column: { type: string, tableId?: string }, tableIds: [string]) {
157
- return column.type === FieldTypes.LINK && column.tableId && tableIds.includes(column.tableId)
156
+ function shouldCopyRelationship(
157
+ column: { type: string; tableId?: string },
158
+ tableIds: [string]
159
+ ) {
160
+ return (
161
+ column.type === FieldTypes.LINK &&
162
+ column.tableId &&
163
+ tableIds.includes(column.tableId)
164
+ )
158
165
  }
159
166
 
160
167
  /**
@@ -165,9 +172,15 @@ function shouldCopyRelationship(column: { type: string, tableId?: string }, tabl
165
172
  * @param column The column to check for options or boolean type.
166
173
  * @param fetchedColumn The fetched column to check for the type in the external database.
167
174
  */
168
- function shouldCopySpecialColumn(column: { type: string }, fetchedColumn: { type: string } | undefined) {
169
- return column.type === FieldTypes.OPTIONS ||
170
- ((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) && column.type === FieldTypes.BOOLEAN)
175
+ function shouldCopySpecialColumn(
176
+ column: { type: string },
177
+ fetchedColumn: { type: string } | undefined
178
+ ) {
179
+ return (
180
+ column.type === FieldTypes.OPTIONS ||
181
+ ((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) &&
182
+ column.type === FieldTypes.BOOLEAN)
183
+ )
171
184
  }
172
185
 
173
186
  /**
@@ -9,11 +9,59 @@ const {
9
9
  } = require("@budibase/backend-core/permissions")
10
10
  const builderMiddleware = require("./builder")
11
11
  const { isWebhookEndpoint } = require("./utils")
12
+ const { buildCsrfMiddleware } = require("@budibase/backend-core/auth")
12
13
 
13
14
  function hasResource(ctx) {
14
15
  return ctx.resourceId != null
15
16
  }
16
17
 
18
+ const csrf = buildCsrfMiddleware()
19
+
20
+ /**
21
+ * Apply authorization to the requested resource:
22
+ * - If this is a builder resource the user must be a builder.
23
+ * - Builders can access all resources.
24
+ * - Otherwise the user must have the required role.
25
+ */
26
+ const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
27
+ // check if this is a builder api and the user is not a builder
28
+ const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
29
+ const isBuilderApi = permType === PermissionTypes.BUILDER
30
+ if (isBuilderApi && !isBuilder) {
31
+ return ctx.throw(403, "Not Authorized")
32
+ }
33
+
34
+ // check for resource authorization
35
+ if (!isBuilder) {
36
+ await checkAuthorizedResource(ctx, resourceRoles, permType, permLevel)
37
+ }
38
+ }
39
+
40
+ const checkAuthorizedResource = async (
41
+ ctx,
42
+ resourceRoles,
43
+ permType,
44
+ permLevel
45
+ ) => {
46
+ // get the user's roles
47
+ const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
48
+ const userRoles = await getUserRoleHierarchy(ctx.appId, roleId, {
49
+ idOnly: false,
50
+ })
51
+ const permError = "User does not have permission"
52
+ // check if the user has the required role
53
+ if (resourceRoles.length > 0) {
54
+ // deny access if the user doesn't have the required resource role
55
+ const found = userRoles.find(role => resourceRoles.indexOf(role._id) !== -1)
56
+ if (!found) {
57
+ ctx.throw(403, permError)
58
+ }
59
+ // fallback to the base permissions when no resource roles are found
60
+ } else if (!doesHaveBasePermission(permType, permLevel, userRoles)) {
61
+ ctx.throw(403, permError)
62
+ }
63
+ }
64
+
17
65
  module.exports =
18
66
  (permType, permLevel = null) =>
19
67
  async (ctx, next) => {
@@ -31,40 +79,26 @@ module.exports =
31
79
  // to find API endpoints which are builder focused
32
80
  await builderMiddleware(ctx, permType)
33
81
 
34
- const isAuthed = ctx.isAuthenticated
35
- // builders for now have permission to do anything
36
- let isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
37
- const isBuilderApi = permType === PermissionTypes.BUILDER
38
- if (isBuilder) {
39
- return next()
40
- } else if (isBuilderApi && !isBuilder) {
41
- return ctx.throw(403, "Not Authorized")
82
+ // get the resource roles
83
+ let resourceRoles = []
84
+ if (ctx.appId && hasResource(ctx)) {
85
+ resourceRoles = await getRequiredResourceRole(ctx.appId, permLevel, ctx)
42
86
  }
43
87
 
44
- // need to check this first, in-case public access, don't check authed until last
45
- const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
46
- const hierarchy = await getUserRoleHierarchy(ctx.appId, roleId, {
47
- idOnly: false,
48
- })
49
- const permError = "User does not have permission"
50
- let possibleRoleIds = []
51
- if (hasResource(ctx)) {
52
- possibleRoleIds = await getRequiredResourceRole(ctx.appId, permLevel, ctx)
53
- }
54
- // check if we found a role, if not fallback to base permissions
55
- if (possibleRoleIds.length > 0) {
56
- const found = hierarchy.find(
57
- role => possibleRoleIds.indexOf(role._id) !== -1
58
- )
59
- return found ? next() : ctx.throw(403, permError)
60
- } else if (!doesHaveBasePermission(permType, permLevel, hierarchy)) {
61
- ctx.throw(403, permError)
88
+ // if the resource is public, proceed
89
+ const isPublicResource = resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC)
90
+ if (isPublicResource) {
91
+ return next()
62
92
  }
63
93
 
64
- // if they are not authed, then anything using the authorized middleware will fail
65
- if (!isAuthed) {
66
- ctx.throw(403, "Session not authenticated")
94
+ // check authenticated
95
+ if (!ctx.isAuthenticated) {
96
+ return ctx.throw(403, "Session not authenticated")
67
97
  }
68
98
 
69
- return next()
99
+ // check authorized
100
+ await checkAuthorized(ctx, resourceRoles, permType, permLevel)
101
+
102
+ // csrf protection
103
+ return csrf(ctx, next)
70
104
  }
@@ -47,15 +47,6 @@ module.exports = async (ctx, next) => {
47
47
  (!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
48
48
  ) {
49
49
  clearCookie(ctx, Cookies.CurrentApp)
50
- // have to set the return url on the server side as client side is not available
51
- setCookie(ctx, ctx.url, Cookies.RETURN_URL, {
52
- // don't sign so the browser can easily read
53
- sign: false,
54
- // use the request domain to match how ui handles the return url cookie.
55
- // it's important we don't use the shared domain here as the builder
56
- // can't delete from it without awareness of the domain.
57
- requestDomain: true,
58
- })
59
50
  return ctx.redirect("/")
60
51
  }
61
52
 
@@ -17,6 +17,7 @@ class TestConfiguration {
17
17
  this.middleware = authorizedMiddleware(role)
18
18
  this.next = jest.fn()
19
19
  this.throw = jest.fn()
20
+ this.headers = {}
20
21
  this.ctx = {
21
22
  headers: {},
22
23
  request: {
@@ -25,7 +26,8 @@ class TestConfiguration {
25
26
  appId: "",
26
27
  auth: {},
27
28
  next: this.next,
28
- throw: this.throw
29
+ throw: this.throw,
30
+ get: (name) => this.headers[name],
29
31
  }
30
32
  }
31
33
 
@@ -46,7 +48,7 @@ class TestConfiguration {
46
48
  }
47
49
 
48
50
  setAuthenticated(isAuthed) {
49
- this.ctx.auth = { authenticated: isAuthed }
51
+ this.ctx.isAuthenticated = isAuthed
50
52
  }
51
53
 
52
54
  setRequestUrl(url) {
@@ -107,7 +109,7 @@ describe("Authorization middleware", () => {
107
109
  expect(config.next).toHaveBeenCalled()
108
110
  })
109
111
 
110
- it("throws if the user has only builder permissions", async () => {
112
+ it("throws if the user does not have builder permissions", async () => {
111
113
  config.setEnvironment(false)
112
114
  config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER)
113
115
  config.setUser({
@@ -133,7 +135,7 @@ describe("Authorization middleware", () => {
133
135
  expect(config.next).toHaveBeenCalled()
134
136
  })
135
137
 
136
- it("throws if the user session is not authenticated after permission checks", async () => {
138
+ it("throws if the user session is not authenticated", async () => {
137
139
  config.setUser({
138
140
  role: {
139
141
  _id: ""
@@ -27,7 +27,7 @@ describe("syncRows", () => {
27
27
  await config.createTable()
28
28
  await config.createRow()
29
29
  // app 2
30
- await config.createApp()
30
+ await config.createApp("second-app")
31
31
  await config.createTable()
32
32
  await config.createRow()
33
33
  await config.createRow()
@@ -22,10 +22,12 @@ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
22
22
  const { createASession } = require("@budibase/backend-core/sessions")
23
23
  const { user: userCache } = require("@budibase/backend-core/cache")
24
24
  const CouchDB = require("../../db")
25
+ const newid = require("../../db/newid")
25
26
  core.init(CouchDB)
26
27
 
27
28
  const GLOBAL_USER_ID = "us_uuid1"
28
29
  const EMAIL = "babs@babs.com"
30
+ const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
29
31
 
30
32
  class TestConfiguration {
31
33
  constructor(openServer = true) {
@@ -85,7 +87,11 @@ class TestConfiguration {
85
87
  roles: roles || {},
86
88
  tenantId: TENANT_ID,
87
89
  }
88
- await createASession(id, { sessionId: "sessionid", tenantId: TENANT_ID })
90
+ await createASession(id, {
91
+ sessionId: "sessionid",
92
+ tenantId: TENANT_ID,
93
+ csrfToken: CSRF_TOKEN,
94
+ })
89
95
  if (builder) {
90
96
  user.builder = { global: true }
91
97
  } else {
@@ -98,7 +104,8 @@ class TestConfiguration {
98
104
  }
99
105
  }
100
106
 
101
- async init(appName = "test_application") {
107
+ // use a new id as the name to avoid name collisions
108
+ async init(appName = newid()) {
102
109
  await this.globalUser()
103
110
  return this.createApp(appName)
104
111
  }
@@ -131,6 +138,7 @@ class TestConfiguration {
131
138
  `${Cookies.Auth}=${authToken}`,
132
139
  `${Cookies.CurrentApp}=${appToken}`,
133
140
  ],
141
+ [Headers.CSRF_TOKEN]: CSRF_TOKEN,
134
142
  }
135
143
  if (this.appId) {
136
144
  headers[Headers.APP_ID] = this.appId
@@ -424,10 +432,6 @@ class TestConfiguration {
424
432
  roles: { [this.prodAppId]: roleId },
425
433
  })
426
434
  }
427
- await createASession(userId, {
428
- sessionId: "sessionid",
429
- tenantId: TENANT_ID,
430
- })
431
435
  // have to fake this
432
436
  const auth = {
433
437
  userId,
@@ -78,6 +78,11 @@ class QueryRunner {
78
78
  return this.execute()
79
79
  }
80
80
 
81
+ // check for undefined response
82
+ if (!rows) {
83
+ rows = []
84
+ }
85
+
81
86
  // needs to an array for next step
82
87
  if (!Array.isArray(rows)) {
83
88
  rows = [rows]
@@ -58,29 +58,6 @@ exports.sendSmtpEmail = async (to, from, subject, contents, automation) => {
58
58
  return response.json()
59
59
  }
60
60
 
61
- exports.getDeployedApps = async () => {
62
- try {
63
- const response = await fetch(
64
- checkSlashesInUrl(env.WORKER_URL + `/api/apps`),
65
- request(null, {
66
- method: "GET",
67
- })
68
- )
69
- const json = await response.json()
70
- const apps = {}
71
- for (let [key, value] of Object.entries(json)) {
72
- if (value.url) {
73
- value.url = value.url.toLowerCase()
74
- apps[key.toLowerCase()] = value
75
- }
76
- }
77
- return apps
78
- } catch (err) {
79
- // error, cannot determine deployed apps, don't stop app creation - sort this later
80
- return {}
81
- }
82
- }
83
-
84
61
  exports.getGlobalSelf = async (ctx, appId = null) => {
85
62
  const endpoint = `/api/global/users/self`
86
63
  const response = await fetch(