@budibase/worker 2.3.18-alpha.2 → 2.3.18-alpha.20

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.
@@ -38,13 +38,6 @@ function buildInviteMultipleValidation() {
38
38
  ))
39
39
  }
40
40
 
41
- function buildInviteLookupValidation() {
42
- // prettier-ignore
43
- return auth.joiValidator.params(Joi.object({
44
- code: Joi.string().required()
45
- }).unknown(true))
46
- }
47
-
48
41
  const createUserAdminOnly = (ctx: any, next: any) => {
49
42
  if (!ctx.request.body._id) {
50
43
  return auth.adminOnly(ctx, next)
@@ -88,22 +81,34 @@ router
88
81
  .get("/api/global/roles/:appId")
89
82
  .post(
90
83
  "/api/global/users/invite",
91
- auth.adminOnly,
84
+ auth.builderOrAdmin,
92
85
  buildInviteValidation(),
93
86
  controller.invite
94
87
  )
88
+ .post(
89
+ "/api/global/users/onboard",
90
+ auth.builderOrAdmin,
91
+ buildInviteMultipleValidation(),
92
+ controller.onboardUsers
93
+ )
95
94
  .post(
96
95
  "/api/global/users/multi/invite",
97
- auth.adminOnly,
96
+ auth.builderOrAdmin,
98
97
  buildInviteMultipleValidation(),
99
98
  controller.inviteMultiple
100
99
  )
101
100
 
102
101
  // non-global endpoints
102
+ .get("/api/global/users/invite/:code", controller.checkInvite)
103
+ .post(
104
+ "/api/global/users/invite/update/:code",
105
+ auth.builderOrAdmin,
106
+ controller.updateInvite
107
+ )
103
108
  .get(
104
- "/api/global/users/invite/:code",
105
- buildInviteLookupValidation(),
106
- controller.checkInvite
109
+ "/api/global/users/invites",
110
+ auth.builderOrAdmin,
111
+ controller.getUserInvites
107
112
  )
108
113
  .post(
109
114
  "/api/global/users/invite/accept",
@@ -18,6 +18,8 @@ import accountRoutes from "./system/accounts"
18
18
  import restoreRoutes from "./system/restore"
19
19
 
20
20
  let userGroupRoutes = api.groups
21
+ let auditLogRoutes = api.auditLogs
22
+
21
23
  export const routes: Router[] = [
22
24
  configRoutes,
23
25
  userRoutes,
@@ -32,6 +34,7 @@ export const routes: Router[] = [
32
34
  selfRoutes,
33
35
  licenseRoutes,
34
36
  userGroupRoutes,
37
+ auditLogRoutes,
35
38
  migrationRoutes,
36
39
  accountRoutes,
37
40
  restoreRoutes,
package/src/db/index.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import * as core from "@budibase/backend-core"
2
2
  import env from "../environment"
3
3
 
4
- export const init = () => {
5
- const dbConfig: any = {}
4
+ export function init() {
5
+ const dbConfig: any = {
6
+ replication: true,
7
+ find: true,
8
+ }
9
+
6
10
  if (env.isTest() && !env.COUCH_DB_URL) {
7
11
  dbConfig.inMemory = true
12
+ dbConfig.allDbs = true
8
13
  }
14
+
9
15
  core.init({ db: dbConfig })
10
16
  }
@@ -26,8 +26,6 @@ function parseIntSafe(number: any) {
26
26
  }
27
27
  }
28
28
 
29
- const selfHosted = !!parseInt(process.env.SELF_HOSTED || "")
30
-
31
29
  const environment = {
32
30
  // auth
33
31
  MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
@@ -51,7 +49,7 @@ const environment = {
51
49
  CLUSTER_PORT: process.env.CLUSTER_PORT,
52
50
  // flags
53
51
  NODE_ENV: process.env.NODE_ENV,
54
- SELF_HOSTED: selfHosted,
52
+ SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""),
55
53
  LOG_LEVEL: process.env.LOG_LEVEL,
56
54
  MULTI_TENANCY: process.env.MULTI_TENANCY,
57
55
  DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
@@ -71,14 +69,6 @@ const environment = {
71
69
  * Mock the email service in use - links to ethereal hosted emails are logged instead.
72
70
  */
73
71
  ENABLE_EMAIL_TEST_MODE: process.env.ENABLE_EMAIL_TEST_MODE,
74
- /**
75
- * Enable to allow an admin user to login using a password.
76
- * This can be useful to prevent lockout when configuring SSO.
77
- * However, this should be turned OFF by default for security purposes.
78
- */
79
- ENABLE_SSO_MAINTENANCE_MODE: selfHosted
80
- ? process.env.ENABLE_SSO_MAINTENANCE_MODE
81
- : false,
82
72
  _set(key: any, value: any) {
83
73
  process.env[key] = value
84
74
  // @ts-ignore
package/src/index.ts CHANGED
@@ -13,7 +13,15 @@ import { Event } from "@sentry/types/dist/event"
13
13
  import Application from "koa"
14
14
  import { bootstrap } from "global-agent"
15
15
  import * as db from "./db"
16
- import { auth, logging, events, middleware } from "@budibase/backend-core"
16
+ import { sdk as proSdk } from "@budibase/pro"
17
+ import {
18
+ auth,
19
+ logging,
20
+ events,
21
+ middleware,
22
+ queue,
23
+ env as coreEnv,
24
+ } from "@budibase/backend-core"
17
25
  db.init()
18
26
  import Koa from "koa"
19
27
  import koaBody from "koa-body"
@@ -23,9 +31,15 @@ import * as redis from "./utilities/redis"
23
31
  const Sentry = require("@sentry/node")
24
32
  const koaSession = require("koa-session")
25
33
  const logger = require("koa-pino-logger")
34
+ const { userAgent } = require("koa-useragent")
35
+
26
36
  import destroyable from "server-destroy"
27
37
 
28
- if (env.ENABLE_SSO_MAINTENANCE_MODE) {
38
+ // configure events to use the pro audit log write
39
+ // can't integrate directly into backend-core due to cyclic issues
40
+ events.processors.init(proSdk.auditLogs.write)
41
+
42
+ if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE) {
29
43
  console.warn(
30
44
  "Warning: ENABLE_SSO_MAINTENANCE_MODE is set. It is recommended this flag is disabled if maintenance is not in progress"
31
45
  )
@@ -43,6 +57,7 @@ app.use(koaBody({ multipart: true }))
43
57
  app.use(koaSession(app))
44
58
  app.use(middleware.logging)
45
59
  app.use(logger(logging.pinoSettings()))
60
+ app.use(userAgent)
46
61
 
47
62
  // authentication
48
63
  app.use(auth.passport.initialize())
@@ -78,6 +93,7 @@ server.on("close", async () => {
78
93
  console.log("Server Closed")
79
94
  await redis.shutdown()
80
95
  await events.shutdown()
96
+ await queue.shutdown()
81
97
  if (!env.isTest()) {
82
98
  process.exit(errCode)
83
99
  }
@@ -58,8 +58,8 @@ export const reset = async (email: string) => {
58
58
  }
59
59
 
60
60
  // exit if user has sso
61
- if (await userSdk.isPreventSSOPasswords(user)) {
62
- throw new HTTPError("SSO user cannot reset password", 400)
61
+ if (await userSdk.isPreventPasswordActions(user)) {
62
+ throw new HTTPError("Password reset is disabled for this user", 400)
63
63
  }
64
64
 
65
65
  // send password reset
@@ -1,26 +1,50 @@
1
1
  import { structures } from "../../../tests"
2
- import * as users from "../users"
3
- import env from "../../../environment"
4
2
  import { mocks } from "@budibase/backend-core/tests"
3
+ import { env } from "@budibase/backend-core"
4
+ import * as users from "../users"
5
5
  import { CloudAccount } from "@budibase/types"
6
+ import { isPreventPasswordActions } from "../users"
7
+
8
+ jest.mock("@budibase/pro")
9
+ import * as _pro from "@budibase/pro"
10
+ const pro = jest.mocked(_pro, true)
6
11
 
7
12
  describe("users", () => {
8
- describe("isPreventSSOPasswords", () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks()
15
+ })
16
+
17
+ describe("isPreventPasswordActions", () => {
18
+ it("returns false for non sso user", async () => {
19
+ const user = structures.users.user()
20
+ const result = await users.isPreventPasswordActions(user)
21
+ expect(result).toBe(false)
22
+ })
23
+
9
24
  it("returns true for sso account user", async () => {
10
25
  const user = structures.users.user()
11
26
  mocks.accounts.getAccount.mockReturnValue(
12
27
  Promise.resolve(structures.accounts.ssoAccount() as CloudAccount)
13
28
  )
14
- const result = await users.isPreventSSOPasswords(user)
29
+ const result = await users.isPreventPasswordActions(user)
15
30
  expect(result).toBe(true)
16
31
  })
17
32
 
18
33
  it("returns true for sso user", async () => {
19
34
  const user = structures.users.ssoUser()
20
- const result = await users.isPreventSSOPasswords(user)
35
+ const result = await users.isPreventPasswordActions(user)
21
36
  expect(result).toBe(true)
22
37
  })
23
38
 
39
+ describe("enforced sso", () => {
40
+ it("returns true for all users when sso is enforced", async () => {
41
+ const user = structures.users.user()
42
+ pro.features.isSSOEnforced.mockReturnValue(Promise.resolve(true))
43
+ const result = await users.isPreventPasswordActions(user)
44
+ expect(result).toBe(true)
45
+ })
46
+ })
47
+
24
48
  describe("sso maintenance mode", () => {
25
49
  beforeEach(() => {
26
50
  env._set("ENABLE_SSO_MAINTENANCE_MODE", true)
@@ -33,7 +57,7 @@ describe("users", () => {
33
57
  describe("non-admin user", () => {
34
58
  it("returns true", async () => {
35
59
  const user = structures.users.ssoUser()
36
- const result = await users.isPreventSSOPasswords(user)
60
+ const result = await users.isPreventPasswordActions(user)
37
61
  expect(result).toBe(true)
38
62
  })
39
63
  })
@@ -43,7 +67,7 @@ describe("users", () => {
43
67
  const user = structures.users.ssoUser({
44
68
  user: structures.users.adminUser(),
45
69
  })
46
- const result = await users.isPreventSSOPasswords(user)
70
+ const result = await users.isPreventPasswordActions(user)
47
71
  expect(result).toBe(false)
48
72
  })
49
73
  })
@@ -14,6 +14,7 @@ import {
14
14
  users as usersCore,
15
15
  utils,
16
16
  ViewName,
17
+ env as coreEnv,
17
18
  } from "@budibase/backend-core"
18
19
  import {
19
20
  AccountMetadata,
@@ -34,7 +35,7 @@ import {
34
35
  } from "@budibase/types"
35
36
  import { sendEmail } from "../../utilities/email"
36
37
  import { EmailTemplatePurpose } from "../../constants"
37
- import { groups as groupsSdk } from "@budibase/pro"
38
+ import * as pro from "@budibase/pro"
38
39
  import * as accountSdk from "../accounts"
39
40
 
40
41
  const PAGE_LIMIT = 8
@@ -56,11 +57,22 @@ export const countUsersByApp = async (appId: string) => {
56
57
  }
57
58
  }
58
59
 
60
+ export const getUsersByAppAccess = async (appId?: string) => {
61
+ const opts: any = {
62
+ include_docs: true,
63
+ limit: 50,
64
+ }
65
+ let response: User[] = await usersCore.searchGlobalUsersByAppAccess(
66
+ appId,
67
+ opts
68
+ )
69
+ return response
70
+ }
71
+
59
72
  export const paginatedUsers = async ({
60
73
  page,
61
74
  email,
62
75
  appId,
63
- userIds,
64
76
  }: SearchUsersRequest = {}) => {
65
77
  const db = tenancy.getGlobalDB()
66
78
  // get one extra document, to have the next page
@@ -122,8 +134,8 @@ const buildUser = async (
122
134
 
123
135
  let hashedPassword
124
136
  if (password) {
125
- if (await isPreventSSOPasswords(user)) {
126
- throw new HTTPError("SSO user cannot set password", 400)
137
+ if (await isPreventPasswordActions(user)) {
138
+ throw new HTTPError("Password change is disabled for this user", 400)
127
139
  }
128
140
  hashedPassword = opts.hashPassword ? await utils.hash(password) : password
129
141
  } else if (dbUser) {
@@ -188,13 +200,18 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
188
200
  }
189
201
  }
190
202
 
191
- export async function isPreventSSOPasswords(user: User) {
203
+ export async function isPreventPasswordActions(user: User) {
192
204
  // when in maintenance mode we allow sso users with the admin role
193
205
  // to perform any password action - this prevents lockout
194
- if (env.ENABLE_SSO_MAINTENANCE_MODE && user.admin?.global) {
206
+ if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE && user.admin?.global) {
195
207
  return false
196
208
  }
197
209
 
210
+ // SSO is enforced for all users
211
+ if (await pro.features.isSSOEnforced()) {
212
+ return true
213
+ }
214
+
198
215
  // Check local sso
199
216
  if (isSSOUser(user)) {
200
217
  return true
@@ -228,7 +245,7 @@ export const save = async (
228
245
  const tenantId = tenancy.getTenantId()
229
246
  const db = tenancy.getGlobalDB()
230
247
 
231
- let { email, _id, userGroups = [] } = user
248
+ let { email, _id, userGroups = [], roles } = user
232
249
 
233
250
  if (!email && !_id) {
234
251
  throw new Error("_id or email is required")
@@ -270,6 +287,10 @@ export const save = async (
270
287
  builtUser.roles = dbUser.roles
271
288
  }
272
289
 
290
+ if (!dbUser && roles?.length) {
291
+ builtUser.roles = { ...roles }
292
+ }
293
+
273
294
  // make sure we set the _id field for a new user
274
295
  // Also if this is a new user, associate groups with them
275
296
  let groupPromises = []
@@ -278,7 +299,7 @@ export const save = async (
278
299
 
279
300
  if (userGroups.length > 0) {
280
301
  for (let groupId of userGroups) {
281
- groupPromises.push(groupsSdk.addUsers(groupId, [_id]))
302
+ groupPromises.push(pro.groups.addUsers(groupId, [_id]))
282
303
  }
283
304
  }
284
305
  }
@@ -456,7 +477,7 @@ export const bulkCreate = async (
456
477
  const groupPromises = []
457
478
  const createdUserIds = saved.map(user => user._id)
458
479
  for (let groupId of groups) {
459
- groupPromises.push(groupsSdk.addUsers(groupId, createdUserIds))
480
+ groupPromises.push(pro.groups.addUsers(groupId, createdUserIds))
460
481
  }
461
482
  await Promise.all(groupPromises)
462
483
  }
@@ -631,7 +652,7 @@ export const invite = async (
631
652
  }
632
653
  await sendEmail(user.email, EmailTemplatePurpose.INVITATION, opts)
633
654
  response.successful.push({ email: user.email })
634
- await events.user.invited()
655
+ await events.user.invited(user.email)
635
656
  } catch (e) {
636
657
  console.error(`Failed to send email invitation email=${user.email}`, e)
637
658
  response.unsuccessful.push({
@@ -0,0 +1,26 @@
1
+ import { AuditLogSearchParams, SearchAuditLogsResponse } from "@budibase/types"
2
+ import TestConfiguration from "../TestConfiguration"
3
+ import { TestAPI } from "./base"
4
+
5
+ export class AuditLogAPI extends TestAPI {
6
+ constructor(config: TestConfiguration) {
7
+ super(config)
8
+ }
9
+
10
+ search = async (search: AuditLogSearchParams) => {
11
+ const res = await this.request
12
+ .post("/api/global/auditlogs/search")
13
+ .send(search)
14
+ .set(this.config.defaultHeaders())
15
+ .expect("Content-Type", /json/)
16
+ .expect(200)
17
+ return res.body as SearchAuditLogsResponse
18
+ }
19
+
20
+ download = (search: AuditLogSearchParams) => {
21
+ const query = encodeURIComponent(JSON.stringify(search))
22
+ return this.request
23
+ .get(`/api/global/auditlogs/download?query=${query}`)
24
+ .set(this.config.defaultHeaders())
25
+ }
26
+ }
@@ -14,6 +14,14 @@ export class ConfigAPI extends TestAPI {
14
14
  .expect("Content-Type", /json/)
15
15
  }
16
16
 
17
+ getPublicSettings = () => {
18
+ return this.request
19
+ .get(`/api/global/configs/public`)
20
+ .set(this.config.defaultHeaders())
21
+ .expect(200)
22
+ .expect("Content-Type", /json/)
23
+ }
24
+
17
25
  saveConfig = (data: any) => {
18
26
  return this.request
19
27
  .post(`/api/global/configs`)
@@ -13,6 +13,7 @@ export class EmailAPI extends TestAPI {
13
13
  email: "test@test.com",
14
14
  purpose,
15
15
  tenantId: this.config.getTenantId(),
16
+ userId: this.config.user?._id!,
16
17
  })
17
18
  .set(this.config.defaultHeaders())
18
19
  .expect("Content-Type", /json/)
@@ -14,6 +14,7 @@ import { GroupsAPI } from "./groups"
14
14
  import { RolesAPI } from "./roles"
15
15
  import { TemplatesAPI } from "./templates"
16
16
  import { LicenseAPI } from "./license"
17
+ import { AuditLogAPI } from "./auditLogs"
17
18
  export default class API {
18
19
  accounts: AccountAPI
19
20
  auth: AuthAPI
@@ -30,6 +31,7 @@ export default class API {
30
31
  roles: RolesAPI
31
32
  templates: TemplatesAPI
32
33
  license: LicenseAPI
34
+ auditLogs: AuditLogAPI
33
35
 
34
36
  constructor(config: TestConfiguration) {
35
37
  this.accounts = new AccountAPI(config)
@@ -47,5 +49,6 @@ export default class API {
47
49
  this.roles = new RolesAPI(config)
48
50
  this.templates = new TemplatesAPI(config)
49
51
  this.license = new LicenseAPI(config)
52
+ this.auditLogs = new AuditLogAPI(config)
50
53
  }
51
54
  }
@@ -1,9 +1,15 @@
1
- import { Config } from "../../constants"
2
1
  import { utils } from "@budibase/backend-core"
2
+ import {
3
+ SettingsConfig,
4
+ ConfigType,
5
+ SMTPConfig,
6
+ GoogleConfig,
7
+ OIDCConfig,
8
+ } from "@budibase/types"
3
9
 
4
- export function oidc(conf?: any) {
10
+ export function oidc(conf?: any): OIDCConfig {
5
11
  return {
6
- type: Config.OIDC,
12
+ type: ConfigType.OIDC,
7
13
  config: {
8
14
  configs: [
9
15
  {
@@ -21,9 +27,9 @@ export function oidc(conf?: any) {
21
27
  }
22
28
  }
23
29
 
24
- export function google(conf?: any) {
30
+ export function google(conf?: any): GoogleConfig {
25
31
  return {
26
- type: Config.GOOGLE,
32
+ type: ConfigType.GOOGLE,
27
33
  config: {
28
34
  clientID: "clientId",
29
35
  clientSecret: "clientSecret",
@@ -33,9 +39,9 @@ export function google(conf?: any) {
33
39
  }
34
40
  }
35
41
 
36
- export function smtp(conf?: any) {
42
+ export function smtp(conf?: any): SMTPConfig {
37
43
  return {
38
- type: Config.SMTP,
44
+ type: ConfigType.SMTP,
39
45
  config: {
40
46
  port: 12345,
41
47
  host: "smtptesthost.com",
@@ -47,25 +53,26 @@ export function smtp(conf?: any) {
47
53
  }
48
54
  }
49
55
 
50
- export function smtpEthereal() {
56
+ export function smtpEthereal(): SMTPConfig {
51
57
  return {
52
- type: Config.SMTP,
58
+ type: ConfigType.SMTP,
53
59
  config: {
54
60
  port: 587,
55
61
  host: "smtp.ethereal.email",
62
+ from: "testfrom@test.com",
56
63
  secure: false,
57
64
  auth: {
58
- user: "don.bahringer@ethereal.email",
59
- pass: "yCKSH8rWyUPbnhGYk9",
65
+ user: "wyatt.zulauf29@ethereal.email",
66
+ pass: "tEwDtHBWWxusVWAPfa",
60
67
  },
61
68
  connectionTimeout: 1000, // must be less than the jest default of 5000
62
69
  },
63
70
  }
64
71
  }
65
72
 
66
- export function settings(conf?: any) {
73
+ export function settings(conf?: any): SettingsConfig {
67
74
  return {
68
- type: Config.SETTINGS,
75
+ type: ConfigType.SETTINGS,
69
76
  config: {
70
77
  platformUrl: "http://localhost:10000",
71
78
  logoUrl: "",
@@ -1,11 +1,11 @@
1
1
  import env from "../environment"
2
- import { EmailTemplatePurpose, TemplateType, Config } from "../constants"
2
+ import { EmailTemplatePurpose, TemplateType } from "../constants"
3
3
  import { getTemplateByPurpose } from "../constants/templates"
4
4
  import { getSettingsTemplateContext } from "./templates"
5
5
  import { processString } from "@budibase/string-templates"
6
6
  import { getResetPasswordCode, getInviteCode } from "./redis"
7
- import { User, Database } from "@budibase/types"
8
- import { tenancy, db as dbCore } from "@budibase/backend-core"
7
+ import { User, SMTPInnerConfig } from "@budibase/types"
8
+ import { configs } from "@budibase/backend-core"
9
9
  const nodemailer = require("nodemailer")
10
10
 
11
11
  type SendEmailOpts = {
@@ -36,24 +36,24 @@ const FULL_EMAIL_PURPOSES = [
36
36
  EmailTemplatePurpose.CUSTOM,
37
37
  ]
38
38
 
39
- function createSMTPTransport(config: any) {
39
+ function createSMTPTransport(config?: SMTPInnerConfig) {
40
40
  let options: any
41
- let secure = config.secure
41
+ let secure = config?.secure
42
42
  // default it if not specified
43
43
  if (secure == null) {
44
- secure = config.port === 465
44
+ secure = config?.port === 465
45
45
  }
46
46
  if (!TEST_MODE) {
47
47
  options = {
48
- port: config.port,
49
- host: config.host,
48
+ port: config?.port,
49
+ host: config?.host,
50
50
  secure: secure,
51
- auth: config.auth,
51
+ auth: config?.auth,
52
52
  }
53
53
  options.tls = {
54
54
  rejectUnauthorized: false,
55
55
  }
56
- if (config.connectionTimeout) {
56
+ if (config?.connectionTimeout) {
57
57
  options.connectionTimeout = config.connectionTimeout
58
58
  }
59
59
  } else {
@@ -134,57 +134,16 @@ async function buildEmail(
134
134
  })
135
135
  }
136
136
 
137
- /**
138
- * Utility function for finding most valid SMTP configuration.
139
- * @param {object} db The CouchDB database which is to be looked up within.
140
- * @param {string|null} workspaceId If using finer grain control of configs a workspace can be used.
141
- * @param {boolean|null} automation Whether or not the configuration is being fetched for an email automation.
142
- * @return {Promise<object|null>} returns the SMTP configuration if it exists
143
- */
144
- async function getSmtpConfiguration(
145
- db: Database,
146
- workspaceId?: string,
147
- automation?: boolean
148
- ) {
149
- const params: any = {
150
- type: Config.SMTP,
151
- }
152
- if (workspaceId) {
153
- params.workspace = workspaceId
154
- }
155
-
156
- const customConfig = await dbCore.getScopedConfig(db, params)
157
-
158
- if (customConfig) {
159
- return customConfig
160
- }
161
-
162
- // Use an SMTP fallback configuration from env variables
163
- if (!automation && env.SMTP_FALLBACK_ENABLED) {
164
- return {
165
- port: env.SMTP_PORT,
166
- host: env.SMTP_HOST,
167
- secure: false,
168
- from: env.SMTP_FROM_ADDRESS,
169
- auth: {
170
- user: env.SMTP_USER,
171
- pass: env.SMTP_PASSWORD,
172
- },
173
- }
174
- }
175
- }
176
-
177
137
  /**
178
138
  * Checks if a SMTP config exists based on passed in parameters.
179
139
  * @return {Promise<boolean>} returns true if there is a configuration that can be used.
180
140
  */
181
- export async function isEmailConfigured(workspaceId?: string) {
141
+ export async function isEmailConfigured() {
182
142
  // when "testing" or smtp fallback is enabled simply return true
183
143
  if (TEST_MODE || env.SMTP_FALLBACK_ENABLED) {
184
144
  return true
185
145
  }
186
- const db = tenancy.getGlobalDB()
187
- const config = await getSmtpConfiguration(db, workspaceId)
146
+ const config = await configs.getSMTPConfig()
188
147
  return config != null
189
148
  }
190
149
 
@@ -202,22 +161,17 @@ export async function sendEmail(
202
161
  purpose: EmailTemplatePurpose,
203
162
  opts: SendEmailOpts
204
163
  ) {
205
- const db = tenancy.getGlobalDB()
206
- let config =
207
- (await getSmtpConfiguration(db, opts?.workspaceId, opts?.automation)) || {}
208
- if (Object.keys(config).length === 0 && !TEST_MODE) {
164
+ const config = await configs.getSMTPConfig(opts?.automation)
165
+ if (!config && !TEST_MODE) {
209
166
  throw "Unable to find SMTP configuration."
210
167
  }
211
168
  const transport = createSMTPTransport(config)
212
169
  // if there is a link code needed this will retrieve it
213
170
  const code = await getLinkCode(purpose, email, opts.user, opts?.info)
214
- let context
215
- if (code) {
216
- context = await getSettingsTemplateContext(purpose, code)
217
- }
171
+ let context = await getSettingsTemplateContext(purpose, code)
218
172
 
219
173
  let message: any = {
220
- from: opts?.from || config.from,
174
+ from: opts?.from || config?.from,
221
175
  html: await buildEmail(purpose, email, context, {
222
176
  user: opts?.user,
223
177
  contents: opts?.contents,
@@ -231,9 +185,9 @@ export async function sendEmail(
231
185
  bcc: opts?.bcc,
232
186
  }
233
187
 
234
- if (opts?.subject || config.subject) {
188
+ if (opts?.subject || config?.subject) {
235
189
  message.subject = await processString(
236
- opts?.subject || config.subject,
190
+ (opts?.subject || config?.subject) as string,
237
191
  context
238
192
  )
239
193
  }
@@ -249,7 +203,7 @@ export async function sendEmail(
249
203
  * @param {object} config an SMTP configuration - this is based on the nodemailer API.
250
204
  * @return {Promise<boolean>} returns true if the configuration is valid.
251
205
  */
252
- export async function verifyConfig(config: any) {
206
+ export async function verifyConfig(config: SMTPInnerConfig) {
253
207
  const transport = createSMTPTransport(config)
254
208
  await transport.verify()
255
209
  }