@budibase/worker 1.0.199 → 1.0.200-alpha.2

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
@@ -0,0 +1,201 @@
1
+ import env from "../../environment"
2
+ import { quotas } from "@budibase/pro"
3
+ import * as apps from "../../utilities/appService"
4
+ import * as eventHelpers from "./events"
5
+ import {
6
+ tenancy,
7
+ utils,
8
+ db as dbUtils,
9
+ constants,
10
+ cache,
11
+ users as usersCore,
12
+ deprovisioning,
13
+ sessions,
14
+ HTTPError,
15
+ accounts,
16
+ migrations,
17
+ } from "@budibase/backend-core"
18
+ import { MigrationType } from "@budibase/types"
19
+
20
+ /**
21
+ * Retrieves all users from the current tenancy.
22
+ */
23
+ export const allUsers = async () => {
24
+ const db = tenancy.getGlobalDB()
25
+ const response = await db.allDocs(
26
+ dbUtils.getGlobalUserParams(null, {
27
+ include_docs: true,
28
+ })
29
+ )
30
+ return response.rows.map((row: any) => row.doc)
31
+ }
32
+
33
+ /**
34
+ * Gets a user by ID from the global database, based on the current tenancy.
35
+ */
36
+ export const getUser = async (userId: string) => {
37
+ const db = tenancy.getGlobalDB()
38
+ let user
39
+ try {
40
+ user = await db.get(userId)
41
+ } catch (err: any) {
42
+ // no user found, just return nothing
43
+ if (err.status === 404) {
44
+ return {}
45
+ }
46
+ throw err
47
+ }
48
+ if (user) {
49
+ delete user.password
50
+ }
51
+ return user
52
+ }
53
+
54
+ interface SaveUserOpts {
55
+ hashPassword?: boolean
56
+ requirePassword?: boolean
57
+ bulkCreate?: boolean
58
+ }
59
+
60
+ export const save = async (
61
+ user: any,
62
+ opts: SaveUserOpts = {
63
+ hashPassword: true,
64
+ requirePassword: true,
65
+ bulkCreate: false,
66
+ }
67
+ ) => {
68
+ const tenantId = tenancy.getTenantId()
69
+ const db = tenancy.getGlobalDB()
70
+ let { email, password, _id } = user
71
+ // make sure another user isn't using the same email
72
+ let dbUser: any
73
+ if (opts.bulkCreate) {
74
+ dbUser = null
75
+ } else if (email) {
76
+ // check budibase users inside the tenant
77
+ dbUser = await usersCore.getGlobalUserByEmail(email)
78
+ if (dbUser != null && (dbUser._id !== _id || Array.isArray(dbUser))) {
79
+ throw `Email address ${email} already in use.`
80
+ }
81
+
82
+ // check budibase users in other tenants
83
+ if (env.MULTI_TENANCY) {
84
+ const tenantUser = await tenancy.getTenantUser(email)
85
+ if (tenantUser != null && tenantUser.tenantId !== tenantId) {
86
+ throw `Email address ${email} already in use.`
87
+ }
88
+ }
89
+
90
+ // check root account users in account portal
91
+ if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
92
+ const account = await accounts.getAccount(email)
93
+ if (account && account.verified && account.tenantId !== tenantId) {
94
+ throw `Email address ${email} already in use.`
95
+ }
96
+ }
97
+ } else if (_id) {
98
+ dbUser = await db.get(_id)
99
+ }
100
+
101
+ // get the password, make sure one is defined
102
+ let hashedPassword
103
+ if (password) {
104
+ hashedPassword = opts.hashPassword ? await utils.hash(password) : password
105
+ } else if (dbUser) {
106
+ hashedPassword = dbUser.password
107
+ } else if (opts.requirePassword) {
108
+ throw "Password must be specified."
109
+ }
110
+
111
+ _id = _id || dbUtils.generateGlobalUserID(email)
112
+ user = {
113
+ createdAt: Date.now(),
114
+ ...dbUser,
115
+ ...user,
116
+ _id,
117
+ password: hashedPassword,
118
+ tenantId,
119
+ }
120
+ // make sure the roles object is always present
121
+ if (!user.roles) {
122
+ user.roles = {}
123
+ }
124
+ // add the active status to a user if its not provided
125
+ if (user.status == null) {
126
+ user.status = constants.UserStatus.ACTIVE
127
+ }
128
+ try {
129
+ const putOpts = {
130
+ password: hashedPassword,
131
+ ...user,
132
+ }
133
+ if (opts.bulkCreate) {
134
+ return putOpts
135
+ }
136
+ // save the user to db
137
+ let response
138
+ const putUserFn = () => {
139
+ return db.put(user)
140
+ }
141
+ if (eventHelpers.isAddingBuilder(user, dbUser)) {
142
+ response = await quotas.addDeveloper(putUserFn)
143
+ } else {
144
+ response = await putUserFn()
145
+ }
146
+ user._rev = response.rev
147
+
148
+ await eventHelpers.handleSaveEvents(user, dbUser)
149
+
150
+ if (env.MULTI_TENANCY) {
151
+ const afterCreateTenant = () =>
152
+ migrations.backPopulateMigrations({
153
+ type: MigrationType.GLOBAL,
154
+ tenantId,
155
+ })
156
+ await tenancy.tryAddTenant(tenantId, _id, email, afterCreateTenant)
157
+ }
158
+ await cache.user.invalidateUser(response.id)
159
+ // let server know to sync user
160
+ await apps.syncUserInApps(user._id)
161
+
162
+ return {
163
+ _id: response.id,
164
+ _rev: response.rev,
165
+ email,
166
+ }
167
+ } catch (err: any) {
168
+ if (err.status === 409) {
169
+ throw "User exists already"
170
+ } else {
171
+ throw err
172
+ }
173
+ }
174
+ }
175
+
176
+ export const destroy = async (id: string, currentUser: any) => {
177
+ const db = tenancy.getGlobalDB()
178
+ const dbUser = await db.get(id)
179
+
180
+ if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
181
+ // root account holder can't be deleted from inside budibase
182
+ const email = dbUser.email
183
+ const account = await accounts.getAccount(email)
184
+ if (account) {
185
+ if (email === currentUser.email) {
186
+ throw new HTTPError('Please visit "Account" to delete this user', 400)
187
+ } else {
188
+ throw new HTTPError("Account holder cannot be deleted", 400)
189
+ }
190
+ }
191
+ }
192
+
193
+ await deprovisioning.removeUserFromInfoDB(dbUser)
194
+ await db.remove(dbUser._id, dbUser._rev)
195
+ await eventHelpers.handleDeleteEvents(dbUser)
196
+ await quotas.removeUser(dbUser)
197
+ await cache.user.invalidateUser(dbUser._id)
198
+ await sessions.invalidateSessions(dbUser._id)
199
+ // let server know to sync user
200
+ await apps.syncUserInApps(dbUser._id)
201
+ }
@@ -1,21 +1,22 @@
1
- require("../../../../db").init()
2
- const env = require("../../../../environment")
1
+ require("./mocks")
2
+ require("../db").init()
3
+ const env = require("../environment")
3
4
  const controllers = require("./controllers")
