UBERF-9661: Use MAIL_URL env for mail integration ()

* UBERF-9661: Add mail url env

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-9661: Use MAIL_URL env

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-9661: Fix test

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-9661: Remove SES_URL

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* UBERF-9661: Update configs

Signed-off-by: Artem Savchenko <armisav@gmail.com>

---------

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artyom Savchenko 2025-03-19 12:56:09 +07:00 committed by GitHub
parent 0334a411cf
commit ed5983a9f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 77 additions and 77 deletions
.vscode
dev
pods/server/src
server-plugins
gmail-resources/src
notification-resources/src
notification/src
server
account-service/src
account/src
server/src

8
.vscode/launch.json vendored
View File

@ -190,7 +190,7 @@
"ACCOUNT_PORT": "3000",
"FRONT_URL": "http://localhost:8080",
"STATS_URL": "http://huly.local:4900",
"SES_URL": "",
"MAIL_URL": "",
// "DB_NS": "account-2",
// "WS_LIVENESS_DAYS": "1",
"MINIO_ACCESS_KEY": "minioadmin",
@ -221,7 +221,7 @@
"ACCOUNT_PORT": "3003",
"FRONT_URL": "http://localhost:8083",
"STATS_URL": "http://huly.local:4901",
"SES_URL": "",
"MAIL_URL": "",
// "DB_NS": "account-2",
// "WS_LIVENESS_DAYS": "1",
"MINIO_ACCESS_KEY": "minioadmin",
@ -267,7 +267,7 @@
"TRANSACTOR_URL": "ws://localhost:3333",
"ACCOUNTS_URL": "http://localhost:3000",
"FRONT_URL": "http://localhost:8080",
"SES_URL": "",
"MAIL_URL": "",
"MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin",
"MINIO_ENDPOINT": "localhost",
@ -300,7 +300,7 @@
"TRANSACTOR_URL": "ws://localhost:3332",
"ACCOUNTS_URL": "http://localhost:3000",
"FRONT_URL": "http://localhost:8080",
"SES_URL": "",
"MAIL_URL": "",
"MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin",
"MINIO_ENDPOINT": "localhost",

View File

@ -99,7 +99,7 @@ services:
- REGION_INFO=|Mongo;cockroach|CockroachDB
# - REGION_INFO=cockroach|CockroachDB
- TRANSACTOR_URL=ws://huly.local:3333,ws://huly.local:3332;;cockroach,
- SES_URL=
- MAIL_URL=
- STORAGE_CONFIG=${STORAGE_CONFIG}
- FRONT_URL=http://huly.local:8087
- RESERVED_DB_NAMES=telegram,gmail,github
@ -135,7 +135,7 @@ services:
- SERVER_SECRET=secret
- DB_URL=${MONGO_URL}
- STATS_URL=http://huly.local:4900
- SES_URL=
- MAIL_URL=
- STORAGE_CONFIG=${STORAGE_CONFIG}
- RESERVED_DB_NAMES=telegram,gmail,github
- MODEL_ENABLED=*
@ -161,7 +161,7 @@ services:
- SERVER_SECRET=secret
- DB_URL=postgresql://root@huly.local:26257/defaultdb?sslmode=disable
- STATS_URL=http://huly.local:4900
- SES_URL=
- MAIL_URL=
- REGION=cockroach
- STORAGE_CONFIG=${STORAGE_CONFIG}
- RESERVED_DB_NAMES=telegram,gmail,github
@ -259,7 +259,7 @@ services:
- STORAGE_CONFIG=${STORAGE_CONFIG}
- FRONT_URL=http://huly.local:8087
# - APM_SERVER_URL=http://apm-server:8200
- SES_URL=''
- MAIL_URL=''
- ACCOUNTS_URL=http://huly.local:3000
- LAST_NAME_FIRST=true
- BRANDING_PATH=/var/cfg/branding.json
@ -294,7 +294,7 @@ services:
- STORAGE_CONFIG=${STORAGE_CONFIG}
- FRONT_URL=http://huly.local:8087
# - APM_SERVER_URL=http://apm-server:8200
- SES_URL=''
- MAIL_URL=''
- ACCOUNTS_URL=http://huly.local:3000
- LAST_NAME_FIRST=true
- BRANDING_PATH=/var/cfg/branding.json

