261 lines
8.8 KiB
TypeScript
261 lines
8.8 KiB
TypeScript
import nodemailer from "nodemailer"
|
|
import { FastifyInstance } from "fastify"
|
|
import { eq } from "drizzle-orm"
|
|
|
|
import { sendMailAsUser } from "../utils/emailengine"
|
|
import { encrypt, decrypt } from "../utils/crypt"
|
|
import { userCredentials } from "../../db/schema"
|
|
// Pfad ggf. anpassen
|
|
|
|
// @ts-ignore
|
|
import MailComposer from "nodemailer/lib/mail-composer/index.js"
|
|
import { ImapFlow } from "imapflow"
|
|
|
|
export default async function emailAsUserRoutes(server: FastifyInstance) {
|
|
|
|
|
|
// ======================================================================
|
|
// CREATE OR UPDATE EMAIL ACCOUNT
|
|
// ======================================================================
|
|
server.post("/email/accounts/:id?", async (req, reply) => {
|
|
try {
|
|
if (!req.user?.tenant_id) {
|
|
return reply.code(400).send({ error: "No tenant selected" })
|
|
}
|
|
|
|
const { id } = req.params as { id?: string }
|
|
|
|
const body = req.body as {
|
|
email: string
|
|
password: string
|
|
smtp_host: string
|
|
smtp_port: number
|
|
smtp_ssl: boolean
|
|
imap_host: string
|
|
imap_port: number
|
|
imap_ssl: boolean
|
|
}
|
|
|
|
// -----------------------------
|
|
// UPDATE EXISTING
|
|
// -----------------------------
|
|
if (id) {
|
|
const saveData = {
|
|
emailEncrypted: body.email ? encrypt(body.email) : undefined,
|
|
passwordEncrypted: body.password ? encrypt(body.password) : undefined,
|
|
smtpHostEncrypted: body.smtp_host ? encrypt(body.smtp_host) : undefined,
|
|
smtpPort: body.smtp_port,
|
|
smtpSsl: body.smtp_ssl,
|
|
imapHostEncrypted: body.imap_host ? encrypt(body.imap_host) : undefined,
|
|
imapPort: body.imap_port,
|
|
imapSsl: body.imap_ssl,
|
|
}
|
|
|
|
await server.db
|
|
.update(userCredentials)
|
|
//@ts-ignore
|
|
.set(saveData)
|
|
.where(eq(userCredentials.id, id))
|
|
|
|
return reply.send({ success: true })
|
|
}
|
|
|
|
// -----------------------------
|
|
// CREATE NEW
|
|
// -----------------------------
|
|
const insertData = {
|
|
userId: req.user.user_id,
|
|
tenantId: req.user.tenant_id,
|
|
type: "mail",
|
|
|
|
emailEncrypted: encrypt(body.email),
|
|
passwordEncrypted: encrypt(body.password),
|
|
|
|
smtpHostEncrypted: encrypt(body.smtp_host),
|
|
smtpPort: body.smtp_port,
|
|
smtpSsl: body.smtp_ssl,
|
|
|
|
imapHostEncrypted: encrypt(body.imap_host),
|
|
imapPort: body.imap_port,
|
|
imapSsl: body.imap_ssl,
|
|
}
|
|
|
|
//@ts-ignore
|
|
await server.db.insert(userCredentials).values(insertData)
|
|
|
|
return reply.send({ success: true })
|
|
} catch (err) {
|
|
console.error("POST /email/accounts error:", err)
|
|
return reply.code(500).send({ error: "Internal Server Error" })
|
|
}
|
|
})
|
|
|
|
|
|
|
|
// ======================================================================
|
|
// GET SINGLE OR ALL ACCOUNTS
|
|
// ======================================================================
|
|
server.get("/email/accounts/:id?", async (req, reply) => {
|
|
try {
|
|
if (!req.user?.tenant_id) {
|
|
return reply.code(400).send({ error: "No tenant selected" })
|
|
}
|
|
|
|
const { id } = req.params as { id?: string }
|
|
|
|
// ============================================================
|
|
// LOAD SINGLE ACCOUNT
|
|
// ============================================================
|
|
if (id) {
|
|
const rows = await server.db
|
|
.select()
|
|
.from(userCredentials)
|
|
.where(eq(userCredentials.id, id))
|
|
|
|
const row = rows[0]
|
|
if (!row) return reply.code(404).send({ error: "Not found" })
|
|
|
|
const returnData: any = {}
|
|
|
|
Object.entries(row).forEach(([key, val]) => {
|
|
if (key.endsWith("Encrypted")) {
|
|
const cleanKey = key.replace("Encrypted", "")
|
|
// @ts-ignore
|
|
returnData[cleanKey] = decrypt(val as string)
|
|
} else {
|
|
returnData[key] = val
|
|
}
|
|
})
|
|
|
|
return reply.send(returnData)
|
|
}
|
|
|
|
// ============================================================
|
|
// LOAD ALL ACCOUNTS FOR TENANT
|
|
// ============================================================
|
|
const rows = await server.db
|
|
.select()
|
|
.from(userCredentials)
|
|
.where(eq(userCredentials.tenantId, req.user.tenant_id))
|
|
|
|
const accounts = rows.map(row => {
|
|
const temp: any = {}
|
|
Object.entries(row).forEach(([key, val]) => {
|
|
if (key.endsWith("Encrypted")) {
|
|
// @ts-ignore
|
|
temp[key.replace("Encrypted", "")] = decrypt(val as string)
|
|
} else {
|
|
temp[key] = val
|
|
}
|
|
})
|
|
return temp
|
|
})
|
|
|
|
return reply.send(accounts)
|
|
|
|
} catch (err) {
|
|
console.error("GET /email/accounts error:", err)
|
|
return reply.code(500).send({ error: "Internal Server Error" })
|
|
}
|
|
})
|
|
|
|
|
|
|
|
// ======================================================================
|
|
// SEND EMAIL + SAVE IN IMAP SENT FOLDER
|
|
// ======================================================================
|
|
server.post("/email/send", async (req, reply) => {
|
|
try {
|
|
const body = req.body as {
|
|
to: string
|
|
cc?: string
|
|
bcc?: string
|
|
subject?: string
|
|
text?: string
|
|
html?: string
|
|
attachments?: any
|
|
account: string
|
|
}
|
|
|
|
// Fetch email credentials
|
|
const rows = await server.db
|
|
.select()
|
|
.from(userCredentials)
|
|
.where(eq(userCredentials.id, body.account))
|
|
|
|
const row = rows[0]
|
|
if (!row) return reply.code(404).send({ error: "Account not found" })
|
|
|
|
const accountData: any = {}
|
|
|
|
Object.entries(row).forEach(([key, val]) => {
|
|
if (key.endsWith("Encrypted")) {
|
|
// @ts-ignore
|
|
accountData[key.replace("Encrypted", "")] = decrypt(val as string)
|
|
} else {
|
|
accountData[key] = val
|
|
}
|
|
})
|
|
|
|
// -------------------------
|
|
// SEND EMAIL VIA SMTP
|
|
// -------------------------
|
|
const transporter = nodemailer.createTransport({
|
|
host: accountData.smtpHost,
|
|
port: accountData.smtpPort,
|
|
secure: accountData.smtpSsl,
|
|
auth: {
|
|
user: accountData.email,
|
|
pass: accountData.password,
|
|
},
|
|
})
|
|
|
|
const message = {
|
|
from: accountData.email,
|
|
to: body.to,
|
|
cc: body.cc,
|
|
bcc: body.bcc,
|
|
subject: body.subject,
|
|
html: body.html,
|
|
text: body.text,
|
|
attachments: body.attachments,
|
|
}
|
|
|
|
const info = await transporter.sendMail(message)
|
|
|
|
// -------------------------
|
|
// SAVE TO IMAP SENT FOLDER
|
|
// -------------------------
|
|
const imap = new ImapFlow({
|
|
host: accountData.imapHost,
|
|
port: accountData.imapPort,
|
|
secure: accountData.imapSsl,
|
|
auth: {
|
|
user: accountData.email,
|
|
pass: accountData.password,
|
|
},
|
|
})
|
|
|
|
await imap.connect()
|
|
|
|
const mail = new MailComposer(message)
|
|
const raw = await mail.compile().build()
|
|
|
|
for await (const mailbox of await imap.list()) {
|
|
if (mailbox.specialUse === "\\Sent") {
|
|
await imap.mailboxOpen(mailbox.path)
|
|
await imap.append(mailbox.path, raw, ["\\Seen"])
|
|
await imap.logout()
|
|
}
|
|
}
|
|
|
|
return reply.send({ success: true })
|
|
|
|
} catch (err) {
|
|
console.error("POST /email/send error:", err)
|
|
return reply.code(500).send({ error: "Failed to send email" })
|
|
}
|
|
})
|
|
|
|
}
|