@budibase/worker 2.3.20 → 2.3.21-alpha.0

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 (180) hide show
  1. package/__mocks__/node-fetch.ts +23 -0
  2. package/jest-testcontainers-config.js +8 -0
  3. package/jest.config.ts +14 -18
  4. package/package.json +12 -8
  5. package/scripts/dev/manage.js +1 -0
  6. package/scripts/test.sh +12 -0
  7. package/src/api/controllers/global/auth.ts +109 -111
  8. package/src/api/controllers/global/configs.ts +150 -142
  9. package/src/api/controllers/global/self.ts +28 -44
  10. package/src/api/controllers/global/users.ts +211 -32
  11. package/src/api/controllers/system/accounts.ts +7 -5
  12. package/src/api/controllers/system/tenants.ts +4 -8
  13. package/src/api/index.ts +4 -20
  14. package/src/api/routes/global/auth.ts +10 -7
  15. package/src/api/routes/global/configs.ts +6 -12
  16. package/src/api/routes/global/tests/auditLogs.spec.ts +111 -0
  17. package/src/api/routes/global/tests/auth.spec.ts +217 -18
  18. package/src/api/routes/global/tests/configs.spec.ts +93 -65
  19. package/src/api/routes/global/tests/realEmail.spec.ts +1 -0
  20. package/src/api/routes/global/tests/roles.spec.ts +20 -14
  21. package/src/api/routes/global/tests/self.spec.ts +3 -4
  22. package/src/api/routes/global/tests/users.spec.ts +29 -28
  23. package/src/api/routes/global/users.ts +19 -14
  24. package/src/api/routes/index.ts +3 -0
  25. package/src/api/routes/system/tenants.ts +1 -1
  26. package/src/api/routes/system/tests/accounts.spec.ts +4 -4
  27. package/src/api/routes/system/tests/migrations.spec.ts +2 -2
  28. package/src/api/routes/system/tests/restore.spec.ts +2 -2
  29. package/src/api/routes/system/tests/status.spec.ts +5 -5
  30. package/src/db/index.ts +8 -2
  31. package/src/ddApm.ts +7 -0
  32. package/src/elasticApm.ts +10 -0
  33. package/src/environment.ts +4 -0
  34. package/src/index.ts +29 -8
  35. package/src/middleware/tests/tenancy.spec.ts +4 -4
  36. package/src/migrations/functions/globalInfoSyncUsers.ts +4 -3
  37. package/src/sdk/accounts/index.ts +2 -1
  38. package/src/sdk/accounts/{accounts.ts → metadata.ts} +0 -1
  39. package/src/sdk/auth/auth.ts +86 -0
  40. package/src/sdk/auth/index.ts +1 -0
  41. package/src/sdk/tenants/index.ts +1 -0
  42. package/src/sdk/tenants/tenants.ts +76 -0
  43. package/src/sdk/users/events.ts +4 -0
  44. package/src/sdk/users/index.ts +1 -0
  45. package/src/sdk/users/tests/users.spec.ts +76 -0
  46. package/src/sdk/users/users.ts +86 -53
  47. package/src/tests/TestConfiguration.ts +41 -62
  48. package/src/tests/api/auditLogs.ts +26 -0
  49. package/src/tests/api/auth.ts +44 -14
  50. package/src/tests/api/base.ts +2 -1
  51. package/src/tests/api/configs.ts +9 -1
  52. package/src/tests/api/email.ts +1 -0
  53. package/src/tests/api/index.ts +3 -0
  54. package/src/tests/api/restore.ts +1 -0
  55. package/src/tests/api/users.ts +2 -2
  56. package/src/tests/jestEnv.ts +2 -1
  57. package/src/tests/jestSetup.ts +10 -6
  58. package/src/tests/logging.ts +34 -0
  59. package/src/tests/structures/configs.ts +20 -13
  60. package/src/tests/structures/index.ts +0 -4
  61. package/src/utilities/email.ts +22 -68
  62. package/src/utilities/redis.ts +42 -2
  63. package/src/utilities/templates.ts +4 -9
  64. package/coverage/clover.xml +0 -2199
  65. package/coverage/coverage-final.json +0 -84
  66. package/coverage/lcov-report/base.css +0 -224
  67. package/coverage/lcov-report/block-navigation.js +0 -87
  68. package/coverage/lcov-report/favicon.png +0 -0
  69. package/coverage/lcov-report/index.html +0 -431
  70. package/coverage/lcov-report/prettify.css +0 -1
  71. package/coverage/lcov-report/prettify.js +0 -2
  72. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  73. package/coverage/lcov-report/sorter.js +0 -196
  74. package/coverage/lcov-report/src/api/controllers/global/auth.ts.html +0 -934
  75. package/coverage/lcov-report/src/api/controllers/global/configs.ts.html +0 -1348
  76. package/coverage/lcov-report/src/api/controllers/global/email.ts.html +0 -196
  77. package/coverage/lcov-report/src/api/controllers/global/events.ts.html +0 -136
  78. package/coverage/lcov-report/src/api/controllers/global/index.html +0 -251
  79. package/coverage/lcov-report/src/api/controllers/global/license.ts.html +0 -187
  80. package/coverage/lcov-report/src/api/controllers/global/roles.ts.html +0 -283
  81. package/coverage/lcov-report/src/api/controllers/global/self.ts.html +0 -577
  82. package/coverage/lcov-report/src/api/controllers/global/templates.ts.html +0 -316
  83. package/coverage/lcov-report/src/api/controllers/global/users.ts.html +0 -838
  84. package/coverage/lcov-report/src/api/controllers/global/workspaces.ts.html +0 -244
  85. package/coverage/lcov-report/src/api/controllers/system/accounts.ts.html +0 -148
  86. package/coverage/lcov-report/src/api/controllers/system/environment.ts.html +0 -124
  87. package/coverage/lcov-report/src/api/controllers/system/index.html +0 -191
  88. package/coverage/lcov-report/src/api/controllers/system/migrations.ts.html +0 -124
  89. package/coverage/lcov-report/src/api/controllers/system/restore.ts.html +0 -124
  90. package/coverage/lcov-report/src/api/controllers/system/status.ts.html +0 -133
  91. package/coverage/lcov-report/src/api/controllers/system/tenants.ts.html +0 -154
  92. package/coverage/lcov-report/src/api/index.html +0 -116
  93. package/coverage/lcov-report/src/api/index.ts.html +0 -595
  94. package/coverage/lcov-report/src/api/routes/global/auth.ts.html +0 -349
  95. package/coverage/lcov-report/src/api/routes/global/configs.ts.html +0 -457
  96. package/coverage/lcov-report/src/api/routes/global/email.ts.html +0 -193
  97. package/coverage/lcov-report/src/api/routes/global/events.ts.html +0 -109
  98. package/coverage/lcov-report/src/api/routes/global/index.html +0 -251
  99. package/coverage/lcov-report/src/api/routes/global/license.ts.html +0 -124
  100. package/coverage/lcov-report/src/api/routes/global/roles.ts.html +0 -133
  101. package/coverage/lcov-report/src/api/routes/global/self.ts.html +0 -139
  102. package/coverage/lcov-report/src/api/routes/global/templates.ts.html +0 -196
  103. package/coverage/lcov-report/src/api/routes/global/users.ts.html +0 -475
  104. package/coverage/lcov-report/src/api/routes/global/workspaces.ts.html +0 -196
  105. package/coverage/lcov-report/src/api/routes/index.html +0 -116
  106. package/coverage/lcov-report/src/api/routes/index.ts.html +0 -202
  107. package/coverage/lcov-report/src/api/routes/system/accounts.ts.html +0 -142
  108. package/coverage/lcov-report/src/api/routes/system/environment.ts.html +0 -109
  109. package/coverage/lcov-report/src/api/routes/system/index.html +0 -191
  110. package/coverage/lcov-report/src/api/routes/system/migrations.ts.html +0 -142
  111. package/coverage/lcov-report/src/api/routes/system/restore.ts.html +0 -109
  112. package/coverage/lcov-report/src/api/routes/system/status.ts.html +0 -109
  113. package/coverage/lcov-report/src/api/routes/system/tenants.ts.html +0 -124
  114. package/coverage/lcov-report/src/api/routes/validation/index.html +0 -131
  115. package/coverage/lcov-report/src/api/routes/validation/index.ts.html +0 -88
  116. package/coverage/lcov-report/src/api/routes/validation/users.ts.html +0 -235
  117. package/coverage/lcov-report/src/constants/index.html +0 -116
  118. package/coverage/lcov-report/src/constants/index.ts.html +0 -637
  119. package/coverage/lcov-report/src/constants/templates/index.html +0 -116
  120. package/coverage/lcov-report/src/constants/templates/index.ts.html +0 -316
  121. package/coverage/lcov-report/src/db/index.html +0 -116
  122. package/coverage/lcov-report/src/db/index.ts.html +0 -115
  123. package/coverage/lcov-report/src/environment.ts.html +0 -388
  124. package/coverage/lcov-report/src/index.html +0 -131
  125. package/coverage/lcov-report/src/index.ts.html +0 -394
  126. package/coverage/lcov-report/src/middleware/cloudRestricted.ts.html +0 -139
  127. package/coverage/lcov-report/src/middleware/index.html +0 -116
  128. package/coverage/lcov-report/src/migrations/functions/globalInfoSyncUsers.ts.html +0 -145
  129. package/coverage/lcov-report/src/migrations/functions/index.html +0 -116
  130. package/coverage/lcov-report/src/migrations/index.html +0 -116
  131. package/coverage/lcov-report/src/migrations/index.ts.html +0 -271
  132. package/coverage/lcov-report/src/sdk/accounts/accounts.ts.html +0 -262
  133. package/coverage/lcov-report/src/sdk/accounts/index.html +0 -131
  134. package/coverage/lcov-report/src/sdk/accounts/index.ts.html +0 -88
  135. package/coverage/lcov-report/src/sdk/index.html +0 -116
  136. package/coverage/lcov-report/src/sdk/index.ts.html +0 -106
  137. package/coverage/lcov-report/src/sdk/users/events.ts.html +0 -601
  138. package/coverage/lcov-report/src/sdk/users/index.html +0 -146
  139. package/coverage/lcov-report/src/sdk/users/index.ts.html +0 -88
  140. package/coverage/lcov-report/src/sdk/users/users.ts.html +0 -1999
  141. package/coverage/lcov-report/src/tests/TestConfiguration.ts.html +0 -1075
  142. package/coverage/lcov-report/src/tests/api/accounts.ts.html +0 -160
  143. package/coverage/lcov-report/src/tests/api/auth.ts.html +0 -220
  144. package/coverage/lcov-report/src/tests/api/base.ts.html +0 -133
  145. package/coverage/lcov-report/src/tests/api/configs.ts.html +0 -226
  146. package/coverage/lcov-report/src/tests/api/email.ts.html +0 -148
  147. package/coverage/lcov-report/src/tests/api/environment.ts.html +0 -130
  148. package/coverage/lcov-report/src/tests/api/groups.ts.html +0 -163
  149. package/coverage/lcov-report/src/tests/api/index.html +0 -356
  150. package/coverage/lcov-report/src/tests/api/index.ts.html +0 -238
  151. package/coverage/lcov-report/src/tests/api/license.ts.html +0 -136
  152. package/coverage/lcov-report/src/tests/api/migrations.ts.html +0 -151
  153. package/coverage/lcov-report/src/tests/api/restore.ts.html +0 -127
  154. package/coverage/lcov-report/src/tests/api/roles.ts.html +0 -181
  155. package/coverage/lcov-report/src/tests/api/self.ts.html +0 -163
  156. package/coverage/lcov-report/src/tests/api/status.ts.html +0 -121
  157. package/coverage/lcov-report/src/tests/api/templates.ts.html +0 -175
  158. package/coverage/lcov-report/src/tests/api/tenants.ts.html +0 -130
  159. package/coverage/lcov-report/src/tests/api/users.ts.html +0 -514
  160. package/coverage/lcov-report/src/tests/controllers.ts.html +0 -100
  161. package/coverage/lcov-report/src/tests/index.html +0 -146
  162. package/coverage/lcov-report/src/tests/index.ts.html +0 -103
  163. package/coverage/lcov-report/src/tests/mocks/email.ts.html +0 -115
  164. package/coverage/lcov-report/src/tests/mocks/index.html +0 -131
  165. package/coverage/lcov-report/src/tests/mocks/index.ts.html +0 -106
  166. package/coverage/lcov-report/src/tests/structures/configs.ts.html +0 -313
  167. package/coverage/lcov-report/src/tests/structures/groups.ts.html +0 -118
  168. package/coverage/lcov-report/src/tests/structures/index.html +0 -161
  169. package/coverage/lcov-report/src/tests/structures/index.ts.html +0 -151
  170. package/coverage/lcov-report/src/tests/structures/users.ts.html +0 -196
  171. package/coverage/lcov-report/src/utilities/appService.ts.html +0 -208
  172. package/coverage/lcov-report/src/utilities/email.ts.html +0 -850
  173. package/coverage/lcov-report/src/utilities/fileSystem.ts.html +0 -100
  174. package/coverage/lcov-report/src/utilities/index.html +0 -206
  175. package/coverage/lcov-report/src/utilities/index.ts.html +0 -112
  176. package/coverage/lcov-report/src/utilities/redis.ts.html +0 -424
  177. package/coverage/lcov-report/src/utilities/templates.ts.html +0 -232
  178. package/coverage/lcov-report/src/utilities/users.ts.html +0 -133
  179. package/coverage/lcov.info +0 -4187
  180. package/src/tests/structures/users.ts +0 -37
