redone routes
This commit is contained in:
@@ -1,169 +1,31 @@
|
||||
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
|
||||
|
||||
import { FastifyInstance } from "fastify";
|
||||
import {sendMailAsUser} from "../utils/emailengine";
|
||||
import {encrypt, decrypt} from "../utils/crypt"
|
||||
import {secrets} from "../utils/secrets";
|
||||
// @ts-ignore
|
||||
import MailComposer from 'nodemailer/lib/mail-composer/index.js'
|
||||
|
||||
import {ImapFlow} from "imapflow"
|
||||
import MailComposer from "nodemailer/lib/mail-composer/index.js"
|
||||
import { ImapFlow } from "imapflow"
|
||||
|
||||
export default async function emailAsUserRoutes(server: FastifyInstance) {
|
||||
|
||||
// Create E-Mail Account
|
||||
|
||||
// ======================================================================
|
||||
// CREATE OR UPDATE EMAIL ACCOUNT
|
||||
// ======================================================================
|
||||
server.post("/email/accounts/:id?", async (req, reply) => {
|
||||
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
|
||||
};
|
||||
|
||||
if(id) {
|
||||
//SAVE Existing
|
||||
let saveData = {
|
||||
email_encrypted: body.email ? encrypt(body.email) : undefined,
|
||||
password_encrypted: body.password ? encrypt(body.password) : undefined,
|
||||
smtp_host_encrypted: body.smtp_host ? encrypt(body.smtp_host) : undefined,
|
||||
smtp_port: body.smtp_port,
|
||||
smtp_ssl: body.smtp_ssl,
|
||||
imap_host_encrypted: body.imap_host ? encrypt(body.imap_host) : undefined,
|
||||
imap_port: body.imap_port,
|
||||
imap_ssl: body.imap_ssl,
|
||||
}
|
||||
|
||||
|
||||
const { data, error } = await server.supabase
|
||||
.from("user_credentials")
|
||||
.update(saveData)
|
||||
.eq("id", id)
|
||||
.select("*")
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
return reply.code(400).send({ error: error.message });
|
||||
} else {
|
||||
return reply.send({success: true})
|
||||
}
|
||||
} else {
|
||||
//Create New
|
||||
let createData = {
|
||||
user_id: req.user.user_id,
|
||||
email_encrypted: encrypt(body.email),
|
||||
password_encrypted: encrypt(body.password),
|
||||
tenant_id: req.user.tenant_id,
|
||||
smtp_host_encrypted: encrypt(body.smtp_host),
|
||||
smtp_port: body.smtp_port,
|
||||
smtp_ssl: body.smtp_ssl,
|
||||
type: "mail",
|
||||
imap_host_encrypted: encrypt(body.imap_host),
|
||||
imap_port: body.imap_port,
|
||||
imap_ssl: body.imap_ssl,
|
||||
}
|
||||
|
||||
|
||||
const { data, error } = await server.supabase
|
||||
.from("user_credentials")
|
||||
.insert(createData)
|
||||
.select("*")
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
return reply.code(400).send({ error: error.message });
|
||||
} else {
|
||||
return reply.send({success: true})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
server.get("/email/accounts/:id?", async (req, reply) => {
|
||||
if (!req.user?.tenant_id) {
|
||||
return reply.code(400).send({ error: "No tenant selected" });
|
||||
}
|
||||
|
||||
const { id } = req.params as { id: string };
|
||||
|
||||
if(id) {
|
||||
let returnData = {}
|
||||
// @ts-ignore
|
||||
const { data, error } = await server.supabase
|
||||
.from("user_credentials")
|
||||
.select("id, email_encrypted, smtp_host_encrypted, smtp_port, smtp_ssl, imap_host_encrypted, imap_port, imap_ssl, user_id, tenant_id")
|
||||
.eq("id", id)
|
||||
.eq("tenant_id", req.user.tenant_id)
|
||||
.eq("type", "mail")
|
||||
.single();
|
||||
|
||||
if (error || !data) {
|
||||
return reply.code(404).send({ error: "Not found" });
|
||||
} else {
|
||||
Object.keys(data).forEach((key) => {
|
||||
if(key.includes("encrypted")){
|
||||
returnData[key.substring(0,key.length-10)] = decrypt(data[key])
|
||||
} else {
|
||||
returnData[key] = data[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return returnData;
|
||||
} else {
|
||||
|
||||
const { data, error } = await server.supabase
|
||||
.from("user_credentials")
|
||||
.select("id, email_encrypted, user_id, tenant_id")
|
||||
.eq("tenant_id", req.user.tenant_id)
|
||||
.eq("type", "mail")
|
||||
|
||||
let accounts = []
|
||||
data.forEach(item => {
|
||||
let temp = {}
|
||||
Object.keys(item).forEach((key) => {
|
||||
if(key.includes("encrypted")){
|
||||
temp[key.substring(0,key.length-10)] = decrypt(item[key])
|
||||
} else {
|
||||
temp[key] = item[key]
|
||||
}
|
||||
})
|
||||
accounts.push(temp)
|
||||
})
|
||||
|
||||
|
||||
|
||||
return accounts
|
||||
}
|
||||
});
|
||||
|
||||
server.post("/email/send", async (req, reply) => {
|
||||
const body = req.body as {
|
||||
to: string
|
||||
cc?: string
|
||||
bcc?: string
|
||||
subject?: string
|
||||
text?: string
|
||||
html?: string
|
||||
attachments?: any,
|
||||
account: string
|
||||
}
|
||||
|
||||
try {
|
||||
if (!req.user?.tenant_id) {
|
||||
return reply.code(400).send({ error: "No tenant selected" })
|
||||
}
|
||||
|
||||
let accountData = {} as {
|
||||
const { id } = req.params as { id?: string }
|
||||
|
||||
const body = req.body as {
|
||||
email: string
|
||||
password: string
|
||||
smtp_host: string
|
||||
@@ -173,32 +35,173 @@ export default async function emailAsUserRoutes(server: FastifyInstance) {
|
||||
imap_port: number
|
||||
imap_ssl: boolean
|
||||
}
|
||||
// @ts-ignore
|
||||
const { data, error } = await server.supabase
|
||||
.from("user_credentials")
|
||||
.select("id, email_encrypted,password_encrypted, smtp_host_encrypted, smtp_port, smtp_ssl,imap_host_encrypted,imap_port, imap_ssl, user_id, tenant_id")
|
||||
.eq("id", body.account)
|
||||
.eq("tenant_id", req.user.tenant_id)
|
||||
.eq("type", "mail")
|
||||
.single();
|
||||
|
||||
if (error || !data) {
|
||||
return reply.code(404).send({ error: "Not found" });
|
||||
} else {
|
||||
Object.keys(data).forEach((key) => {
|
||||
if(key.includes("encrypted")){
|
||||
accountData[key.substring(0,key.length-10)] = decrypt(data[key])
|
||||
} else {
|
||||
accountData[key] = data[key]
|
||||
}
|
||||
})
|
||||
// -----------------------------
|
||||
// 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)
|
||||
.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,
|
||||
}
|
||||
|
||||
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.smtp_host,
|
||||
port: accountData.smtp_port,
|
||||
secure: accountData.smtp_ssl,
|
||||
host: accountData.smtpHost,
|
||||
port: accountData.smtpPort,
|
||||
secure: accountData.smtpSsl,
|
||||
auth: {
|
||||
user: accountData.email,
|
||||
pass: accountData.password,
|
||||
@@ -208,62 +211,48 @@ export default async function emailAsUserRoutes(server: FastifyInstance) {
|
||||
const message = {
|
||||
from: accountData.email,
|
||||
to: body.to,
|
||||
cc: body.cc ? body.cc : undefined,
|
||||
bcc: body.bcc ? body.bcc : undefined,
|
||||
cc: body.cc,
|
||||
bcc: body.bcc,
|
||||
subject: body.subject,
|
||||
html: body.html ? body.html : undefined,
|
||||
html: body.html,
|
||||
text: body.text,
|
||||
attachments: body.attachments ? body.attachments : undefined,
|
||||
attachments: body.attachments,
|
||||
}
|
||||
|
||||
|
||||
const info = await transporter.sendMail(message)
|
||||
|
||||
const imapClient = new ImapFlow({
|
||||
host: accountData.imap_host,
|
||||
port: accountData.imap_port,
|
||||
secure: accountData.imap_ssl,
|
||||
// -------------------------
|
||||
// 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,
|
||||
},
|
||||
logger: false
|
||||
})
|
||||
|
||||
await imapClient.connect()
|
||||
await imap.connect()
|
||||
|
||||
const mail = new MailComposer(message)
|
||||
const raw = await mail.compile().build()
|
||||
|
||||
const raw = await mail.compile().build() // → Buffer mit kompletter MIME
|
||||
|
||||
|
||||
for await (const mailbox of await imapClient.list()) {
|
||||
// mailbox.flags enthält z. B. ['\\Sent', '\\HasChildren']
|
||||
console.log(mailbox.specialUse)
|
||||
if (mailbox.specialUse == '\\Sent') {
|
||||
console.log('📨 Sent folder gefunden:', mailbox.path)
|
||||
await imapClient.mailboxOpen(mailbox.path)
|
||||
|
||||
await imapClient.append(mailbox.path, raw, ['\\Seen'])
|
||||
|
||||
await imapClient.logout()
|
||||
|
||||
break
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
if(info.response.includes("OK")){
|
||||
reply.send({success: true})
|
||||
}{
|
||||
reply.status(500)
|
||||
}
|
||||
|
||||
return reply.send({ success: true })
|
||||
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
reply.code(500).send({ error: "Failed to send E-Mail as User" })
|
||||
console.error("POST /email/send error:", err)
|
||||
return reply.code(500).send({ error: "Failed to send email" })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user