From 19612a91d565d74e74c7e316df7f3466db6a70cf Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 17 Apr 2025 17:34:29 -0400 Subject: [PATCH 1/3] feat: add account deleting, some quality of life fixes and linting --- README.md | 23 +++----- package.json | 1 + src/actions/accounts/deleteAccount.ts | 80 +++++++++++++++++++++++++++ src/server.ts | 14 ++++- src/utils/updateAccountsCache.ts | 1 + 5 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 src/actions/accounts/deleteAccount.ts diff --git a/README.md b/README.md index 2f0852c..746e521 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # mail-connect -API bridge for docker-mailserver - -*mail-connect is still in early beta* +API bridge for docker-mailserver. **Still in beta.** ## What is it @@ -12,7 +10,7 @@ We provide an extendable API which interacts with the `setup` utility via a Dock ## What this API is NOT -This API is insecure by nature, however not completely. `mail-connect` is intended to be an API which is used internally _only_. The systems connected to this API should have proper protections against abuse. Think about it... would you like me to direct your mailserver security? I sure hope not... +This API is insecure by nature, however not completely. `mail-connect` is intended to be an API which is used internally **only**. The systems connected to this API should have proper protections against abuse. Think about it... would you like me to direct your mailserver security? I sure hope not... As such, users who have access to this API are able to create unlimited accounts, and modify anyone's email address. Thus, your code should be the only user of this API. Once again, **do not make this API public**. @@ -59,13 +57,13 @@ All features marked with an **E** are extended features, and are not a part of t ### Email -- [X] Create email -- [X] List emails -- [X] **E** View individual user details -- [X] **E** Create email from file +- [X] Create account/email +- [X] List accounts +- [X] **E** View individual account details +- [X] **E** Create an account from file - [X] Change password -- [ ] Delete email -- [ ] Restrict email +- [X] Delete account/email +- [ ] Restrict account ### Alias @@ -113,8 +111,3 @@ All features marked with an **E** are extended features, and are not a part of t I plan to implement a *much* more powerful API, when everything else has settled. I will be taking a look at the setup utility itself, and seeing how a more efficient approach can be taken. Since `docker-mailserver` is built on Dovecot and Postfix, I am confident we can improve this API to be speedy and efficient as ever. - -## To-Do - -- [ ] Implement aforementioned features -- [ ] Swagger support diff --git a/package.json b/package.json index 62ab0eb..ff2c2d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "mail-connect", "module": "src/server.ts", + "version": "0.1.2", "type": "module", "scripts": { "start": "bun src/server.js", diff --git a/src/actions/accounts/deleteAccount.ts b/src/actions/accounts/deleteAccount.ts new file mode 100644 index 0000000..f859916 --- /dev/null +++ b/src/actions/accounts/deleteAccount.ts @@ -0,0 +1,80 @@ +import { Request, Response } from "express"; +import { accounts } from "../../db/schema"; +import { eq } from "drizzle-orm"; +import { validateEmail } from "../../utils/validators"; +import { drizzle } from "drizzle-orm/bun-sqlite"; +import { containerExec } from "../../utils/docker"; +import { updateAccountsCache } from "../../utils/updateAccountsCache"; + +const db = drizzle(process.env.DB_FILE_NAME!); + +export const deleteAccount = async (req: Request, res: Response): Promise => { + const { email } = req.body; + + if (!email) { + console.log("[!] Error\nTASK| deleteAccount\nERR | Missing email\nACC |", email); + res.status(400).json({ success: false, error: "Missing email" }); + return + } + + if (!validateEmail(email)) { + console.log("[!] Error\nTASK| deleteAccount\nERR | Invalid email format\nACC |", email); + res.status(400).json({ success: false, error: "Invalid email format" }); + return; + } + + const accInDb = await db + .select() + .from(accounts) + .where(eq(accounts.email, email)) + .limit(1); + + if (!accInDb || accInDb.length === 0) { + console.log("[!] Error\nTASK| deleteAccount\nERR | Account not found\nACC |", email); + res.status(404).json({ success: false, error: "Account not found" }); + return; + } + + console.log("[*] Task started\nTASK| deleteAccount\nACC |", email); + + try { + await db.delete(accounts).where(eq(accounts.email, email)); + console.log("[+] Internal account deleted\nTASK| deleteAccount\nACC |", email); + + try { + const output = await containerExec(["setup", "email", "del", email]); + if (/ERROR/i.test(output)) { + console.log("[!] Error\nTASK| deleteAccount\nERR | Mail account removal failed\nACC |", email); + res.status(500).json({ success: false, error: "Mail account removal failed" }); + return; + } else { + console.log("[+] Mail account removed\nTASK| deleteAccount\nACC |", email); + } + } catch (err) { + console.log("[!] Error\nTASK| deleteAccount\nERR | Mail account removal failed\nACC |", email); + res.status(500).json({ success: false, error: "Mail account removal failed" }); + return; + } + + console.log("[-] Updating accounts cache"); + await updateAccountsCache(); + + console.log("[-] Performing final checks"); + const accInDb = await db + .select() + .from(accounts) + .where(eq(accounts.email, email)) + .limit(1); + + if (!accInDb || accInDb.length === 0) { + console.log("[*] Task completed\nTASK| deleteAccount\nACC |", email); + res.json({ success: true }); + } else { + console.log("[!] Error\nTASK| deleteAccount\nERR | Account not found\nACC |", email); + res.status(404).json({ success: false, error: "Account not found" }); + } + } catch (err) { + console.log("[!] Error\nTASK| deleteAccount\nERR | Unspecified error\nACC |", email); + res.status(500).json({ success: false, error: (err as Error).message }); + } +}; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 85b0094..f3cd026 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,6 +6,8 @@ import { getUserAccount } from "./actions/accounts/getUserAccount"; import { updatePassword } from "./actions/accounts/updatePassword"; import { addAccount } from "./actions/accounts/addAccount"; import initialChecks from "./actions/initialChecks"; +import { deleteAccount } from "./actions/accounts/deleteAccount"; +import { updateAccountsCache } from "./utils/updateAccountsCache"; const app = express(); app.use(express.json()); @@ -19,6 +21,11 @@ if (!rCResult) { console.log("\n==== SELF CHECK PASS ====\n"); } +// Get version +const pkg = Bun.file("package.json"); +const pkgData = await pkg.json(); +const version = pkgData.version; + interface RateLimitOptions { windowMs: number; limit: number; @@ -37,19 +44,22 @@ app.get("/accounts/list", listAccounts); app.post("/accounts/user", getUserAccount); app.post("/accounts/update/password", updatePassword); app.post("/accounts/add", addAccount); +app.post("/accounts/delete", deleteAccount); const PORT = 3000; app.listen(PORT, () => { figlet('mail-connect', (err, data) => { if (err) { console.log('mail-connect'); - console.log('Version: 0.1.1'); + console.log(`Version: ${version}`); console.log(`API listening on port ${PORT}\n`); console.log("[!] " + err); + updateAccountsCache(); } else { console.log(data); - console.log('Version: 0.1.1'); + console.log(`Version: ${version}`); console.log(`API listening on port ${PORT}\n`); + updateAccountsCache(); } }); }); \ No newline at end of file diff --git a/src/utils/updateAccountsCache.ts b/src/utils/updateAccountsCache.ts index d571ce2..a98e16a 100644 --- a/src/utils/updateAccountsCache.ts +++ b/src/utils/updateAccountsCache.ts @@ -14,4 +14,5 @@ export const updateAccountsCache = async () => { .insert(cacheInfo) .values({ lastUpdated: Date.now() }) .onConflictDoUpdate({ target: cacheInfo.id, set: { lastUpdated: Date.now() } }); + console.log("[+] Accounts cache updated"); }; \ No newline at end of file -- 2.47.1 From 122d3c36f79ddade21c2f298e7bf745e169c3070 Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 17 Apr 2025 17:46:51 -0400 Subject: [PATCH 2/3] docker/docs: use .example files instead for cleaner git --- .gitignore | 3 +++ README.md | 7 ++++--- docker-compose.yml => docker-compose.yml.example | 0 ratelimit.json | 10 ---------- 4 files changed, 7 insertions(+), 13 deletions(-) rename docker-compose.yml => docker-compose.yml.example (100%) delete mode 100644 ratelimit.json diff --git a/.gitignore b/.gitignore index fddc3f2..249f09c 100644 --- a/.gitignore +++ b/.gitignore @@ -137,4 +137,7 @@ drizzle/ bun.lockb migrate.txt db.sqlite3 +ratelimit.json.example +# Docker +docker-compose.yml \ No newline at end of file diff --git a/README.md b/README.md index 746e521..3760272 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,13 @@ All features marked with an **E** are extended features, and are not a part of t bunx drizzle-kit migrate ``` -3. **Copy/modify necessary files** +3. **Copy, move, and modify necessary files** ```bash + mv docker-compose.yml.example docker-compose.yml # depending on your use case, you might have to change some things here touch migrate.txt # put emails (one per line) which already exist on the server which users can claim - cp .env.example .env # you don't need to change anything here - vim ratelimit.json # optional, customize to your liking... + mv .env.example .env # you don't need to change anything here + mv ratelimit.json.example ratelimit.json # customize to your liking ``` **Note:** If you are running mail-connect outside a Docker container (or changing the binds), please change the `MAILCONNECT_ROOT_DIR` to match your environment. diff --git a/docker-compose.yml b/docker-compose.yml.example similarity index 100% rename from docker-compose.yml rename to docker-compose.yml.example diff --git a/ratelimit.json b/ratelimit.json deleted file mode 100644 index 87128d4..0000000 --- a/ratelimit.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "/list": { - "windowMs": 60000, - "limit": 20 - }, - "/add": { - "windowMs": 60000, - "limit": 10 - } -} \ No newline at end of file -- 2.47.1 From 279abe89d992fa5b7c9b1e99830fbf646de60c91 Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 17 Apr 2025 17:49:17 -0400 Subject: [PATCH 3/3] hf: didn't mean to exclude .example file --- .gitignore | 2 +- ratelimit.json.example | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 ratelimit.json.example diff --git a/.gitignore b/.gitignore index 249f09c..8b225f2 100644 --- a/.gitignore +++ b/.gitignore @@ -137,7 +137,7 @@ drizzle/ bun.lockb migrate.txt db.sqlite3 -ratelimit.json.example +ratelimit.json # Docker docker-compose.yml \ No newline at end of file diff --git a/ratelimit.json.example b/ratelimit.json.example new file mode 100644 index 0000000..87128d4 --- /dev/null +++ b/ratelimit.json.example @@ -0,0 +1,10 @@ +{ + "/list": { + "windowMs": 60000, + "limit": 20 + }, + "/add": { + "windowMs": 60000, + "limit": 10 + } +} \ No newline at end of file -- 2.47.1