const path = require('path'); const fs = require('fs').promises; const crypto = require('crypto'); const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js')); const BaseController = require(path.resolve(process.env.ROOT_PATH, './controllers/BaseController.js')); const { format, addDays } = require('date-fns'); const crypt = require('unix-crypt-td-js'); const Docker = require('dockerode'); const docker = new Docker(); require('dotenv').config(); class MailboxController extends BaseController { constructor() { super(); // Public endpoints this.protected('createTemporaryMailbox', 'getMailboxes', 'getMailbox', 'deleteMailbox'); } /** * Create a temporary mailbox with random username * * @swagger * /mailboxes/create: * post: * summary: Create a temporary mailbox * description: Creates a temporary mailbox with a random username on a random active domain * tags: [Mailboxes] * security: * - bearerAuth: [] * responses: * 201: * description: Mailbox created successfully * content: * application/json: * schema: * type: object * properties: * id: * type: integer * description: Mailbox ID * username: * type: string * description: Full email address * password: * type: string * description: Plain text password for the mailbox * expires: * type: string * format: date-time * description: Expiration date (14 days from creation) * 400: * description: No active domains found * 500: * description: Server error */ async createTemporaryMailbox(req, res) { try { // Validate domain exists const randomDomain = await models.Domain.query().where('active', 1).orderByRaw('RAND()').limit(1).first(); if (!randomDomain) { return res.status(400).json({ error: 'No active domains found' }); } // Generate random username (local part) const localPart = this._generateRandomUsername(); const username = `${localPart}@${randomDomain.domain}`; const password = this._generateRandomPassword(); // Create mailbox in database const mailbox = await models.Mailbox.query().insert({ username, password: password.hashedPassword, name: `Temporary Mailbox ${localPart}`, domain: randomDomain.domain, local_part: localPart, quota: 102400000, // 100MB quota maildir: `/var/mail/${randomDomain.domain}/${localPart}`, active: 1, created: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), modified: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), expires: format(addDays(new Date(), 14), 'yyyy-MM-dd HH:mm:ss') }); await models.Alias.query().insert({ address: username, goto: username, domain: randomDomain.domain, created: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), modified: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), active: 1 }); const mailData = { id: mailbox.id, username: mailbox.username, expires: mailbox.expires } // Instead of manually creating directory, use docker-mailserver setup command try { const container = docker.getContainer(process.env.MAILSERVER_CONTAINER); // Add email await container.exec({ Cmd: ['setup', 'email', 'add', username, password.password], AttachStdout: true, AttachStderr: true }).then(exec => exec.start()); // Add alias await container.exec({ Cmd: ['setup', 'alias', 'add', username, username], AttachStdout: true, AttachStderr: true }).then(exec => exec.start()); console.log(`Created mailbox for ${username} using setup script`); } catch (err) { console.error(`Error creating mailbox via setup script: ${err.message}`); // Clean up the database entry since mailbox creation failed await models.Mailbox.query().deleteById(mailbox.id); await models.Alias.query().where('address', username).delete(); return res.status(500).json({ error: 'Failed to create mailbox' }); } return res.status(201).json(mailData); } catch (error) { console.error('Error creating temporary mailbox:', error); return res.status(500).json({ error: 'Failed to create mailbox' }); } } /** * Get all mailboxes for the authenticated user * * @swagger * /mailboxes: * get: * summary: Get all mailboxes * description: Retrieves all mailboxes for the authenticated user * tags: [Mailboxes] * security: * - bearerAuth: [] * responses: * 200: * description: List of mailboxes * content: * application/json: * schema: * type: array * items: * type: object * properties: * id: * type: integer * username: * type: string * domain: * type: string * local_part: * type: string * created: * type: string * format: date-time * expires: * type: string * format: date-time * 500: * description: Server error */ async getMailboxes(req, res) { try { const mailboxes = await models.Mailbox.query() .select(['id', 'username', 'domain', 'local_part', 'created', 'expires']); return res.json(mailboxes); } catch (error) { console.error('Error fetching mailboxes:', error); return res.status(500).json({ error: 'Failed to fetch mailboxes' }); } } /** * Get a specific mailbox by ID * * @swagger * /mailboxes/{id}: * get: * summary: Get mailbox by ID * description: Retrieves a specific mailbox by its ID * tags: [Mailboxes] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: Mailbox ID * responses: * 200: * description: Mailbox details * content: * application/json: * schema: * type: object * properties: * id: * type: integer * username: * type: string * domain: * type: string * local_part: * type: string * created: * type: string * format: date-time * expires: * type: string * format: date-time * 404: * description: Mailbox not found * 500: * description: Server error */ async getMailbox(req, res) { try { const { id } = req.params; const mailbox = await models.Mailbox.query() .findById(id) .select(['id', 'username', 'domain', 'local_part', 'created', 'expires']); if (!mailbox) { return res.status(404).json({ error: 'Mailbox not found' }); } return res.json(mailbox); } catch (error) { console.error('Error fetching mailbox:', error); return res.status(500).json({ error: 'Failed to fetch mailbox' }); } } /** * Delete a mailbox * * @swagger * /mailboxes/{id}: * delete: * summary: Delete mailbox * description: Deletes a mailbox by ID, including physical directory * tags: [Mailboxes] * security: * - bearerAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: integer * description: Mailbox ID * responses: * 200: * description: Mailbox deleted successfully * content: * application/json: * schema: * type: object * properties: * message: * type: string * example: Mailbox deleted successfully * 404: * description: Mailbox not found * 500: * description: Server error */ async deleteMailbox(req, res) { try { const { id } = req.params; const mailbox = await models.Mailbox.query().where('id', id).first(); if (!mailbox) { return res.status(404).json({ error: 'Mailbox not found' }); } try { const container = docker.getContainer('mailserver'); // Delete email await container.exec({ Cmd: ['setup', 'email', 'del', '-y', mailbox.username], AttachStdout: true, AttachStderr: true }).then(exec => exec.start()); // Delete alias await container.exec({ Cmd: ['setup', 'alias', 'del', '-y', mailbox.username], AttachStdout: true, AttachStderr: true }).then(exec => exec.start()); console.log(`Deleted mailbox for ${mailbox.username} using setup script`); } catch (err) { console.error(`Error deleting mailbox via setup script: ${err.message}`); // Continue anyway to delete from database as the mailbox might already be gone } // Delete from database await models.Mailbox.query().where('id', id).delete(); await models.Alias.query().where('address', mailbox.username).delete(); return res.json({ message: 'Mailbox deleted successfully' }); } catch (error) { console.error('Error deleting mailbox:', error); return res.status(500).json({ error: 'Failed to delete mailbox' }); } } /** * Generate a random username * @private */ _generateRandomUsername() { // Generate a random string of 8 characters return crypto.randomBytes(4).toString('hex'); } /** * Generate a random password * @private */ _generateRandomPassword() { const password = crypto.randomBytes(6).toString('hex'); // Generate a salt (8 characters) const salt = crypto.randomBytes(4).toString('hex').substring(0, 8); // Create MD5 crypt hash in the format $1$salt$hash using the proper algorithm const hashedPassword = crypt(password, `$1$${salt}`); return { password: password, hashedPassword: hashedPassword } } } module.exports = new MailboxController();