@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 +1 -1
- package/package.json +7 -6
- package/src/api/controllers/global/auth.ts +6 -6
- package/src/api/routes/global/tests/auditLogs.spec.ts +111 -0
- package/src/api/routes/global/tests/auth.spec.ts +1 -1
- package/src/api/routes/index.ts +3 -0
- package/src/db/index.ts +8 -2
- package/src/index.ts +10 -0
- package/src/sdk/users/users.ts +1 -1
- package/src/tests/api/auditLogs.ts +26 -0
- package/src/tests/api/index.ts +3 -0
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.
|
|
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.
|
|
40
|
-
"@budibase/pro": "2.3.18-alpha.
|
|
41
|
-
"@budibase/string-templates": "2.3.18-alpha.
|
|
42
|
-
"@budibase/types": "2.3.18-alpha.
|
|
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": "
|
|
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")
|
package/src/api/routes/index.ts
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/sdk/users/users.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/tests/api/index.ts
CHANGED
|
@@ -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
|
}
|