@budibase/backend-core 2.9.40-alpha.6 → 2.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/dist/index.js +5 -4
  2. package/dist/index.js.map +2 -2
  3. package/dist/index.js.meta.json +1 -1
  4. package/dist/package.json +6 -6
  5. package/dist/src/cache/appMetadata.js +1 -1
  6. package/dist/src/cache/appMetadata.js.map +1 -1
  7. package/dist/src/constants/misc.d.ts +0 -2
  8. package/dist/src/constants/misc.js +0 -2
  9. package/dist/src/constants/misc.js.map +1 -1
  10. package/dist/src/environment.js +5 -4
  11. package/dist/src/environment.js.map +1 -1
  12. package/dist/src/logging/system.d.ts +1 -1
  13. package/dist/src/timers/timers.d.ts +1 -1
  14. package/package.json +6 -6
  15. package/src/accounts/accounts.ts +82 -0
  16. package/src/accounts/api.ts +59 -0
  17. package/src/accounts/index.ts +1 -0
  18. package/src/auth/auth.ts +208 -0
  19. package/src/auth/index.ts +1 -0
  20. package/src/auth/tests/auth.spec.ts +14 -0
  21. package/src/blacklist/blacklist.ts +54 -0
  22. package/src/blacklist/index.ts +1 -0
  23. package/src/blacklist/tests/blacklist.spec.ts +46 -0
  24. package/src/cache/appMetadata.ts +88 -0
  25. package/src/cache/base/index.ts +92 -0
  26. package/src/cache/generic.ts +30 -0
  27. package/src/cache/index.ts +5 -0
  28. package/src/cache/tests/writethrough.spec.ts +138 -0
  29. package/src/cache/user.ts +83 -0
  30. package/src/cache/writethrough.ts +133 -0
  31. package/src/configs/configs.ts +257 -0
  32. package/src/configs/index.ts +1 -0
  33. package/src/configs/tests/configs.spec.ts +184 -0
  34. package/src/constants/db.ts +63 -0
  35. package/src/constants/index.ts +2 -0
  36. package/src/constants/misc.ts +50 -0
  37. package/src/context/Context.ts +14 -0
  38. package/src/context/identity.ts +58 -0
  39. package/src/context/index.ts +3 -0
  40. package/src/context/mainContext.ts +310 -0
  41. package/src/context/tests/index.spec.ts +147 -0
  42. package/src/context/types.ts +11 -0
  43. package/src/db/Replication.ts +84 -0
  44. package/src/db/constants.ts +10 -0
  45. package/src/db/couch/DatabaseImpl.ts +238 -0
  46. package/src/db/couch/connections.ts +77 -0
  47. package/src/db/couch/index.ts +5 -0
  48. package/src/db/couch/pouchDB.ts +97 -0
  49. package/src/db/couch/pouchDump.ts +0 -0
  50. package/src/db/couch/utils.ts +50 -0
  51. package/src/db/db.ts +43 -0
  52. package/src/db/errors.ts +14 -0
  53. package/src/db/index.ts +12 -0
  54. package/src/db/lucene.ts +750 -0
  55. package/src/db/searchIndexes/index.ts +1 -0
  56. package/src/db/searchIndexes/searchIndexes.ts +62 -0
  57. package/src/db/tests/index.spec.js +25 -0
  58. package/src/db/tests/lucene.spec.ts +368 -0
  59. package/src/db/tests/pouch.spec.js +62 -0
  60. package/src/db/tests/utils.spec.ts +63 -0
  61. package/src/db/utils.ts +207 -0
  62. package/src/db/views.ts +241 -0
  63. package/src/docIds/conversions.ts +59 -0
  64. package/src/docIds/ids.ts +113 -0
  65. package/src/docIds/index.ts +2 -0
  66. package/src/docIds/newid.ts +5 -0
  67. package/src/docIds/params.ts +174 -0
  68. package/src/docUpdates/index.ts +29 -0
  69. package/src/environment.ts +201 -0
  70. package/src/errors/errors.ts +119 -0
  71. package/src/errors/index.ts +1 -0
  72. package/src/events/analytics.ts +6 -0
  73. package/src/events/asyncEvents/index.ts +2 -0
  74. package/src/events/asyncEvents/publisher.ts +12 -0
  75. package/src/events/asyncEvents/queue.ts +22 -0
  76. package/src/events/backfill.ts +183 -0
  77. package/src/events/documentId.ts +56 -0
  78. package/src/events/events.ts +40 -0
  79. package/src/events/identification.ts +310 -0
  80. package/src/events/index.ts +14 -0
  81. package/src/events/processors/AnalyticsProcessor.ts +64 -0
  82. package/src/events/processors/AuditLogsProcessor.ts +93 -0
  83. package/src/events/processors/LoggingProcessor.ts +37 -0
  84. package/src/events/processors/Processors.ts +52 -0
  85. package/src/events/processors/async/DocumentUpdateProcessor.ts +43 -0
  86. package/src/events/processors/index.ts +19 -0
  87. package/src/events/processors/posthog/PosthogProcessor.ts +118 -0
  88. package/src/events/processors/posthog/index.ts +2 -0
  89. package/src/events/processors/posthog/rateLimiting.ts +106 -0
  90. package/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +168 -0
  91. package/src/events/processors/types.ts +1 -0
  92. package/src/events/publishers/account.ts +35 -0
  93. package/src/events/publishers/app.ts +155 -0
  94. package/src/events/publishers/auditLog.ts +26 -0
  95. package/src/events/publishers/auth.ts +73 -0
  96. package/src/events/publishers/automation.ts +110 -0
  97. package/src/events/publishers/backfill.ts +74 -0
  98. package/src/events/publishers/backup.ts +42 -0
  99. package/src/events/publishers/datasource.ts +48 -0
  100. package/src/events/publishers/email.ts +17 -0
  101. package/src/events/publishers/environmentVariable.ts +38 -0
  102. package/src/events/publishers/group.ts +99 -0
  103. package/src/events/publishers/index.ts +24 -0
  104. package/src/events/publishers/installation.ts +38 -0
  105. package/src/events/publishers/layout.ts +26 -0
  106. package/src/events/publishers/license.ts +84 -0
  107. package/src/events/publishers/org.ts +37 -0
  108. package/src/events/publishers/plugin.ts +47 -0
  109. package/src/events/publishers/query.ts +88 -0
  110. package/src/events/publishers/role.ts +62 -0
  111. package/src/events/publishers/rows.ts +29 -0
  112. package/src/events/publishers/screen.ts +36 -0
  113. package/src/events/publishers/serve.ts +43 -0
  114. package/src/events/publishers/table.ts +70 -0
  115. package/src/events/publishers/user.ts +202 -0
  116. package/src/events/publishers/view.ts +107 -0
  117. package/src/features/index.ts +78 -0
  118. package/src/features/installation.ts +17 -0
  119. package/src/features/tests/featureFlags.spec.ts +85 -0
  120. package/src/helpers.ts +9 -0
  121. package/src/index.ts +54 -0
  122. package/src/installation.ts +107 -0
  123. package/src/logging/alerts.ts +26 -0
  124. package/src/logging/correlation/correlation.ts +13 -0
  125. package/src/logging/correlation/index.ts +1 -0
  126. package/src/logging/correlation/middleware.ts +17 -0
  127. package/src/logging/index.ts +4 -0
  128. package/src/logging/pino/logger.ts +232 -0
  129. package/src/logging/pino/middleware.ts +45 -0
  130. package/src/logging/system.ts +81 -0
  131. package/src/logging/tests/system.spec.ts +61 -0
  132. package/src/middleware/adminOnly.ts +9 -0
  133. package/src/middleware/auditLog.ts +6 -0
  134. package/src/middleware/authenticated.ts +193 -0
  135. package/src/middleware/builderOnly.ts +21 -0
  136. package/src/middleware/builderOrAdmin.ts +21 -0
  137. package/src/middleware/csrf.ts +81 -0
  138. package/src/middleware/errorHandling.ts +29 -0
  139. package/src/middleware/index.ts +21 -0
  140. package/src/middleware/internalApi.ts +23 -0
  141. package/src/middleware/joi-validator.ts +45 -0
  142. package/src/middleware/matchers.ts +47 -0
  143. package/src/middleware/passport/datasource/google.ts +95 -0
  144. package/src/middleware/passport/local.ts +54 -0
  145. package/src/middleware/passport/sso/google.ts +77 -0
  146. package/src/middleware/passport/sso/oidc.ts +154 -0
  147. package/src/middleware/passport/sso/sso.ts +165 -0
  148. package/src/middleware/passport/sso/tests/google.spec.ts +67 -0
  149. package/src/middleware/passport/sso/tests/oidc.spec.ts +152 -0
  150. package/src/middleware/passport/sso/tests/sso.spec.ts +197 -0
  151. package/src/middleware/passport/utils.ts +38 -0
  152. package/src/middleware/querystringToBody.ts +28 -0
  153. package/src/middleware/tenancy.ts +36 -0
  154. package/src/middleware/tests/builder.spec.ts +180 -0
  155. package/src/middleware/tests/matchers.spec.ts +134 -0
  156. package/src/migrations/definitions.ts +40 -0
  157. package/src/migrations/index.ts +2 -0
  158. package/src/migrations/migrations.ts +191 -0
  159. package/src/migrations/tests/__snapshots__/migrations.spec.ts.snap +11 -0
  160. package/src/migrations/tests/migrations.spec.ts +64 -0
  161. package/src/objectStore/buckets/app.ts +40 -0
  162. package/src/objectStore/buckets/global.ts +29 -0
  163. package/src/objectStore/buckets/index.ts +3 -0
  164. package/src/objectStore/buckets/plugins.ts +71 -0
  165. package/src/objectStore/buckets/tests/app.spec.ts +171 -0
  166. package/src/objectStore/buckets/tests/global.spec.ts +74 -0
  167. package/src/objectStore/buckets/tests/plugins.spec.ts +111 -0
  168. package/src/objectStore/cloudfront.ts +41 -0
  169. package/src/objectStore/index.ts +3 -0
  170. package/src/objectStore/objectStore.ts +440 -0
  171. package/src/objectStore/utils.ts +27 -0
  172. package/src/platform/index.ts +3 -0
  173. package/src/platform/platformDb.ts +6 -0
  174. package/src/platform/tenants.ts +101 -0
  175. package/src/platform/tests/tenants.spec.ts +26 -0
  176. package/src/platform/users.ts +90 -0
  177. package/src/plugin/index.ts +1 -0
  178. package/src/plugin/tests/validation.spec.ts +83 -0
  179. package/src/plugin/utils.ts +156 -0
  180. package/src/queue/constants.ts +6 -0
  181. package/src/queue/inMemoryQueue.ts +141 -0
  182. package/src/queue/index.ts +2 -0
  183. package/src/queue/listeners.ts +195 -0
  184. package/src/queue/queue.ts +54 -0
  185. package/src/redis/index.ts +6 -0
  186. package/src/redis/init.ts +86 -0
  187. package/src/redis/redis.ts +308 -0
  188. package/src/redis/redlockImpl.ts +139 -0
  189. package/src/redis/utils.ts +117 -0
  190. package/src/security/encryption.ts +179 -0
  191. package/src/security/permissions.ts +158 -0
  192. package/src/security/roles.ts +389 -0
  193. package/src/security/sessions.ts +120 -0
  194. package/src/security/tests/encryption.spec.ts +31 -0
  195. package/src/security/tests/permissions.spec.ts +145 -0
  196. package/src/security/tests/sessions.spec.ts +12 -0
  197. package/src/tenancy/db.ts +6 -0
  198. package/src/tenancy/index.ts +2 -0
  199. package/src/tenancy/tenancy.ts +140 -0
  200. package/src/tenancy/tests/tenancy.spec.ts +184 -0
  201. package/src/timers/index.ts +1 -0
  202. package/src/timers/timers.ts +22 -0
  203. package/src/users/db.ts +484 -0
  204. package/src/users/events.ts +176 -0
  205. package/src/users/index.ts +4 -0
  206. package/src/users/lookup.ts +102 -0
  207. package/src/users/users.ts +276 -0
  208. package/src/users/utils.ts +55 -0
  209. package/src/utils/hashing.ts +14 -0
  210. package/src/utils/index.ts +3 -0
  211. package/src/utils/stringUtils.ts +8 -0
  212. package/src/utils/tests/utils.spec.ts +191 -0
  213. package/src/utils/utils.ts +239 -0
  214. package/tests/core/logging.ts +34 -0
  215. package/tests/core/utilities/index.ts +6 -0
  216. package/tests/core/utilities/jestUtils.ts +30 -0
  217. package/tests/core/utilities/mocks/alerts.ts +3 -0
  218. package/tests/core/utilities/mocks/date.ts +2 -0
  219. package/tests/core/utilities/mocks/events.ts +131 -0
  220. package/tests/core/utilities/mocks/fetch.ts +17 -0
  221. package/tests/core/utilities/mocks/index.ts +10 -0
  222. package/tests/core/utilities/mocks/licenses.ts +115 -0
  223. package/tests/core/utilities/mocks/posthog.ts +7 -0
  224. package/tests/core/utilities/structures/Chance.ts +20 -0
  225. package/tests/core/utilities/structures/accounts.ts +115 -0
  226. package/tests/core/utilities/structures/apps.ts +21 -0
  227. package/tests/core/utilities/structures/common.ts +7 -0
  228. package/tests/core/utilities/structures/db.ts +12 -0
  229. package/tests/core/utilities/structures/documents/index.ts +1 -0
  230. package/tests/core/utilities/structures/documents/platform/index.ts +1 -0
  231. package/tests/core/utilities/structures/documents/platform/installation.ts +12 -0
  232. package/tests/core/utilities/structures/generator.ts +2 -0
  233. package/tests/core/utilities/structures/index.ts +15 -0
  234. package/tests/core/utilities/structures/koa.ts +16 -0
  235. package/tests/core/utilities/structures/licenses.ts +167 -0
  236. package/tests/core/utilities/structures/plugins.ts +19 -0
  237. package/tests/core/utilities/structures/quotas.ts +67 -0
  238. package/tests/core/utilities/structures/scim.ts +80 -0
  239. package/tests/core/utilities/structures/shared.ts +19 -0
  240. package/tests/core/utilities/structures/sso.ts +119 -0
  241. package/tests/core/utilities/structures/tenants.ts +5 -0
  242. package/tests/core/utilities/structures/userGroups.ts +10 -0
  243. package/tests/core/utilities/structures/users.ts +73 -0
  244. package/tests/core/utilities/testContainerUtils.ts +85 -0
  245. package/tests/core/utilities/utils/index.ts +1 -0
  246. package/tests/core/utilities/utils/time.ts +3 -0
  247. package/tests/extra/DBTestConfiguration.ts +36 -0
  248. package/tests/extra/index.ts +2 -0
  249. package/tests/extra/testEnv.ts +95 -0
  250. package/tests/index.ts +1 -0
  251. package/tests/jestEnv.ts +6 -0
  252. package/tests/jestSetup.ts +28 -0
