initial commit
This commit is contained in:
113
backend/src/routes/mailboxes.ts
Normal file
113
backend/src/routes/mailboxes.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Router } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { pool } from '../db.js';
|
||||
import { config } from '../config.js';
|
||||
import { requireAuth, canAccessDomain } from '../middleware/auth.js';
|
||||
import { DmsService } from '../services/dms.js';
|
||||
import { SyncService } from '../services/sync.js';
|
||||
import { DynamoRulesService } from '../services/dynamodb.js';
|
||||
import { audit } from '../services/audit.js';
|
||||
import { domainFromEmail, localPartFromEmail, normalizeEmail } from '../utils/email.js';
|
||||
|
||||
export const mailboxesRouter = Router();
|
||||
mailboxesRouter.use(requireAuth);
|
||||
|
||||
const dms = new DmsService();
|
||||
const sync = new SyncService(dms);
|
||||
const dynamo = new DynamoRulesService();
|
||||
|
||||
function ensureDomain(req: any, domain: string): void {
|
||||
if (!canAccessDomain(req.user, domain)) throw Object.assign(new Error('Forbidden'), { status: 403 });
|
||||
}
|
||||
|
||||
mailboxesRouter.get('/', async (req, res) => {
|
||||
const domain = String(req.query.domain ?? '').toLowerCase();
|
||||
if (domain) ensureDomain(req, domain);
|
||||
const params: unknown[] = [config.nodeName];
|
||||
let where = 'WHERE node_name=$1';
|
||||
if (domain) { params.push(domain); where += ` AND domain=$${params.length}`; }
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM mailboxes ${where} ORDER BY domain, local_part`,
|
||||
params,
|
||||
);
|
||||
res.json(result.rows.filter((r) => canAccessDomain(req.user!, r.domain)));
|
||||
});
|
||||
|
||||
mailboxesRouter.post('/', async (req, res) => {
|
||||
const body = z.object({ email: z.string().email(), password: z.string().min(8) }).parse(req.body);
|
||||
const email = normalizeEmail(body.email);
|
||||
const domain = domainFromEmail(email);
|
||||
ensureDomain(req, domain);
|
||||
await dms.addMailbox(email, body.password);
|
||||
await sync.syncFromDms();
|
||||
await audit(req.user!.email, 'mailbox.create', 'mailbox', email, { domain }, req.ip);
|
||||
res.status(201).json({ email, domain, local_part: localPartFromEmail(email) });
|
||||
});
|
||||
|
||||
mailboxesRouter.delete('/:email', async (req, res) => {
|
||||
const email = normalizeEmail(req.params.email);
|
||||
const domain = domainFromEmail(email);
|
||||
ensureDomain(req, domain);
|
||||
await dms.deleteMailbox(email);
|
||||
await dms.syncSesDomain(domain).catch(() => undefined);
|
||||
await pool.query(`UPDATE mailboxes SET status='deleted', deleted_at=now(), updated_at=now() WHERE email_address=$1`, [email]);
|
||||
await audit(req.user!.email, 'mailbox.delete', 'mailbox', email, { domain }, req.ip);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
mailboxesRouter.post('/:email/password', async (req, res) => {
|
||||
const body = z.object({ password: z.string().min(8) }).parse(req.body);
|
||||
const email = normalizeEmail(req.params.email);
|
||||
ensureDomain(req, domainFromEmail(email));
|
||||
await dms.updatePassword(email, body.password);
|
||||
await audit(req.user!.email, 'mailbox.password_update', 'mailbox', email, {}, req.ip);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
mailboxesRouter.post('/usage/rescan', async (req, res) => {
|
||||
const domain = String(req.body?.domain ?? '').toLowerCase();
|
||||
if (domain) ensureDomain(req, domain);
|
||||
const params: unknown[] = [config.nodeName];
|
||||
let where = `WHERE node_name=$1 AND status='active'`;
|
||||
if (domain) { params.push(domain); where += ` AND domain=$${params.length}`; }
|
||||
const rows = (await pool.query(`SELECT email_address, domain FROM mailboxes ${where}`, params)).rows;
|
||||
let count = 0;
|
||||
for (const row of rows) {
|
||||
if (!canAccessDomain(req.user!, row.domain)) continue;
|
||||
const bytes = await dms.getMailboxUsageBytes(row.email_address);
|
||||
await pool.query(`UPDATE mailboxes SET used_bytes=$2, usage_scanned_at=now(), updated_at=now() WHERE email_address=$1`, [row.email_address, bytes]);
|
||||
count++;
|
||||
}
|
||||
await audit(req.user!.email, 'mailbox.usage_rescan', 'domain', domain || '*', { count }, req.ip);
|
||||
res.json({ count });
|
||||
});
|
||||
|
||||
mailboxesRouter.get('/:email/rules', async (req, res) => {
|
||||
const email = normalizeEmail(req.params.email);
|
||||
ensureDomain(req, domainFromEmail(email));
|
||||
res.json(await dynamo.getRules(email));
|
||||
});
|
||||
|
||||
mailboxesRouter.put('/:email/rules', async (req, res) => {
|
||||
const email = normalizeEmail(req.params.email);
|
||||
ensureDomain(req, domainFromEmail(email));
|
||||
const body = z.object({ ooo_active: z.boolean().optional(), ooo_message: z.string().optional(), forwards: z.array(z.string().email()).optional() }).parse(req.body);
|
||||
const saved = await dynamo.putRules({ email_address: email, ooo_active: body.ooo_active, ooo_message: body.ooo_message, forwards: body.forwards });
|
||||
await audit(req.user!.email, 'mailbox.rules_update', 'mailbox', email, saved, req.ip);
|
||||
res.json(saved);
|
||||
});
|
||||
|
||||
mailboxesRouter.get('/:email/blocklist', async (req, res) => {
|
||||
const email = normalizeEmail(req.params.email);
|
||||
ensureDomain(req, domainFromEmail(email));
|
||||
res.json(await dynamo.getBlocklist(email));
|
||||
});
|
||||
|
||||
mailboxesRouter.put('/:email/blocklist', async (req, res) => {
|
||||
const email = normalizeEmail(req.params.email);
|
||||
ensureDomain(req, domainFromEmail(email));
|
||||
const body = z.object({ blocked_patterns: z.array(z.string()) }).parse(req.body);
|
||||
const saved = await dynamo.putBlocklist(email, body.blocked_patterns);
|
||||
await audit(req.user!.email, 'mailbox.blocklist_update', 'mailbox', email, saved, req.ip);
|
||||
res.json(saved);
|
||||
});
|
||||
Reference in New Issue
Block a user