View File

@ -42,7 +42,7 @@ services:
- SERVER_SECRET=secret
- MONGO_URL=mongodb://huly.local:27017?compressors=snappy
- TRANSACTOR_URL=ws://transactor:3333;ws://localhost:3333
- SES_URL=
- MAIL_URL=
- STORAGE_CONFIG=${STORAGE_CONFIG}
- FRONT_URL=http://localhost:8087
- RESERVED_DB_NAMES=telegram,gmail,github
@ -129,7 +129,7 @@ services:
- FRONT_URL=http://localhost:8087
- UPLOAD_URL=http://localhost:8087/files
# - APM_SERVER_URL=http://apm-server:8200
- SES_URL=''
- MAIL_URL=''
- ACCOUNTS_URL=http://account:3000
- LAST_NAME_FIRST=true
- ELASTIC_INDEX_NAME=local_storage_index

View File

@ -71,9 +71,9 @@ setMetadata(contactPlugin.metadata.LastNameFirst, lastNameFirst)
setMetadata(serverCore.metadata.FrontUrl, config.frontUrl)
setMetadata(serverCore.metadata.FilesUrl, config.filesUrl)
setMetadata(serverToken.metadata.Secret, config.serverSecret)
setMetadata(serverNotification.metadata.SesUrl, config.sesUrl ?? '')
setMetadata(serverNotification.metadata.SesAuthToken, config.sesAuthToken)
setMetadata(serverNotification.metadata.WebPushUrl, config.webPushUrl ?? config.sesUrl)
setMetadata(serverNotification.metadata.MailUrl, config.mailUrl ?? '')
setMetadata(serverNotification.metadata.MailAuthToken, config.mailAuthToken)
setMetadata(serverNotification.metadata.WebPushUrl, config.webPushUrl)
setMetadata(serverTelegram.metadata.BotUrl, process.env.TELEGRAM_BOT_URL)
setMetadata(serverAiBot.metadata.EndpointURL, process.env.AI_BOT_URL)
setMetadata(serverCalendar.metadata.EndpointURL, process.env.CALENDAR_URL)

View File

