@budibase/worker 2.22.11 → 2.22.13

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
@@ -2,7 +2,7 @@ import { Config } from "@jest/types"
2
2
  import * as fs from "fs"
3
3
 
4
4
  const config: Config.InitialOptions = {
5
- preset: "@trendyol/jest-testcontainers",
5
+ globalSetup: "./../../globalSetup.ts",
6
6
  setupFiles: ["./src/tests/jestEnv.ts"],
7
7
  setupFilesAfterEnv: ["./src/tests/jestSetup.ts"],
8
8
  collectCoverageFrom: ["src/**/*.{js,ts}", "../backend-core/src/**/*.{js,ts}"],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/worker",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.22.11",
4
+ "version": "2.22.13",
5
5
  "description": "Budibase background service",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -37,10 +37,10 @@
37
37
  "author": "Budibase",
38
38
  "license": "GPL-3.0",
39
39
  "dependencies": {
40
- "@budibase/backend-core": "2.22.11",
41
- "@budibase/pro": "2.22.11",
42
- "@budibase/string-templates": "2.22.11",
43
- "@budibase/types": "2.22.11",
40
+ "@budibase/backend-core": "2.22.13",
41
+ "@budibase/pro": "2.22.13",
42
+ "@budibase/string-templates": "2.22.13",
43
+ "@budibase/types": "2.22.13",
44
44
  "@koa/router": "8.0.8",
45
45
  "@techpass/passport-openidconnect": "0.3.2",
46
46
  "@types/global-agent": "2.1.1",
@@ -75,7 +75,6 @@
75
75
  "devDependencies": {
76
76
  "@swc/core": "1.3.71",
77
77
  "@swc/jest": "0.2.27",
78
- "@trendyol/jest-testcontainers": "2.1.1",
79
78
  "@types/jest": "29.5.5",
80
79
  "@types/jsonwebtoken": "9.0.3",
81
80
  "@types/koa": "2.13.4",
@@ -86,6 +85,7 @@
86
85
  "@types/supertest": "2.0.14",
87
86
  "@types/uuid": "8.3.4",
88
87
  "jest": "29.7.0",
88
+ "nock": "^13.5.4",
89
89
  "nodemon": "2.0.15",
90
90
  "rimraf": "3.0.2",
91
91
  "supertest": "6.3.3",
@@ -108,5 +108,5 @@
108
108
  }
109
109
  }
110
110
  },
111
- "gitHead": "7aa98dd5445fa3a34c0fe3ffabc27dd3f20f0826"
111
+ "gitHead": "a8479c046fc0ccb320f5f128aafeb9421dde5db5"
112
112
  }
