@budibase/worker 2.3.18-alpha.14 → 2.3.18-alpha.15

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/jest.config.ts CHANGED
@@ -7,7 +7,7 @@ const config: Config.InitialOptions = {
7
7
  preset: "@trendyol/jest-testcontainers",
8
8
  setupFiles: ["./src/tests/jestEnv.ts"],
9
9
  setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
10
- collectCoverageFrom: ["src/**/*.{js,ts}"],
10
+ collectCoverageFrom: ["src/**/*.{js,ts}", "../backend-core/src/**/*.{js,ts}"],
11
11
  coverageReporters: ["lcov", "json", "clover"],
12
12
  transform: {
13
13
  "^.+\\.ts?$": "@swc/jest",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/worker",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.3.18-alpha.14",
4
+ "version": "2.3.18-alpha.15",
5
5
  "description": "Budibase background service",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "author": "Budibase",
37
37
  "license": "GPL-3.0",
38
38
  "dependencies": {
39
- "@budibase/backend-core": "2.3.18-alpha.14",
40
- "@budibase/pro": "2.3.18-alpha.13",
41
- "@budibase/string-templates": "2.3.18-alpha.14",
42
- "@budibase/types": "2.3.18-alpha.14",
39
+ "@budibase/backend-core": "2.3.18-alpha.15",
40
+ "@budibase/pro": "2.3.18-alpha.14",
41
+ "@budibase/string-templates": "2.3.18-alpha.15",
42
+ "@budibase/types": "2.3.18-alpha.15",
43
43
  "@koa/router": "8.0.8",
44
44
  "@sentry/node": "6.17.7",
45
45
  "@techpass/passport-openidconnect": "0.3.2",
@@ -60,6 +60,7 @@
60
60
  "koa-send": "5.0.1",
61
61
  "koa-session": "5.13.1",
62
62
  "koa-static": "5.0.0",
63
+ "koa-useragent": "^4.1.0",
63
64
  "node-fetch": "2.6.7",
64
65
  "nodemailer": "6.7.2",
65
66
  "passport-google-oauth": "2.0.0",
@@ -100,5 +101,5 @@
100
101
  "typescript": "4.7.3",
101
102
  "update-dotenv": "1.1.1"
102
103
  },
103
- "gitHead": "6303105b71c3ef85e5bc8a89a7fd763cf55179e4"
104
+ "gitHead": "78287e11da6973374962afac59b35c598a54efc3"
104
105
  }
@@ -69,8 +69,8 @@ export const login = async (ctx: Ctx<LoginRequest>, next: any) => {
69
69
  "local",
70
70
  async (err: any, user: User, info: any) => {
71
71
  await passportCallback(ctx, user, err, info)
72
- await context.identity.doInUserContext(user, async () => {
73
- await events.auth.login("local")
72
+ await context.identity.doInUserContext(user, ctx, async () => {
73
+ await events.auth.login("local", user.email)
74
74
  })
75
75
  ctx.status = 200
76
76
  }
@@ -207,8 +207,8 @@ export const googleCallback = async (ctx: any, next: any) => {
207
207
  { successRedirect: "/", failureRedirect: "/error" },
208
208
  async (err: any, user: SSOUser, info: any) => {
209
209
  await passportCallback(ctx, user, err, info)
210
- await context.identity.doInUserContext(user, async () => {
211
- await events.auth.login("google-internal")
210
+ await context.identity.doInUserContext(user, ctx, async () => {
211
+ await events.auth.login("google-internal", user.email)
212
212
  })
213
213
  ctx.redirect("/")
214
214
  }
@@ -272,8 +272,8 @@ export const oidcCallback = async (ctx: any, next: any) => {
272
272
  { successRedirect: "/", failureRedirect: "/error" },
273
273
  async (err: any, user: SSOUser, info: any) => {
274
274
  await passportCallback(ctx, user, err, info)
275
- await context.identity.doInUserContext(user, async () => {
276
- await events.auth.login("oidc")
275
+ await context.identity.doInUserContext(user, ctx, async () => {
276
+ await events.auth.login("oidc", user.email)
277
277
  })
278
278
  ctx.redirect("/")
279
279
  }
@@ -0,0 +1,111 @@
1
+ import { mocks, structures } from "@budibase/backend-core/tests"
2
+ import { context, events } from "@budibase/backend-core"
3
+ import { Event, IdentityType } from "@budibase/types"
4
+ import { TestConfiguration } from "../../../../tests"
5
+
6
+ mocks.licenses.useAuditLogs()
7
+
8
+ const BASE_IDENTITY = {
9
+ account: undefined,
10
+ type: IdentityType.USER,
11
+ }
12
+ const USER_AUDIT_LOG_COUNT = 3
13
+ const APP_ID = "app_1"
14
+
15
+ describe("/api/global/auditlogs", () => {
16
+ const config = new TestConfiguration()
17
+
18
+ beforeAll(async () => {
19
+ await config.beforeAll()
20
+ })
21
+
22
+ afterAll(async () => {
23
+ await config.afterAll()
24
+ })
25
+
26
+ describe("POST /api/global/auditlogs/search", () => {
27
+ it("should be able to fire some events (create audit logs)", async () => {
28
+ await context.doInTenant(config.tenantId, async () => {
29
+ const userId = config.user!._id!
30
+ const identity = {
31
+ ...BASE_IDENTITY,
32
+ _id: userId,
33
+ tenantId: config.tenantId,
34
+ }
35
+ await context.doInIdentityContext(identity, async () => {
36
+ for (let i = 0; i < USER_AUDIT_LOG_COUNT; i++) {
37
+ await events.user.created(structures.users.user())
38
+ }
39
+ await context.doInAppContext(APP_ID, async () => {
40
+ await events.app.created(structures.apps.app(APP_ID))
41
+ })
42
+ // fetch the user created events
43
+ const response = await config.api.auditLogs.search({
44
+ events: [Event.USER_CREATED],
45
+ })
46
+ expect(response.data).toBeDefined()
47
+ // there will be an initial event which comes from the default user creation
48
+ expect(response.data.length).toBe(USER_AUDIT_LOG_COUNT + 1)
49
+ })
50
+ })
51
+ })
52
+
53
+ it("should be able to search by event", async () => {
54
+ const response = await config.api.auditLogs.search({
55
+ events: [Event.USER_CREATED],
56
+ })
57
+ expect(response.data.length).toBeGreaterThan(0)
58
+ for (let log of response.data) {
59
+ expect(log.event).toBe(Event.USER_CREATED)
60
+ }
61
+ })
62
+
63
+ it("should be able to search by time range (frozen)", async () => {
64
+ // this is frozen, only need to add 1 and minus 1
65
+ const now = new Date()
66
+ const start = new Date()
67
+ start.setSeconds(now.getSeconds() - 1)
68
+ const end = new Date()
69
+ end.setSeconds(now.getSeconds() + 1)
70
+ const response = await config.api.auditLogs.search({
71
+ startDate: start.toISOString(),
72
+ endDate: end.toISOString(),
73
+ })
74
+ expect(response.data.length).toBeGreaterThan(0)
75
+ for (let log of response.data) {
76
+ expect(log.timestamp).toBe(now.toISOString())
77
+ }
78
+ })
79
+
80
+ it("should be able to search by user ID", async () => {
81
+ const userId = config.user!._id!
82
+ const response = await config.api.auditLogs.search({
83
+ userIds: [userId],
84
+ })
85
+ expect(response.data.length).toBeGreaterThan(0)
86
+ for (let log of response.data) {
87
+ expect(log.user._id).toBe(userId)
88
+ }
89
+ })
90
+
91
+ it("should be able to search by app ID", async () => {
92
+ const response = await config.api.auditLogs.search({
93
+ appIds: [APP_ID],
94
+ })
95
+ expect(response.data.length).toBeGreaterThan(0)
96
+ for (let log of response.data) {
97
+ expect(log.app?._id).toBe(APP_ID)
98
+ }
99
+ })
100
+
101
+ it("should be able to search by full string", async () => {
102
+ const response = await config.api.auditLogs.search({
103
+ fullSearch: "User",
104
+ })
105
+ expect(response.data.length).toBeGreaterThan(0)
106
+ for (let log of response.data) {
107
+ expect(log.name.includes("User")).toBe(true)
108
+ }
109
+ })
110
+ })
111
+ })
@@ -367,7 +367,7 @@ describe("/api/global/auth", () => {
367
367
 
368
368
  const res = await config.api.configs.OIDCCallback(configId, preAuthRes)
369
369
 
370
- expect(events.auth.login).toBeCalledWith("oidc")
370
+ expect(events.auth.login).toBeCalledWith("oidc", "oauth@example.com")
371
371
  expect(events.auth.login).toBeCalledTimes(1)
372
372
  expect(res.status).toBe(302)
373
373
  const location: string = res.get("location")
@@ -18,6 +18,8 @@ import accountRoutes from "./system/accounts"
18
18
  import restoreRoutes from "./system/restore"
19
19
 
20
20
  let userGroupRoutes = api.groups
21
+ let auditLogRoutes = api.auditLogs
22
+
21
23
  export const routes: Router[] = [
22
24
  configRoutes,
23
25
  userRoutes,
@@ -32,6 +34,7 @@ export const routes: Router[] = [
32
34
  selfRoutes,
33
35
  licenseRoutes,
34
36
  userGroupRoutes,
37
+ auditLogRoutes,
35
38
  migrationRoutes,
36
39
  accountRoutes,
37
40
  restoreRoutes,
package/src/db/index.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import * as core from "@budibase/backend-core"
2
2
  import env from "../environment"
3
3
 
4
- export const init = () => {
5
- const dbConfig: any = {}
4
+ export function init() {
5
+ const dbConfig: any = {
6
+ replication: true,
7
+ find: true,
8
+ }
9
+
6
10
  if (env.isTest() && !env.COUCH_DB_URL) {
7
11
  dbConfig.inMemory = true
12
+ dbConfig.allDbs = true
8
13
  }
14
+
9
15
  core.init({ db: dbConfig })
10
16
  }
package/src/index.ts CHANGED
@@ -13,11 +13,13 @@ import { Event } from "@sentry/types/dist/event"
13
13
  import Application from "koa"
14
14
  import { bootstrap } from "global-agent"
15
15
  import * as db from "./db"
16
+ import { sdk as proSdk } from "@budibase/pro"
16
17
  import {
17
18
  auth,
18
19
  logging,
19
20
  events,
20
21
  middleware,
22
+ queue,
21
23
  env as coreEnv,
22
24
  } from "@budibase/backend-core"
23
25
  db.init()
@@ -29,8 +31,14 @@ import * as redis from "./utilities/redis"
29
31
  const Sentry = require("@sentry/node")
30
32
  const koaSession = require("koa-session")
31
33
  const logger = require("koa-pino-logger")
34
+ const { userAgent } = require("koa-useragent")
35
+
32
36
  import destroyable from "server-destroy"
33
37
 
38
+ // configure events to use the pro audit log write
39
+ // can't integrate directly into backend-core due to cyclic issues
40
+ events.processors.init(proSdk.auditLogs.write)
41
+
34
42
  if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE) {
35
43
  console.warn(
36
44
  "Warning: ENABLE_SSO_MAINTENANCE_MODE is set. It is recommended this flag is disabled if maintenance is not in progress"
@@ -49,6 +57,7 @@ app.use(koaBody({ multipart: true }))
49
57
  app.use(koaSession(app))
50
58
  app.use(middleware.logging)
51
59
  app.use(logger(logging.pinoSettings()))
60
+ app.use(userAgent)
52
61
 
53
62
  // authentication
54
63
  app.use(auth.passport.initialize())
@@ -84,6 +93,7 @@ server.on("close", async () => {
84
93
  console.log("Server Closed")
85
94
  await redis.shutdown()
86
95
  await events.shutdown()
96
+ await queue.shutdown()
87
97
  if (!env.isTest()) {
88
98
  process.exit(errCode)
89
99
  }
@@ -637,7 +637,7 @@ export const invite = async (
637
637
  }
638
638
  await sendEmail(user.email, EmailTemplatePurpose.INVITATION, opts)
639
639
  response.successful.push({ email: user.email })
640
- await events.user.invited()
640
+ await events.user.invited(user.email)
641
641
  } catch (e) {
642
642
  console.error(`Failed to send email invitation email=${user.email}`, e)
643
643
  response.unsuccessful.push({
@@ -0,0 +1,26 @@
1
+ import { AuditLogSearchParams, SearchAuditLogsResponse } from "@budibase/types"
2
+ import TestConfiguration from "../TestConfiguration"
3
+ import { TestAPI } from "./base"
4
+
5
+ export class AuditLogAPI extends TestAPI {
6
+ constructor(config: TestConfiguration) {
7
+ super(config)
8
+ }
9
+
10
+ search = async (search: AuditLogSearchParams) => {
11
+ const res = await this.request
12
+ .post("/api/global/auditlogs/search")
13
+ .send(search)
14
+ .set(this.config.defaultHeaders())
15
+ .expect("Content-Type", /json/)
16
+ .expect(200)
17
+ return res.body as SearchAuditLogsResponse
18
+ }
19
+
20
+ download = (search: AuditLogSearchParams) => {
21
+ const query = encodeURIComponent(JSON.stringify(search))
22
+ return this.request
23
+ .get(`/api/global/auditlogs/download?query=${query}`)
24
+ .set(this.config.defaultHeaders())
25
+ }
26
+ }
@@ -14,6 +14,7 @@ import { GroupsAPI } from "./groups"
14
14
  import { RolesAPI } from "./roles"
15
15
  import { TemplatesAPI } from "./templates"
16
16
  import { LicenseAPI } from "./license"
17
+ import { AuditLogAPI } from "./auditLogs"
17
18
  export default class API {
18
19
  accounts: AccountAPI
19
20
  auth: AuthAPI
@@ -30,6 +31,7 @@ export default class API {
30
31
  roles: RolesAPI
31
32
  templates: TemplatesAPI
32
33
  license: LicenseAPI
34
+ auditLogs: AuditLogAPI
33
35
 
34
36
  constructor(config: TestConfiguration) {
35
37
  this.accounts = new AccountAPI(config)
@@ -47,5 +49,6 @@ export default class API {
47
49
  this.roles = new RolesAPI(config)
48
50
  this.templates = new TemplatesAPI(config)
49
51
  this.license = new LicenseAPI(config)
52
+ this.auditLogs = new AuditLogAPI(config)
50
53
  }
51
54
  }