@ -112,18 +112,18 @@ export async function sendEmailNotification (
receiver: string
): Promise<void> {
try {
const sesURL = getMetadata(serverNotification.metadata.SesUrl)
if (sesURL === undefined || sesURL === '') {
const mailURL = getMetadata(serverNotification.metadata.MailUrl)
if (mailURL === undefined || mailURL === '') {
ctx.error('Please provide email service url to enable email notifications.')
return
}
const sesAuth: string | undefined = getMetadata(serverNotification.metadata.SesAuthToken)
await fetch(concatLink(sesURL, '/send'), {
const mailAuth: string | undefined = getMetadata(serverNotification.metadata.MailAuthToken)
await fetch(concatLink(mailURL, '/send'), {
method: 'post',
keepalive: true,
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
...(mailAuth != null ? { Authorization: `Bearer ${mailAuth}` } : {})
},
body: JSON.stringify({
text,

View File

@ -157,7 +157,7 @@ export async function createPushNotification (
): Promise<void> {
const pushURL: string | undefined = getMetadata(serverNotification.metadata.WebPushUrl)
// TODO: Remove auth token after migration to new services
const authToken: string | undefined = getMetadata(serverNotification.metadata.SesAuthToken)
const authToken: string | undefined = getMetadata(serverNotification.metadata.MailAuthToken)
if (pushURL === undefined || pushURL === '') return
const userSubscriptions = subscriptions.filter((it) => it.user === target)
const data: PushData = {
@ -190,7 +190,7 @@ export async function createPushNotification (
async function sendPushToSubscription (
pushURL: string,
sesAuth: string | undefined,
mailAuth: string | undefined,
control: TriggerControl,
targetUser: AccountUuid,
subscriptions: PushSubscription[],
@ -204,7 +204,7 @@ async function sendPushToSubscription (
keepalive: true,
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
...(mailAuth != null ? { Authorization: `Bearer ${mailAuth}` } : {})
},
body: JSON.stringify({
subscriptions,

View File

@ -129,8 +129,8 @@ export const PUSH_NOTIFICATION_TITLE_SIZE = 80
*/
export default plugin(serverNotificationId, {
metadata: {
SesUrl: '' as Metadata<string>,
SesAuthToken: '' as Metadata<string>,
MailUrl: '' as Metadata<string>,
MailAuthToken: '' as Metadata<string>,
WebPushUrl: '' as Metadata<string>,
InboxOnlyNotifications: '' as Metadata<boolean>
},

View File

@ -72,8 +72,8 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap
}
})
const ses = process.env.SES_URL
const sesAuthToken = process.env.SES_AUTH_TOKEN
const mailUrl = process.env.MAIL_URL
const mailAuthToken = process.env.MAIL_AUTH_TOKEN
const frontURL = process.env.FRONT_URL
const productName = process.env.PRODUCT_NAME
@ -95,8 +95,8 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap
setMetadata(account.metadata.ProductName, productName)
setMetadata(account.metadata.OtpTimeToLiveSec, parseInt(process.env.OTP_TIME_TO_LIVE ?? '60'))
setMetadata(account.metadata.OtpRetryDelaySec, parseInt(process.env.OTP_RETRY_DELAY ?? '60'))
setMetadata(account.metadata.SES_URL, ses)
setMetadata(account.metadata.SES_AUTH_TOKEN, sesAuthToken)
setMetadata(account.metadata.MAIL_URL, mailUrl)
setMetadata(account.metadata.MAIL_AUTH_TOKEN, mailAuthToken)
setMetadata(account.metadata.FrontURL, frontURL)
setMetadata(account.metadata.WsLivenessDays, wsLivenessDays)

View File

@ -58,7 +58,7 @@ import {
getPersonName,
getInviteEmail,
getFrontUrl,
getSesUrl,
getMailUrl,
getSocialIdByKey,
getWorkspaceInvite,
loginOrSignUpWithProvider,
@ -654,9 +654,9 @@ describe('account utils', () => {
return 30
case accountPlugin.metadata.OtpTimeToLiveSec:
return 60
case accountPlugin.metadata.SES_URL:
case accountPlugin.metadata.MAIL_URL:
return sesUrl
case accountPlugin.metadata.SES_AUTH_TOKEN:
case accountPlugin.metadata.MAIL_AUTH_TOKEN:
return sesAuth
case accountPlugin.metadata.ProductName:
return 'Test Product'
@ -833,9 +833,9 @@ describe('account utils', () => {
mockFetch.mockResolvedValue({ ok: true })
;(getMetadata as jest.Mock).mockImplementation((key) => {
switch (key) {
case accountPlugin.metadata.SES_URL:
case accountPlugin.metadata.MAIL_URL:
return 'https://ses.example.com'
case accountPlugin.metadata.SES_AUTH_TOKEN:
case accountPlugin.metadata.MAIL_AUTH_TOKEN:
return 'test-auth-token'
case accountPlugin.metadata.ProductName:
return 'Test Product'
@ -871,14 +871,14 @@ describe('account utils', () => {
)
})
test('should throw error if SES_URL is missing', async () => {
test('should throw error if MAIL_URL is missing', async () => {
;(getMetadata as jest.Mock).mockReturnValue(undefined)
await expect(sendEmailConfirmation(mockCtx, mockBranding, account, email)).rejects.toThrow(
new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
)
expect(mockCtx.error).toHaveBeenCalledWith('Please provide SES_URL to enable email confirmations.')
expect(mockCtx.error).toHaveBeenCalledWith('Please provide MAIL_URL to enable email confirmations.')
})
test('should use branding front URL if available', async () => {
@ -999,9 +999,9 @@ describe('account utils', () => {
beforeEach(() => {
;(getMetadata as jest.Mock).mockImplementation((key) => {
switch (key) {
case accountPlugin.metadata.SES_URL:
case accountPlugin.metadata.MAIL_URL:
return 'https://ses.example.com'
case accountPlugin.metadata.SES_AUTH_TOKEN:
case accountPlugin.metadata.MAIL_AUTH_TOKEN:
return 'test-token'
default:
return undefined
@ -1832,9 +1832,9 @@ describe('account utils', () => {
beforeEach(() => {
;(getMetadata as jest.Mock).mockImplementation((key) => {
switch (key) {
case accountPlugin.metadata.SES_URL:
case accountPlugin.metadata.MAIL_URL:
return 'https://ses.example.com'
case accountPlugin.metadata.SES_AUTH_TOKEN:
case accountPlugin.metadata.MAIL_AUTH_TOKEN:
return 'test-token'
default:
return undefined
@ -1847,17 +1847,17 @@ describe('account utils', () => {
})
test('should return SES URL and auth token when configured', () => {
const result = getSesUrl()
const result = getMailUrl()
expect(result).toEqual({
sesURL: 'https://ses.example.com',
sesAuth: 'test-token'
mailURL: 'https://ses.example.com',
mailAuth: 'test-token'
})
})
test('should throw error when SES URL not configured', () => {
;(getMetadata as jest.Mock).mockReturnValue(undefined)
expect(() => getSesUrl()).toThrow('Please provide email service url')
expect(() => getMailUrl()).toThrow('Please provide email service url')
})
})

View File

@ -77,7 +77,7 @@ import {
getPersonName,
getRegions,
getRolePower,
getSesUrl,
getMailUrl,
getSocialIdByKey,
getWorkspaceById,
getWorkspaceInfoWithStatusById,
@ -216,14 +216,14 @@ export async function signUp (
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
}
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
const forceConfirmation = sesURL !== undefined && sesURL !== ''
const mailURL = getMetadata(accountPlugin.metadata.MAIL_URL)
const forceConfirmation = mailURL !== undefined && mailURL !== ''
if (forceConfirmation) {
const normalizedEmail = cleanEmail(email)
await sendEmailConfirmation(ctx, branding, account, normalizedEmail)
} else {
ctx.warn('Please provide SES_URL to enable sign up email confirmations.')
ctx.warn('Please provide MAIL_URL to enable sign up email confirmations.')
await confirmEmail(ctx, db, account, email)
}
@ -760,7 +760,7 @@ export async function requestPasswordReset (
)
}
const { sesURL, sesAuth } = getSesUrl()
const { mailURL, mailAuth } = getMailUrl()
const front = getFrontUrl(branding)
const token = generateToken(account.uuid, undefined, {
@ -773,11 +773,11 @@ export async function requestPasswordReset (
const html = await translate(accountPlugin.string.RecoveryHTML, { link }, lang)
const subject = await translate(accountPlugin.string.RecoverySubject, {}, lang)
await fetch(concatLink(sesURL, '/send'), {
await fetch(concatLink(mailURL, '/send'), {
method: 'post',
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
...(mailAuth != null ? { Authorization: `Bearer ${mailAuth}` } : {})
},
body: JSON.stringify({
text,

View File

@ -11,8 +11,8 @@ export const accountId = 'account' as Plugin
export const accountPlugin = plugin(accountId, {
metadata: {
FrontURL: '' as Metadata<string>,
SES_URL: '' as Metadata<string>,
SES_AUTH_TOKEN: '' as Metadata<string>,
MAIL_URL: '' as Metadata<string>,
MAIL_AUTH_TOKEN: '' as Metadata<string>,
ProductName: '' as Metadata<string>,
Transactors: '' as Metadata<string>,
OtpTimeToLiveSec: '' as Metadata<number>,

View File

@ -390,12 +390,12 @@ export async function sendOtpEmail (
otp: string,
email: string
): Promise<void> {
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
if (sesURL === undefined || sesURL === '') {
const mailURL = getMetadata(accountPlugin.metadata.MAIL_URL)
if (mailURL === undefined || mailURL === '') {
ctx.error('Please provide email service url to enable email otp')
return
}
const sesAuth = getMetadata(accountPlugin.metadata.SES_AUTH_TOKEN)
const mailAuth = getMetadata(accountPlugin.metadata.MAIL_AUTH_TOKEN)
const lang = branding?.language
const app = branding?.title ?? getMetadata(accountPlugin.metadata.ProductName)
@ -405,11 +405,11 @@ export async function sendOtpEmail (
const subject = await translate(accountPlugin.string.OtpSubject, { code: otp, app }, lang)
const to = email
await fetch(concatLink(sesURL, '/send'), {
await fetch(concatLink(mailURL, '/send'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
...(mailAuth != null ? { Authorization: `Bearer ${mailAuth}` } : {})
},
body: JSON.stringify({
text,
@ -756,13 +756,13 @@ export async function sendEmailConfirmation (
account: PersonUuid,
email: string
): Promise<void> {
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
if (sesURL === undefined || sesURL === '') {
ctx.error('Please provide SES_URL to enable email confirmations.')
const mailURL = getMetadata(accountPlugin.metadata.MAIL_URL)
if (mailURL === undefined || mailURL === '') {
ctx.error('Please provide MAIL_URL to enable email confirmations.')
throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {}))
}
const sesAuth = getMetadata(accountPlugin.metadata.SES_AUTH_TOKEN)
const mailAuth = getMetadata(accountPlugin.metadata.MAIL_AUTH_TOKEN)
const front = branding?.front ?? getMetadata(accountPlugin.metadata.FrontURL)
if (front === undefined || front === '') {
@ -782,11 +782,11 @@ export async function sendEmailConfirmation (
const html = await translate(accountPlugin.string.ConfirmationHTML, { name, link }, lang)
const subject = await translate(accountPlugin.string.ConfirmationSubject, { name }, lang)
await fetch(concatLink(sesURL, '/send'), {
await fetch(concatLink(mailURL, '/send'), {
method: 'post',
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
...(mailAuth != null ? { Authorization: `Bearer ${mailAuth}` } : {})
},
body: JSON.stringify({
text,
@ -893,15 +893,15 @@ export async function getEmailSocialId (db: AccountDB, email: string): Promise<S
return await db.socialId.findOne({ type: SocialIdType.EMAIL, value: email })
}
export function getSesUrl (): { sesURL: string, sesAuth: string | undefined } {
const sesURL = getMetadata(accountPlugin.metadata.SES_URL)
export function getMailUrl (): { mailURL: string, mailAuth: string | undefined } {
const mailURL = getMetadata(accountPlugin.metadata.MAIL_URL)
if (sesURL === undefined || sesURL === '') {
if (mailURL === undefined || mailURL === '') {
throw new Error('Please provide email service url')
}
const sesAuth = getMetadata(accountPlugin.metadata.SES_AUTH_TOKEN)
const mailAuth = getMetadata(accountPlugin.metadata.MAIL_AUTH_TOKEN)
return { sesURL, sesAuth }
return { mailURL, mailAuth }
}
export function getFrontUrl (branding: Branding | null): string {
@ -1167,12 +1167,12 @@ interface EmailInfo {
export async function sendEmail (info: EmailInfo): Promise<void> {
const { text, html, subject, to } = info
const { sesURL, sesAuth } = getSesUrl()
await fetch(concatLink(sesURL, '/send'), {
const { mailURL, mailAuth } = getMailUrl()
await fetch(concatLink(mailURL, '/send'), {
method: 'post',
headers: {
'Content-Type': 'application/json',
...(sesAuth != null ? { Authorization: `Bearer ${sesAuth}` } : {})
...(mailAuth != null ? { Authorization: `Bearer ${mailAuth}` } : {})
},
body: JSON.stringify({
text,

View File

@ -5,8 +5,8 @@ export interface ServerEnv {
serverSecret: string
frontUrl: string
filesUrl: string | undefined
sesUrl: string | undefined
sesAuthToken: string | undefined
mailUrl: string | undefined
mailAuthToken: string | undefined
webPushUrl: string | undefined
accountsUrl: string
serverPort: number
@ -45,8 +45,8 @@ export function serverConfigFromEnv (): ServerEnv {
}
const filesUrl = process.env.FILES_URL
const sesUrl = process.env.SES_URL
const sesAuthToken = process.env.SES_AUTH_TOKEN
const mailUrl = process.env.MAIL_URL
const mailAuthToken = process.env.MAIL_AUTH_TOKEN
const webPushUrl = process.env.WEB_PUSH_URL
const accountsUrl = process.env.ACCOUNTS_URL
@ -64,8 +64,8 @@ export function serverConfigFromEnv (): ServerEnv {
serverSecret,
frontUrl,
filesUrl,
sesUrl,
sesAuthToken,
mailUrl,
mailAuthToken,
webPushUrl,
accountsUrl,
serverPort,