Some worker typescript conversions.

This commit is contained in:
mike12345567 2022-11-16 18:13:34 +00:00
parent 29dd98a7fc
commit cdc25d7032
5 changed files with 192 additions and 161 deletions

View File

@ -1,97 +1,97 @@
const { Config } = require("@budibase/backend-core/constants")
import { constants } from "@budibase/backend-core"
exports.LOGO_URL =
export const LOGO_URL =
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
exports.UserStatus = {
ACTIVE: "active",
INACTIVE: "inactive",
export enum UserStatus {
ACTIVE = "active",
INACTIVE = "inactive",
}
exports.Config = Config
export const Config = constants.Config
exports.ConfigUploads = {
LOGO: "logo",
OIDC_LOGO: "oidc_logo",
export enum ConfigUpload {
LOGO = "logo",
OIDC_LOGO = "oidc_logo",
}
const TemplateTypes = {
EMAIL: "email",
export enum TemplateType {
EMAIL = "email",
}
const EmailTemplatePurpose = {
BASE: "base",
PASSWORD_RECOVERY: "password_recovery",
INVITATION: "invitation",
WELCOME: "welcome",
CUSTOM: "custom",
export enum EmailTemplatePurpose {
BASE = "base",
PASSWORD_RECOVERY = "password_recovery",
INVITATION = "invitation",
WELCOME = "welcome",
CUSTOM = "custom",
}
const InternalTemplateBindings = {
PLATFORM_URL: "platformUrl",
COMPANY: "company",
LOGO_URL: "logoUrl",
EMAIL: "email",
USER: "user",
REQUEST: "request",
DOCS_URL: "docsUrl",
LOGIN_URL: "loginUrl",
CURRENT_YEAR: "currentYear",
CURRENT_DATE: "currentDate",
BODY: "body",
STYLES: "styles",
RESET_URL: "resetUrl",
RESET_CODE: "resetCode",
INVITE_URL: "inviteUrl",
INVITE_CODE: "inviteUrl",
CONTENTS: "contents",
export enum InternalTemplateBinding {
PLATFORM_URL = "platformUrl",
COMPANY = "company",
LOGO_URL = "logoUrl",
EMAIL = "email",
USER = "user",
REQUEST = "request",
DOCS_URL = "docsUrl",
LOGIN_URL = "loginUrl",
CURRENT_YEAR = "currentYear",
CURRENT_DATE = "currentDate",
BODY = "body",
STYLES = "styles",
RESET_URL = "resetUrl",
RESET_CODE = "resetCode",
INVITE_URL = "inviteUrl",
INVITE_CODE = "inviteUrl",
CONTENTS = "contents",
}
const TemplateBindings = {
export const TemplateBindings = {
PLATFORM_URL: {
name: InternalTemplateBindings.PLATFORM_URL,
name: InternalTemplateBinding.PLATFORM_URL,
description: "The URL used to access the budibase platform",
},
COMPANY: {
name: InternalTemplateBindings.COMPANY,
name: InternalTemplateBinding.COMPANY,
description: "The name of your organization",
},
LOGO_URL: {
name: InternalTemplateBindings.LOGO_URL,
name: InternalTemplateBinding.LOGO_URL,
description: "The URL of your organizations logo.",
},
EMAIL: {
name: InternalTemplateBindings.EMAIL,
name: InternalTemplateBinding.EMAIL,
description: "The recipients email address.",
},
USER: {
name: InternalTemplateBindings.USER,
name: InternalTemplateBinding.USER,
description: "The recipients user object.",
},
REQUEST: {
name: InternalTemplateBindings.REQUEST,
name: InternalTemplateBinding.REQUEST,
description: "Additional request metadata.",
},
DOCS_URL: {
name: InternalTemplateBindings.DOCS_URL,
name: InternalTemplateBinding.DOCS_URL,
description: "Organization documentation URL.",
},
LOGIN_URL: {
name: InternalTemplateBindings.LOGIN_URL,
name: InternalTemplateBinding.LOGIN_URL,
description: "The URL used to log into the organization budibase instance.",
},
CURRENT_YEAR: {
name: InternalTemplateBindings.CURRENT_YEAR,
name: InternalTemplateBinding.CURRENT_YEAR,
description: "The current year.",
},
CURRENT_DATE: {
name: InternalTemplateBindings.CURRENT_DATE,
name: InternalTemplateBinding.CURRENT_DATE,
description: "The current date.",
},
}
const TemplateMetadata = {
[TemplateTypes.EMAIL]: [
export const TemplateMetadata = {
[TemplateType.EMAIL]: [
{
name: "Base format",
description:
@ -100,11 +100,11 @@ const TemplateMetadata = {
purpose: EmailTemplatePurpose.BASE,
bindings: [
{
name: InternalTemplateBindings.BODY,
name: InternalTemplateBinding.BODY,
description: "The main body of another email template.",
},
{
name: InternalTemplateBindings.STYLES,
name: InternalTemplateBinding.STYLES,
description: "The contents of the Styling email template.",
},
],
@ -117,12 +117,12 @@ const TemplateMetadata = {
purpose: EmailTemplatePurpose.PASSWORD_RECOVERY,
bindings: [
{
name: InternalTemplateBindings.RESET_URL,
name: InternalTemplateBinding.RESET_URL,
description:
"The URL the recipient must click to reset their password.",
},
{
name: InternalTemplateBindings.RESET_CODE,
name: InternalTemplateBinding.RESET_CODE,
description:
"The temporary password reset code used in the recipients password reset URL.",
},
@ -144,12 +144,12 @@ const TemplateMetadata = {
purpose: EmailTemplatePurpose.INVITATION,
bindings: [
{
name: InternalTemplateBindings.INVITE_URL,
name: InternalTemplateBinding.INVITE_URL,
description:
"The URL the recipient must click to accept the invitation and activate their account.",
},
{
name: InternalTemplateBindings.INVITE_CODE,
name: InternalTemplateBinding.INVITE_CODE,
description:
"The temporary invite code used in the recipients invitation URL.",
},
@ -163,7 +163,7 @@ const TemplateMetadata = {
purpose: EmailTemplatePurpose.CUSTOM,
bindings: [
{
name: InternalTemplateBindings.CONTENTS,
name: InternalTemplateBinding.CONTENTS,
description: "Custom content body.",
},
],
@ -172,12 +172,5 @@ const TemplateMetadata = {
}
// all purpose combined
exports.TemplatePurpose = {
...EmailTemplatePurpose,
}
exports.TemplateTypes = TemplateTypes
exports.EmailTemplatePurpose = EmailTemplatePurpose
exports.TemplateMetadata = TemplateMetadata
exports.TemplateBindings = TemplateBindings
exports.InternalTemplateBindings = InternalTemplateBindings
exports.GLOBAL_OWNER = "global"
export { EmailTemplatePurpose as TemplatePurpose }
export const GLOBAL_OWNER = "global"

View File

@ -1,7 +1,7 @@
const { readStaticFile } = require("../../utilities/fileSystem")
const {
EmailTemplatePurpose,
TemplateTypes,
TemplateType,
TemplatePurpose,
GLOBAL_OWNER,
} = require("../index")
@ -26,7 +26,7 @@ exports.EmailTemplates = {
exports.addBaseTemplates = (templates, type = null) => {
let purposeList
switch (type) {
case TemplateTypes.EMAIL:
case TemplateType.EMAIL:
purposeList = Object.values(EmailTemplatePurpose)
break
default:

View File

@ -1,15 +1,33 @@
import env from "../environment"
import { EmailTemplatePurpose, TemplateType, Config } from "../constants"
import { getTemplateByPurpose } from "../constants/templates"
import { getSettingsTemplateContext } from "./templates"
import { processString } from "@budibase/string-templates"
import { getResetPasswordCode, getInviteCode } from "./redis"
import { User } from "@budibase/types"
import { tenancy, db as dbCore, PouchLike } from "@budibase/backend-core"
const nodemailer = require("nodemailer")
const env = require("../environment")
const { getScopedConfig } = require("@budibase/backend-core/db")
const { EmailTemplatePurpose, TemplateTypes, Config } = require("../constants")
const { getTemplateByPurpose } = require("../constants/templates")
const { getSettingsTemplateContext } = require("./templates")
const { processString } = require("@budibase/string-templates")
const { getResetPasswordCode, getInviteCode } = require("../utilities/redis")
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
type SendEmailOpts = {
// workspaceId If finer grain controls being used then this will lookup config for workspace.
workspaceId?: string
// user If sending to an existing user the object can be provided, this is used in the context.
user: User
// from If sending from an address that is not what is configured in the SMTP config.
from?: string
// contents If sending a custom email then can supply contents which will be added to it.
contents?: string
// subject A custom subject can be specified if the config one is not desired.
subject?: string
// info Pass in a structure of information to be stored alongside the invitation.
info?: any
cc?: boolean
bcc?: boolean
automation?: boolean
}
const TEST_MODE = false
const TYPE = TemplateTypes.EMAIL
const TYPE = TemplateType.EMAIL
const FULL_EMAIL_PURPOSES = [
EmailTemplatePurpose.INVITATION,
@ -18,8 +36,8 @@ const FULL_EMAIL_PURPOSES = [
EmailTemplatePurpose.CUSTOM,
]
function createSMTPTransport(config) {
let options
function createSMTPTransport(config: any) {
let options: any
let secure = config.secure
// default it if not specified
if (secure == null) {
@ -52,10 +70,15 @@ function createSMTPTransport(config) {
return nodemailer.createTransport(options)
}
async function getLinkCode(purpose, email, user, info = null) {
async function getLinkCode(
purpose: EmailTemplatePurpose,
email: string,
user: User,
info: any = null
) {
switch (purpose) {
case EmailTemplatePurpose.PASSWORD_RECOVERY:
return getResetPasswordCode(user._id, info)
return getResetPasswordCode(user._id!, info)
case EmailTemplatePurpose.INVITATION:
return getInviteCode(email, info)
default:
@ -72,7 +95,12 @@ async function getLinkCode(purpose, email, user, info = null) {
* @param {string|null} contents if using a custom template can supply contents for context.
* @return {Promise<string>} returns the built email HTML if all provided parameters were valid.
*/
async function buildEmail(purpose, email, context, { user, contents } = {}) {
async function buildEmail(
purpose: EmailTemplatePurpose,
email: string,
context: any,
{ user, contents }: any = {}
) {
// this isn't a full email
if (FULL_EMAIL_PURPOSES.indexOf(purpose) === -1) {
throw `Unable to build an email of type ${purpose}`
@ -113,15 +141,19 @@ async function buildEmail(purpose, email, context, { user, contents } = {}) {
* @param {boolean|null} automation Whether or not the configuration is being fetched for an email automation.
* @return {Promise<object|null>} returns the SMTP configuration if it exists
*/
async function getSmtpConfiguration(db, workspaceId = null, automation) {
const params = {
async function getSmtpConfiguration(
db: PouchLike,
workspaceId?: string,
automation?: boolean
) {
const params: any = {
type: Config.SMTP,
}
if (workspaceId) {
params.workspace = workspaceId
}
const customConfig = await getScopedConfig(db, params)
const customConfig = await dbCore.getScopedConfig(db, params)
if (customConfig) {
return customConfig
@ -146,12 +178,12 @@ async function getSmtpConfiguration(db, workspaceId = null, automation) {
* Checks if a SMTP config exists based on passed in parameters.
* @return {Promise<boolean>} returns true if there is a configuration that can be used.
*/
exports.isEmailConfigured = async (workspaceId = null) => {
export async function isEmailConfigured(workspaceId?: string) {
// when "testing" or smtp fallback is enabled simply return true
if (TEST_MODE || env.SMTP_FALLBACK_ENABLED) {
return true
}
const db = getGlobalDB()
const db = tenancy.getGlobalDB()
const config = await getSmtpConfiguration(db, workspaceId)
return config != null
}
@ -161,48 +193,49 @@ exports.isEmailConfigured = async (workspaceId = null) => {
* send an email using it.
* @param {string} email The email address to send to.
* @param {string} purpose The purpose of the email being sent (e.g. reset password).
* @param {string|undefined} workspaceId If finer grain controls being used then this will lookup config for workspace.
* @param {object|undefined} user If sending to an existing user the object can be provided, this is used in the context.
* @param {string|undefined} from If sending from an address that is not what is configured in the SMTP config.
* @param {string|undefined} contents If sending a custom email then can supply contents which will be added to it.
* @param {string|undefined} subject A custom subject can be specified if the config one is not desired.
* @param {object|undefined} info Pass in a structure of information to be stored alongside the invitation.
* @param {boolean|undefined} disableFallback Prevent email being sent from SMTP fallback to avoid spam.
* @param {object} opts The options for sending the email.
* @return {Promise<object>} returns details about the attempt to send email, e.g. if it is successful; based on
* nodemailer response.
*/
exports.sendEmail = async (
email,
purpose,
{ workspaceId, user, from, contents, subject, info, cc, bcc, automation } = {}
) => {
const db = getGlobalDB()
let config = (await getSmtpConfiguration(db, workspaceId, automation)) || {}
export async function sendEmail(
email: string,
purpose: EmailTemplatePurpose,
opts: SendEmailOpts
) {
const db = tenancy.getGlobalDB()
let config =
(await getSmtpConfiguration(db, opts?.workspaceId, opts?.automation)) || {}
if (Object.keys(config).length === 0 && !TEST_MODE) {
throw "Unable to find SMTP configuration."
}
const transport = createSMTPTransport(config)
// if there is a link code needed this will retrieve it
const code = await getLinkCode(purpose, email, user, info)
const context = await getSettingsTemplateContext(purpose, code)
const code = await getLinkCode(purpose, email, opts.user, opts?.info)
let context
if (code) {
context = await getSettingsTemplateContext(purpose, code)
}
let message = {
from: from || config.from,
let message: any = {
from: opts?.from || config.from,
html: await buildEmail(purpose, email, context, {
user,
contents,
user: opts?.user,
contents: opts?.contents,
}),
}
message = {
...message,
to: email,
cc: cc,
bcc: bcc,
cc: opts?.cc,
bcc: opts?.bcc,
}
if (subject || config.subject) {
message.subject = await processString(subject || config.subject, context)
if (opts?.subject || config.subject) {
message.subject = await processString(
opts?.subject || config.subject,
context
)
}
const response = await transport.sendMail(message)
if (TEST_MODE) {
@ -216,7 +249,7 @@ exports.sendEmail = async (
* @param {object} config an SMTP configuration - this is based on the nodemailer API.
* @return {Promise<boolean>} returns true if the configuration is valid.
*/
exports.verifyConfig = async config => {
export async function verifyConfig(config: any) {
const transport = createSMTPTransport(config)
await transport.verify()
}

View File

@ -1,36 +1,35 @@
const { Client, utils } = require("@budibase/backend-core/redis")
const { newid } = require("@budibase/backend-core/utils")
import { redis, utils } from "@budibase/backend-core"
function getExpirySecondsForDB(db) {
function getExpirySecondsForDB(db: string) {
switch (db) {
case utils.Databases.PW_RESETS:
case redis.utils.Databases.PW_RESETS:
// a hour
return 3600
case utils.Databases.INVITATIONS:
case redis.utils.Databases.INVITATIONS:
// a day
return 86400
}
}
let pwResetClient, invitationClient
let pwResetClient: any, invitationClient: any
function getClient(db) {
function getClient(db: string) {
switch (db) {
case utils.Databases.PW_RESETS:
case redis.utils.Databases.PW_RESETS:
return pwResetClient
case utils.Databases.INVITATIONS:
case redis.utils.Databases.INVITATIONS:
return invitationClient
}
}
async function writeACode(db, value) {
async function writeACode(db: string, value: any) {
const client = await getClient(db)
const code = newid()
const code = utils.newid()
await client.store(code, value, getExpirySecondsForDB(db))
return code
}
async function getACode(db, code, deleteCode = true) {
async function getACode(db: string, code: string, deleteCode = true) {
const client = await getClient(db)
const value = await client.get(code)
if (!value) {
@ -42,9 +41,9 @@ async function getACode(db, code, deleteCode = true) {
return value
}
exports.init = async () => {
pwResetClient = new Client(utils.Databases.PW_RESETS)
invitationClient = new Client(utils.Databases.INVITATIONS)
export async function init() {
pwResetClient = new redis.Client(redis.utils.Databases.PW_RESETS)
invitationClient = new redis.Client(redis.utils.Databases.INVITATIONS)
await pwResetClient.init()
await invitationClient.init()
}
@ -52,7 +51,7 @@ exports.init = async () => {
/**
* make sure redis connection is closed.
*/
exports.shutdown = async () => {
export async function shutdown() {
if (pwResetClient) await pwResetClient.finish()
if (invitationClient) await invitationClient.finish()
console.log("Redis shutdown")
@ -65,8 +64,8 @@ exports.shutdown = async () => {
* @param {object} info Info about the user/the reset process.
* @return {Promise<string>} returns the code that was stored to redis.
*/
exports.getResetPasswordCode = async (userId, info) => {
return writeACode(utils.Databases.PW_RESETS, { userId, info })
export async function getResetPasswordCode(userId: string, info: any) {
return writeACode(redis.utils.Databases.PW_RESETS, { userId, info })
}
/**
@ -75,9 +74,12 @@ exports.getResetPasswordCode = async (userId, info) => {
* @param {boolean} deleteCode If the code is used/finished with this will delete it - defaults to true.
* @return {Promise<string>} returns the user ID if it is found
*/
exports.checkResetPasswordCode = async (resetCode, deleteCode = true) => {
export async function checkResetPasswordCode(
resetCode: string,
deleteCode = true
) {
try {
return getACode(utils.Databases.PW_RESETS, resetCode, deleteCode)
return getACode(redis.utils.Databases.PW_RESETS, resetCode, deleteCode)
} catch (err) {
throw "Provided information is not valid, cannot reset password - please try again."
}
@ -89,8 +91,8 @@ exports.checkResetPasswordCode = async (resetCode, deleteCode = true) => {
* @param {object|null} info Information to be carried along with the invitation.
* @return {Promise<string>} returns the code that was stored to redis.
*/
exports.getInviteCode = async (email, info) => {
return writeACode(utils.Databases.INVITATIONS, { email, info })
export async function getInviteCode(email: string, info: any) {
return writeACode(redis.utils.Databases.INVITATIONS, { email, info })
}
/**
@ -99,9 +101,12 @@ exports.getInviteCode = async (email, info) => {
* @param {boolean} deleteCode whether or not the code should be deleted after retrieval - defaults to true.
* @return {Promise<object>} If the code is valid then an email address will be returned.
*/
exports.checkInviteCode = async (inviteCode, deleteCode = true) => {
export async function checkInviteCode(
inviteCode: string,
deleteCode: boolean = true
) {
try {
return getACode(utils.Databases.INVITATIONS, inviteCode, deleteCode)
return getACode(redis.utils.Databases.INVITATIONS, inviteCode, deleteCode)
} catch (err) {
throw "Invitation is not valid or has expired, please request a new one."
}

View File

@ -1,47 +1,47 @@
const { getScopedConfig } = require("@budibase/backend-core/db")
const {
import { db as dbCore, tenancy } from "@budibase/backend-core"
import {
Config,
InternalTemplateBindings,
InternalTemplateBinding,
LOGO_URL,
EmailTemplatePurpose,
} = require("../constants")
const { checkSlashesInUrl } = require("./index")
const {
getGlobalDB,
addTenantToUrl,
} = require("@budibase/backend-core/tenancy")
} from "../constants"
import { checkSlashesInUrl } from "./index"
const BASE_COMPANY = "Budibase"
exports.getSettingsTemplateContext = async (purpose, code = null) => {
const db = getGlobalDB()
export async function getSettingsTemplateContext(
purpose: EmailTemplatePurpose,
code?: string
) {
const db = tenancy.getGlobalDB()
// TODO: use more granular settings in the future if required
let settings = (await getScopedConfig(db, { type: Config.SETTINGS })) || {}
let settings =
(await dbCore.getScopedConfig(db, { type: Config.SETTINGS })) || {}
const URL = settings.platformUrl
const context = {
[InternalTemplateBindings.LOGO_URL]:
const context: any = {
[InternalTemplateBinding.LOGO_URL]:
checkSlashesInUrl(`${URL}/${settings.logoUrl}`) || LOGO_URL,
[InternalTemplateBindings.PLATFORM_URL]: URL,
[InternalTemplateBindings.COMPANY]: settings.company || BASE_COMPANY,
[InternalTemplateBindings.DOCS_URL]:
[InternalTemplateBinding.PLATFORM_URL]: URL,
[InternalTemplateBinding.COMPANY]: settings.company || BASE_COMPANY,
[InternalTemplateBinding.DOCS_URL]:
settings.docsUrl || "https://docs.budibase.com/",
[InternalTemplateBindings.LOGIN_URL]: checkSlashesInUrl(
addTenantToUrl(`${URL}/login`)
[InternalTemplateBinding.LOGIN_URL]: checkSlashesInUrl(
tenancy.addTenantToUrl(`${URL}/login`)
),
[InternalTemplateBindings.CURRENT_DATE]: new Date().toISOString(),
[InternalTemplateBindings.CURRENT_YEAR]: new Date().getFullYear(),
[InternalTemplateBinding.CURRENT_DATE]: new Date().toISOString(),
[InternalTemplateBinding.CURRENT_YEAR]: new Date().getFullYear(),
}
// attach purpose specific context
switch (purpose) {
case EmailTemplatePurpose.PASSWORD_RECOVERY:
context[InternalTemplateBindings.RESET_CODE] = code
context[InternalTemplateBindings.RESET_URL] = checkSlashesInUrl(
addTenantToUrl(`${URL}/builder/auth/reset?code=${code}`)
context[InternalTemplateBinding.RESET_CODE] = code
context[InternalTemplateBinding.RESET_URL] = checkSlashesInUrl(
tenancy.addTenantToUrl(`${URL}/builder/auth/reset?code=${code}`)
)
break
case EmailTemplatePurpose.INVITATION:
context[InternalTemplateBindings.INVITE_CODE] = code
context[InternalTemplateBindings.INVITE_URL] = checkSlashesInUrl(
addTenantToUrl(`${URL}/builder/invite?code=${code}`)
context[InternalTemplateBinding.INVITE_CODE] = code
context[InternalTemplateBinding.INVITE_URL] = checkSlashesInUrl(
tenancy.addTenantToUrl(`${URL}/builder/invite?code=${code}`)
)
break
}