@budibase/backend-core 2.10.10 → 2.10.12-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.
@@ -1 +1 @@
1
- {"version":3,"file":"users.js","sourceRoot":"","sources":["../../../../../tests/core/utilities/structures/users.ts"],"names":[],"mappings":";;;AAOA,qCAA+B;AAC/B,+BAAmC;AAEnC,mCAAyC;AAAhC,8FAAA,IAAI,OAAA;AAAE,kGAAA,QAAQ,OAAA;AAEhB,MAAM,SAAS,GAAG,CAAC,SAAe,EAAa,EAAE;IACtD,uCACK,IAAA,aAAI,EAAC,SAAS,CAAC,KAClB,KAAK,EAAE;YACL,MAAM,EAAE,IAAI;SACb,EACD,OAAO,EAAE;YACP,MAAM,EAAE,IAAI;SACb,IACF;AACH,CAAC,CAAA;AAVY,QAAA,SAAS,aAUrB;AAEM,MAAM,aAAa,GAAG,CAAC,SAAe,EAAiB,EAAE;IAC9D,uCACK,IAAA,aAAI,EAAC,SAAS,CAAC,KAClB,KAAK,EAAE;YACL,MAAM,EAAE,IAAI;SACb,IACF;AACH,CAAC,CAAA;AAPY,QAAA,aAAa,iBAOzB;AAEM,MAAM,WAAW,GAAG,CAAC,SAAe,EAAe,EAAE;IAC1D,uCACK,IAAA,aAAI,EAAC,SAAS,CAAC,KAClB,OAAO,EAAE;YACP,MAAM,EAAE,IAAI;SACb,IACF;AACH,CAAC,CAAA;AAPY,QAAA,WAAW,eAOvB;AAEM,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,SAAe,EAAe,EAAE;IAC5E,uCACK,IAAA,aAAI,EAAC,SAAS,CAAC,KAClB,OAAO,EAAE;YACP,IAAI,EAAE,CAAC,KAAK,CAAC;SACd,IACF;AACH,CAAC,CAAA;AAPY,QAAA,cAAc,kBAO1B;AAED,SAAgB,OAAO,CACrB,OAAiD,EAAE;;IAEnD,MAAM,IAAI,GAAG,IAAA,aAAI,EAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5B,OAAO,IAAI,CAAC,QAAQ,CAAA;IAEpB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;QACjB,IAAI,CAAC,OAAO,GAAG,IAAA,iBAAW,EAAC,IAAI,CAAC,CAAA;KACjC;IAED,uCACK,IAAI,KACP,kBAAkB,EAAE,KAAK,EACzB,MAAM,EAAE,MAAA,IAAI,CAAC,OAAO,0CAAE,MAAM,EAC5B,QAAQ,EAAE,MAAA,IAAI,CAAC,OAAO,0CAAE,QAAS,EACjC,YAAY,EAAE,MAAA,IAAI,CAAC,OAAO,0CAAE,YAAa,EACzC,iBAAiB,EAAE;YACjB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,UAAU;SACzB,IACF;AACH,CAAC;AArBD,0BAqBC"}
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../../../../tests/core/utilities/structures/users.ts"],"names":[],"mappings":";;;AAQA,+BAAmC;AACnC,qCAA+B;AAC/B,2CAAuC;AACvC,wBAA0B;AAC1B,mDAA6D;AAEtD,MAAM,QAAQ,GAAG,GAAG,EAAE;IAC3B,OAAO,GAAG,IAAA,aAAI,GAAE,WAAW,CAAA;AAC7B,CAAC,CAAA;AAFY,QAAA,QAAQ,YAEpB;AAEM,MAAM,IAAI,GAAG,CAAC,SAAyC,EAAQ,EAAE;IACtE,MAAM,MAAM,GAAG,CAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,GAAG,KAAI,IAAA,6BAAoB,GAAE,CAAA;IACvD,uBACE,GAAG,EAAE,MAAM,EACX,MAAM,EACN,KAAK,EAAE,IAAA,gBAAQ,GAAE,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAC5B,SAAS,EAAE,qBAAS,CAAC,KAAK,EAAE,EAC5B,QAAQ,EAAE,qBAAS,CAAC,IAAI,EAAE,EAC1B,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,SAAM,CAAC,EAAE,EAAE,IAClB,SAAS,EACb;AACH,CAAC,CAAA;AAdY,QAAA,IAAI,QAchB;AAEM,MAAM,SAAS,GAAG,CAAC,SAAe,EAAa,EAAE;IACtD,uCACK,IAAA,YAAI,EAAC,SAAS,CAAC,KAClB,KAAK,EAAE;YACL,MAAM,EAAE,IAAI;SACb,EACD,OAAO,EAAE;YACP,MAAM,EAAE,IAAI;SACb,IACF;AACH,CAAC,CAAA;AAVY,QAAA,SAAS,aAUrB;AAEM,MAAM,aAAa,GAAG,CAAC,SAAe,EAAiB,EAAE;IAC9D,uCACK,IAAA,YAAI,EAAC,SAAS,CAAC,KAClB,KAAK,EAAE;YACL,MAAM,EAAE,IAAI;SACb,IACF;AACH,CAAC,CAAA;AAPY,QAAA,aAAa,iBAOzB;AAEM,MAAM,WAAW,GAAG,CAAC,SAAe,EAAe,EAAE;IAC1D,uCACK,IAAA,YAAI,EAAC,SAAS,CAAC,KAClB,OAAO,EAAE;YACP,MAAM,EAAE,IAAI;SACb,IACF;AACH,CAAC,CAAA;AAPY,QAAA,WAAW,eAOvB;AAEM,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,SAAe,EAAe,EAAE;IAC5E,uCACK,IAAA,YAAI,EAAC,SAAS,CAAC,KAClB,OAAO,EAAE;YACP,IAAI,EAAE,CAAC,KAAK,CAAC;SACd,IACF;AACH,CAAC,CAAA;AAPY,QAAA,cAAc,kBAO1B;AAED,SAAgB,OAAO,CACrB,OAAiD,EAAE;;IAEnD,MAAM,IAAI,GAAG,IAAA,YAAI,EAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5B,OAAO,IAAI,CAAC,QAAQ,CAAA;IAEpB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;QACjB,IAAI,CAAC,OAAO,GAAG,IAAA,iBAAW,EAAC,IAAI,CAAC,CAAA;KACjC;IAED,uCACK,IAAI,KACP,kBAAkB,EAAE,KAAK,EACzB,MAAM,EAAE,MAAA,IAAI,CAAC,OAAO,0CAAE,MAAM,EAC5B,QAAQ,EAAE,MAAA,IAAI,CAAC,OAAO,0CAAE,QAAS,EACjC,YAAY,EAAE,MAAA,IAAI,CAAC,OAAO,0CAAE,YAAa,EACzC,iBAAiB,EAAE;YACjB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,UAAU;SACzB,IACF;AACH,CAAC;AArBD,0BAqBC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/backend-core",
3
- "version": "2.10.10",
3
+ "version": "2.10.12-alpha.0",
4
4
  "description": "Budibase backend core libraries used in server and worker",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -23,8 +23,8 @@
