@budibase/worker 1.0.198 → 1.0.199-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 (40) hide show
  1. package/Dockerfile +3 -0
  2. package/nodemon.json +1 -1
  3. package/package.json +56 -46
  4. package/scripts/dev/manage.js +2 -0
  5. package/scripts/jestSetup.js +9 -0
  6. package/scripts/load/users.js +4 -4
  7. package/src/api/controllers/global/auth.ts +21 -14
  8. package/src/api/controllers/global/configs.js +126 -2
  9. package/src/api/controllers/global/roles.js +3 -3
  10. package/src/api/controllers/global/self.js +12 -3
  11. package/src/api/controllers/global/users.ts +48 -88
  12. package/src/api/routes/global/self.js +2 -2
  13. package/src/api/routes/global/users.js +3 -3
  14. package/src/api/routes/tests/auth.spec.js +38 -16
  15. package/src/api/routes/tests/configs.spec.js +223 -7
  16. package/src/api/routes/tests/email.spec.js +8 -7
  17. package/src/api/routes/tests/realEmail.spec.js +5 -5
  18. package/src/api/routes/tests/self.spec.js +58 -0
  19. package/src/api/routes/tests/users.spec.js +299 -13
  20. package/src/api/routes/validation/index.ts +1 -0
  21. package/src/api/{utilities/validation.js → routes/validation/users.ts} +4 -4
  22. package/src/index.ts +2 -0
  23. package/src/sdk/index.ts +1 -0
  24. package/src/sdk/users/events.ts +161 -0
  25. package/src/sdk/users/index.ts +1 -0
  26. package/src/sdk/users/users.ts +201 -0
  27. package/src/{api/routes/tests/utilities → tests}/TestConfiguration.js +68 -111
  28. package/src/tests/controllers.js +7 -0
  29. package/src/tests/index.js +12 -0
  30. package/src/tests/mocks/email.js +10 -0
  31. package/src/tests/mocks/index.js +5 -0
  32. package/src/tests/structures/configs.js +76 -0
  33. package/src/tests/structures/index.js +12 -0
  34. package/src/tests/structures/users.ts +28 -0
  35. package/tsconfig.build.json +25 -0
  36. package/tsconfig.json +21 -15
  37. package/src/api/routes/tests/utilities/controllers.js +0 -7
  38. package/src/api/routes/tests/utilities/index.js +0 -41
  39. package/src/api/routes/tests/utilities/structures.js +0 -2
  40. package/src/api/utilities/index.js +0 -33
package/Dockerfile CHANGED
@@ -21,4 +21,7 @@ EXPOSE 4001
21
21
  # which are actually needed to get this environment up and running
22
22
  ENV NODE_ENV=production
23
23
  ENV CLUSTER_MODE=${CLUSTER_MODE}
24
+ ENV SERVICE=worker-service
25
+ ENV POSTHOG_TOKEN=phc_fg5I3nDOf6oJVMHSaycEhpPdlgS8rzXG2r6F2IpxCHS
26
+
24
27
  CMD ["./docker_run.sh"]
package/nodemon.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "watch": ["src", "../backend-core", "../../../budibase-pro/packages/pro"],
3
3
  "ext": "js,ts,json",
4
- "ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
4
+ "ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../backend-core/dist/**/*"],
5
5
  "exec": "ts-node src/index.ts"
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/worker",
3
3
  "email": "hi@budibase.com",
4
- "version": "1.0.198",
4
+ "version": "1.0.199-alpha.0",
5
5
  "description": "Budibase background service",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -12,8 +12,10 @@
12
12
  "budibase"
13
13
  ],