@@ -3,6 +3,7 @@ import env from "../../../environment"
3
3
  import {
4
4
  AcceptUserInviteRequest,
5
5
  AcceptUserInviteResponse,
6
+ AddSSoUserRequest,
6
7
  BulkUserRequest,
7
8
  BulkUserResponse,
8
9
  CloudAccount,
@@ -15,6 +16,7 @@ import {
15
16
  LockName,
16
17
  LockType,
17
18
  MigrationType,
19
+ PlatformUserByEmail,
18
20
  SaveUserResponse,
19
21
  SearchUsersRequest,
20
22
  User,
@@ -53,6 +55,25 @@ export const save = async (ctx: UserCtx<User, SaveUserResponse>) => {
53
55
  }
54
56
  }
55
57
 
58
+ export const addSsoSupport = async (ctx: Ctx<AddSSoUserRequest>) => {
59
+ const { email, ssoId } = ctx.request.body
60
+ try {
61
+ // Status is changed to 404 from getUserDoc if user is not found
62
+ let userByEmail = (await platform.users.getUserDoc(
63
+ email
64
+ )) as PlatformUserByEmail
65
+ await platform.users.addSsoUser(
66
+ ssoId,
67
+ email,
68
+ userByEmail.userId,
69
+ userByEmail.tenantId
70
+ )
71
+ ctx.status = 200
72
+ } catch (err: any) {
73
+ ctx.throw(err.status || 400, err)
74
+ }
75
+ }
76
+
56
77
  const bulkDelete = async (userIds: string[], currentUserId: string) => {
57
78
  if (userIds?.indexOf(currentUserId) !== -1) {
58
79
  throw new Error("Unable to delete self.")
@@ -127,8 +148,8 @@ export const adminUser = async (
127
148
  try {
128
149
  const finalUser = await userSdk.db.createAdminUser(
129
150
  email,
130
- password,
131
151
  tenantId,
152
+ password,
132
153
  {
133
154
  ssoId,
134
155
  hashPassword,
package/src/api/index.ts CHANGED
@@ -41,6 +41,10 @@ const PUBLIC_ENDPOINTS = [
41
41
  route: "/api/global/users/init",
42
42
  method: "POST",
43
43
  },
44
+ {
45
+ route: "/api/global/users/sso",
46
+ method: "POST",
47
+ },
44
48
  {
45
49
  route: "/api/global/users/invite/accept",
46
50
  method: "POST",
@@ -81,6 +85,11 @@ const NO_TENANCY_ENDPOINTS = [
81
85
  route: "/api/global/users/init",
82
86
  method: "POST",
83
87
  },
88
+ // tenant is retrieved from the user found by the requested email
89
+ {
90
+ route: "/api/global/users/sso",
91
+ method: "POST",
92
+ },
84
93
  // deprecated single tenant sso callback
85
94
  {
86
95
  route: "/api/admin/auth/google/callback",
@@ -13,6 +13,8 @@ import { events, constants } from "@budibase/backend-core"
13
13
  import { Response } from "superagent"
14
14
 
15
15
  import * as userSdk from "../../../../sdk/users"
16
+ import nock from "nock"
17
+ import * as jwt from "jsonwebtoken"
16
18
 
17
19
  function getAuthCookie(response: Response) {
18
20
  return response.headers["set-cookie"]
@@ -274,45 +276,9 @@ describe("/api/global/auth", () => {
274
276
  })
275
277
  })
276
278
 
277
- describe("init", () => {
278
- describe("POST /api/global/auth/init", () => {})
279
-
280
- describe("GET /api/global/auth/init", () => {})
281
- })
282
-
283
- describe("datasource", () => {
284
- // MULTI TENANT
285
-
286
- describe("GET /api/global/auth/:tenantId/datasource/:provider", () => {})
287
-
288
- describe("GET /api/global/auth/:tenantId/datasource/:provider/callback", () => {})
289
-
290
- // SINGLE TENANT
291
-
292
- describe("GET /api/global/auth/datasource/:provider/callback", () => {})
293
- })
294
-
295
- describe("google", () => {
296
- // MULTI TENANT
297
-
298
- describe("GET /api/global/auth/:tenantId/google", () => {})
299
-
300
- describe("GET /api/global/auth/:tenantId/google/callback", () => {})
301
-
302
- // SINGLE TENANT
303
-
304
- describe("GET /api/global/auth/google/callback", () => {})
305
-
306
- describe("GET /api/admin/auth/google/callback", () => {})
307
- })
308
-
309
279
  describe("oidc", () => {
310
- beforeEach(async () => {
311
- jest.clearAllMocks()
312
- mockGetWellKnownConfig()
313
-
314
- // see: __mocks__/oauth
315
- // for associated mocking inside passport
280
+ afterEach(() => {
281
+ nock.cleanAll()
316
282
  })
317
283
 
318
284
  const generateOidcConfig = async () => {
@@ -321,21 +287,16 @@ describe("/api/global/auth", () => {
321
287
  return chosenConfig.uuid
322
288
  }
323
289
 
324
- const mockGetWellKnownConfig = () => {
325
- mocks.fetch.mockReturnValue({
326
- ok: true,
327
- json: () => ({
290
+ // MULTI TENANT
291
+ describe("GET /api/global/auth/:tenantId/oidc/configs/:configId", () => {
292
+ it("redirects to auth provider", async () => {
293
+ nock("http://someconfigurl").get("/").times(1).reply(200, {
328
294
  issuer: "test",
329
295
  authorization_endpoint: "http://localhost/auth",
330
296
  token_endpoint: "http://localhost/token",
331
297
  userinfo_endpoint: "http://localhost/userinfo",
332
- }),
333
- })
334
- }
298
+ })
335
299
 
336
- // MULTI TENANT
337
- describe("GET /api/global/auth/:tenantId/oidc/configs/:configId", () => {
338
- it("redirects to auth provider", async () => {
339
300
  const configId = await generateOidcConfig()
340
301
 
341
302
  const res = await config.api.configs.getOIDCConfig(configId)
@@ -352,10 +313,43 @@ describe("/api/global/auth", () => {
352
313
 
353
314
  describe("GET /api/global/auth/:tenantId/oidc/callback", () => {
354
315
  it("logs in", async () => {
316
+ nock("http://someconfigurl").get("/").times(2).reply(200, {
317
+ issuer: "test",
318
+ authorization_endpoint: "http://localhost/auth",
319
+ token_endpoint: "http://localhost/token",
320
+ userinfo_endpoint: "http://localhost/userinfo",
321
+ })
322
+
323
+ const token = jwt.sign(
324
+ {
325
+ iss: "test",
326
+ sub: "sub",
327
+ aud: "clientId",
328
+ exp: Math.floor(Date.now() / 1000) + 60 * 60,
329
+ email: "oauth@example.com",
330
+ },
331
+ "secret"
332
+ )
333
+
334
+ nock("http://localhost").post("/token").reply(200, {
335
+ access_token: "access",
336
+ refresh_token: "refresh",
337
+ id_token: token,
338
+ })
339
+
340
+ nock("http://localhost").get("/userinfo?schema=openid").reply(200, {
341
+ sub: "sub",
342
+ email: "oauth@example.com",
343
+ })
344
+
355
345
  const configId = await generateOidcConfig()
356
346
  const preAuthRes = await config.api.configs.getOIDCConfig(configId)
357
-
358
347
  const res = await config.api.configs.OIDCCallback(configId, preAuthRes)
348
+ if (res.status > 399) {
349
+ throw new Error(
350
+ `OIDC callback failed with status ${res.status}: ${res.text}`
351
+ )
352
+ }
359
353
 
360
354
  expect(events.auth.login).toHaveBeenCalledWith(
361
355
  "oidc",
@@ -520,10 +520,51 @@ describe("/api/global/users", () => {
520
520
  })
521
521
  }
522
522
 
523
+ function createPasswordUser() {
524
+ return config.doInTenant(() => {
525
+ const user = structures.users.user()
526
+ return userSdk.db.save(user)
527
+ })
528
+ }
529
+
523
530
  it("should be able to update an sso user that has no password", async () => {
524
531
  const user = await createSSOUser()
525
532
  await config.api.users.saveUser(user)
526
533
  })
534
+
535
+ it("sso support couldn't be used by admin. It is cloud restricted and needs internal key", async () => {
536
+ const user = await config.createUser()
537
+ const ssoId = "fake-ssoId"
538
+ await config.api.users
539
+ .addSsoSupportDefaultAuth(ssoId, user.email)
540
+ .expect("Content-Type", /json/)
541
+ .expect(403)
542
+ })
543
+
544
+ it("if user email doesn't exist, SSO support couldn't be added. Not found error returned", async () => {
545
+ const ssoId = "fake-ssoId"
546
+ const email = "fake-email@budibase.com"
547
+ await config.api.users
548
+ .addSsoSupportInternalAPIAuth(ssoId, email)
549
+ .expect("Content-Type", /json/)
550
+ .expect(404)
551
+ })
552
+
553
+ it("if user email exist, SSO support is added", async () => {
554
+ const user = await createPasswordUser()
555
+ const ssoId = "fakessoId"
556
+ await config.api.users
557
+ .addSsoSupportInternalAPIAuth(ssoId, user.email)
558
+ .expect(200)
559
+ })
560
+
561
+ it("if user ssoId is already assigned, no change will be applied", async () => {
562
+ const user = await createSSOUser()
563
+ user.ssoId = "testssoId"
564
+ await config.api.users
565
+ .addSsoSupportInternalAPIAuth(user.ssoId, user.email)
566
+ .expect(200)
567
+ })
527
568
  })
528
569
  })
529
570
 
@@ -7,12 +7,13 @@ import { users } from "../validation"
7
7
  import * as selfController from "../../controllers/global/self"
8
8
 
9
9
  const router: Router = new Router()
10
+ const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
10
11
 
11
12
  function buildAdminInitValidation() {
12
13
  return auth.joiValidator.body(
13
14
  Joi.object({
14
15
  email: Joi.string().required(),
15
- password: Joi.string(),
16
+ password: OPTIONAL_STRING,
16
17
  tenantId: Joi.string().required(),
17
18
  ssoId: Joi.string(),
18
19
  })
@@ -64,6 +65,12 @@ router
64
65
  users.buildUserSaveValidation(),
65
66
  controller.save
66
67
  )
68
+ .post(
69
+ "/api/global/users/sso",
70
+ cloudRestricted,
71
+ users.buildAddSsoSupport(),
72
+ controller.addSsoSupport
73
+ )
67
74
  .post(
68
75
  "/api/global/users/bulk",
69
76
  auth.adminOnly,
@@ -41,6 +41,15 @@ export const buildUserSaveValidation = () => {
41
41
  return auth.joiValidator.body(Joi.object(schema).required().unknown(true))
42
42
  }
43
43
 
44
+ export const buildAddSsoSupport = () => {
45
+ return auth.joiValidator.body(
46
+ Joi.object({
47
+ ssoId: Joi.string().required(),
48
+ email: Joi.string().required(),
49
+ }).required()
50
+ )
51
+ }
52
+
44
53
  export const buildUserBulkUserValidation = (isSelf = false) => {
45
54
  if (!isSelf) {
46
55
  schema = {
@@ -127,6 +127,20 @@ export class UserAPI extends TestAPI {
127
127
  .expect(status ? status : 200)
128
128
  }
129
129
 
130
+ addSsoSupportInternalAPIAuth = (ssoId: string, email: string) => {
131
+ return this.request
132
+ .post(`/api/global/users/sso`)
133
+ .send({ ssoId, email })
134
+ .set(this.config.internalAPIHeaders())
135
+ }
136
+
137
+ addSsoSupportDefaultAuth = (ssoId: string, email: string) => {
138
+ return this.request
139
+ .post(`/api/global/users/sso`)
140
+ .send({ ssoId, email })
141
+ .set(this.config.defaultHeaders())
142
+ }
143
+
130
144
  deleteUser = (userId: string, status?: number) => {
131
145
  return this.request
132
146
  .delete(`/api/global/users/${userId}`)
@@ -11,3 +11,5 @@ process.env.INTERNAL_API_KEY = "tet"
11
11
  process.env.DISABLE_ACCOUNT_PORTAL = "0"
12
12
  process.env.MOCK_REDIS = "1"
13
13
  process.env.BUDIBASE_VERSION = "0.0.0+jest"
14
+ process.env.COUCH_DB_PASSWORD = "budibase"
15
+ process.env.COUCH_DB_USER = "budibase"
@@ -1,142 +0,0 @@
1
- {
2
- // Use IntelliSense to learn about possible attributes.
3
- // Hover to view descriptions of existing attributes.
4
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
- "version": "0.2.0",
6
- "configurations": [
7
- {
8
- "name": "Start Server",
9
- "type": "node",
10
- "request": "launch",
11
- "runtimeExecutable": "node",
12
- "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
13
- "args": ["src/index.ts"],
14
- "cwd": "${workspaceRoot}",
15
- },
16
- {
17
- "type": "node",
18
- "request": "launch",
19
- "name": "Jest - All",
20
- "program": "${workspaceFolder}/node_modules/.bin/jest",
21
- "args": [],
22
- "console": "integratedTerminal",
23
- "internalConsoleOptions": "neverOpen",
24
- "disableOptimisticBPs": true,
25
- "windows": {
26
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
27
- }
28
- },
29
- {
30
- "type": "node",
31
- "request": "launch",
32
- "name": "Jest - Users",
33
- "program": "${workspaceFolder}/node_modules/.bin/jest",
34
- "args": ["user.spec", "--runInBand"],
35
- "console": "integratedTerminal",
36
- "internalConsoleOptions": "neverOpen",
37
- "disableOptimisticBPs": true,
38
- "windows": {
39
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
40
- }
41
- },
42
- {
43
- "type": "node",
44
- "request": "launch",
45
- "name": "Jest - Instances",
46
- "program": "${workspaceFolder}/node_modules/.bin/jest",
47
- "args": ["instance.spec", "--runInBand"],
48
- "console": "integratedTerminal",
49
- "internalConsoleOptions": "neverOpen",
50
- "disableOptimisticBPs": true,
51
- "windows": {
52
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
53
- }
54
- },
55
- {
56
- "type": "node",
57
- "request": "launch",
58
- "name": "Jest - Roles",
59
- "program": "${workspaceFolder}/node_modules/.bin/jest",
60
- "args": ["role.spec", "--runInBand"],
61
- "console": "integratedTerminal",
62
- "internalConsoleOptions": "neverOpen",
63
- "disableOptimisticBPs": true,
64
- "windows": {
65
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
66
- }
67
- },
68
- {
69
- "type": "node",
70
- "request": "launch",
71
- "name": "Jest - Records",
72
- "program": "${workspaceFolder}/node_modules/.bin/jest",
73
- "args": ["record.spec", "--runInBand"],
74
- "console": "integratedTerminal",
75
- "internalConsoleOptions": "neverOpen",
76
- "disableOptimisticBPs": true,
77
- "windows": {
78
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
79
- }
80
- },
81
- {
82
- "type": "node",
83
- "request": "launch",
84
- "name": "Jest - Models",
85
- "program": "${workspaceFolder}/node_modules/.bin/jest",
86
- "args": ["table.spec", "--runInBand"],
87
- "console": "integratedTerminal",
88
- "internalConsoleOptions": "neverOpen",
89
- "disableOptimisticBPs": true,
90
- "windows": {
91
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
92
- }
93
- },
94
- {
95
- "type": "node",
96
- "request": "launch",
97
- "name": "Jest - Views",
98
- "program": "${workspaceFolder}/node_modules/.bin/jest",
99
- "args": ["view.spec", "--runInBand"],
100
- "console": "integratedTerminal",
101
- "internalConsoleOptions": "neverOpen",
102
- "disableOptimisticBPs": true,
103
- "windows": {
104
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
105
- }
106
- },
107
- {
108
- "type": "node",
109
- "request": "launch",
110
- "name": "Jest - Applications",
111
- "program": "${workspaceFolder}/node_modules/.bin/jest",
112
- "args": ["application.spec", "--runInBand"],
113
- "console": "integratedTerminal",
114
- "internalConsoleOptions": "neverOpen",
115
- "disableOptimisticBPs": true,
116
- "windows": {
117
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
118
- }
119
- },
120
- {
121
- "type": "node",
122
- "request": "launch",
123
- "name": "Jest Builder",
124
- "program": "${workspaceFolder}/node_modules/.bin/jest",
125
- "args": ["builder", "--runInBand"],
126
- "console": "integratedTerminal",
127
- "internalConsoleOptions": "neverOpen",
128
- "disableOptimisticBPs": true,
129
- "windows": {
130
- "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest",
131
- }
132
- },
133
- {
134
- "type": "node",
135
- "request": "launch",
136
- "name": "Initialise Budibase",
137
- "program": "yarn",
138
- "args": ["run", "initialise"],
139
- "console": "externalTerminal"
140
- }
141
- ]
142
- }
@@ -1,15 +0,0 @@
1
- const mockS3 = {
2
- headBucket: jest.fn().mockReturnThis(),
3
- deleteObject: jest.fn().mockReturnThis(),
4
- deleteObjects: jest.fn().mockReturnThis(),
5
- createBucket: jest.fn().mockReturnThis(),
6
- listObjects: jest.fn().mockReturnThis(),
7
- promise: jest.fn().mockReturnThis(),
8
- catch: jest.fn(),
9
- }
10
-
11
- const AWS = {
12
- S3: jest.fn(() => mockS3),
13
- }
14
-
15
- export default AWS
@@ -1,23 +0,0 @@
1
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2
- module FetchMock {
3
- const fetch = jest.requireActual("node-fetch")
4
-
5
- const func = async (url: any, opts: any) => {
6
- if (url.includes("http://someconfigurl")) {
7
- return {
8
- ok: true,
9
- json: () => ({
10
- issuer: "test",
11
- authorization_endpoint: "http://localhost/auth",
12
- token_endpoint: "http://localhost/token",
13
- userinfo_endpoint: "http://localhost/userinfo",
14
- }),
15
- }
16
- }
17
- return fetch(url, opts)
18
- }
19
-
20
- func.Headers = fetch.Headers
21
-
22
- module.exports = func
23
- }
@@ -1,57 +0,0 @@
1
- import * as jwt from "jsonwebtoken"
2
-
3
- const mockOAuth2 = {
4
- getOAuthAccessToken: (code: string, p: any, cb: any) => {
5
- const err = null
6
- const accessToken = "access_token"
7
- const refreshToken = "refresh_token"
8
-
9
- const exp = new Date()
10
- exp.setDate(exp.getDate() + 1)
11
-
12
- const iat = new Date()
13
- iat.setDate(iat.getDate() - 1)
14
-
15
- const claims = {
16
- iss: "test",
17
- sub: "sub",
18
- aud: "clientId",
19
- exp: exp.getTime() / 1000,
20
- iat: iat.getTime() / 1000,
21
- email: "oauth@example.com",
22
- }
23
-
24
- const idToken = jwt.sign(claims, "secret")
25
-
26
- const params = {
27
- id_token: idToken,
28
- }
29
- return cb(err, accessToken, refreshToken, params)
30
- },
31
- _request: (
32
- method: string,
33
- url: string,
34
- headers: any,
35
- postBody: any,
36
- accessToken: string,
37
- cb: any
38
- ) => {
39
- const err = null
40
- const body = {
41
- sub: "sub",
42
- user_id: "userId",
43
- name: "OAuth",
44
- family_name: "2",
45
- given_name: "OAuth",
46
- middle_name: "",
47
- }
48
- const res = {}
49
- return cb(err, JSON.stringify(body), res)
50
- },
51
- }
52
-
53
- const oauth = {
54
- OAuth2: jest.fn(() => mockOAuth2),
55
- }
56
-
57
- export = oauth
@@ -1,8 +0,0 @@
1
- const { join } = require("path")
2
- require("dotenv").config({
3
- path: join(__dirname, "..", "..", "hosting", ".env"),
4
- })
5
-
6
- const jestTestcontainersConfigGenerator = require("../../jestTestcontainersConfigGenerator")
7
-
8
- module.exports = jestTestcontainersConfigGenerator()