23
23
  "dependencies": {
24
24
  "@budibase/nano": "10.1.2",
25
25
  "@budibase/pouchdb-replication-stream": "1.2.10",
26
- "@budibase/shared-core": "2.10.10",
27
- "@budibase/types": "2.10.10",
26
+ "@budibase/shared-core": "2.10.12-alpha.0",
27
+ "@budibase/types": "2.10.12-alpha.0",
28
28
  "@techpass/passport-openidconnect": "0.3.2",
29
29
  "aws-cloudfront-sign": "2.2.0",
30
30
  "aws-sdk": "2.1030.0",
@@ -104,5 +104,5 @@
104
104
  }
105
105
  }
106
106
  },
107
- "gitHead": "4e2d324324bda5cac4a80d7444755c0617258382"
107
+ "gitHead": "dc46ef326f5f6d9eecee14c8b1ffdc73b4bdfa54"
108
108
  }
@@ -0,0 +1,145 @@
1
+ import { User } from "@budibase/types"
2
+ import { generator, structures } from "../../../tests"
3
+ import { DBTestConfiguration } from "../../../tests/extra"
4
+ import { getUsers } from "../user"
5
+ import { getGlobalDB } from "../../context"
6
+ import _ from "lodash"
7
+
8
+ import * as redis from "../../redis/init"
9
+ import { UserDB } from "../../users"
10
+
11
+ const config = new DBTestConfiguration()
12
+
13
+ describe("user cache", () => {
14
+ describe("getUsers", () => {
15
+ const users: User[] = []
16
+ beforeAll(async () => {
17
+ const userCount = 10
18
+ const userIds = generator.arrayOf(() => generator.guid(), {
19
+ min: userCount,
20
+ max: userCount,
21
+ })
22
+
23
+ await config.doInTenant(async () => {
24
+ const db = getGlobalDB()
25
+ for (const userId of userIds) {
26
+ const user = structures.users.user({ _id: userId })
27
+ await db.put(user)
28
+ users.push(user)
29
+ }
30
+ })
31
+ })
32
+
33
+ beforeEach(async () => {
34
+ jest.clearAllMocks()
35
+
36
+ const redisClient = await redis.getUserClient()
37
+ await redisClient.clear()
38
+ })
39
+
40
+ it("when no user is in cache, all of them are retrieved from db", async () => {
41
+ const usersToRequest = _.sampleSize(users, 5)
42
+
43
+ const userIdsToRequest = usersToRequest.map(x => x._id!)
44
+
45
+ jest.spyOn(UserDB, "bulkGet")
46
+
47
+ const results = await config.doInTenant(() => getUsers(userIdsToRequest))
48
+
49
+ expect(results.users).toHaveLength(5)
50
+ expect(results).toEqual({
51
+ users: usersToRequest.map(u => ({
52
+ ...u,
53
+ budibaseAccess: true,
54
+ _rev: expect.any(String),
55
+ })),
56
+ })
57
+
58
+ expect(UserDB.bulkGet).toBeCalledTimes(1)
59
+ expect(UserDB.bulkGet).toBeCalledWith(userIdsToRequest)
60
+ })
61
+
62
+ it("on a second all, all of them are retrieved from cache", async () => {
63
+ const usersToRequest = _.sampleSize(users, 5)
64
+
65
+ const userIdsToRequest = usersToRequest.map(x => x._id!)
66
+
67
+ jest.spyOn(UserDB, "bulkGet")
68
+
69
+ await config.doInTenant(() => getUsers(userIdsToRequest))
70
+ const resultsFromCache = await config.doInTenant(() =>
71
+ getUsers(userIdsToRequest)
72
+ )
73
+
74
+ expect(resultsFromCache.users).toHaveLength(5)
75
+ expect(resultsFromCache).toEqual({
76
+ users: expect.arrayContaining(
77
+ usersToRequest.map(u => ({
78
+ ...u,
79
+ budibaseAccess: true,
80
+ _rev: expect.any(String),
81
+ }))
82
+ ),
83
+ })
84
+
85
+ expect(UserDB.bulkGet).toBeCalledTimes(1)
86
+ })
87
+
88
+ it("when some users are cached, only the missing ones are retrieved from db", async () => {
89
+ const usersToRequest = _.sampleSize(users, 5)
90
+
91
+ const userIdsToRequest = usersToRequest.map(x => x._id!)
92
+
93
+ jest.spyOn(UserDB, "bulkGet")
94
+
95
+ await config.doInTenant(() =>
96
+ getUsers([userIdsToRequest[0], userIdsToRequest[3]])
97
+ )
98
+ ;(UserDB.bulkGet as jest.Mock).mockClear()
99
+
100
+ const results = await config.doInTenant(() => getUsers(userIdsToRequest))
101
+
102
+ expect(results.users).toHaveLength(5)
103
+ expect(results).toEqual({
104
+ users: expect.arrayContaining(
105
+ usersToRequest.map(u => ({
106
+ ...u,
107
+ budibaseAccess: true,
108
+ _rev: expect.any(String),
109
+ }))
110
+ ),
111
+ })
112
+
113
+ expect(UserDB.bulkGet).toBeCalledTimes(1)
114
+ expect(UserDB.bulkGet).toBeCalledWith([
115
+ userIdsToRequest[1],
116
+ userIdsToRequest[2],
117
+ userIdsToRequest[4],
118
+ ])
119
+ })
120
+
121
+ it("requesting existing and unexisting ids will return found ones", async () => {
122
+ const usersToRequest = _.sampleSize(users, 3)
123
+ const missingIds = [generator.guid(), generator.guid()]
124
+
125
+ const userIdsToRequest = _.shuffle([
126
+ ...missingIds,
127
+ ...usersToRequest.map(x => x._id!),
128
+ ])
129
+
130
+ const results = await config.doInTenant(() => getUsers(userIdsToRequest))
131
+
132
+ expect(results.users).toHaveLength(3)
133
+ expect(results).toEqual({
134
+ users: expect.arrayContaining(
135
+ usersToRequest.map(u => ({
136
+ ...u,
137
+ budibaseAccess: true,
138
+ _rev: expect.any(String),
139
+ }))
140
+ ),
141
+ notFoundIds: expect.arrayContaining(missingIds),
142
+ })
143
+ })
144
+ })
145
+ })
package/src/cache/user.ts CHANGED
@@ -6,6 +6,7 @@ import env from "../environment"
6
6
  import * as accounts from "../accounts"
