@budibase/server 1.0.48 → 1.0.49-alpha.3
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/builder/assets/{index.e083dcaf.js → index.21e57eb4.js} +234 -231
- package/builder/assets/index.83c0944f.css +1 -0
- package/builder/assets/{vendor.f9faf6ab.js → vendor.d526bfe8.js} +12 -12
- package/builder/index.html +3 -3
- package/coverage/clover.xml +2338 -2028
- package/coverage/coverage-final.json +100 -95
- package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
- package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/controllers/application.js.html +210 -126
- package/coverage/lcov-report/api/controllers/auth.js.html +18 -6
- package/coverage/lcov-report/api/controllers/automation.js.html +21 -21
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +4 -4
- package/coverage/lcov-report/api/controllers/datasource.js.html +72 -36
- package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
- package/coverage/lcov-report/api/controllers/index.html +53 -53
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +6 -6
- package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
- package/coverage/lcov-report/api/controllers/migrations.js.html +119 -0
- package/coverage/lcov-report/api/controllers/permission.js.html +15 -15
- package/coverage/lcov-report/api/controllers/query/index.html +1 -1
- package/coverage/lcov-report/api/controllers/query/index.js.html +19 -19
- package/coverage/lcov-report/api/controllers/query/validation.js.html +6 -6
- package/coverage/lcov-report/api/controllers/role.js.html +9 -9
- package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/external.js.html +16 -16
- package/coverage/lcov-report/api/controllers/row/index.html +48 -33
- package/coverage/lcov-report/api/controllers/row/index.js.html +26 -26
- package/coverage/lcov-report/api/controllers/row/internal.js.html +95 -158
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +41 -17
- package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +551 -0
- package/coverage/lcov-report/api/controllers/row/utils.js.html +39 -57
- package/coverage/lcov-report/api/controllers/screen.js.html +7 -7
- package/coverage/lcov-report/api/controllers/script.js.html +4 -4
- package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +629 -0
- package/coverage/lcov-report/api/controllers/table/external.js.html +12 -12
- package/coverage/lcov-report/api/controllers/table/index.html +46 -31
- package/coverage/lcov-report/api/controllers/table/index.js.html +53 -77
- package/coverage/lcov-report/api/controllers/table/internal.js.html +59 -35
- package/coverage/lcov-report/api/controllers/table/utils.js.html +217 -73
- package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
- package/coverage/lcov-report/api/controllers/user.js.html +30 -24
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +4 -4
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +14 -14
- package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +7 -7
- package/coverage/lcov-report/api/controllers/webhook.js.html +15 -15
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +8 -8
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/application.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +18 -18
- package/coverage/lcov-report/api/routes/index.js.html +3 -3
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/{hosting.js.html → migrations.js.html} +17 -14
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +1 -1
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/row.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/static.js.html +17 -2
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +24 -24
- package/coverage/lcov-report/automations/automationUtils.js.html +5 -5
- package/coverage/lcov-report/automations/bullboard.js.html +16 -16
- package/coverage/lcov-report/automations/index.html +1 -1
- package/coverage/lcov-report/automations/index.js.html +1 -1
- package/coverage/lcov-report/automations/steps/bash.js.html +6 -6
- package/coverage/lcov-report/automations/steps/createRow.js.html +13 -19
- package/coverage/lcov-report/automations/steps/delay.js.html +4 -4
- package/coverage/lcov-report/automations/steps/deleteRow.js.html +11 -20
- package/coverage/lcov-report/automations/steps/discord.js.html +7 -7
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +6 -6
- package/coverage/lcov-report/automations/steps/executeScript.js.html +6 -6
- package/coverage/lcov-report/automations/steps/filter.js.html +7 -7
- package/coverage/lcov-report/automations/steps/index.html +13 -13
- package/coverage/lcov-report/automations/steps/integromat.js.html +5 -5
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +8 -8
- package/coverage/lcov-report/automations/steps/queryRows.js.html +10 -10
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +5 -5
- package/coverage/lcov-report/automations/steps/serverLog.js.html +3 -3
- package/coverage/lcov-report/automations/steps/slack.js.html +5 -5
- package/coverage/lcov-report/automations/steps/updateRow.js.html +6 -6
- package/coverage/lcov-report/automations/steps/utils.js.html +3 -3
- package/coverage/lcov-report/automations/steps/zapier.js.html +5 -5
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
- package/coverage/lcov-report/automations/triggers.js.html +18 -18
- package/coverage/lcov-report/automations/utils.js.html +32 -32
- package/coverage/lcov-report/constants/index.html +5 -5
- package/coverage/lcov-report/constants/index.js.html +53 -26
- package/coverage/lcov-report/constants/layouts.js.html +5 -5
- package/coverage/lcov-report/constants/screens.js.html +4 -4
- package/coverage/lcov-report/db/client.js.html +18 -18
- package/coverage/lcov-report/db/inMemoryView.js.html +7 -7
- package/coverage/lcov-report/db/index.html +15 -15
- package/coverage/lcov-report/db/index.js.html +3 -3
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +18 -18
- package/coverage/lcov-report/db/linkedRows/index.html +10 -10
- package/coverage/lcov-report/db/linkedRows/index.js.html +38 -41
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +15 -15
- package/coverage/lcov-report/db/newid.js.html +4 -4
- package/coverage/lcov-report/db/utils.js.html +86 -86
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +5 -5
- package/coverage/lcov-report/events/utils.js.html +3 -3
- package/coverage/lcov-report/index.html +114 -99
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
- package/coverage/lcov-report/middleware/authorized.js.html +166 -64
- package/coverage/lcov-report/middleware/builder.js.html +8 -8
- package/coverage/lcov-report/middleware/currentapp.js.html +29 -29
- package/coverage/lcov-report/middleware/index.html +30 -30
- package/coverage/lcov-report/middleware/joi-validator.js.html +3 -3
- package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/usageQuota.js.html +270 -42
- package/coverage/lcov-report/middleware/utils.js.html +2 -2
- package/coverage/lcov-report/threads/automation.js.html +1 -1
- package/coverage/lcov-report/threads/index.html +15 -15
- package/coverage/lcov-report/threads/index.js.html +10 -10
- package/coverage/lcov-report/threads/query.js.html +23 -8
- package/coverage/lcov-report/threads/utils.js.html +14 -14
- package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +7 -7
- package/coverage/lcov-report/utilities/centralPath.js.html +4 -4
- package/coverage/lcov-report/utilities/csvParser.js.html +6 -6
- package/coverage/lcov-report/utilities/global.js.html +26 -26
- package/coverage/lcov-report/utilities/index.html +19 -19
- package/coverage/lcov-report/utilities/index.js.html +25 -25
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +8 -8
- package/coverage/lcov-report/utilities/queue/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +27 -27
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +115 -79
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +75 -27
- package/coverage/lcov-report/utilities/scriptRunner.js.html +5 -5
- package/coverage/lcov-report/utilities/security.js.html +8 -8
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.html +141 -0
- package/coverage/lcov-report/utilities/usageQuota/index.js.html +356 -0
- package/coverage/lcov-report/utilities/usageQuota/rows.js.html +302 -0
- package/coverage/lcov-report/{api/controllers/hosting.js.html → utilities/usageQuota/usageQuoteReset.js.html} +36 -48
- package/coverage/lcov-report/utilities/users.js.html +5 -5
- package/coverage/lcov-report/utilities/workerRequests.js.html +39 -108
- package/coverage/lcov.info +3656 -3057
- package/dist/api/controllers/application.js +61 -34
- package/dist/api/controllers/auth.js +4 -0
- package/dist/api/controllers/datasource.js +11 -0
- package/dist/api/controllers/migrations.js +21 -0
- package/dist/api/controllers/row/internal.js +30 -53
- package/dist/api/controllers/row/internalSearch.js +12 -4
- package/dist/api/controllers/row/staticFormula.js +147 -0
- package/dist/api/controllers/row/utils.js +6 -8
- package/dist/api/controllers/static/index.js +59 -10
- package/dist/api/controllers/table/bulkFormula.js +160 -0
- package/dist/api/controllers/table/index.js +12 -9
- package/dist/api/controllers/table/internal.js +9 -0
- package/dist/api/controllers/table/utils.js +41 -12
- package/dist/api/controllers/user.js +2 -0
- package/dist/api/routes/index.js +2 -2
- package/dist/api/routes/migrations.js +9 -0
- package/dist/api/routes/static.js +2 -1
- package/dist/app.js +35 -5
- package/dist/automations/steps/createRow.js +2 -4
- package/dist/automations/steps/deleteRow.js +1 -4
- package/dist/constants/index.js +7 -0
- package/dist/db/linkedRows/index.js +4 -5
- package/dist/definitions/datasource.js +1 -0
- package/dist/environment.js +2 -0
- package/dist/integrations/base/sql.js +13 -6
- package/dist/integrations/googlesheets.js +340 -0
- package/dist/integrations/index.js +3 -0
- package/dist/integrations/utils.js +57 -9
- package/dist/middleware/authorized.js +55 -30
- package/dist/middleware/usageQuota.js +80 -13
- package/dist/migrations/functions/appUrls.js +46 -0
- package/dist/migrations/functions/quotas1.js +50 -0
- package/dist/migrations/functions/usageQuotas/index.js +20 -0
- package/dist/migrations/functions/usageQuotas/syncApps.js +32 -0
- package/dist/migrations/functions/usageQuotas/syncRows.js +35 -0
- package/dist/migrations/functions/userEmailViewCasing.js +24 -0
- package/dist/migrations/index.js +62 -0
- package/dist/package.json +8 -5
- package/dist/tests/utilities/TestConfiguration.js +13 -6
- package/dist/threads/query.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utilities/rowProcessor/index.js +18 -11
- package/dist/utilities/rowProcessor/utils.js +14 -4
- package/dist/utilities/{usageQuota.js → usageQuota/index.js} +44 -31
- package/dist/utilities/usageQuota/rows.js +77 -0
- package/dist/utilities/usageQuota/usageQuoteReset.js +16 -0
- package/dist/utilities/workerRequests.js +0 -20
- package/nodemon.json +1 -1
- package/package.json +9 -6
- package/src/api/controllers/application.js +52 -24
- package/src/api/controllers/auth.js +4 -0
- package/src/api/controllers/datasource.js +12 -0
- package/src/api/controllers/migrations.js +13 -0
- package/src/api/controllers/row/internal.js +29 -50
- package/src/api/controllers/row/internalSearch.js +12 -4
- package/src/api/controllers/row/staticFormula.js +157 -0
- package/src/api/controllers/row/utils.js +6 -12
- package/src/api/controllers/static/index.js +63 -10
- package/src/api/controllers/table/bulkFormula.js +183 -0
- package/src/api/controllers/table/index.js +16 -24
- package/src/api/controllers/table/internal.js +9 -1
- package/src/api/controllers/table/utils.js +57 -9
- package/src/api/controllers/user.js +2 -0
- package/src/api/routes/index.js +2 -2
- package/src/api/routes/migrations.js +14 -0
- package/src/api/routes/static.js +5 -0
- package/src/api/routes/tests/application.spec.js +2 -2
- package/src/api/routes/tests/auth.spec.js +1 -2
- package/src/api/routes/tests/static.spec.js +98 -0
- package/src/app.ts +18 -5
- package/src/automations/steps/createRow.js +2 -4
- package/src/automations/steps/deleteRow.js +1 -4
- package/src/constants/index.js +9 -0
- package/src/db/linkedRows/index.js +4 -5
- package/src/definitions/common.ts +3 -0
- package/src/definitions/datasource.ts +3 -0
- package/src/environment.js +2 -0
- package/src/integrations/base/sql.ts +14 -7
- package/src/integrations/googlesheets.ts +353 -0
- package/src/integrations/index.ts +3 -0
- package/src/integrations/tests/sql.spec.js +16 -0
- package/src/integrations/utils.ts +71 -11
- package/src/middleware/authorized.js +64 -30
- package/src/middleware/tests/authorized.spec.js +6 -4
- package/src/middleware/tests/usageQuota.spec.js +18 -24
- package/src/middleware/usageQuota.js +91 -15
- package/src/migrations/functions/appUrls.ts +34 -0
- package/src/migrations/functions/quotas1.ts +20 -0
- package/src/migrations/functions/tests/appUrls.spec.js +29 -0
- package/src/migrations/functions/tests/quotas1.spec.js +27 -0
- package/src/migrations/functions/tests/userEmailViewCasing.spec.js +25 -0
- package/src/migrations/functions/usageQuotas/index.ts +8 -0
- package/src/migrations/functions/usageQuotas/syncApps.ts +18 -0
- package/src/migrations/functions/usageQuotas/syncRows.ts +21 -0
- package/src/migrations/functions/usageQuotas/tests/syncApps.spec.js +37 -0
- package/src/migrations/functions/usageQuotas/tests/syncRows.spec.js +43 -0
- package/src/migrations/functions/userEmailViewCasing.ts +13 -0
- package/src/migrations/index.ts +56 -0
- package/src/tests/utilities/TestConfiguration.js +14 -6
- package/src/threads/query.js +5 -0
- package/src/utilities/rowProcessor/index.js +24 -12
- package/src/utilities/rowProcessor/utils.js +23 -7
- package/src/utilities/tests/usageQuota/usageQuota.spec.js +72 -0
- package/src/utilities/usageQuota/index.js +92 -0
- package/src/utilities/usageQuota/rows.js +74 -0
- package/src/utilities/usageQuota/usageQuoteReset.js +18 -0
- package/src/utilities/workerRequests.js +0 -23
- package/builder/assets/index.39e01193.css +0 -1
- package/dist/api/controllers/hosting.js +0 -30
- package/dist/api/routes/hosting.js +0 -11
- package/src/api/controllers/hosting.js +0 -22
- package/src/api/routes/hosting.js +0 -13
- package/src/api/routes/tests/hosting.spec.js +0 -36
- package/src/utilities/usageQuota.js +0 -73
|
@@ -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
|
|
@@ -119,6 +119,22 @@ describe("SQL query builder", () => {
|
|
|
119
119
|
})
|
|
120
120
|
})
|
|
121
121
|
|
|
122
|
+
it("should allow filtering on a related field", () => {
|
|
123
|
+
const query = sql._query(generateReadJson({
|
|
124
|
+
filters: {
|
|
125
|
+
equal: {
|
|
126
|
+
age: 10,
|
|
127
|
+
"task.name": "task 1",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}))
|
|
131
|
+
// order of bindings changes because relationship filters occur outside inner query
|
|
132
|
+
expect(query).toEqual({
|
|
133
|
+
bindings: [10, limit, "task 1"],
|
|
134
|
+
sql: `select * from (select * from "${TABLE_NAME}" where "${TABLE_NAME}"."age" = $1 limit $2) as "${TABLE_NAME}" where "task"."name" = $3`
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
122
138
|
it("should test an create statement", () => {
|
|
123
139
|
const query = sql._query(generateCreateJson(TABLE_NAME, {
|
|
124
140
|
name: "Michael",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SqlQuery } from "../definitions/datasource"
|
|
1
|
+
import { SourceNames, SqlQuery } from "../definitions/datasource"
|
|
2
2
|
import { Datasource, Table } from "../definitions/common"
|
|
3
|
-
import { SourceNames } from "../definitions/datasource"
|
|
4
3
|
import { DocumentTypes, SEPARATOR } from "../db/utils"
|
|
5
4
|
import { FieldTypes, BuildSchemaErrors, InvalidColumns } from "../constants"
|
|
6
5
|
|
|
@@ -127,7 +126,12 @@ export function isSQL(datasource: Datasource): boolean {
|
|
|
127
126
|
if (!datasource || !datasource.source) {
|
|
128
127
|
return false
|
|
129
128
|
}
|
|
130
|
-
const SQL = [
|
|
129
|
+
const SQL = [
|
|
130
|
+
SourceNames.POSTGRES,
|
|
131
|
+
SourceNames.SQL_SERVER,
|
|
132
|
+
SourceNames.MYSQL,
|
|
133
|
+
SourceNames.ORACLE,
|
|
134
|
+
]
|
|
131
135
|
return SQL.indexOf(datasource.source) !== -1
|
|
132
136
|
}
|
|
133
137
|
|
|
@@ -139,11 +143,60 @@ export function isIsoDateString(str: string) {
|
|
|
139
143
|
return d.toISOString() === str
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
|
|
146
|
+
/**
|
|
147
|
+
* This function will determine whether a column is a relationship and whether it
|
|
148
|
+
* is currently valid. The reason for the validity check is that tables can be deleted
|
|
149
|
+
* outside of Budibase control and if this is the case it will break Budibase relationships.
|
|
150
|
+
* The tableIds is a list passed down from the main finalise tables function, which is
|
|
151
|
+
* based on the tables that have just been fetched. This will only really be used on subsequent
|
|
152
|
+
* fetches to the first one - if the user is periodically refreshing Budibase knowledge of tables.
|
|
153
|
+
* @param column The column to check, to see if it is a valid relationship.
|
|
154
|
+
* @param tableIds The IDs of the tables which currently exist.
|
|
155
|
+
*/
|
|
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
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Similar function to the shouldCopyRelationship function, but instead this looks for options and boolean
|
|
169
|
+
* types. It is possible to switch a string -> options and a number -> boolean (and vice versus) need to make
|
|
170
|
+
* sure that these get copied over when tables are fetched. Also checks whether they are still valid, if a
|
|
171
|
+
* column has changed type in the external database then copying it over may not be possible.
|
|
172
|
+
* @param column The column to check for options or boolean type.
|
|
173
|
+
* @param fetchedColumn The fetched column to check for the type in the external database.
|
|
174
|
+
*/
|
|
175
|
+
function shouldCopySpecialColumn(
|
|
176
|
+
column: { type: string },
|
|
177
|
+
fetchedColumn: { type: string } | undefined
|
|
178
|
+
) {
|
|
179
|
+
return (
|
|
180
|
+
column.type === FieldTypes.OPTIONS ||
|
|
181
|
+
column.type === FieldTypes.ARRAY ||
|
|
182
|
+
((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) &&
|
|
183
|
+
column.type === FieldTypes.BOOLEAN)
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Looks for columns which need to be copied over into the new table definitions, like relationships
|
|
189
|
+
* and options types.
|
|
190
|
+
* @param tableName The name of the table which is being checked.
|
|
191
|
+
* @param table The specific table which is being checked.
|
|
192
|
+
* @param entities All the tables that existed before - the old table definitions.
|
|
193
|
+
* @param tableIds The IDs of the tables which exist now, to check if anything has been removed.
|
|
194
|
+
*/
|
|
143
195
|
function copyExistingPropsOver(
|
|
144
196
|
tableName: string,
|
|
145
197
|
table: Table,
|
|
146
|
-
entities: { [key: string]: any }
|
|
198
|
+
entities: { [key: string]: any },
|
|
199
|
+
tableIds: [string]
|
|
147
200
|
) {
|
|
148
201
|
if (entities && entities[tableName]) {
|
|
149
202
|
if (entities[tableName].primaryDisplay) {
|
|
@@ -154,12 +207,10 @@ function copyExistingPropsOver(
|
|
|
154
207
|
if (!existingTableSchema.hasOwnProperty(key)) {
|
|
155
208
|
continue
|
|
156
209
|
}
|
|
210
|
+
const column = existingTableSchema[key]
|
|
157
211
|
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
existingTableSchema[key].type === FieldTypes.ARRAY ||
|
|
161
|
-
((!table.schema[key] || table.schema[key].type === FieldTypes.NUMBER) &&
|
|
162
|
-
existingTableSchema[key].type === FieldTypes.BOOLEAN)
|
|
212
|
+
shouldCopyRelationship(column, tableIds) ||
|
|
213
|
+
shouldCopySpecialColumn(column, table.schema[key])
|
|
163
214
|
) {
|
|
164
215
|
table.schema[key] = existingTableSchema[key]
|
|
165
216
|
}
|
|
@@ -168,6 +219,13 @@ function copyExistingPropsOver(
|
|
|
168
219
|
return table
|
|
169
220
|
}
|
|
170
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Look through the final table definitions to see if anything needs to be
|
|
224
|
+
* copied over from the old and if any errors have occurred mark them so
|
|
225
|
+
* that the user can be made aware.
|
|
226
|
+
* @param tables The list of tables that have been retrieved from the external database.
|
|
227
|
+
* @param entities The old list of tables, if there was any to look for definitions in.
|
|
228
|
+
*/
|
|
171
229
|
export function finaliseExternalTables(
|
|
172
230
|
tables: { [key: string]: any },
|
|
173
231
|
entities: { [key: string]: any }
|
|
@@ -175,6 +233,8 @@ export function finaliseExternalTables(
|
|
|
175
233
|
const invalidColumns = Object.values(InvalidColumns)
|
|
176
234
|
let finalTables: { [key: string]: any } = {}
|
|
177
235
|
const errors: { [key: string]: string } = {}
|
|
236
|
+
// @ts-ignore
|
|
237
|
+
const tableIds: [string] = Object.values(tables).map(table => table._id)
|
|
178
238
|
for (let [name, table] of Object.entries(tables)) {
|
|
179
239
|
const schemaFields = Object.keys(table.schema)
|
|
180
240
|
// make sure every table has a key
|
|
@@ -186,7 +246,7 @@ export function finaliseExternalTables(
|
|
|
186
246
|
continue
|
|
187
247
|
}
|
|
188
248
|
// make sure all previous props have been added back
|
|
189
|
-
finalTables[name] = copyExistingPropsOver(name, table, entities)
|
|
249
|
+
finalTables[name] = copyExistingPropsOver(name, table, entities, tableIds)
|
|
190
250
|
}
|
|
191
251
|
// sort the tables by name
|
|
192
252
|
finalTables = Object.entries(finalTables)
|
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
//
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
65
|
-
if (!
|
|
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
|
-
|
|
99
|
+
// check authorized
|
|
100
|
+
await checkAuthorized(ctx, resourceRoles, permType, permLevel)
|
|
101
|
+
|
|
102
|
+
// csrf protection
|
|
103
|
+
return csrf(ctx, next)
|
|
70
104
|
}
|
|
@@ -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.
|
|
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
|
|
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
|
|
138
|
+
it("throws if the user session is not authenticated", async () => {
|
|
137
139
|
config.setUser({
|
|
138
140
|
role: {
|
|
139
141
|
_id: ""
|