@@ -1,11 +1,11 @@
1
1
  import env from "../environment"
2
- import { EmailTemplatePurpose, TemplateType, Config } from "../constants"
2
+ import { EmailTemplatePurpose, TemplateType } from "../constants"
3
3
  import { getTemplateByPurpose } from "../constants/templates"
4
4
  import { getSettingsTemplateContext } from "./templates"
5
5
  import { processString } from "@budibase/string-templates"
6
6
  import { getResetPasswordCode, getInviteCode } from "./redis"
7
- import { User, Database } from "@budibase/types"
8
- import { tenancy, db as dbCore } from "@budibase/backend-core"
7
+ import { User, SMTPInnerConfig } from "@budibase/types"
8
+ import { configs } from "@budibase/backend-core"
9
9
  const nodemailer = require("nodemailer")
10
10
 
11
11
  type SendEmailOpts = {
@@ -26,7 +26,7 @@ type SendEmailOpts = {
26
26
  automation?: boolean
27
27
  }
28
28
 
29
- const TEST_MODE = false
29
+ const TEST_MODE = env.ENABLE_EMAIL_TEST_MODE && env.isDev()
30
30
  const TYPE = TemplateType.EMAIL
31
31
 
32
32
  const FULL_EMAIL_PURPOSES = [
@@ -36,24 +36,24 @@ const FULL_EMAIL_PURPOSES = [
36
36
  EmailTemplatePurpose.CUSTOM,
37
37
  ]
38
38
 
39
- function createSMTPTransport(config: any) {
39
+ function createSMTPTransport(config?: SMTPInnerConfig) {
40
40
  let options: any
41
- let secure = config.secure
41
+ let secure = config?.secure
42
42
  // default it if not specified
43
43
  if (secure == null) {
44
- secure = config.port === 465
44
+ secure = config?.port === 465
45
45
  }
46
46
  if (!TEST_MODE) {
47
47
  options = {
48
- port: config.port,
49
- host: config.host,
48
+ port: config?.port,
49
+ host: config?.host,
50
50
  secure: secure,
51
- auth: config.auth,
51
+ auth: config?.auth,
52
52
  }
53
53
  options.tls = {
54
54
  rejectUnauthorized: false,
55
55
  }
56
- if (config.connectionTimeout) {
56
+ if (config?.connectionTimeout) {
57
57
  options.connectionTimeout = config.connectionTimeout
58
58
  }
59
59
  } else {
@@ -62,8 +62,8 @@ function createSMTPTransport(config: any) {
62
62
  host: "smtp.ethereal.email",
63
63
  secure: false,
64
64
  auth: {
65
- user: "don.bahringer@ethereal.email",
66
- pass: "yCKSH8rWyUPbnhGYk9",
65
+ user: "wyatt.zulauf29@ethereal.email",
66
+ pass: "tEwDtHBWWxusVWAPfa",
67
67
  },
68
68
  }
69
69
  }
@@ -134,57 +134,16 @@ async function buildEmail(
134
134
  })
135
135
  }
136
136
 
137
- /**
138
- * Utility function for finding most valid SMTP configuration.
139
- * @param {object} db The CouchDB database which is to be looked up within.
140
- * @param {string|null} workspaceId If using finer grain control of configs a workspace can be used.
141
- * @param {boolean|null} automation Whether or not the configuration is being fetched for an email automation.
142
- * @return {Promise<object|null>} returns the SMTP configuration if it exists
143
- */
144
- async function getSmtpConfiguration(
145
- db: Database,
146
- workspaceId?: string,
147
- automation?: boolean
148
- ) {
149
- const params: any = {
150
- type: Config.SMTP,
151
- }
152
- if (workspaceId) {
153
- params.workspace = workspaceId
154
- }
155
-
156
- const customConfig = await dbCore.getScopedConfig(db, params)
157
-
158
- if (customConfig) {
159
- return customConfig
160
- }
161
-
162
- // Use an SMTP fallback configuration from env variables
163
- if (!automation && env.SMTP_FALLBACK_ENABLED) {
164
- return {
165
- port: env.SMTP_PORT,
166
- host: env.SMTP_HOST,
167
- secure: false,
168
- from: env.SMTP_FROM_ADDRESS,
169
- auth: {
170
- user: env.SMTP_USER,
171
- pass: env.SMTP_PASSWORD,
172
- },
173
- }
174
- }
175
- }
176
-
177
137
  /**
178
138
  * Checks if a SMTP config exists based on passed in parameters.
179
139
  * @return {Promise<boolean>} returns true if there is a configuration that can be used.
180
140
  */
181
- export async function isEmailConfigured(workspaceId?: string) {
141
+ export async function isEmailConfigured() {
182
142
  // when "testing" or smtp fallback is enabled simply return true
183
143
  if (TEST_MODE || env.SMTP_FALLBACK_ENABLED) {
184
144
  return true
185
145
  }
186
- const db = tenancy.getGlobalDB()
187
- const config = await getSmtpConfiguration(db, workspaceId)
146
+ const config = await configs.getSMTPConfig()
188
147
  return config != null
189
148
  }
190
149
 
@@ -202,22 +161,17 @@ export async function sendEmail(
202
161
  purpose: EmailTemplatePurpose,
203
162
  opts: SendEmailOpts
204
163
  ) {
205
- const db = tenancy.getGlobalDB()
206
- let config =
207
- (await getSmtpConfiguration(db, opts?.workspaceId, opts?.automation)) || {}
208
- if (Object.keys(config).length === 0 && !TEST_MODE) {
164
+ const config = await configs.getSMTPConfig(opts?.automation)
165
+ if (!config && !TEST_MODE) {
209
166
  throw "Unable to find SMTP configuration."
210
167
  }
211
168
  const transport = createSMTPTransport(config)
212
169
  // if there is a link code needed this will retrieve it
213
170
  const code = await getLinkCode(purpose, email, opts.user, opts?.info)
214
- let context
215
- if (code) {
216
- context = await getSettingsTemplateContext(purpose, code)
217
- }
171
+ let context = await getSettingsTemplateContext(purpose, code)
218
172
 
219
173
  let message: any = {
220
- from: opts?.from || config.from,
174
+ from: opts?.from || config?.from,
221
175
  html: await buildEmail(purpose, email, context, {
222
176
  user: opts?.user,
223
177
  contents: opts?.contents,
@@ -231,9 +185,9 @@ export async function sendEmail(
231
185
  bcc: opts?.bcc,
232
186
  }
233
187
 
234
- if (opts?.subject || config.subject) {
188
+ if (opts?.subject || config?.subject) {
235
189
  message.subject = await processString(
236
- opts?.subject || config.subject,
190
+ (opts?.subject || config?.subject) as string,
237
191
  context
238
192
  )
239
193
  }
@@ -249,7 +203,7 @@ export async function sendEmail(
249
203
  * @param {object} config an SMTP configuration - this is based on the nodemailer API.
250
204
  * @return {Promise<boolean>} returns true if the configuration is valid.
251
205
  */
252
- export async function verifyConfig(config: any) {
206
+ export async function verifyConfig(config: SMTPInnerConfig) {
253
207
  const transport = createSMTPTransport(config)
254
208
  await transport.verify()
255
209
  }
@@ -7,7 +7,7 @@ function getExpirySecondsForDB(db: string) {
7
7
  return 3600
8
8
  case redis.utils.Databases.INVITATIONS:
9
9
  // a day
10
- return 86400
10
+ return 604800
11
11
  }
12
12
  }
13
13
 
@@ -29,11 +29,25 @@ async function writeACode(db: string, value: any) {
29
29
  return code
30
30
  }
31
31
 
32
+ async function updateACode(db: string, code: string, value: any) {
33
+ const client = await getClient(db)
34
+ await client.store(code, value, getExpirySecondsForDB(db))
35
+ }
36
+
37
+ /**
38
+ * Given an invite code and invite body, allow the update an existing/valid invite in redis
39
+ * @param {string} inviteCode The invite code for an invite in redis
40
+ * @param {object} value The body of the updated user invitation
41
+ */
42
+ export async function updateInviteCode(inviteCode: string, value: string) {
43
+ await updateACode(redis.utils.Databases.INVITATIONS, inviteCode, value)
44
+ }
45
+
32
46
  async function getACode(db: string, code: string, deleteCode = true) {
33
47
  const client = await getClient(db)
34
48
  const value = await client.get(code)
35
49
  if (!value) {
36
- throw "Invalid code."
50
+ throw new Error("Invalid code.")
37
51
  }
38
52
  if (deleteCode) {
39
53
  await client.delete(code)
@@ -54,6 +68,8 @@ export async function init() {
54
68
  export async function shutdown() {
55
69
  if (pwResetClient) await pwResetClient.finish()
56
70
  if (invitationClient) await invitationClient.finish()
71
+ // shutdown core clients
72
+ await redis.clients.shutdown()
57
73
  console.log("Redis shutdown")
58
74
  }
59
75
 
@@ -111,3 +127,27 @@ export async function checkInviteCode(
111
127
  throw "Invitation is not valid or has expired, please request a new one."
112
128
  }
113
129
  }
130
+
131
+ /**
132
+ Get all currently available user invitations.
133
+ @return {Object[]} A list of all objects containing invite metadata
134
+ **/
135
+ export async function getInviteCodes(tenantIds?: string[]) {
136
+ const client = await getClient(redis.utils.Databases.INVITATIONS)
137
+ const invites: any[] = await client.scan()
138
+
139
+ const results = invites.map(invite => {
140
+ return {
141
+ ...invite.value,
142
+ code: invite.key,
143
+ }
144
+ })
145
+ return results.reduce((acc, invite) => {
146
+ if (tenantIds?.length && tenantIds.includes(invite.info.tenantId)) {
147
+ acc.push(invite)
148
+ } else {
149
+ acc.push(invite)
150
+ }
151
+ return acc
152
+ }, [])
153
+ }
@@ -1,6 +1,5 @@
1
- import { db as dbCore, tenancy } from "@budibase/backend-core"
1
+ import { tenancy, configs } from "@budibase/backend-core"
2
2
  import {
3
- Config,
4
3
  InternalTemplateBinding,
5
4
  LOGO_URL,
6
5
  EmailTemplatePurpose,
@@ -10,20 +9,16 @@ const BASE_COMPANY = "Budibase"
10
9
 
11
10
  export async function getSettingsTemplateContext(
12
11
  purpose: EmailTemplatePurpose,
13
- code?: string
12
+ code?: string | null
14
13
  ) {
15
- const db = tenancy.getGlobalDB()
16
- // TODO: use more granular settings in the future if required
17
- let settings =
18
- (await dbCore.getScopedConfig(db, { type: Config.SETTINGS })) || {}
14
+ let settings = await configs.getSettingsConfig()
19
15
  const URL = settings.platformUrl
20
16
  const context: any = {
21
17
  [InternalTemplateBinding.LOGO_URL]:
22
18
  checkSlashesInUrl(`${URL}/${settings.logoUrl}`) || LOGO_URL,
23
19
  [InternalTemplateBinding.PLATFORM_URL]: URL,
24
20
  [InternalTemplateBinding.COMPANY]: settings.company || BASE_COMPANY,
25
- [InternalTemplateBinding.DOCS_URL]:
26
- settings.docsUrl || "https://docs.budibase.com/",
21
+ [InternalTemplateBinding.DOCS_URL]: "https://docs.budibase.com/",
27
22
  [InternalTemplateBinding.LOGIN_URL]: checkSlashesInUrl(
28
23
  tenancy.addTenantToUrl(`${URL}/login`)
29
24
  ),