14
14
  "scripts": {
15
- "build": "rimraf dist/ && tsc",
15
+ "prebuild": "rimraf dist/",
16
+ "build": "tsc -p tsconfig.build.json",
16
17
  "postbuild": "copyfiles -u 1 src/**/*.hbs dist/",
18
+ "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
17
19
  "run:docker": "node dist/index.js",
18
20
  "run:docker:cluster": "pm2-runtime start pm2.config.js",
19
21
  "build:docker": "docker build . -t worker-service --label version=$BUDIBASE_RELEASE_VERSION",
@@ -32,63 +34,71 @@
32
34
  "author": "Budibase",
33
35
  "license": "GPL-3.0",
34
36
  "dependencies": {
35
- "@budibase/backend-core": "^1.0.198",
36
- "@budibase/pro": "1.0.197",
37
- "@budibase/string-templates": "^1.0.198",
38
- "@koa/router": "^8.0.0",
37
+ "@budibase/backend-core": "^1.0.199-alpha.0",
38
+ "@budibase/pro": "1.0.198",
39
+ "@budibase/string-templates": "^1.0.199-alpha.0",
40
+ "@koa/router": "8.0.8",
39
41
  "@sentry/node": "6.17.7",
40
- "@techpass/passport-openidconnect": "^0.3.0",
41
- "@types/global-agent": "^2.1.1",
42
- "aws-sdk": "^2.811.0",
43
- "bcryptjs": "^2.4.3",
44
- "dotenv": "^8.2.0",
45
- "global-agent": "^3.0.0",
46
- "got": "^11.8.1",
47
- "joi": "^17.4.0",
48
- "koa": "^2.7.0",
49
- "koa-body": "^4.2.0",
50
- "koa-compress": "^4.0.1",
51
- "koa-passport": "^4.1.4",
52
- "koa-pino-logger": "^3.0.0",
53
- "koa-send": "^5.0.0",
54
- "koa-session": "^5.12.0",
55
- "koa-static": "^5.0.0",
56
- "node-fetch": "^2.6.1",
57
- "nodemailer": "^6.5.0",
58
- "passport-google-oauth": "^2.0.0",
59
- "passport-jwt": "^4.0.0",
60
- "passport-local": "^1.0.0",
61
- "pino-pretty": "^4.0.0",
42
+ "@techpass/passport-openidconnect": "0.3.2",
43
+ "@types/global-agent": "2.1.1",
44
+ "aws-sdk": "2.1030.0",
45
+ "bcryptjs": "2.4.3",
46
+ "dotenv": "8.6.0",
47
+ "global-agent": "3.0.0",
48
+ "got": "11.8.3",
49
+ "joi": "17.6.0",
50
+ "koa": "2.13.4",
51
+ "koa-body": "4.2.0",
52
+ "koa-compress": "4.0.1",
53
+ "koa-passport": "4.1.4",
54
+ "koa-pino-logger": "3.0.0",
55
+ "koa-send": "5.0.1",
56
+ "koa-session": "5.13.1",
57
+ "koa-static": "5.0.0",
58
+ "node-fetch": "2.6.7",
59
+ "nodemailer": "6.7.2",
60
+ "passport-google-oauth": "2.0.0",
61
+ "passport-jwt": "4.0.0",
62
+ "passport-local": "1.0.0",
63
+ "pino-pretty": "4.8.0",
62
64
  "pouchdb": "7.3.0",
63
- "pouchdb-all-dbs": "^1.0.2",
64
- "server-destroy": "^1.0.1"
65
+ "pouchdb-all-dbs": "1.1.1",
66
+ "server-destroy": "1.0.1"
65
67
  },
66
68
  "devDependencies": {
67
- "@types/jest": "^26.0.23",
68
- "@types/koa": "^2.13.3",
69
- "@types/koa-router": "^7.4.2",
70
- "@types/koa__router": "^8.0.11",
71
- "@types/node": "^15.12.4",
69
+ "@budibase/types": "^1.0.199-alpha.0",
70
+ "@types/jest": "26.0.23",
71
+ "@types/koa": "2.13.4",
72
+ "@types/koa-router": "7.4.4",
73
+ "@types/koa__router": "8.0.11",
74
+ "@types/node": "14.18.20",
72
75
  "@typescript-eslint/parser": "5.12.0",
73
- "copyfiles": "^2.4.1",
74
- "eslint": "^6.8.0",
75
- "jest": "^27.0.5",
76
- "nodemon": "^2.0.7",
77
- "pouchdb-adapter-memory": "^7.2.2",
76
+ "copyfiles": "2.4.1",
77
+ "eslint": "6.8.0",
78
+ "jest": "27.4.7",
79
+ "nodemon": "2.0.15",
80
+ "pouchdb-adapter-memory": "7.2.2",
78
81
  "prettier": "2.3.1",
79
- "rimraf": "^3.0.2",
80
- "supertest": "^6.1.3",
81
- "ts-jest": "^27.0.3",
82
- "ts-node": "^10.0.0",
82
+ "rimraf": "3.0.2",
83
+ "supertest": "6.2.2",
84
+ "timekeeper": "2.2.0",
85
+ "ts-jest": "27.1.3",
86
+ "ts-node": "10.4.0",
87
+ "tsconfig-paths": "4.0.0",
83
88
  "typescript": "4.5.5",
84
- "update-dotenv": "^1.1.1"
89
+ "update-dotenv": "1.1.1"
85
90
  },
86
91
  "jest": {
87
92
  "preset": "ts-jest",
88
93
  "testEnvironment": "node",
94
+ "moduleNameMapper": {
95
+ "@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
96
+ "@budibase/backend-core": "<rootDir>/../backend-core/src",
97
+ "@budibase/types": "<rootDir>/../types/src"
98
+ },
89
99
  "setupFiles": [
90
100
  "./scripts/jestSetup.js"
91
101
  ]
92
102
  },
93
- "gitHead": "f16afcf7809998f9fca109e2199778af79c61e30"
103
+ "gitHead": "669d080a99dbec21584af40ad3b8fbc697051586"
94
104
  }