@@ -0,0 +1,17 @@
1
+ import { Header } from "../../constants"
2
+ import { v4 as uuid } from "uuid"
3
+ const correlator = require("correlation-id")
4
+
5
+ const correlation = (ctx: any, next: any) => {
6
+ // use the provided correlation id header if present
7
+ let correlationId = ctx.headers[Header.CORRELATION_ID]
8
+ if (!correlationId) {
9
+ correlationId = uuid()
10
+ }
11
+
12
+ return correlator.withId(correlationId, () => {
13
+ return next()
14
+ })
15
+ }
16
+
17
+ export default correlation
@@ -0,0 +1,4 @@
1
+ export * as correlation from "./correlation/correlation"
2
+ export { logger } from "./pino/logger"
3
+ export * from "./alerts"
4
+ export * as system from "./system"
@@ -0,0 +1,232 @@
1
+ import pino, { LoggerOptions } from "pino"
2
+ import pinoPretty from "pino-pretty"
3
+
4
+ import { IdentityType } from "@budibase/types"
5
+ import env from "../../environment"
6
+ import * as context from "../../context"
7
+ import * as correlation from "../correlation"
8
+
9
+ import { localFileDestination } from "../system"
10
+
11
+ // LOGGER
12
+
13
+ let pinoInstance: pino.Logger | undefined
14
+ if (!env.DISABLE_PINO_LOGGER) {
15
+ const level = env.LOG_LEVEL
16
+ const pinoOptions: LoggerOptions = {
17
+ level,
18
+ formatters: {
19
+ level: level => {
20
+ return { level: level.toUpperCase() }
21
+ },
22
+ bindings: () => {
23
+ if (env.SELF_HOSTED) {
24
+ // "service" is being injected in datadog using the pod names,
25
+ // so we should leave it blank to allow the default behaviour if it's not running self-hosted
26
+ return {
27
+ service: env.SERVICE_NAME,
28
+ }
29
+ } else {
30
+ return {}
31
+ }
32
+ },
33
+ },
34
+ timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
35
+ }
36
+
37
+ const destinations: pino.StreamEntry[] = []
38
+
39
+ destinations.push(
40
+ env.isDev()
41
+ ? {
42
+ stream: pinoPretty({ singleLine: true }),
43
+ level: level as pino.Level,
44
+ }
45
+ : { stream: process.stdout, level: level as pino.Level }
46
+ )
47
+
48
+ if (env.SELF_HOSTED) {
49
+ destinations.push({
50
+ stream: localFileDestination(),
51
+ level: level as pino.Level,
52
+ })
53
+ }
54
+
55
+ pinoInstance = destinations.length
56
+ ? pino(pinoOptions, pino.multistream(destinations))
57
+ : pino(pinoOptions)
58
+
59
+ // CONSOLE OVERRIDES
60
+
61
+ interface MergingObject {
62
+ objects?: any[]
63
+ tenantId?: string
64
+ appId?: string
65
+ automationId?: string
66
+ identityId?: string
67
+ identityType?: IdentityType
68
+ correlationId?: string
69
+ err?: Error
70
+ }
71
+
72
+ function isPlainObject(obj: any) {
73
+ return typeof obj === "object" && obj !== null && !(obj instanceof Error)
74
+ }
75
+
76
+ function isError(obj: any) {
77
+ return obj instanceof Error
78
+ }
79
+
80
+ function isMessage(obj: any) {
81
+ return typeof obj === "string"
82
+ }
83
+
84
+ /**
85
+ * Backwards compatibility between console logging statements
86
+ * and pino logging requirements.
87
+ */
88
+ function getLogParams(args: any[]): [MergingObject, string] {
89
+ let error = undefined
90
+ let objects: any[] = []
91
+ let message = ""
92
+
93
+ args.forEach(arg => {
94
+ if (isMessage(arg)) {
95
+ message = `${message} ${arg}`.trimStart()
96
+ }
97
+ if (isPlainObject(arg)) {
98
+ objects.push(arg)
99
+ }
100
+ if (isError(arg)) {
101
+ error = arg
102
+ }
103
+ })
104
+
105
+ const identity = getIdentity()
106
+
107
+ let contextObject = {}
108
+
109
+ contextObject = {
110
+ tenantId: getTenantId(),
111
+ appId: getAppId(),
112
+ automationId: getAutomationId(),
113
+ identityId: identity?._id,
114
+ identityType: identity?.type,
115
+ correlationId: correlation.getId(),
116
+ }
117
+
118
+ const mergingObject: any = {
119
+ err: error,
120
+ pid: process.pid,
121
+ ...contextObject,
122
+ }
123
+
124
+ if (objects.length) {
125
+ // init generic data object for params supplied that don't have a
126
+ // '_logKey' field. This prints an object using argument index as the key
127
+ // e.g. { 0: {}, 1: {} }
128
+ const data: any = {}
129
+ let dataIndex = 0
130
+
131
+ for (let i = 0; i < objects.length; i++) {
132
+ const object = objects[i]
133
+ // the object has specified a log key
134
+ // use this instead of generic key
135
+ const logKey = object._logKey
136
+ if (logKey) {
137
+ delete object._logKey
138
+ mergingObject[logKey] = object
139
+ } else {
140
+ data[dataIndex] = object
141
+ dataIndex++
142
+ }
143
+ }
144
+
145
+ if (Object.keys(data).length) {
146
+ mergingObject.data = data
147
+ }
148
+ }
149
+
150
+ return [mergingObject, message]
151
+ }
152
+
153
+ console.log = (...arg: any[]) => {
154
+ const [obj, msg] = getLogParams(arg)
155
+ pinoInstance?.info(obj, msg)
156
+ }
157
+ console.info = (...arg: any[]) => {
158
+ const [obj, msg] = getLogParams(arg)
159
+ pinoInstance?.info(obj, msg)
160
+ }
161
+ console.warn = (...arg: any[]) => {
162
+ const [obj, msg] = getLogParams(arg)
163
+ pinoInstance?.warn(obj, msg)
164
+ }
165
+ console.error = (...arg: any[]) => {
166
+ const [obj, msg] = getLogParams(arg)
167
+ pinoInstance?.error(obj, msg)
168
+ }
169
+
170
+ /**
171
+ * custom trace impl - this resembles the node trace behaviour rather
172
+ * than traditional trace logging
173
+ * @param arg
174
+ */
175
+ console.trace = (...arg: any[]) => {
176
+ const [obj, msg] = getLogParams(arg)
177
+ if (!obj.err) {
178
+ // to get stack trace
179
+ obj.err = new Error()
180
+ }
181
+ pinoInstance?.trace(obj, msg)
182
+ }
183
+
184
+ console.debug = (...arg: any) => {
185
+ const [obj, msg] = getLogParams(arg)
186
+ pinoInstance?.debug(obj, msg)
187
+ }
188
+
189
+ // CONTEXT
190
+
191
+ const getTenantId = () => {
192
+ let tenantId
193
+ try {
194
+ tenantId = context.getTenantId()
195
+ } catch (e: any) {
196
+ // do nothing
197
+ }
198
+ return tenantId
199
+ }
200
+
201
+ const getAppId = () => {
202
+ let appId
203
+ try {
204
+ appId = context.getAppId()
205
+ } catch (e) {
206
+ // do nothing
207
+ }
208
+ return appId
209
+ }
210
+
211
+ const getAutomationId = () => {
212
+ let appId
213
+ try {
214
+ appId = context.getAutomationId()
215
+ } catch (e) {
216
+ // do nothing
217
+ }
218
+ return appId
219
+ }
220
+
221
+ const getIdentity = () => {
222
+ let identity
223
+ try {
224
+ identity = context.getIdentity()
225
+ } catch (e) {
226
+ // do nothing
227
+ }
228
+ return identity
229
+ }
230
+ }
231
+
232
+ export const logger = pinoInstance
@@ -0,0 +1,45 @@
1
+ import env from "../../environment"
2
+ import { logger } from "./logger"
3
+ import { IncomingMessage } from "http"
4
+ const pino = require("koa-pino-logger")
5
+ import { Options } from "pino-http"
6
+ import { Ctx } from "@budibase/types"
7
+ const correlator = require("correlation-id")
8
+
9
+ export function pinoSettings(): Options {
10
+ return {
11
+ logger,
12
+ genReqId: correlator.getId,
13
+ autoLogging: {
14
+ ignore: (req: IncomingMessage) => !!req.url?.includes("/health"),
15
+ },
16
+ serializers: {
17
+ req: req => {
18
+ return {
19
+ method: req.method,
20
+ url: req.url,
21
+ correlationId: req.id,
22
+ }
23
+ },
24
+ res: res => {
25
+ return {
26
+ status: res.statusCode,
27
+ }
28
+ },
29
+ },
30
+ }
31
+ }
32
+
33
+ function getMiddleware() {
34
+ if (env.HTTP_LOGGING) {
35
+ return pino(pinoSettings())
36
+ } else {
37
+ return (ctx: Ctx, next: any) => {
38
+ return next()
39
+ }
40
+ }
41
+ }
42
+
43
+ const pinoMiddleware = getMiddleware()
44
+
45
+ export default pinoMiddleware
@@ -0,0 +1,81 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import * as rfs from "rotating-file-stream"
4
+
5
+ import env from "../environment"
6
+ import { budibaseTempDir } from "../objectStore"
7
+
8
+ const logsFileName = `budibase.log`
9
+ const budibaseLogsHistoryFileName = "budibase-logs-history.txt"
10
+
11
+ const logsPath = path.join(budibaseTempDir(), "systemlogs")
12
+
13
+ function getFullPath(fileName: string) {
14
+ return path.join(logsPath, fileName)
15
+ }
16
+
17
+ export function getSingleFileMaxSizeInfo(totalMaxSize: string) {
18
+ const regex = /(\d+)([A-Za-z])/
19
+ const match = totalMaxSize?.match(regex)
20
+ if (!match) {
21
+ console.warn(`totalMaxSize does not have a valid value`, {
22
+ totalMaxSize,
23
+ })
24
+ return undefined
25
+ }
26
+
27
+ const size = +match[1]
28
+ const unit = match[2]
29
+ if (size === 1) {
30
+ switch (unit) {
31
+ case "B":
32
+ return { size: `${size}B`, totalHistoryFiles: 1 }
33
+ case "K":
34
+ return { size: `${(size * 1000) / 2}B`, totalHistoryFiles: 1 }
35
+ case "M":
36
+ return { size: `${(size * 1000) / 2}K`, totalHistoryFiles: 1 }
37
+ case "G":
38
+ return { size: `${(size * 1000) / 2}M`, totalHistoryFiles: 1 }
39
+ default:
40
+ return undefined
41
+ }
42
+ }
43
+
44
+ if (size % 2 === 0) {
45
+ return { size: `${size / 2}${unit}`, totalHistoryFiles: 1 }
46
+ }
47
+
48
+ return { size: `1${unit}`, totalHistoryFiles: size - 1 }
49
+ }
50
+
51
+ export function localFileDestination() {
52
+ const fileInfo = getSingleFileMaxSizeInfo(env.ROLLING_LOG_MAX_SIZE)
53
+ const outFile = rfs.createStream(logsFileName, {
54
+ // As we have a rolling size, we want to half the max size
55
+ size: fileInfo?.size,
56
+ path: logsPath,
57
+ maxFiles: fileInfo?.totalHistoryFiles || 1,
58
+ immutable: true,
59
+ history: budibaseLogsHistoryFileName,
60
+ initialRotation: false,
61
+ })
62
+
63
+ return outFile
64
+ }
65
+
66
+ export function getLogReadStream() {
67
+ const streams = []
68
+ const historyFile = getFullPath(budibaseLogsHistoryFileName)
69
+ if (fs.existsSync(historyFile)) {
70
+ const fileContent = fs.readFileSync(historyFile, "utf-8")
71
+ const historyFiles = fileContent.split("\n")
72
+ for (const historyFile of historyFiles.filter(x => x)) {
73
+ streams.push(fs.readFileSync(historyFile))
74
+ }
75
+ }
76
+
77
+ streams.push(fs.readFileSync(getFullPath(logsFileName)))
78
+
79
+ const combinedContent = Buffer.concat(streams)
80
+ return combinedContent
81
+ }
@@ -0,0 +1,61 @@
1
+ import { getSingleFileMaxSizeInfo } from "../system"
2
+
3
+ describe("system", () => {
4
+ describe("getSingleFileMaxSizeInfo", () => {
5
+ it.each([
6
+ ["100B", "50B"],
7
+ ["200K", "100K"],
8
+ ["20M", "10M"],
9
+ ["4G", "2G"],
10
+ ])(
11
+ "Halving even number (%s) returns halved size and 1 history file (%s)",
12
+ (totalValue, expectedMaxSize) => {
13
+ const result = getSingleFileMaxSizeInfo(totalValue)
14
+ expect(result).toEqual({
15
+ size: expectedMaxSize,
16
+ totalHistoryFiles: 1,
17
+ })
18
+ }
19
+ )
20
+
21
+ it.each([
22
+ ["5B", "1B", 4],
23
+ ["17K", "1K", 16],
24
+ ["21M", "1M", 20],
25
+ ["3G", "1G", 2],
26
+ ])(
27
+ "Halving an odd number (%s) returns as many files as size (-1) (%s)",
28
+ (totalValue, expectedMaxSize, totalHistoryFiles) => {
29
+ const result = getSingleFileMaxSizeInfo(totalValue)
30
+ expect(result).toEqual({
31
+ size: expectedMaxSize,
32
+ totalHistoryFiles,
33
+ })
34
+ }
35
+ )
36
+
37
+ it.each([
38
+ ["1B", "1B"],
39
+ ["1K", "500B"],
40
+ ["1M", "500K"],
41
+ ["1G", "500M"],
42
+ ])(
43
+ "Halving '%s' returns halved unit (%s)",
44
+ (totalValue, expectedMaxSize) => {
45
+ const result = getSingleFileMaxSizeInfo(totalValue)
46
+ expect(result).toEqual({
47
+ size: expectedMaxSize,
48
+ totalHistoryFiles: 1,
49
+ })
50
+ }
51
+ )
52
+
53
+ it.each([[undefined], [""], ["50"], ["wrongvalue"]])(
54
+ "Halving wrongly formatted value ('%s') returns undefined",
55
+ totalValue => {
56
+ const result = getSingleFileMaxSizeInfo(totalValue!)
57
+ expect(result).toBeUndefined()
58
+ }
59
+ )
60
+ })
61
+ })
@@ -0,0 +1,9 @@
1
+ import { UserCtx } from "@budibase/types"
2
+ import { isAdmin } from "../users"
3
+
4
+ export default async (ctx: UserCtx, next: any) => {
5
+ if (!ctx.internal && !isAdmin(ctx.user)) {
6
+ ctx.throw(403, "Admin user only endpoint.")
7
+ }
8
+ return next()
9
+ }
@@ -0,0 +1,6 @@
1
+ import { BBContext } from "@budibase/types"
2
+
3
+ export default async (ctx: BBContext | any, next: any) => {
4
+ // Placeholder for audit log middleware
5
+ return next()
6
+ }
@@ -0,0 +1,193 @@
1
+ import { Cookie, Header } from "../constants"
2
+ import {
3
+ getCookie,
4
+ clearCookie,
5
+ openJwt,
6
+ isValidInternalAPIKey,
7
+ } from "../utils"
8
+ import { getUser } from "../cache/user"
9
+ import { getSession, updateSessionTTL } from "../security/sessions"
10
+ import { buildMatcherRegex, matches } from "./matchers"
11
+ import { SEPARATOR, queryGlobalView, ViewName } from "../db"
12
+ import { getGlobalDB, doInTenant } from "../context"
13
+ import { decrypt } from "../security/encryption"
14
+ import * as identity from "../context/identity"
15
+ import env from "../environment"
16
+ import { Ctx, EndpointMatcher } from "@budibase/types"
17
+ import { InvalidAPIKeyError, ErrorCode } from "../errors"
18
+
19
+ const ONE_MINUTE = env.SESSION_UPDATE_PERIOD
20
+ ? parseInt(env.SESSION_UPDATE_PERIOD)
21
+ : 60 * 1000
22
+
23
+ interface FinaliseOpts {
24
+ authenticated?: boolean
25
+ internal?: boolean
26
+ publicEndpoint?: boolean
27
+ version?: string
28
+ user?: any
29
+ }
30
+
31
+ function timeMinusOneMinute() {
32
+ return new Date(Date.now() - ONE_MINUTE).toISOString()
33
+ }
34
+
35
+ function finalise(ctx: any, opts: FinaliseOpts = {}) {
36
+ ctx.publicEndpoint = opts.publicEndpoint || false
37
+ ctx.isAuthenticated = opts.authenticated || false
38
+ ctx.user = opts.user
39
+ ctx.internal = opts.internal || false
40
+ ctx.version = opts.version
41
+ }
42
+
43
+ async function checkApiKey(apiKey: string, populateUser?: Function) {
44
+ // check both the primary and the fallback internal api keys
45
+ // this allows for rotation
46
+ if (isValidInternalAPIKey(apiKey)) {
47
+ return { valid: true, user: undefined }
48
+ }
49
+ const decrypted = decrypt(apiKey)
50
+ const tenantId = decrypted.split(SEPARATOR)[0]
51
+ return doInTenant(tenantId, async () => {
52
+ let userId
53
+ try {
54
+ const db = getGlobalDB()
55
+ // api key is encrypted in the database
56
+ userId = (await queryGlobalView(
57
+ ViewName.BY_API_KEY,
58
+ {
59
+ key: apiKey,
60
+ },
61
+ db
62
+ )) as string
63
+ } catch (err) {
64
+ userId = undefined
65
+ }
66
+ if (userId) {
67
+ return {
68
+ valid: true,
69
+ user: await getUser(userId, tenantId, populateUser),
70
+ }
71
+ } else {
72
+ throw new InvalidAPIKeyError()
73
+ }
74
+ })
75
+ }
76
+
77
+ /**
78
+ * This middleware is tenancy aware, so that it does not depend on other middlewares being used.
79
+ * The tenancy modules should not be used here and it should be assumed that the tenancy context
80
+ * has not yet been populated.
81
+ */
82
+ export default function (
83
+ noAuthPatterns: EndpointMatcher[] = [],
84
+ opts: { publicAllowed?: boolean; populateUser?: Function } = {
85
+ publicAllowed: false,
86
+ }
87
+ ) {
88
+ const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
89
+ return async (ctx: Ctx | any, next: any) => {
90
+ let publicEndpoint = false
91
+ const version = ctx.request.headers[Header.API_VER]
92
+ // the path is not authenticated
93
+ const found = matches(ctx, noAuthOptions)
94
+ if (found) {
95
+ publicEndpoint = true
96
+ }
97
+ try {
98
+ // check the actual user is authenticated first, try header or cookie
99
+ let headerToken = ctx.request.headers[Header.TOKEN]
100
+
101
+ const authCookie = getCookie(ctx, Cookie.Auth) || openJwt(headerToken)
102
+ let apiKey = ctx.request.headers[Header.API_KEY]
103
+
104
+ if (!apiKey && ctx.request.headers[Header.AUTHORIZATION]) {
105
+ apiKey = ctx.request.headers[Header.AUTHORIZATION].split(" ")[1]
106
+ }
107
+
108
+ const tenantId = ctx.request.headers[Header.TENANT_ID]
109
+ let authenticated = false,
110
+ user = null,
111
+ internal = false
112
+ if (authCookie && !apiKey) {
113
+ const sessionId = authCookie.sessionId
114
+ const userId = authCookie.userId
115
+ let session
116
+ try {
117
+ // getting session handles error checking (if session exists etc)
118
+ session = await getSession(userId, sessionId)
119
+ if (opts && opts.populateUser) {
120
+ user = await getUser(
121
+ userId,
122
+ session.tenantId,
123
+ opts.populateUser(ctx)
124
+ )
125
+ } else {
126
+ user = await getUser(userId, session.tenantId)
127
+ }
128
+ user.csrfToken = session.csrfToken
129
+
130
+ if (session?.lastAccessedAt < timeMinusOneMinute()) {
131
+ // make sure we denote that the session is still in use
132
+ await updateSessionTTL(session)
133
+ }
134
+ authenticated = true
135
+ } catch (err: any) {
136
+ authenticated = false
137
+ console.error(`Auth Error: ${err.message}`)
138
+ console.error(err)
139
+ // remove the cookie as the user does not exist anymore
140
+ clearCookie(ctx, Cookie.Auth)
141
+ }
142
+ }
143
+ // this is an internal request, no user made it
144
+ if (!authenticated && apiKey) {
145
+ const populateUser = opts.populateUser ? opts.populateUser(ctx) : null
146
+ const { valid, user: foundUser } = await checkApiKey(
147
+ apiKey,
148
+ populateUser
149
+ )
150
+ if (valid && foundUser) {
151
+ authenticated = true
152
+ user = foundUser
153
+ } else if (valid) {
154
+ authenticated = true
155
+ internal = true
156
+ }
157
+ }
158
+ if (!user && tenantId) {
159
+ user = { tenantId }
160
+ } else if (user) {
161
+ delete user.password
162
+ }
163
+ // be explicit
164
+ if (!authenticated) {
165
+ authenticated = false
166
+ }
167
+ // isAuthenticated is a function, so use a variable to be able to check authed state
168
+ finalise(ctx, { authenticated, user, internal, version, publicEndpoint })
169
+
170
+ if (user && user.email) {
171
+ return identity.doInUserContext(user, ctx, next)
172
+ } else {
173
+ return next()
174
+ }
175
+ } catch (err: any) {
176
+ console.error(`Auth Error: ${err.message}`)
177
+ console.error(err)
178
+ // invalid token, clear the cookie
179
+ if (err?.name === "JsonWebTokenError") {
180
+ clearCookie(ctx, Cookie.Auth)
181
+ } else if (err?.code === ErrorCode.INVALID_API_KEY) {
182
+ ctx.throw(403, err.message)
183
+ }
184
+ // allow configuring for public access
185
+ if ((opts && opts.publicAllowed) || publicEndpoint) {
186
+ finalise(ctx, { authenticated: false, version, publicEndpoint })
187
+ return next()
188
+ } else {
189
+ ctx.throw(err.status || 403, err)
190
+ }
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,21 @@
1
+ import { UserCtx } from "@budibase/types"
2
+ import { isBuilder, hasBuilderPermissions } from "../users"
3
+ import { getAppId } from "../context"
4
+ import env from "../environment"
5
+
6
+ export default async (ctx: UserCtx, next: any) => {
7
+ const appId = getAppId()
8
+ const builderFn =
9
+ env.isWorker() || !appId
10
+ ? hasBuilderPermissions
11
+ : env.isApps()
12
+ ? isBuilder
13
+ : undefined
14
+ if (!builderFn) {
15
+ throw new Error("Service name unknown - middleware inactive.")
16
+ }
17
+ if (!ctx.internal && !builderFn(ctx.user, appId)) {
18
+ ctx.throw(403, "Builder user only endpoint.")
19
+ }
20
+ return next()
21
+ }
@@ -0,0 +1,21 @@
1
+ import { UserCtx } from "@budibase/types"
2
+ import { isBuilder, isAdmin, hasBuilderPermissions } from "../users"
3
+ import { getAppId } from "../context"
4
+ import env from "../environment"
5
+
6
+ export default async (ctx: UserCtx, next: any) => {
7
+ const appId = getAppId()
8
+ const builderFn =
9
+ env.isWorker() || !appId
10
+ ? hasBuilderPermissions
11
+ : env.isApps()
12
+ ? isBuilder
13
+ : undefined
14
+ if (!builderFn) {
15
+ throw new Error("Service name unknown - middleware inactive.")
16
+ }
17
+ if (!ctx.internal && !builderFn(ctx.user, appId) && !isAdmin(ctx.user)) {
18
+ ctx.throw(403, "Admin/Builder user only endpoint.")
19
+ }
20
+ return next()
21
+ }