Moving things around to get rid of cyclics created by moving invite/passwordReset into backend-core - also updating currentapp test case to mock a bit better.

This commit is contained in:
mike12345567 2023-11-17 16:20:10 +00:00
parent 7412fd5a5c
commit 7fb6c0927a
12 changed files with 95 additions and 141 deletions

View File

@ -1,6 +1,6 @@
const BaseCache = require("./base")
import BaseCache from "./base"
const GENERIC = new BaseCache.default()
const GENERIC = new BaseCache()
export enum CacheKey {
CHECKLIST = "checklist",
@ -18,13 +18,9 @@ export enum TTL {
ONE_DAY = 86400,
}
function performExport(funcName: string) {
return (...args: any) => GENERIC[funcName](...args)
}
export const keys = performExport("keys")
export const get = performExport("get")
export const store = performExport("store")
export const destroy = performExport("delete")
export const withCache = performExport("withCache")
export const bustCache = performExport("bustCache")
export const keys = GENERIC.keys
export const get = GENERIC.get
export const store = GENERIC.store
export const destroy = GENERIC.delete
export const withCache = GENERIC.withCache
export const bustCache = GENERIC.bustCache

View File

@ -2,4 +2,6 @@ export * as generic from "./generic"
export * as user from "./user"
export * as app from "./appMetadata"
export * as writethrough from "./writethrough"
export * as invite from "./invite"
export * as passwordReset from "./passwordReset"
export * from "./generic"

View File

@ -1,7 +1,10 @@
import { utils, tenancy, redis } from "../"
import * as utils from "../utils"
import { Duration, DurationType } from "../utils"
import env from "../environment"
import { getTenantId } from "../context"
import * as redis from "../redis/init"
const TTL_SECONDS = 60 * 60 * 24 * 7
const TTL_SECONDS = Duration.fromDays(7).to(DurationType.SECONDS)
interface Invite {
email: string
@ -12,25 +15,13 @@ interface InviteWithCode extends Invite {
code: string
}
let client: redis.Client
export async function init() {
if (!client) {
client = new redis.Client(redis.utils.Databases.INVITATIONS)
}
return client
}
export async function shutdown() {
if (client) await client.finish()
}
/**
* Given an invite code and invite body, allow the update an existing/valid invite in redis
* @param inviteCode The invite code for an invite in redis
* @param code The invite code for an invite in redis
* @param value The body of the updated user invitation
*/
export async function updateCode(code: string, value: Invite) {
const client = await redis.getInviteClient()
await client.store(code, value, TTL_SECONDS)
}
@ -42,16 +33,18 @@ export async function updateCode(code: string, value: Invite) {
*/
export async function createCode(email: string, info: any): Promise<string> {
const code = utils.newid()
const client = await redis.getInviteClient()
await client.store(code, { email, info }, TTL_SECONDS)
return code
}
/**
* Checks that the provided invite code is valid - will return the email address of user that was invited.
* @param inviteCode the invite code that was provided as part of the link.
* @param code the invite code that was provided as part of the link.
* @return If the code is valid then an email address will be returned.
*/
export async function getCode(code: string): Promise<Invite> {
const client = await redis.getInviteClient()
const value = (await client.get(code)) as Invite | undefined
if (!value) {
throw "Invitation is not valid or has expired, please request a new one."
@ -60,13 +53,15 @@ export async function getCode(code: string): Promise<Invite> {
}
export async function deleteCode(code: string) {
const client = await redis.getInviteClient()
await client.delete(code)
}
/**
Get all currently available user invitations for the current tenant.
**/
**/
export async function getInviteCodes(): Promise<InviteWithCode[]> {
const client = await redis.getInviteClient()
const invites: { key: string; value: Invite }[] = await client.scan()
const results: InviteWithCode[] = invites.map(invite => {
@ -78,7 +73,7 @@ export async function getInviteCodes(): Promise<InviteWithCode[]> {
if (!env.MULTI_TENANCY) {
return results
}
const tenantId = tenancy.getTenantId()
const tenantId = getTenantId()
return results.filter(invite => tenantId === invite.info.tenantId)
}

View File

@ -1,25 +1,14 @@
import { redis, utils } from "../"
import * as redis from "../redis/init"
import * as utils from "../utils"
import { Duration, DurationType } from "../utils"
const TTL_SECONDS = 60 * 60
const TTL_SECONDS = Duration.fromHours(1).to(DurationType.SECONDS)
interface PasswordReset {
userId: string
info: any
}
let client: redis.Client
export async function init() {
if (!client) {
client = new redis.Client(redis.utils.Databases.PW_RESETS)
}
return client
}
export async function shutdown() {
if (client) await client.finish()
}
/**
* Given a user ID this will store a code (that is returned) for an hour in redis.
* The user can then return this code for resetting their password (through their reset link).
@ -29,6 +18,7 @@ export async function shutdown() {
*/
export async function createCode(userId: string, info: any): Promise<string> {
const code = utils.newid()
const client = await redis.getPasswordResetClient()
await client.store(code, { userId, info }, TTL_SECONDS)
return code
}
@ -39,6 +29,7 @@ export async function createCode(userId: string, info: any): Promise<string> {
* @return returns the user ID if it is found
*/
export async function getCode(code: string): Promise<PasswordReset> {
const client = await redis.getPasswordResetClient()
const value = (await client.get(code)) as PasswordReset | undefined
if (!value) {
throw "Provided information is not valid, cannot reset password - please try again."

View File

@ -4,5 +4,3 @@ export { default as Client } from "./redis"
export * as utils from "./utils"
export * as clients from "./init"
export * as locks from "./redlockImpl"
export * as invite from "./invite"
export * as passwordReset from "./passwordReset"

View File

@ -7,7 +7,9 @@ let userClient: Client,
cacheClient: Client,
writethroughClient: Client,
lockClient: Client,
socketClient: Client
socketClient: Client,
inviteClient: Client,
passwordResetClient: Client
async function init() {
userClient = await new Client(utils.Databases.USER_CACHE).init()
@ -16,6 +18,8 @@ async function init() {
cacheClient = await new Client(utils.Databases.GENERIC_CACHE).init()
lockClient = await new Client(utils.Databases.LOCKS).init()
writethroughClient = await new Client(utils.Databases.WRITE_THROUGH).init()
inviteClient = await new Client(utils.Databases.INVITATIONS).init()
passwordResetClient = await new Client(utils.Databases.PW_RESETS).init()
socketClient = await new Client(
utils.Databases.SOCKET_IO,
utils.SelectableDatabase.SOCKET_IO
@ -29,6 +33,8 @@ export async function shutdown() {
if (cacheClient) await cacheClient.finish()
if (writethroughClient) await writethroughClient.finish()
if (lockClient) await lockClient.finish()
if (inviteClient) await inviteClient.finish()
if (passwordResetClient) await passwordResetClient.finish()
if (socketClient) await socketClient.finish()
}
@ -84,3 +90,17 @@ export async function getSocketClient() {
}
return socketClient
}
export async function getInviteClient() {
if (!inviteClient) {
await init()
}
return inviteClient
}
export async function getPasswordResetClient() {
if (!passwordResetClient) {
await init()
}
return passwordResetClient
}

View File

@ -6,7 +6,7 @@ import {
} from "@budibase/types"
import * as dbUtils from "../db"
import { ViewName } from "../constants"
import { getExistingInvites } from "../redis/invite"
import { getExistingInvites } from "../cache/invite"
/**
* Apply a system-wide search on emails:

View File

@ -9,11 +9,11 @@ function mockWorker() {
return {
_id: "us_uuid1",
roles: {
"app_test": "BASIC",
app_test: "BASIC",
},
roleId: "BASIC",
}
}
},
}))
}
@ -37,27 +37,12 @@ function mockAuthWithNoCookie() {
mockWorker()
jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
return {
...core,
db: {
...core.db,
dbExists: () => true,
},
cache: {
user: {
getUser: async id => {
return {
_id: "us_uuid1",
}
},
},
},
utils: {
getAppIdFromCtx: jest.fn(),
setCookie: jest.fn(),
getCookie: jest.fn(),
},
}
core.db.dbExists = () => true
core.cache.user.getUser = () => ({ _id: "us_uuid1" })
core.utils.getAppIdFromCtx = jest.fn()
core.utils.setCookie = jest.fn()
core.utils.getCookie = jest.fn()
return core
})
}
@ -66,30 +51,13 @@ function mockAuthWithCookie() {
mockWorker()
jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
return {
...core,
db: {
...core.db,
dbExists: () => true,
},
utils: {
getAppIdFromCtx: () => {
return "app_test"
},
setCookie: jest.fn(),
clearCookie: jest.fn(),
getCookie: () => ({ appId: "app_different", roleId: "PUBLIC" }),
},
cache: {
user: {
getUser: async id => {
return {
_id: "us_uuid1",
}
},
},
},
}
core.db.dbExists = () => true
core.cache.user.getUser = () => ({ _id: "us_uuid1" })
core.utils.getAppIdFromCtx = () => "app_test"
core.utils.getCookie = () => ({ appId: "app_different", roleId: "PUBLIC" })
core.utils.setCookie = jest.fn()
core.utils.clearCookie = jest.fn()
return core
})
}
@ -109,7 +77,7 @@ class TestConfiguration {
path: "",
cookies: {
set: jest.fn(),
}
},
}
}
@ -177,29 +145,16 @@ describe("Current app middleware", () => {
mockReset()
jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
return {
...core,
db: {
...core.db,
dbExists: () => true,
},
utils: {
getAppIdFromCtx: () => {
return "app_test"
},
setCookie: jest.fn(),
getCookie: jest.fn(),
},
cache: {
user: {
getUser: async id => {
return {
_id: "us_uuid1",
}
},
},
},
}
core.db.dbExists = () => true
core.cache.user.getUser = () => ({ _id: "us_uuid1" })
core.utils.getAppIdFromCtx = () => "app_test"
core.utils.getCookie = () => ({
appId: "app_different",
roleId: "PUBLIC",
})
core.utils.setCookie = jest.fn()
core.utils.clearCookie = jest.fn()
return core
})
await checkExpected()
})

View File

@ -1,4 +1,3 @@
import { redis } from "@budibase/backend-core"
import * as userSdk from "../../../sdk/users"
import env from "../../../environment"
import {
@ -308,7 +307,7 @@ export const checkInvite = async (ctx: any) => {
const { code } = ctx.params
let invite
try {
invite = await redis.invite.getCode(code)
invite = await cache.invite.getCode(code)
} catch (e) {
console.warn("Error getting invite from code", e)
ctx.throw(400, "There was a problem with the invite")
@ -322,7 +321,7 @@ export const checkInvite = async (ctx: any) => {
export const getUserInvites = async (ctx: any) => {
try {
// Restricted to the currently authenticated tenant
ctx.body = await redis.invite.getInviteCodes()
ctx.body = await cache.invite.getInviteCodes()
} catch (e) {
ctx.throw(400, "There was a problem fetching invites")
}
@ -336,7 +335,7 @@ export const updateInvite = async (ctx: any) => {
let invite
try {
invite = await redis.invite.getCode(code)
invite = await cache.invite.getCode(code)
} catch (e) {
ctx.throw(400, "There was a problem with the invite")
return
@ -364,7 +363,7 @@ export const updateInvite = async (ctx: any) => {
}
}
await redis.invite.updateCode(code, updated)
await cache.invite.updateCode(code, updated)
ctx.body = { ...invite }
}
@ -374,8 +373,8 @@ export const inviteAccept = async (
const { inviteCode, password, firstName, lastName } = ctx.request.body
try {
// info is an extension of the user object that was stored by global
const { email, info }: any = await redis.invite.getCode(inviteCode)
await redis.invite.deleteCode(inviteCode)
const { email, info }: any = await cache.invite.getCode(inviteCode)
await cache.invite.deleteCode(inviteCode)
const user = await tenancy.doInTenant(info.tenantId, async () => {
let request: any = {
firstName,

View File

@ -72,9 +72,8 @@ server.on("close", async () => {
shuttingDown = true
console.log("Server Closed")
timers.cleanup()
await redis.invite.shutdown()
await redis.passwordReset.shutdown()
await events.shutdown()
events.shutdown()
await redis.clients.shutdown()
await queue.shutdown()
if (!env.isTest()) {
process.exit(errCode)
@ -89,8 +88,8 @@ const shutdown = () => {
export default server.listen(parseInt(env.PORT || "4002"), async () => {
console.log(`Worker running on ${JSON.stringify(server.address())}`)
await initPro()
await redis.invite.init()
await redis.passwordReset.init()
await redis.clients.getInviteClient()
await redis.clients.getPasswordResetClient()
// configure events to use the pro audit log write
// can't integrate directly into backend-core due to cyclic issues
await events.processors.init(proSdk.auditLogs.write)

View File

@ -6,7 +6,7 @@ import {
sessions,
tenancy,
utils as coreUtils,
redis,
cache,
} from "@budibase/backend-core"
import { PlatformLogoutOpts, User } from "@budibase/types"
import jwt from "jsonwebtoken"
@ -73,7 +73,7 @@ export const reset = async (email: string) => {
* Perform the user password update if the provided reset code is valid.
*/
export const resetUpdate = async (resetCode: string, password: string) => {
const { userId } = await redis.passwordReset.getCode(resetCode)
const { userId } = await cache.passwordReset.getCode(resetCode)
let user = await userSdk.db.getUser(userId)
user.password = password

View File

@ -3,9 +3,8 @@ import { EmailTemplatePurpose, TemplateType } from "../constants"
import { getTemplateByPurpose, EmailTemplates } from "../constants/templates"
import { getSettingsTemplateContext } from "./templates"
import { processString } from "@budibase/string-templates"
import { redis } from "@budibase/backend-core"
import { User, SendEmailOpts, SMTPInnerConfig } from "@budibase/types"
import { configs } from "@budibase/backend-core"
import { configs, cache } from "@budibase/backend-core"
import ical from "ical-generator"
const nodemailer = require("nodemailer")
@ -61,9 +60,9 @@ async function getLinkCode(
) {
switch (purpose) {
case EmailTemplatePurpose.PASSWORD_RECOVERY:
return redis.passwordReset.createCode(user._id!, info)
return cache.passwordReset.createCode(user._id!, info)
case EmailTemplatePurpose.INVITATION:
return redis.invite.createCode(email, info)
return cache.invite.createCode(email, info)
default:
return null
}