@@ -26,6 +26,8 @@ async function init() {
26
26
  ACCOUNT_PORTAL_API_KEY: "budibase",
27
27
  PLATFORM_URL: "http://localhost:10000",
28
28
  APPS_URL: "http://localhost:4001",
29
+ SERVICE: "worker-service",
30
+ DEPLOYMENT_ENVIRONMENT: "development",
29
31
  }
30
32
  let envFile = ""
31
33
  Object.keys(envFileJson).forEach(key => {
@@ -5,3 +5,12 @@ env._set("NODE_ENV", "jest")
5
5
  env._set("JWT_SECRET", "test-jwtsecret")
6
6
  env._set("LOG_LEVEL", "silent")
7
7
  env._set("MULTI_TENANCY", true)
8
+
9
+ const { mocks } = require("@budibase/backend-core/tests")
10
+
11
+ // mock all dates to 2020-01-01T00:00:00.000Z
12
+ // use tk.reset() to use real dates in individual tests
13
+ const tk = require("timekeeper")
14
+ tk.freeze(mocks.date.MOCK_DATE)
15
+
16
+ global.console.log = jest.fn() // console.log are ignored in tests
@@ -6,7 +6,7 @@ const {
6
6
  generateGlobalUserID,
7
7
  } = require("@budibase/backend-core/db")
8
8
  const { doInTenant, getGlobalDB } = require("@budibase/backend-core/tenancy")
9
- const { internalSaveUser } = require("@budibase/backend-core/utils")
9
+ const users = require("../../src/sdk/users")
10
10
  const { publicApiUserFix } = require("../../src/utilities/users")
11
11
  const { hash } = require("@budibase/backend-core/utils")
12
12
 
@@ -73,15 +73,15 @@ async function run() {
73
73
  },
74
74
  })
75
75
  userSavePromises.push(
76
- internalSaveUser(ctx.request.body, TENANT_ID, {
76
+ users.save(ctx.request.body, {
77
77
  hashPassword: false,
78
78
  requirePassword: true,
79
79
  bulkCreate: true,
80
80
  })
81
81
  )
82
82
  }
83
- const users = await Promise.all(userSavePromises)
84
- await db.bulkDocs(users)
83
+ const allUsers = await Promise.all(userSavePromises)
84
+ await db.bulkDocs(allUsers)
85
85
  console.log(`${i + BATCH_SIZE} users have been created.`)
86
86
  }
87
87
  })
@@ -4,14 +4,7 @@ const { google } = require("@budibase/backend-core/middleware")
4
4
  const { oidc } = require("@budibase/backend-core/middleware")
