@budibase/worker 1.2.58-alpha.2 → 1.2.58-alpha.5
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "1.2.58-alpha.
|
|
4
|
+
"version": "1.2.58-alpha.5",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"author": "Budibase",
|
|
36
36
|
"license": "GPL-3.0",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@budibase/backend-core": "1.2.58-alpha.
|
|
39
|
-
"@budibase/pro": "1.2.58-alpha.
|
|
40
|
-
"@budibase/string-templates": "1.2.58-alpha.
|
|
41
|
-
"@budibase/types": "1.2.58-alpha.
|
|
38
|
+
"@budibase/backend-core": "1.2.58-alpha.5",
|
|
39
|
+
"@budibase/pro": "1.2.58-alpha.4",
|
|
40
|
+
"@budibase/string-templates": "1.2.58-alpha.5",
|
|
41
|
+
"@budibase/types": "1.2.58-alpha.5",
|
|
42
42
|
"@koa/router": "8.0.8",
|
|
43
43
|
"@sentry/node": "6.17.7",
|
|
44
44
|
"@techpass/passport-openidconnect": "0.3.2",
|
|
@@ -103,5 +103,5 @@
|
|
|
103
103
|
"./scripts/jestSetup.js"
|
|
104
104
|
]
|
|
105
105
|
},
|
|
106
|
-
"gitHead": "
|
|
106
|
+
"gitHead": "6de0352299284a56f8d1bdd351cd2447a5b4c286"
|
|
107
107
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { EmailTemplatePurpose } from "../../../constants"
|
|
2
1
|
import { checkInviteCode } from "../../../utilities/redis"
|
|
3
|
-
import { sendEmail } from "../../../utilities/email"
|
|
4
2
|
import { users } from "../../../sdk"
|
|
5
3
|
import env from "../../../environment"
|
|
6
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
BulkDeleteUsersRequest,
|
|
6
|
+
CloudAccount,
|
|
7
|
+
InviteUserRequest,
|
|
8
|
+
InviteUsersRequest,
|
|
9
|
+
User,
|
|
10
|
+
} from "@budibase/types"
|
|
7
11
|
import {
|
|
8
12
|
accounts,
|
|
9
13
|
cache,
|
|
@@ -191,58 +195,27 @@ export const tenantUserLookup = async (ctx: any) => {
|
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
export const invite = async (ctx: any) => {
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
info: userInfo,
|
|
198
|
+
const request = ctx.request.body as InviteUserRequest
|
|
199
|
+
const response = await users.invite([request])
|
|
200
|
+
|
|
201
|
+
// explicitly throw for single user invite
|
|
202
|
+
if (response.unsuccessful.length) {
|
|
203
|
+
const reason = response.unsuccessful[0].reason
|
|
204
|
+
if (reason === "Unavailable") {
|
|
205
|
+
ctx.throw(400, reason)
|
|
206
|
+
} else {
|
|
207
|
+
ctx.throw(500, reason)
|
|
208
|
+
}
|
|
206
209
|
}
|
|
207
|
-
|
|
210
|
+
|
|
208
211
|
ctx.body = {
|
|
209
212
|
message: "Invitation has been sent.",
|
|
210
213
|
}
|
|
211
|
-
await events.user.invited()
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
export const inviteMultiple = async (ctx: any) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
let existingEmail
|
|
218
|
-
for (let user of users) {
|
|
219
|
-
if (await usersCore.getGlobalUserByEmail(user.email)) {
|
|
220
|
-
existing = true
|
|
221
|
-
existingEmail = user.email
|
|
222
|
-
break
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (existing) {
|
|
227
|
-
ctx.throw(400, `${existingEmail} already exists`)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
for (let i = 0; i < users.length; i++) {
|
|
231
|
-
let userInfo = users[i].userInfo
|
|
232
|
-
if (!userInfo) {
|
|
233
|
-
userInfo = {}
|
|
234
|
-
}
|
|
235
|
-
userInfo.tenantId = tenancy.getTenantId()
|
|
236
|
-
const opts: any = {
|
|
237
|
-
subject: "{{ company }} platform invitation",
|
|
238
|
-
info: userInfo,
|
|
239
|
-
}
|
|
240
|
-
await sendEmail(users[i].email, EmailTemplatePurpose.INVITATION, opts)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
ctx.body = {
|
|
244
|
-
message: "Invitations have been sent.",
|
|
245
|
-
}
|
|
217
|
+
const request = ctx.request.body as InviteUsersRequest
|
|
218
|
+
ctx.body = await users.invite(request)
|
|
246
219
|
}
|
|
247
220
|
|
|
248
221
|
export const inviteAccept = async (ctx: any) => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { InviteUsersResponse } from "@budibase/types"
|
|
2
|
+
|
|
1
3
|
jest.mock("nodemailer")
|
|
2
4
|
import {
|
|
3
5
|
TestConfiguration,
|
|
@@ -27,7 +29,8 @@ describe("/api/global/users", () => {
|
|
|
27
29
|
|
|
28
30
|
describe("invite", () => {
|
|
29
31
|
it("should be able to generate an invitation", async () => {
|
|
30
|
-
const
|
|
32
|
+
const email = structures.users.newEmail()
|
|
33
|
+
const { code, res } = await api.users.sendUserInvite(sendMailMock, email)
|
|
31
34
|
|
|
32
35
|
expect(res.body).toEqual({ message: "Invitation has been sent." })
|
|
33
36
|
expect(sendMailMock).toHaveBeenCalled()
|
|
@@ -35,13 +38,27 @@ describe("/api/global/users", () => {
|
|
|
35
38
|
expect(events.user.invited).toBeCalledTimes(1)
|
|
36
39
|
})
|
|
37
40
|
|
|
41
|
+
it("should not be able to generate an invitation for existing user", async () => {
|
|
42
|
+
const { code, res } = await api.users.sendUserInvite(
|
|
43
|
+
sendMailMock,
|
|
44
|
+
config.defaultUser!.email,
|
|
45
|
+
400
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
expect(res.body.message).toBe("Unavailable")
|
|
49
|
+
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
50
|
+
expect(code).toBeUndefined()
|
|
51
|
+
expect(events.user.invited).toBeCalledTimes(0)
|
|
52
|
+
})
|
|
53
|
+
|
|
38
54
|
it("should be able to create new user from invite", async () => {
|
|
39
|
-
const
|
|
55
|
+
const email = structures.users.newEmail()
|
|
56
|
+
const { code } = await api.users.sendUserInvite(sendMailMock, email)
|
|
40
57
|
|
|
41
58
|
const res = await api.users.acceptInvite(code)
|
|
42
59
|
|
|
43
60
|
expect(res.body._id).toBeDefined()
|
|
44
|
-
const user = await config.getUser(
|
|
61
|
+
const user = await config.getUser(email)
|
|
45
62
|
expect(user).toBeDefined()
|
|
46
63
|
expect(user._id).toEqual(res.body._id)
|
|
47
64
|
expect(events.user.inviteAccepted).toBeCalledTimes(1)
|
|
@@ -49,6 +66,37 @@ describe("/api/global/users", () => {
|
|
|
49
66
|
})
|
|
50
67
|
})
|
|
51
68
|
|
|
69
|
+
describe("inviteMultiple", () => {
|
|
70
|
+
it("should be able to generate an invitation", async () => {
|
|
71
|
+
const newUserInvite = () => ({
|
|
72
|
+
email: structures.users.newEmail(),
|
|
73
|
+
userInfo: {},
|
|
74
|
+
})
|
|
75
|
+
const request = [newUserInvite(), newUserInvite()]
|
|
76
|
+
|
|
77
|
+
const res = await api.users.sendMultiUserInvite(request)
|
|
78
|
+
|
|
79
|
+
const body = res.body as InviteUsersResponse
|
|
80
|
+
expect(body.successful.length).toBe(2)
|
|
81
|
+
expect(body.unsuccessful.length).toBe(0)
|
|
82
|
+
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
|
83
|
+
expect(events.user.invited).toBeCalledTimes(2)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("should not be able to generate an invitation for existing user", async () => {
|
|
87
|
+
const request = [{ email: config.defaultUser!.email, userInfo: {} }]
|
|
88
|
+
|
|
89
|
+
const res = await api.users.sendMultiUserInvite(request)
|
|
90
|
+
|
|
91
|
+
const body = res.body as InviteUsersResponse
|
|
92
|
+
expect(body.successful.length).toBe(0)
|
|
93
|
+
expect(body.unsuccessful.length).toBe(1)
|
|
94
|
+
expect(body.unsuccessful[0].reason).toBe("Unavailable")
|
|
95
|
+
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
96
|
+
expect(events.user.invited).toBeCalledTimes(0)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
52
100
|
describe("bulkCreate", () => {
|
|
53
101
|
it("should ignore users existing in the same tenant", async () => {
|
|
54
102
|
const user = await config.createUser()
|
package/src/sdk/users/users.ts
CHANGED
|
@@ -16,12 +16,12 @@ import {
|
|
|
16
16
|
migrations,
|
|
17
17
|
StaticDatabases,
|
|
18
18
|
ViewName,
|
|
19
|
+
events,
|
|
19
20
|
} from "@budibase/backend-core"
|
|
20
21
|
import {
|
|
21
22
|
MigrationType,
|
|
22
23
|
PlatformUserByEmail,
|
|
23
24
|
User,
|
|
24
|
-
Account,
|
|
25
25
|
BulkCreateUsersResponse,
|
|
26
26
|
CreateUserResponse,
|
|
27
27
|
BulkDeleteUsersResponse,
|
|
@@ -30,8 +30,12 @@ import {
|
|
|
30
30
|
RowResponse,
|
|
31
31
|
BulkDocsResponse,
|
|
32
32
|
AccountMetadata,
|
|
33
|
+
InviteUsersRequest,
|
|
34
|
+
InviteUsersResponse,
|
|
33
35
|
} from "@budibase/types"
|
|
34
36
|
import { groups as groupUtils } from "@budibase/pro"
|
|
37
|
+
import { sendEmail } from "../../utilities/email"
|
|
38
|
+
import { EmailTemplatePurpose } from "../../constants"
|
|
35
39
|
|
|
36
40
|
const PAGE_LIMIT = 8
|
|
37
41
|
|
|
@@ -551,3 +555,53 @@ const bulkDeleteProcessing = async (dbUser: User) => {
|
|
|
551
555
|
// let server know to sync user
|
|
552
556
|
await apps.syncUserInApps(userId)
|
|
553
557
|
}
|
|
558
|
+
|
|
559
|
+
export const invite = async (
|
|
560
|
+
users: InviteUsersRequest
|
|
561
|
+
): Promise<InviteUsersResponse> => {
|
|
562
|
+
const response: InviteUsersResponse = {
|
|
563
|
+
successful: [],
|
|
564
|
+
unsuccessful: [],
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const matchedEmails = await searchExistingEmails(users.map(u => u.email))
|
|
568
|
+
const newUsers = []
|
|
569
|
+
|
|
570
|
+
// separate duplicates from new users
|
|
571
|
+
for (let user of users) {
|
|
572
|
+
if (matchedEmails.includes(user.email)) {
|
|
573
|
+
response.unsuccessful.push({ email: user.email, reason: "Unavailable" })
|
|
574
|
+
} else {
|
|
575
|
+
newUsers.push(user)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// overwrite users with new only
|
|
579
|
+
users = newUsers
|
|
580
|
+
|
|
581
|
+
// send the emails for new users
|
|
582
|
+
const tenantId = tenancy.getTenantId()
|
|
583
|
+
for (let user of users) {
|
|
584
|
+
try {
|
|
585
|
+
let userInfo = user.userInfo
|
|
586
|
+
if (!userInfo) {
|
|
587
|
+
userInfo = {}
|
|
588
|
+
}
|
|
589
|
+
userInfo.tenantId = tenantId
|
|
590
|
+
const opts: any = {
|
|
591
|
+
subject: "{{ company }} platform invitation",
|
|
592
|
+
info: userInfo,
|
|
593
|
+
}
|
|
594
|
+
await sendEmail(user.email, EmailTemplatePurpose.INVITATION, opts)
|
|
595
|
+
response.successful.push({ email: user.email })
|
|
596
|
+
await events.user.invited()
|
|
597
|
+
} catch (e) {
|
|
598
|
+
console.error(`Failed to send email invitation email=${user.email}`, e)
|
|
599
|
+
response.unsuccessful.push({
|
|
600
|
+
email: user.email,
|
|
601
|
+
reason: "Failed to send email",
|
|
602
|
+
})
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return response
|
|
607
|
+
}
|
package/src/tests/api/users.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
BulkCreateUsersResponse,
|
|
4
4
|
BulkDeleteUsersRequest,
|
|
5
5
|
CreateUserResponse,
|
|
6
|
+
InviteUsersRequest,
|
|
6
7
|
User,
|
|
7
8
|
UserDetails,
|
|
8
9
|
} from "@budibase/types"
|
|
@@ -19,17 +20,21 @@ export class UserAPI {
|
|
|
19
20
|
|
|
20
21
|
// INVITE
|
|
21
22
|
|
|
22
|
-
sendUserInvite = async (sendMailMock: any) => {
|
|
23
|
+
sendUserInvite = async (sendMailMock: any, email: string, status = 200) => {
|
|
23
24
|
await this.config.saveSmtpConfig()
|
|
24
25
|
await this.config.saveSettingsConfig()
|
|
25
26
|
const res = await this.request
|
|
26
27
|
.post(`/api/global/users/invite`)
|
|
27
28
|
.send({
|
|
28
|
-
email
|
|
29
|
+
email,
|
|
29
30
|
})
|
|
30
31
|
.set(this.config.defaultHeaders())
|
|
31
32
|
.expect("Content-Type", /json/)
|
|
32
|
-
.expect(
|
|
33
|
+
.expect(status)
|
|
34
|
+
|
|
35
|
+
if (status !== 200) {
|
|
36
|
+
return { code: undefined, res }
|
|
37
|
+
}
|
|
33
38
|
|
|
34
39
|
const emailCall = sendMailMock.mock.calls[0][0]
|
|
35
40
|
// after this URL there should be a code
|
|
@@ -51,6 +56,17 @@ export class UserAPI {
|
|
|
51
56
|
.expect(200)
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
sendMultiUserInvite = async (request: InviteUsersRequest, status = 200) => {
|
|
60
|
+
await this.config.saveSmtpConfig()
|
|
61
|
+
await this.config.saveSettingsConfig()
|
|
62
|
+
return this.request
|
|
63
|
+
.post(`/api/global/users/multi/invite`)
|
|
64
|
+
.send(request)
|
|
65
|
+
.set(this.config.defaultHeaders())
|
|
66
|
+
.expect("Content-Type", /json/)
|
|
67
|
+
.expect(status)
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
// BULK
|
|
55
71
|
|
|
56
72
|
bulkCreateUsers = async (users: User[], groups: any[] = []) => {
|