7
7
  import { UserDB } from "../users"
8
8
  import { sdk } from "@budibase/shared-core"
9
+ import { User } from "@budibase/types"
9
10
 
10
11
  const EXPIRY_SECONDS = 3600
11
12
 
@@ -27,6 +28,35 @@ async function populateFromDB(userId: string, tenantId: string) {
27
28
  return user
28
29
  }
29
30
 
31
+ async function populateUsersFromDB(
32
+ userIds: string[]
33
+ ): Promise<{ users: User[]; notFoundIds?: string[] }> {
34
+ const getUsersResponse = await UserDB.bulkGet(userIds)
35
+
36
+ // Handle missed user ids
37
+ const notFoundIds = userIds.filter((uid, i) => !getUsersResponse[i])
38
+
39
+ const users = getUsersResponse.filter(x => x)
40
+
41
+ await Promise.all(
42
+ users.map(async (user: any) => {
43
+ user.budibaseAccess = true
44
+ if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
45
+ const account = await accounts.getAccount(user.email)
46
+ if (account) {
47
+ user.account = account
48
+ user.accountPortalAccess = true
49
+ }
50
+ }
51
+ })
52
+ )
53
+
54
+ if (notFoundIds.length) {
55
+ return { users, notFoundIds }
56
+ }
57
+ return { users }
58
+ }
59
+
30
60
  /**
31
61
  * Get the requested user by id.
32
62
  * Use redis cache to first read the user.
@@ -77,6 +107,36 @@ export async function getUser(
77
107
  return user
78
108
  }
79
109
 
110
+ /**
111
+ * Get the requested users by id.
112
+ * Use redis cache to first read the users.
113
+ * If not present fallback to loading the users directly and re-caching.
114
+ * @param {*} userIds the ids of the user to get
115
+ * @param {*} tenantId the tenant of the users to get
116
+ * @returns
117
+ */
118
+ export async function getUsers(
119
+ userIds: string[]
120
+ ): Promise<{ users: User[]; notFoundIds?: string[] }> {
121
+ const client = await redis.getUserClient()
122
+ // try cache
123
+ let usersFromCache = await client.bulkGet(userIds)
124
+ const missingUsersFromCache = userIds.filter(uid => !usersFromCache[uid])
125
+ const users = Object.values(usersFromCache)
126
+ let notFoundIds
127
+
128
+ if (missingUsersFromCache.length) {
129
+ const usersFromDb = await populateUsersFromDB(missingUsersFromCache)
130
+
131
+ notFoundIds = usersFromDb.notFoundIds
132
+ for (const userToCache of usersFromDb.users) {
133
+ await client.store(userToCache._id!, userToCache, EXPIRY_SECONDS)
134
+ }
135
+ users.push(...usersFromDb.users)
136
+ }
137
+ return { users, notFoundIds: notFoundIds }
138
+ }
139
+
80
140
  export async function invalidateUser(userId: string) {
81
141
  const client = await redis.getUserClient()
82
142
  await client.delete(userId)
@@ -102,6 +102,7 @@ describe("sso", () => {
102
102
 
103
103
  // modified external id to match user format
104
104
  ssoUser._id = "us_" + details.userId
105
+ delete ssoUser.userId
105
106
 
106
107
  // new sso user won't have a password
107
108
  delete ssoUser.password
@@ -250,7 +250,7 @@ class RedisWrapper {
250
250
  const prefixedKeys = keys.map(key => addDbPrefix(db, key))
251
251
  let response = await this.getClient().mget(prefixedKeys)
252
252
  if (Array.isArray(response)) {
253
- let final: any = {}
253
+ let final: Record<string, any> = {}
254
254
  let count = 0
255
255
  for (let result of response) {
256
256
  if (result) {
@@ -13,8 +13,7 @@ import {
13
13
  } from "@budibase/types"
14
14
  import { generator } from "./generator"
15
15
  import { email, uuid } from "./common"
16
- import * as shared from "./shared"
17
- import { user } from "./shared"
16
+ import * as users from "./users"
18
17
  import sample from "lodash/sample"
19
18
 
20
19
  export function OAuth(): OAuth2 {
@@ -26,7 +25,7 @@ export function OAuth(): OAuth2 {
26
25
 
27
26
  export function authDetails(userDoc?: User): SSOAuthDetails {
28
27
  if (!userDoc) {
29
- userDoc = user()
28
+ userDoc = users.user()
30
29
  }
31
30
 
32
31
  const userId = userDoc._id || uuid()
@@ -52,7 +51,7 @@ export function providerType(): SSOProviderType {
52
51
 
53
52
  export function ssoProfile(user?: User): SSOProfile {
54
53
  if (!user) {
55
- user = shared.user()
54
+ user = users.user()
56
55
  }
57
56
  return {
58
57
  id: user._id!,
@@ -4,11 +4,33 @@ import {
4
4
  BuilderUser,
5
5
  SSOAuthDetails,
6
6
  SSOUser,
7
+ User,
7
8
  } from "@budibase/types"
8
- import { user } from "./shared"
9
9
  import { authDetails } from "./sso"
10
+ import { uuid } from "./common"
11
+ import { generator } from "./generator"
12
+ import { tenant } from "."
13
+ import { generateGlobalUserID } from "../../../../src/docIds"
10
14
 
11
- export { user, newEmail } from "./shared"
15
+ export const newEmail = () => {
16
+ return `${uuid()}@test.com`
17
+ }
18
+
19
+ export const user = (userProps?: Partial<Omit<User, "userId">>): User => {
20
+ const userId = userProps?._id || generateGlobalUserID()
21
+ return {
22
+ _id: userId,
23
+ userId,
24
+ email: newEmail(),
25
+ password: "test",
26
+ roles: { app_test: "admin" },
27
+ firstName: generator.first(),
28
+ lastName: generator.last(),
29
+ pictureUrl: "http://test.com",
30
+ tenantId: tenant.id(),
31
+ ...userProps,
32
+ }
33
+ }
12
34
 
13
35
  export const adminUser = (userProps?: any): AdminUser => {
14
36
  return {
@@ -1,3 +0,0 @@
1
- import { User } from "@budibase/types";
2
- export declare const newEmail: () => string;
3
- export declare const user: (userProps?: any) => User;
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.user = exports.newEmail = void 0;
4
- const generator_1 = require("./generator");
5
- const common_1 = require("./common");
6
- const newEmail = () => {
7
- return `${(0, common_1.uuid)()}@test.com`;
8
- };
9
- exports.newEmail = newEmail;
10
- const user = (userProps) => {
11
- return Object.assign({ email: (0, exports.newEmail)(), password: "test", roles: { app_test: "admin" }, firstName: generator_1.generator.first(), lastName: generator_1.generator.last(), pictureUrl: "http://test.com" }, userProps);
12
- };
13
- exports.user = user;
14
- //# sourceMappingURL=shared.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../../../tests/core/utilities/structures/shared.ts"],"names":[],"mappings":";;;AACA,2CAAuC;AACvC,qCAA+B;AAExB,MAAM,QAAQ,GAAG,GAAG,EAAE;IAC3B,OAAO,GAAG,IAAA,aAAI,GAAE,WAAW,CAAA;AAC7B,CAAC,CAAA;AAFY,QAAA,QAAQ,YAEpB;AAEM,MAAM,IAAI,GAAG,CAAC,SAAe,EAAQ,EAAE;IAC5C,uBACE,KAAK,EAAE,IAAA,gBAAQ,GAAE,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAC5B,SAAS,EAAE,qBAAS,CAAC,KAAK,EAAE,EAC5B,QAAQ,EAAE,qBAAS,CAAC,IAAI,EAAE,EAC1B,UAAU,EAAE,iBAAiB,IAC1B,SAAS,EACb;AACH,CAAC,CAAA;AAVY,QAAA,IAAI,QAUhB"}
@@ -1,19 +0,0 @@
1
- import { User } from "@budibase/types"
2
- import { generator } from "./generator"
3
- import { uuid } from "./common"
4
-
5
- export const newEmail = () => {
6
- return `${uuid()}@test.com`
7
- }
8
-
9
- export const user = (userProps?: any): User => {
10
- return {
11
- email: newEmail(),
12
- password: "test",
13
- roles: { app_test: "admin" },
14
- firstName: generator.first(),
15
- lastName: generator.last(),
16
- pictureUrl: "http://test.com",
17
- ...userProps,
18
- }
19
- }