@budibase/worker 2.13.10 → 2.13.11
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 +6 -6
- package/src/api/controllers/global/users.ts +36 -67
- package/src/api/routes/global/tests/users.spec.ts +59 -2
- package/src/index.ts +4 -4
- package/src/sdk/auth/auth.ts +2 -2
- package/src/sdk/users/users.ts +8 -2
- package/src/tests/api/users.ts +21 -0
- package/src/utilities/email.ts +3 -4
- package/src/utilities/redis.ts +0 -150
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.13.
|
|
4
|
+
"version": "2.13.11",
|
|
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.13.
|
|
41
|
-
"@budibase/pro": "2.13.
|
|
42
|
-
"@budibase/string-templates": "2.13.
|
|
43
|
-
"@budibase/types": "2.13.
|
|
40
|
+
"@budibase/backend-core": "2.13.11",
|
|
41
|
+
"@budibase/pro": "2.13.11",
|
|
42
|
+
"@budibase/string-templates": "2.13.11",
|
|
43
|
+
"@budibase/types": "2.13.11",
|
|
44
44
|
"@koa/router": "8.0.8",
|
|
45
45
|
"@techpass/passport-openidconnect": "0.3.2",
|
|
46
46
|
"@types/global-agent": "2.1.1",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "230e8d171197a2db905600d2914decbca281f8e0"
|
|
111
111
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
checkInviteCode,
|
|
3
|
-
getInviteCodes,
|
|
4
|
-
updateInviteCode,
|
|
5
|
-
} from "../../../utilities/redis"
|
|
6
1
|
import * as userSdk from "../../../sdk/users"
|
|
7
2
|
import env from "../../../environment"
|
|
8
3
|
import {
|
|
@@ -16,6 +11,7 @@ import {
|
|
|
16
11
|
Ctx,
|
|
17
12
|
InviteUserRequest,
|
|
18
13
|
InviteUsersRequest,
|
|
14
|
+
InviteUsersResponse,
|
|
19
15
|
MigrationType,
|
|
20
16
|
SaveUserResponse,
|
|
21
17
|
SearchUsersRequest,
|
|
@@ -249,59 +245,35 @@ export const tenantUserLookup = async (ctx: any) => {
|
|
|
249
245
|
/*
|
|
250
246
|
Encapsulate the app user onboarding flows here.
|
|
251
247
|
*/
|
|
252
|
-
export const onboardUsers = async (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (isBulkCreate) {
|
|
261
|
-
// @ts-ignore
|
|
262
|
-
const { users, groups, roles } = request.create
|
|
263
|
-
const assignUsers = users.map((user: User) => (user.roles = roles))
|
|
264
|
-
onboardingResponse = await userSdk.db.bulkCreate(assignUsers, groups)
|
|
265
|
-
ctx.body = onboardingResponse
|
|
266
|
-
} else if (emailConfigured) {
|
|
267
|
-
onboardingResponse = await inviteMultiple(ctx)
|
|
268
|
-
} else if (!emailConfigured) {
|
|
269
|
-
const inviteRequest = ctx.request.body
|
|
270
|
-
|
|
271
|
-
let createdPasswords: any = {}
|
|
272
|
-
|
|
273
|
-
const users: User[] = inviteRequest.map(invite => {
|
|
274
|
-
let password = Math.random().toString(36).substring(2, 22)
|
|
275
|
-
|
|
276
|
-
// Temp password to be passed to the user.
|
|
277
|
-
createdPasswords[invite.email] = password
|
|
278
|
-
|
|
279
|
-
return {
|
|
280
|
-
email: invite.email,
|
|
281
|
-
password,
|
|
282
|
-
forceResetPassword: true,
|
|
283
|
-
roles: invite.userInfo.apps,
|
|
284
|
-
admin: invite.userInfo.admin,
|
|
285
|
-
builder: invite.userInfo.builder,
|
|
286
|
-
tenantId: tenancy.getTenantId(),
|
|
287
|
-
}
|
|
288
|
-
})
|
|
289
|
-
let bulkCreateReponse = await userSdk.db.bulkCreate(users, [])
|
|
248
|
+
export const onboardUsers = async (
|
|
249
|
+
ctx: Ctx<InviteUsersRequest, InviteUsersResponse>
|
|
250
|
+
) => {
|
|
251
|
+
if (await isEmailConfigured()) {
|
|
252
|
+
await inviteMultiple(ctx)
|
|
253
|
+
return
|
|
254
|
+
}
|
|
290
255
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
256
|
+
let createdPasswords: Record<string, string> = {}
|
|
257
|
+
const users: User[] = ctx.request.body.map(invite => {
|
|
258
|
+
let password = Math.random().toString(36).substring(2, 22)
|
|
259
|
+
createdPasswords[invite.email] = password
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
email: invite.email,
|
|
263
|
+
password,
|
|
264
|
+
forceResetPassword: true,
|
|
265
|
+
roles: invite.userInfo.apps,
|
|
266
|
+
admin: invite.userInfo.admin,
|
|
267
|
+
builder: invite.userInfo.builder,
|
|
268
|
+
tenantId: tenancy.getTenantId(),
|
|
301
269
|
}
|
|
302
|
-
}
|
|
303
|
-
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
let resp = await userSdk.db.bulkCreate(users)
|
|
273
|
+
for (const user of resp.successful) {
|
|
274
|
+
user.password = createdPasswords[user.email]
|
|
304
275
|
}
|
|
276
|
+
ctx.body = { ...resp, created: true }
|
|
305
277
|
}
|
|
306
278
|
|
|
307
279
|
export const invite = async (ctx: Ctx<InviteUserRequest>) => {
|
|
@@ -328,18 +300,18 @@ export const invite = async (ctx: Ctx<InviteUserRequest>) => {
|
|
|
328
300
|
}
|
|
329
301
|
|
|
330
302
|
export const inviteMultiple = async (ctx: Ctx<InviteUsersRequest>) => {
|
|
331
|
-
|
|
332
|
-
ctx.body = await userSdk.invite(request)
|
|
303
|
+
ctx.body = await userSdk.invite(ctx.request.body)
|
|
333
304
|
}
|
|
334
305
|
|
|
335
306
|
export const checkInvite = async (ctx: any) => {
|
|
336
307
|
const { code } = ctx.params
|
|
337
308
|
let invite
|
|
338
309
|
try {
|
|
339
|
-
invite = await
|
|
310
|
+
invite = await cache.invite.getCode(code)
|
|
340
311
|
} catch (e) {
|
|
341
312
|
console.warn("Error getting invite from code", e)
|
|
342
313
|
ctx.throw(400, "There was a problem with the invite")
|
|
314
|
+
return
|
|
343
315
|
}
|
|
344
316
|
ctx.body = {
|
|
345
317
|
email: invite.email,
|
|
@@ -347,14 +319,12 @@ export const checkInvite = async (ctx: any) => {
|
|
|
347
319
|
}
|
|
348
320
|
|
|
349
321
|
export const getUserInvites = async (ctx: any) => {
|
|
350
|
-
let invites
|
|
351
322
|
try {
|
|
352
323
|
// Restricted to the currently authenticated tenant
|
|
353
|
-
|
|
324
|
+
ctx.body = await cache.invite.getInviteCodes()
|
|
354
325
|
} catch (e) {
|
|
355
326
|
ctx.throw(400, "There was a problem fetching invites")
|
|
356
327
|
}
|
|
357
|
-
ctx.body = invites
|
|
358
328
|
}
|
|
359
329
|
|
|
360
330
|
export const updateInvite = async (ctx: any) => {
|
|
@@ -365,12 +335,10 @@ export const updateInvite = async (ctx: any) => {
|
|
|
365
335
|
|
|
366
336
|
let invite
|
|
367
337
|
try {
|
|
368
|
-
invite = await
|
|
369
|
-
if (!invite) {
|
|
370
|
-
throw new Error("The invite could not be retrieved")
|
|
371
|
-
}
|
|
338
|
+
invite = await cache.invite.getCode(code)
|
|
372
339
|
} catch (e) {
|
|
373
340
|
ctx.throw(400, "There was a problem with the invite")
|
|
341
|
+
return
|
|
374
342
|
}
|
|
375
343
|
|
|
376
344
|
let updated = {
|
|
@@ -395,7 +363,7 @@ export const updateInvite = async (ctx: any) => {
|
|
|
395
363
|
}
|
|
396
364
|
}
|
|
397
365
|
|
|
398
|
-
await
|
|
366
|
+
await cache.invite.updateCode(code, updated)
|
|
399
367
|
ctx.body = { ...invite }
|
|
400
368
|
}
|
|
401
369
|
|
|
@@ -405,7 +373,8 @@ export const inviteAccept = async (
|
|
|
405
373
|
const { inviteCode, password, firstName, lastName } = ctx.request.body
|
|
406
374
|
try {
|
|
407
375
|
// info is an extension of the user object that was stored by global
|
|
408
|
-
const { email, info }: any = await
|
|
376
|
+
const { email, info }: any = await cache.invite.getCode(inviteCode)
|
|
377
|
+
await cache.invite.deleteCode(inviteCode)
|
|
409
378
|
const user = await tenancy.doInTenant(info.tenantId, async () => {
|
|
410
379
|
let request: any = {
|
|
411
380
|
firstName,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { InviteUsersResponse, User } from "@budibase/types"
|
|
2
2
|
|
|
3
|
-
jest.mock("nodemailer")
|
|
4
3
|
import { TestConfiguration, mocks, structures } from "../../../../tests"
|
|
5
|
-
const sendMailMock = mocks.email.mock()
|
|
6
4
|
import { events, tenancy, accounts as _accounts } from "@budibase/backend-core"
|
|
7
5
|
import * as userSdk from "../../../../sdk/users"
|
|
8
6
|
|
|
7
|
+
jest.mock("nodemailer")
|
|
8
|
+
const sendMailMock = mocks.email.mock()
|
|
9
|
+
|
|
9
10
|
const accounts = jest.mocked(_accounts)
|
|
10
11
|
|
|
11
12
|
describe("/api/global/users", () => {
|
|
@@ -54,6 +55,24 @@ describe("/api/global/users", () => {
|
|
|
54
55
|
expect(events.user.invited).toBeCalledTimes(0)
|
|
55
56
|
})
|
|
56
57
|
|
|
58
|
+
it("should not invite the same user twice", async () => {
|
|
59
|
+
const email = structures.users.newEmail()
|
|
60
|
+
await config.api.users.sendUserInvite(sendMailMock, email)
|
|
61
|
+
|
|
62
|
+
jest.clearAllMocks()
|
|
63
|
+
|
|
64
|
+
const { code, res } = await config.api.users.sendUserInvite(
|
|
65
|
+
sendMailMock,
|
|
66
|
+
email,
|
|
67
|
+
400
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
expect(res.body.message).toBe(`Unavailable`)
|
|
71
|
+
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
72
|
+
expect(code).toBeUndefined()
|
|
73
|
+
expect(events.user.invited).toBeCalledTimes(0)
|
|
74
|
+
})
|
|
75
|
+
|
|
57
76
|
it("should be able to create new user from invite", async () => {
|
|
58
77
|
const email = structures.users.newEmail()
|
|
59
78
|
const { code } = await config.api.users.sendUserInvite(
|
|
@@ -101,6 +120,23 @@ describe("/api/global/users", () => {
|
|
|
101
120
|
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
102
121
|
expect(events.user.invited).toBeCalledTimes(0)
|
|
103
122
|
})
|
|
123
|
+
|
|
124
|
+
it("should not be able to generate an invitation for user that has already been invited", async () => {
|
|
125
|
+
const email = structures.users.newEmail()
|
|
126
|
+
await config.api.users.sendUserInvite(sendMailMock, email)
|
|
127
|
+
|
|
128
|
+
jest.clearAllMocks()
|
|
129
|
+
|
|
130
|
+
const request = [{ email: email, userInfo: {} }]
|
|
131
|
+
const res = await config.api.users.sendMultiUserInvite(request)
|
|
132
|
+
|
|
133
|
+
const body = res.body as InviteUsersResponse
|
|
134
|
+
expect(body.successful.length).toBe(0)
|
|
135
|
+
expect(body.unsuccessful.length).toBe(1)
|
|
136
|
+
expect(body.unsuccessful[0].reason).toBe("Unavailable")
|
|
137
|
+
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
138
|
+
expect(events.user.invited).toBeCalledTimes(0)
|
|
139
|
+
})
|
|
104
140
|
})
|
|
105
141
|
|
|
106
142
|
describe("POST /api/global/users/bulk", () => {
|
|
@@ -633,4 +669,25 @@ describe("/api/global/users", () => {
|
|
|
633
669
|
expect(response.body.message).toBe("Unable to delete self.")
|
|
634
670
|
})
|
|
635
671
|
})
|
|
672
|
+
|
|
673
|
+
describe("POST /api/global/users/onboard", () => {
|
|
674
|
+
it("should successfully onboard a user", async () => {
|
|
675
|
+
const response = await config.api.users.onboardUser([
|
|
676
|
+
{ email: structures.users.newEmail(), userInfo: {} },
|
|
677
|
+
])
|
|
678
|
+
expect(response.successful.length).toBe(1)
|
|
679
|
+
expect(response.unsuccessful.length).toBe(0)
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
it("should not onboard a user who has been invited", async () => {
|
|
683
|
+
const email = structures.users.newEmail()
|
|
684
|
+
await config.api.users.sendUserInvite(sendMailMock, email)
|
|
685
|
+
|
|
686
|
+
const response = await config.api.users.onboardUser([
|
|
687
|
+
{ email, userInfo: {} },
|
|
688
|
+
])
|
|
689
|
+
expect(response.successful.length).toBe(0)
|
|
690
|
+
expect(response.unsuccessful.length).toBe(1)
|
|
691
|
+
})
|
|
692
|
+
})
|
|
636
693
|
})
|
package/src/index.ts
CHANGED
|
@@ -16,13 +16,13 @@ import {
|
|
|
16
16
|
queue,
|
|
17
17
|
env as coreEnv,
|
|
18
18
|
timers,
|
|
19
|
+
redis,
|
|
19
20
|
} from "@budibase/backend-core"
|
|
20
21
|
db.init()
|
|
21
22
|
import Koa from "koa"
|
|
22
23
|
import koaBody from "koa-body"
|
|
23
24
|
import http from "http"
|
|
24
25
|
import api from "./api"
|
|
25
|
-
import * as redis from "./utilities/redis"
|
|
26
26
|
|
|
27
27
|
const koaSession = require("koa-session")
|
|
28
28
|
import { userAgent } from "koa-useragent"
|
|
@@ -72,8 +72,8 @@ server.on("close", async () => {
|
|
|
72
72
|
shuttingDown = true
|
|
73
73
|
console.log("Server Closed")
|
|
74
74
|
timers.cleanup()
|
|
75
|
-
|
|
76
|
-
await
|
|
75
|
+
events.shutdown()
|
|
76
|
+
await redis.clients.shutdown()
|
|
77
77
|
await queue.shutdown()
|
|
78
78
|
if (!env.isTest()) {
|
|
79
79
|
process.exit(errCode)
|
|
@@ -88,7 +88,7 @@ const shutdown = () => {
|
|
|
88
88
|
export default server.listen(parseInt(env.PORT || "4002"), async () => {
|
|
89
89
|
console.log(`Worker running on ${JSON.stringify(server.address())}`)
|
|
90
90
|
await initPro()
|
|
91
|
-
await redis.init()
|
|
91
|
+
await redis.clients.init()
|
|
92
92
|
// configure events to use the pro audit log write
|
|
93
93
|
// can't integrate directly into backend-core due to cyclic issues
|
|
94
94
|
await events.processors.init(proSdk.auditLogs.write)
|
package/src/sdk/auth/auth.ts
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
sessions,
|
|
7
7
|
tenancy,
|
|
8
8
|
utils as coreUtils,
|
|
9
|
+
cache,
|
|
9
10
|
} from "@budibase/backend-core"
|
|
10
11
|
import { PlatformLogoutOpts, User } from "@budibase/types"
|
|
11
12
|
import jwt from "jsonwebtoken"
|
|
12
13
|
import * as userSdk from "../users"
|
|
13
14
|
import * as emails from "../../utilities/email"
|
|
14
|
-
import * as redis from "../../utilities/redis"
|
|
15
15
|
import { EmailTemplatePurpose } from "../../constants"
|
|
16
16
|
|
|
17
17
|
// LOGIN / LOGOUT
|
|
@@ -73,7 +73,7 @@ export const reset = async (email: string) => {
|
|
|
73
73
|
* Perform the user password update if the provided reset code is valid.
|
|
74
74
|
*/
|
|
75
75
|
export const resetUpdate = async (resetCode: string, password: string) => {
|
|
76
|
-
const { userId } = await
|
|
76
|
+
const { userId } = await cache.passwordReset.getCode(resetCode)
|
|
77
77
|
|
|
78
78
|
let user = await userSdk.db.getUser(userId)
|
|
79
79
|
user.password = password
|
package/src/sdk/users/users.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { events, tenancy, users as usersCore } from "@budibase/backend-core"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
InviteUserRequest,
|
|
4
|
+
InviteUsersRequest,
|
|
5
|
+
InviteUsersResponse,
|
|
6
|
+
} from "@budibase/types"
|
|
3
7
|
import { sendEmail } from "../../utilities/email"
|
|
4
8
|
import { EmailTemplatePurpose } from "../../constants"
|
|
5
9
|
|
|
@@ -14,11 +18,13 @@ export async function invite(
|
|
|
14
18
|
const matchedEmails = await usersCore.searchExistingEmails(
|
|
15
19
|
users.map(u => u.email)
|
|
16
20
|
)
|
|
17
|
-
const newUsers = []
|
|
21
|
+
const newUsers: InviteUserRequest[] = []
|
|
18
22
|
|
|
19
23
|
// separate duplicates from new users
|
|
20
24
|
for (let user of users) {
|
|
21
25
|
if (matchedEmails.includes(user.email)) {
|
|
26
|
+
// This "Unavailable" is load bearing. The tests and frontend both check for it
|
|
27
|
+
// specifically
|
|
22
28
|
response.unsuccessful.push({ email: user.email, reason: "Unavailable" })
|
|
23
29
|
} else {
|
|
24
30
|
newUsers.push(user)
|
package/src/tests/api/users.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
User,
|
|
6
6
|
CreateAdminUserRequest,
|
|
7
7
|
SearchQuery,
|
|
8
|
+
InviteUsersResponse,
|
|
8
9
|
} from "@budibase/types"
|
|
9
10
|
import structures from "../structures"
|
|
10
11
|
import { generator } from "@budibase/backend-core/tests"
|
|
@@ -176,4 +177,24 @@ export class UserAPI extends TestAPI {
|
|
|
176
177
|
.expect("Content-Type", /json/)
|
|
177
178
|
.expect(200)
|
|
178
179
|
}
|
|
180
|
+
|
|
181
|
+
onboardUser = async (
|
|
182
|
+
req: InviteUsersRequest
|
|
183
|
+
): Promise<InviteUsersResponse> => {
|
|
184
|
+
const resp = await this.request
|
|
185
|
+
.post(`/api/global/users/onboard`)
|
|
186
|
+
.send(req)
|
|
187
|
+
.set(this.config.defaultHeaders())
|
|
188
|
+
.expect("Content-Type", /json/)
|
|
189
|
+
|
|
190
|
+
if (resp.status !== 200) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`request failed with status ${resp.status} and body ${JSON.stringify(
|
|
193
|
+
resp.body
|
|
194
|
+
)}`
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return resp.body as InviteUsersResponse
|
|
199
|
+
}
|
|
179
200
|
}
|
package/src/utilities/email.ts
CHANGED
|
@@ -3,9 +3,8 @@ import { EmailTemplatePurpose, TemplateType } from "../constants"
|
|
|
3
3
|
import { getTemplateByPurpose, EmailTemplates } from "../constants/templates"
|
|
4
4
|
import { getSettingsTemplateContext } from "./templates"
|
|
5
5
|
import { processString } from "@budibase/string-templates"
|
|
6
|
-
import { getResetPasswordCode, getInviteCode } from "./redis"
|
|
7
6
|
import { User, SendEmailOpts, SMTPInnerConfig } from "@budibase/types"
|
|
8
|
-
import { configs } from "@budibase/backend-core"
|
|
7
|
+
import { configs, cache } from "@budibase/backend-core"
|
|
9
8
|
import ical from "ical-generator"
|
|
10
9
|
const nodemailer = require("nodemailer")
|
|
11
10
|
|
|
@@ -61,9 +60,9 @@ async function getLinkCode(
|
|
|
61
60
|
) {
|
|
62
61
|
switch (purpose) {
|
|
63
62
|
case EmailTemplatePurpose.PASSWORD_RECOVERY:
|
|
64
|
-
return
|
|
63
|
+
return cache.passwordReset.createCode(user._id!, info)
|
|
65
64
|
case EmailTemplatePurpose.INVITATION:
|
|
66
|
-
return
|
|
65
|
+
return cache.invite.createCode(email, info)
|
|
67
66
|
default:
|
|
68
67
|
return null
|
|
69
68
|
}
|
package/src/utilities/redis.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { redis, utils, tenancy } from "@budibase/backend-core"
|
|
2
|
-
import env from "../environment"
|
|
3
|
-
|
|
4
|
-
function getExpirySecondsForDB(db: string) {
|
|
5
|
-
switch (db) {
|
|
6
|
-
case redis.utils.Databases.PW_RESETS:
|
|
7
|
-
// a hour
|
|
8
|
-
return 3600
|
|
9
|
-
case redis.utils.Databases.INVITATIONS:
|
|
10
|
-
// a week
|
|
11
|
-
return 604800
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let pwResetClient: any, invitationClient: any
|
|
16
|
-
|
|
17
|
-
function getClient(db: string) {
|
|
18
|
-
switch (db) {
|
|
19
|
-
case redis.utils.Databases.PW_RESETS:
|
|
20
|
-
return pwResetClient
|
|
21
|
-
case redis.utils.Databases.INVITATIONS:
|
|
22
|
-
return invitationClient
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function writeACode(db: string, value: any) {
|
|
27
|
-
const client = await getClient(db)
|
|
28
|
-
const code = utils.newid()
|
|
29
|
-
await client.store(code, value, getExpirySecondsForDB(db))
|
|
30
|
-
return code
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function updateACode(db: string, code: string, value: any) {
|
|
34
|
-
const client = await getClient(db)
|
|
35
|
-
await client.store(code, value, getExpirySecondsForDB(db))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Given an invite code and invite body, allow the update an existing/valid invite in redis
|
|
40
|
-
* @param inviteCode The invite code for an invite in redis
|
|
41
|
-
* @param value The body of the updated user invitation
|
|
42
|
-
*/
|
|
43
|
-
export async function updateInviteCode(inviteCode: string, value: string) {
|
|
44
|
-
await updateACode(redis.utils.Databases.INVITATIONS, inviteCode, value)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function getACode(db: string, code: string, deleteCode = true) {
|
|
48
|
-
const client = await getClient(db)
|
|
49
|
-
const value = await client.get(code)
|
|
50
|
-
if (!value) {
|
|
51
|
-
throw new Error("Invalid code.")
|
|
52
|
-
}
|
|
53
|
-
if (deleteCode) {
|
|
54
|
-
await client.delete(code)
|
|
55
|
-
}
|
|
56
|
-
return value
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function init() {
|
|
60
|
-
pwResetClient = new redis.Client(redis.utils.Databases.PW_RESETS)
|
|
61
|
-
invitationClient = new redis.Client(redis.utils.Databases.INVITATIONS)
|
|
62
|
-
await pwResetClient.init()
|
|
63
|
-
await invitationClient.init()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* make sure redis connection is closed.
|
|
68
|
-
*/
|
|
69
|
-
export async function shutdown() {
|
|
70
|
-
if (pwResetClient) await pwResetClient.finish()
|
|
71
|
-
if (invitationClient) await invitationClient.finish()
|
|
72
|
-
// shutdown core clients
|
|
73
|
-
await redis.clients.shutdown()
|
|
74
|
-
console.log("Redis shutdown")
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Given a user ID this will store a code (that is returned) for an hour in redis.
|
|
79
|
-
* The user can then return this code for resetting their password (through their reset link).
|
|
80
|
-
* @param userId the ID of the user which is to be reset.
|
|
81
|
-
* @param info Info about the user/the reset process.
|
|
82
|
-
* @return returns the code that was stored to redis.
|
|
83
|
-
*/
|
|
84
|
-
export async function getResetPasswordCode(userId: string, info: any) {
|
|
85
|
-
return writeACode(redis.utils.Databases.PW_RESETS, { userId, info })
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Given a reset code this will lookup to redis, check if the code is valid and delete if required.
|
|
90
|
-
* @param resetCode The code provided via the email link.
|
|
91
|
-
* @param deleteCode If the code is used/finished with this will delete it - defaults to true.
|
|
92
|
-
* @return returns the user ID if it is found
|
|
93
|
-
*/
|
|
94
|
-
export async function checkResetPasswordCode(
|
|
95
|
-
resetCode: string,
|
|
96
|
-
deleteCode = true
|
|
97
|
-
) {
|
|
98
|
-
try {
|
|
99
|
-
return getACode(redis.utils.Databases.PW_RESETS, resetCode, deleteCode)
|
|
100
|
-
} catch (err) {
|
|
101
|
-
throw "Provided information is not valid, cannot reset password - please try again."
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Generates an invitation code and writes it to redis - which can later be checked for user creation.
|
|
107
|
-
* @param email the email address which the code is being sent to (for use later).
|
|
108
|
-
* @param info Information to be carried along with the invitation.
|
|
109
|
-
* @return returns the code that was stored to redis.
|
|
110
|
-
*/
|
|
111
|
-
export async function getInviteCode(email: string, info: any) {
|
|
112
|
-
return writeACode(redis.utils.Databases.INVITATIONS, { email, info })
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Checks that the provided invite code is valid - will return the email address of user that was invited.
|
|
117
|
-
* @param inviteCode the invite code that was provided as part of the link.
|
|
118
|
-
* @param deleteCode whether or not the code should be deleted after retrieval - defaults to true.
|
|
119
|
-
* @return If the code is valid then an email address will be returned.
|
|
120
|
-
*/
|
|
121
|
-
export async function checkInviteCode(
|
|
122
|
-
inviteCode: string,
|
|
123
|
-
deleteCode: boolean = true
|
|
124
|
-
) {
|
|
125
|
-
try {
|
|
126
|
-
return getACode(redis.utils.Databases.INVITATIONS, inviteCode, deleteCode)
|
|
127
|
-
} catch (err) {
|
|
128
|
-
throw "Invitation is not valid or has expired, please request a new one."
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
Get all currently available user invitations for the current tenant.
|
|
134
|
-
**/
|
|
135
|
-
export async function getInviteCodes() {
|
|
136
|
-
const client = await getClient(redis.utils.Databases.INVITATIONS)
|
|
137
|
-
const invites: any[] = await client.scan()
|
|
138
|
-
|
|
139
|
-
const results = invites.map(invite => {
|
|
140
|
-
return {
|
|
141
|
-
...invite.value,
|
|
142
|
-
code: invite.key,
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
if (!env.MULTI_TENANCY) {
|
|
146
|
-
return results
|
|
147
|
-
}
|
|
148
|
-
const tenantId = tenancy.getTenantId()
|
|
149
|
-
return results.filter(invite => tenantId === invite.info.tenantId)
|
|
150
|
-
}
|