5
5
  const { Configs, EmailTemplatePurpose } = require("../../../constants")
6
6
  const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
7
- const {
8
- setCookie,
9
- getCookie,
10
- clearCookie,
11
- getGlobalUserByEmail,
12
- hash,
13
- platformLogout,
14
- } = core.utils
7
+ const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
15
8
  const { Cookies, Headers } = core.constants
16
9
  const { passport } = core.auth
17
10
  const { checkResetPasswordCode } = require("../../../utilities/redis")
@@ -21,7 +14,9 @@ const {
21
14
  isMultiTenant,
22
15
  } = require("@budibase/backend-core/tenancy")
23
16
  const env = require("../../../environment")
24
- import { users } from "@budibase/pro"
17
+ import { events, users as usersCore, context } from "@budibase/backend-core"
18
+ import { users } from "../../../sdk"
19
+ import { User } from "@budibase/types"
25
20
 
26
21
  const ssoCallbackUrl = async (config: any, type: any) => {
27
22
  // incase there is a callback URL from before
@@ -75,8 +70,11 @@ async function authInternal(ctx: any, user: any, err = null, info = null) {
75
70
  export const authenticate = async (ctx: any, next: any) => {
76
71
  return passport.authenticate(
77
72
  "local",
78
- async (err: any, user: any, info: any) => {
73
+ async (err: any, user: User, info: any) => {
79
74
  await authInternal(ctx, user, err, info)
75
+ await context.identity.doInUserContext(user, async () => {
76
+ await events.auth.login("local")
77
+ })
80
78
  ctx.status = 200
81
79
  }
82
80
  )(ctx, next)
@@ -110,13 +108,14 @@ export const reset = async (ctx: any) => {
110
108
  )
111
109
  }
112
110
  try {
113
- const user = await getGlobalUserByEmail(email)
111
+ const user = (await usersCore.getGlobalUserByEmail(email)) as User
114
112
  // only if user exists, don't error though if they don't
115
113
  if (user) {
116
114
  await sendEmail(email, EmailTemplatePurpose.PASSWORD_RECOVERY, {
117
115
  user,
118
116
  subject: "{{ company }} platform password reset",
119
117
  })
118
+ await events.user.passwordResetRequested(user)
120
119
  }
121
120
  } catch (err) {
122
121
  console.log(err)
@@ -141,7 +140,11 @@ export const resetUpdate = async (ctx: any) => {
141
140
  ctx.body = {
142
141
  message: "password reset successfully.",
143
142
  }
143
+ // remove password from the user before sending events
144
+ delete user.password
145
+ await events.user.passwordReset(user)
144
146
  } catch (err) {
147
+ console.error(err)
145
148
  ctx.throw(400, "Cannot reset password.")
146
149
  }
147
150
  }
@@ -211,9 +214,11 @@ export const googleAuth = async (ctx: any, next: any) => {
211
214
  return passport.authenticate(
212
215
  strategy,
213
216
  { successRedirect: "/", failureRedirect: "/error" },
214
- async (err: any, user: any, info: any) => {
217
+ async (err: any, user: User, info: any) => {
215
218
  await authInternal(ctx, user, err, info)
216
-
219
+ await context.identity.doInUserContext(user, async () => {
220
+ await events.auth.login("google-internal")
221
+ })
217
222
  ctx.redirect("/")
218
223
  }
219
224
  )(ctx, next)
@@ -257,7 +262,9 @@ export const oidcAuth = async (ctx: any, next: any) => {
257
262
  { successRedirect: "/", failureRedirect: "/error" },
258
263
  async (err: any, user: any, info: any) => {
259
264
  await authInternal(ctx, user, err, info)
260
-
265
+ await context.identity.doInUserContext(user, async () => {
266
+ await events.auth.login("oidc")
267
+ })
261
268
  ctx.redirect("/")
262
269
  }
263
270
  )(ctx, next)
@@ -19,13 +19,132 @@ const {
19
19
  CacheKeys,
20
20
  bustCache,
21
21
  } = require("@budibase/backend-core/cache")
22
+ const { events } = require("@budibase/backend-core")
22
23
 
23
24
  const BB_TENANT_CDN = "https://tenants.cdn.budi.live"
24
25
 
26
+ const getEventFns = async (db, config) => {
27
+ const fns = []
28
+ const type = config.type
29
+
30
+ let existing
31
+ if (config._id) {
32
+ existing = await db.get(config._id)
33
+ }
34
+
35
+ if (!existing) {
36
+ switch (config.type) {
37
+ case Configs.SMTP: {
38
+ fns.push(events.email.SMTPCreated)
39
+ break
40
+ }
41
+ case Configs.GOOGLE: {
42
+ fns.push(() => events.auth.SSOCreated(type))
43
+ if (config.config.activated) {
44
+ fns.push(() => events.auth.SSOActivated(type))
45
+ }
46
+ break
47
+ }
48
+ case Configs.OIDC: {
49
+ fns.push(() => events.auth.SSOCreated(type))
50
+ if (config.config.configs[0].activated) {
51
+ fns.push(() => events.auth.SSOActivated(type))
52
+ }
53
+ break
54
+ }
55
+ case Configs.SETTINGS: {
56
+ // company
57
+ const company = config.config.company
58
+ if (company && company !== "Budibase") {
59
+ fns.push(events.org.nameUpdated)
60
+ }
61
+
62
+ // logo
63
+ const logoUrl = config.config.logoUrl
64
+ if (logoUrl) {
65
+ fns.push(events.org.logoUpdated)
66
+ }
67
+
68
+ // platform url
69
+ const platformUrl = config.config.platformUrl
70
+ if (
71
+ platformUrl &&
72
+ platformUrl !== "http://localhost:10000" &&
73
+ env.SELF_HOSTED
74
+ ) {
75
+ fns.push(events.org.platformURLUpdated)
76
+ }
77
+ break
78
+ }
79
+ }
80
+ } else {
81
+ switch (config.type) {
82
+ case Configs.SMTP: {
83
+ fns.push(events.email.SMTPUpdated)
84
+ break
85
+ }
86
+ case Configs.GOOGLE: {
87
+ fns.push(() => events.auth.SSOUpdated(type))
88
+ if (!existing.config.activated && config.config.activated) {
89
+ fns.push(() => events.auth.SSOActivated(type))
90
+ } else if (existing.config.activated && !config.config.activated) {
91
+ fns.push(() => events.auth.SSODeactivated(type))
92
+ }
93
+ break
94
+ }
95
+ case Configs.OIDC: {
96
+ fns.push(() => events.auth.SSOUpdated(type))
97
+ if (
98
+ !existing.config.configs[0].activated &&
99
+ config.config.configs[0].activated
100
+ ) {
101
+ fns.push(() => events.auth.SSOActivated(type))
102
+ } else if (
103
+ existing.config.configs[0].activated &&
104
+ !config.config.configs[0].activated
105
+ ) {
106
+ fns.push(() => events.auth.SSODeactivated(type))
107
+ }
108
+ break
109
+ }
110
+ case Configs.SETTINGS: {
111
+ // company
112
+ const existingCompany = existing.config.company
113
+ const company = config.config.company
114
+ if (company && company !== "Budibase" && existingCompany !== company) {
115
+ fns.push(events.org.nameUpdated)
116
+ }
117
+
118
+ // logo
119
+ const existingLogoUrl = existing.config.logoUrl
120
+ const logoUrl = config.config.logoUrl
121
+ if (logoUrl && existingLogoUrl !== logoUrl) {
122
+ fns.push(events.org.logoUpdated)
123
+ }
124
+
125
+ // platform url
126
+ const existingPlatformUrl = existing.config.platformUrl
127
+ const platformUrl = config.config.platformUrl
128
+ if (
129
+ platformUrl &&
130
+ platformUrl !== "http://localhost:10000" &&
131
+ existingPlatformUrl !== platformUrl &&
132
+ env.SELF_HOSTED
133
+ ) {
134
+ fns.push(events.org.platformURLUpdated)
135
+ }
136
+ break
137
+ }
138
+ }
139
+ }
140
+
141
+ return fns
142
+ }
143
+
25
144
  exports.save = async function (ctx) {
26
145
  const db = getGlobalDB()
27
146
  const { type, workspace, user, config } = ctx.request.body
28
-
147
+ let eventFns = await getEventFns(db, ctx.request.body)
29
148
  // Config does not exist yet
30
149
  if (!ctx.request.body._id) {
31
150
  ctx.request.body._id = generateConfigID({
@@ -34,7 +153,6 @@ exports.save = async function (ctx) {
34
153
  user,
35
154
  })
36
155
  }
37
-
38
156
  try {
39
157
  // verify the configuration
40
158
  switch (type) {
@@ -49,6 +167,12 @@ exports.save = async function (ctx) {
49
167
  try {
50
168
  const response = await db.put(ctx.request.body)
51
169
  await bustCache(CacheKeys.CHECKLIST)
170
+ await bustCache(CacheKeys.ANALYTICS_ENABLED)
171
+
172
+ for (const fn of eventFns) {
173
+ await fn()
174
+ }
175
+
52
176
  ctx.body = {
53
177
  type,
54
178
  _id: response.id,
@@ -7,7 +7,7 @@ const {
7
7
  const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
8
8
  const { user: userCache } = require("@budibase/backend-core/cache")
9
9
  const { getGlobalDB } = require("@budibase/backend-core/tenancy")
10
- const { allUsers } = require("../../utilities")
10
+ const { users } = require("../../../sdk")
11
11
 
12
12
  exports.fetch = async ctx => {
13
13
  const tenantId = ctx.user.tenantId
@@ -49,10 +49,10 @@ exports.find = async ctx => {
49
49
  exports.removeAppRole = async ctx => {
50
50
  const { appId } = ctx.params
51
51
  const db = getGlobalDB()
52
- const users = await allUsers(ctx)
52
+ const allUsers = await users.allUsers(ctx)
53
53
  const bulk = []
54
54
  const cacheInvalidations = []
55
- for (let user of users) {
55
+ for (let user of allUsers) {
56
56
  if (user.roles[appId]) {
57
57
  cacheInvalidations.push(userCache.invalidateUser(user._id))
58
58
  delete user.roles[appId]
@@ -13,9 +13,9 @@ const {
13
13
  } = require("@budibase/backend-core/utils")
14
14
  const { encrypt } = require("@budibase/backend-core/encryption")
15
15
  const { newid } = require("@budibase/backend-core/utils")
16
- const { getUser } = require("../../utilities")
16
+ const { users } = require("../../../sdk")
17
17
  const { Cookies } = require("@budibase/backend-core/constants")
18
- const { featureFlags } = require("@budibase/backend-core")
18
+ const { events, featureFlags } = require("@budibase/backend-core")
19
19
 
20
20
  function newApiKey() {
21
21
  return encrypt(`${getTenantId()}${SEPARATOR}${newid()}`)
@@ -104,7 +104,7 @@ exports.getSelf = async ctx => {
104
104
  checkCurrentApp(ctx)
105
105
 
106
106
  // get the main body of the user
107
- ctx.body = await getUser(userId)
107
+ ctx.body = await users.getUser(userId)
108
108
 
109
109
  // add the feature flags for this tenant
110
110
  const tenantId = getTenantId()
@@ -116,8 +116,10 @@ exports.getSelf = async ctx => {
116
116
  exports.updateSelf = async ctx => {
117
117
  const db = getGlobalDB()
118
118
  const user = await db.get(ctx.user._id)
119
+ let passwordChange = false
119
120
  if (ctx.request.body.password) {
120
121
  // changing password
122
+ passwordChange = true
121
123
  ctx.request.body.password = await hash(ctx.request.body.password)
122
124
  // Log all other sessions out apart from the current one
123
125
  await platformLogout({
@@ -140,4 +142,11 @@ exports.updateSelf = async ctx => {
140
142
  _id: response.id,
141
143
  _rev: response.rev,
142
144
  }
145
+
146
+ // remove the old password from the user before sending events
147
+ delete user.password
148
+ await events.user.updated(user)
149
+ if (passwordChange) {
150
+ await events.user.passwordUpdated(user)
151
+ }
143
152
  }