4
5
  const supertest = require("supertest")
5
6
  const { jwt } = require("@budibase/backend-core/auth")
6
7
  const { Cookies, Headers } = require("@budibase/backend-core/constants")
7
- const { Configs, LOGO_URL } = require("../../../../constants")
8
- const { getGlobalUserByEmail } = require("@budibase/backend-core/utils")
8
+ const { Configs } = require("../constants")
9
+ const { users } = require("@budibase/backend-core")
9
10
  const { createASession } = require("@budibase/backend-core/sessions")
10
- const { newid } = require("@budibase/backend-core/src/hashing")
11
11
  const { TENANT_ID, CSRF_TOKEN } = require("./structures")
12
+ const structures = require("./structures")
12
13
  const { doInTenant } = require("@budibase/backend-core/tenancy")
13
14
 
14
15
  class TestConfiguration {
15
16
  constructor(openServer = true) {
16
17
  if (openServer) {
17
- env.PORT = 4012
18
- this.server = require("../../../../index")
18
+ env.PORT = "0" // random port
19
+ this.server = require("../index")
19
20
  // we need the request for logging in, involves cookies, hard to fake
20
21
  this.request = supertest(this.server)
21
22
  }
@@ -25,6 +26,8 @@ class TestConfiguration {
25
26
  return this.request
26
27
  }
27
28
 
29
+ // UTILS
30
+
28
31
  async _req(config, params, controlFunc) {
29
32
  const request = {}
30
33
  // fake cookies, we don't need them
@@ -48,25 +51,37 @@ class TestConfiguration {
48
51
  return request.body
49
52
  }
50
53
 
51
- async init(createUser = true) {
52
- if (createUser) {
53
- // create a test user
54
- await this._req(
55
- {
56
- email: "test@test.com",
57
- password: "test",
58
- _id: "us_uuid1",
59
- builder: {
60
- global: true,
61
- },
62
- admin: {
63
- global: true,
64
- },
65
- },
66
- null,
67
- controllers.users.save
68
- )
54
+ // SETUP / TEARDOWN
55
+
56
+ async beforeAll() {
57
+ await this.login()
58
+ }
59
+
60
+ async afterAll() {
61
+ if (this.server) {
62
+ await this.server.close()
69
63
  }
64
+ }
65
+
66
+ // USER / AUTH
67
+
68
+ async login() {
69
+ // create a test user
70
+ await this._req(
71
+ {
72
+ email: "test@test.com",
73
+ password: "test",
74
+ _id: "us_uuid1",
75
+ builder: {
76
+ global: true,
77
+ },
78
+ admin: {
79
+ global: true,
80
+ },
81
+ },
82
+ null,
83
+ controllers.users.save
84
+ )
70
85
  await createASession("us_uuid1", {
71
86
  sessionId: "sessionid",
72
87
  tenantId: TENANT_ID,
@@ -74,12 +89,6 @@ class TestConfiguration {
74
89
  })
75
90
  }
76
91
 
77
- async end() {
78
- if (this.server) {
79
- await this.server.close()
80
- }
81
- }
82
-
83
92
  cookieHeader(cookies) {
84
93
  return {
85
94
  Cookie: [cookies],
@@ -103,25 +112,32 @@ class TestConfiguration {
103
112
 
104
113
  async getUser(email) {
105
114
  return doInTenant(TENANT_ID, () => {
106
- return getGlobalUserByEmail(email)
115
+ return users.getGlobalUserByEmail(email)
107
116
  })
108
117
  }
109
118
 
110
- async createUser(email = "test@test.com", password = "test") {
111
- const user = await this.getUser(email)
119
+ async createUser(email, password) {
120
+ const user = await this.getUser(structures.users.email)
112
121
  if (user) {
113
122
  return user
114
123
  }
115
124
  await this._req(
116
- {
117
- email,
118
- password,
119
- },
125
+ structures.users.user({ email, password }),
120
126
  null,
121
127
  controllers.users.save
122
128
  )
123
129
  }
124
130
 
131
+ async saveAdminUser() {
132
+ await this._req(
133
+ structures.users.user({ tenantId: TENANT_ID }),
134
+ null,
135
+ controllers.users.adminUser
136
+ )
137
+ }
138
+
139
+ // CONFIGS
140
+
125
141
  async deleteConfig(type) {
126
142
  try {
127
143
  const cfg = await this._req(
@@ -146,37 +162,26 @@ class TestConfiguration {
146
162
  }
147
163
  }
148
164
 
165
+ // CONFIGS - SETTINGS
166
+
149
167
  async saveSettingsConfig() {
150
168
  await this.deleteConfig(Configs.SETTINGS)
151
169
  await this._req(
152
- {
153
- type: Configs.SETTINGS,
154
- config: {
155
- platformUrl: "http://localhost:10000",
156
- logoUrl: LOGO_URL,
157
- company: "Budibase",
158
- },
159
- },
170
+ structures.configs.settings(),
160
171
  null,
161
172
  controllers.config.save
162
173
  )
163
174
  }
164
175
 
165
- async saveOAuthConfig() {
176
+ // CONFIGS - GOOGLE
177
+
178
+ async saveGoogleConfig() {
166
179
  await this.deleteConfig(Configs.GOOGLE)
167
- await this._req(
168
- {
169
- type: Configs.GOOGLE,
170
- config: {
171
- clientID: "clientId",
172
- clientSecret: "clientSecret",
173
- },
174
- },
175
- null,
176
- controllers.config.save
177
- )
180
+ await this._req(structures.configs.google(), null, controllers.config.save)
178
181
  }
179
182
 
183
+ // CONFIGS - OIDC
184
+
180
185
  getOIDConfigCookie(configId) {
181
186
  const token = jwt.sign(configId, env.JWT_SECRET)
182
187
  return this.cookieHeader([[`${Cookies.OIDC_CONFIG}=${token}`]])
@@ -184,75 +189,27 @@ class TestConfiguration {
184
189
 
185
190
  async saveOIDCConfig() {
186
191
  await this.deleteConfig(Configs.OIDC)
187
- const config = {
188
- type: Configs.OIDC,
189
- config: {
190
- configs: [
191
- {
192
- configUrl: "http://someconfigurl",
193
- clientID: "clientId",
194
- clientSecret: "clientSecret",
195
- logo: "Microsoft",
196
- name: "Active Directory",
197
- uuid: newid(),
198
- },
199
- ],
200
- },
201
- }
192
+ const config = structures.configs.oidc()
202
193
 
203
194
  await this._req(config, null, controllers.config.save)
204
195
  return config
205
196
  }
206
197
 
198
+ // CONFIGS - SMTP
199
+
207
200
  async saveSmtpConfig() {
208
201
  await this.deleteConfig(Configs.SMTP)
209
- await this._req(
210
- {
211
- type: Configs.SMTP,
212
- config: {
213
- port: 12345,
214
- host: "smtptesthost.com",
215
- from: "testfrom@test.com",
216
- subject: "Hello!",
217
- },
218
- },
219
- null,
220
- controllers.config.save
221
- )
202
+ await this._req(structures.configs.smtp(), null, controllers.config.save)
222
203
  }
223
204
 
224
205
  async saveEtherealSmtpConfig() {
225
206
  await this.deleteConfig(Configs.SMTP)
226
207
  await this._req(
227
- {
228
- type: Configs.SMTP,
229
- config: {
230
- port: 587,
231
- host: "smtp.ethereal.email",
232
- secure: false,
233
- auth: {
234
- user: "don.bahringer@ethereal.email",
235
- pass: "yCKSH8rWyUPbnhGYk9",
236
- },
237
- connectionTimeout: 1000, // must be less than the jest default of 5000
238
- },
239
- },
208
+ structures.configs.smtpEthereal(),
240
209
  null,
241
210
  controllers.config.save
242
211
  )
243
212
  }
244
-
245
- async saveAdminUser() {
246
- await this._req(
247
- {
248
- email: "testuser@test.com",
249
- password: "test@test.com",
250
- tenantId: TENANT_ID,
251
- },
252
- null,
253
- controllers.users.adminUser
254
- )
255
- }
256
213
  }
257
214
 
258
215
  module.exports = TestConfiguration
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ email: require("../api/controllers/global/email"),
3
+ workspaces: require("../api/controllers/global/workspaces"),
4
+ config: require("../api/controllers/global/configs"),
5
+ templates: require("../api/controllers/global/templates"),
6
+ users: require("../api/controllers/global/users"),
7
+ }
@@ -0,0 +1,12 @@
1
+ const TestConfiguration = require("./TestConfiguration")
2
+ const structures = require("./structures")
3
+ const mocks = require("./mocks")
4
+ const config = new TestConfiguration()
5
+ const request = config.getRequest()
6
+
7
+ module.exports = {
8
+ structures,
9
+ mocks,
10
+ config,
11
+ request,
12
+ }
@@ -0,0 +1,10 @@
1
+ exports.mock = () => {
2
+ // mock the email system
3
+ const sendMailMock = jest.fn()
4
+ const nodemailer = require("nodemailer")
5
+ nodemailer.createTransport.mockReturnValue({
6
+ sendMail: sendMailMock,
7
+ verify: jest.fn(),
8
+ })
9
+ return sendMailMock
10
+ }
@@ -0,0 +1,5 @@
1
+ const email = require("./email")
2
+
3
+ module.exports = {
4
+ email,
5
+ }
@@ -0,0 +1,76 @@
1
+ const { Configs } = require("../../constants")
2
+ const { utils } = require("@budibase/backend-core")
3
+
4
+ exports.oidc = conf => {
5
+ return {
6
+ type: Configs.OIDC,
7
+ config: {
8
+ configs: [
9
+ {
10
+ configUrl: "http://someconfigurl",
11
+ clientID: "clientId",
12
+ clientSecret: "clientSecret",
13
+ logo: "Microsoft",
14
+ name: "Active Directory",
15
+ uuid: utils.newid(),
16
+ activated: true,
17
+ ...conf,
18
+ },
19
+ ],
20
+ },
21
+ }
22
+ }
23
+
24
+ exports.google = conf => {
25
+ return {
26
+ type: Configs.GOOGLE,
27
+ config: {
28
+ clientID: "clientId",
29
+ clientSecret: "clientSecret",
30
+ activated: true,
31
+ ...conf,
32
+ },
33
+ }
34
+ }
35
+
36
+ exports.smtp = conf => {
37
+ return {
38
+ type: Configs.SMTP,
39
+ config: {
40
+ port: 12345,
41
+ host: "smtptesthost.com",
42
+ from: "testfrom@test.com",
43
+ subject: "Hello!",
44
+ secure: false,
45
+ ...conf,
46
+ },
47
+ }
48
+ }
49
+
50
+ exports.smtpEthereal = () => {
51
+ return {
52
+ type: Configs.SMTP,
53
+ config: {
54
+ port: 587,
55
+ host: "smtp.ethereal.email",
56
+ secure: false,
57
+ auth: {
58
+ user: "don.bahringer@ethereal.email",
59
+ pass: "yCKSH8rWyUPbnhGYk9",
60
+ },
61
+ connectionTimeout: 1000, // must be less than the jest default of 5000
62
+ },
63
+ }
64
+ }
65
+
66
+ exports.settings = conf => {
67
+ return {
68
+ type: Configs.SETTINGS,
69
+ config: {
70
+ platformUrl: "http://localhost:10000",
71
+ logoUrl: "",
72
+ company: "Budibase",
73
+ ...conf,
74
+ },
75
+ }
76
+ }
@@ -0,0 +1,12 @@
1
+ const configs = require("./configs")
2
+ const users = require("./users")
3
+
4
+ const TENANT_ID = "default"
5
+ const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
6
+
7
+ module.exports = {
8
+ configs,
9
+ users,
10
+ TENANT_ID,
11
+ CSRF_TOKEN,
12
+ }
@@ -0,0 +1,28 @@
1
+ export const email = "test@test.com"
2
+
3
+ export const user = (userProps: any) => {
4
+ return {
5
+ email: "test@test.com",
6
+ password: "test",
7
+ roles: {},
8
+ ...userProps,
9
+ }
10
+ }
11
+
12
+ export const adminUser = (userProps: any) => {
13
+ return {
14
+ ...user(userProps),
15
+ admin: {
16
+ global: true,
17
+ },
18
+ }
19
+ }
20
+
21
+ export const builderUser = (userProps: any) => {
22
+ return {
23
+ ...user(userProps),
24
+ builder: {
25
+ global: true,
26
+ },
27
+ }
28
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "lib": ["es2020"],
6
+ "allowJs": true,
7
+ "strict": true,
8
+ "noImplicitAny": true,
9
+ "esModuleInterop": true,
10
+ "resolveJsonModule": true,
11
+ "incremental": true,
12
+ "types": [ "node", "jest" ],
13
+ "outDir": "dist",
14
+ "skipLibCheck": true
15
+ },
16
+ "include": [
17
+ "src/**/*"
18
+ ],
19
+ "exclude": [
20
+ "node_modules",
21
+ "dist",
22
+ "**/*.spec.ts",
23
+ "**/*.spec.js"
24
+ ]
25
+ }
package/tsconfig.json CHANGED
@@ -1,23 +1,29 @@
1
1
  {
2
+ "extends": "./tsconfig.build.json",
2
3
  "compilerOptions": {
3
- "target": "es6",
4
- "module": "commonjs",
5
- "lib": ["es2019"],
6
- "allowJs": true,
7
- "outDir": "dist",
8
- "strict": true,
9
- "noImplicitAny": true,
10
- "esModuleInterop": true,
11
- "resolveJsonModule": true,
12
- "incremental": true
4
+ "composite": true,
5
+ "declaration": true,
6
+ "sourceMap": true,
7
+ "baseUrl": ".",
8
+ "paths": {
9
+ "@budibase/types": ["../types/src"],
10
+ "@budibase/backend-core": ["../backend-core/src"],
11
+ "@budibase/backend-core/*": ["../backend-core/*"]
12
+ }
13
13
  },
14
+ "ts-node": {
15
+ "require": ["tsconfig-paths/register"]
16
+ },
17
+ "references": [
18
+ { "path": "../types" },
19
+ { "path": "../backend-core" },
20
+ ],
14
21
  "include": [
15
- "./src/**/*"
22
+ "src/**/*",
23
+ "package.json"
16
24
  ],
17
25
  "exclude": [
18
26
  "node_modules",
19
- "**/*.json",
20
- "**/*.spec.ts",
21
- "**/*.spec.js"
27
+ "dist"
22
28
  ]
23
- }
29
+ }