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'); 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, password: password.password, expires: mailbox.expires } // Create physical mailbox directory try { const mailboxPath = path.join('/var/mail', randomDomain.domain, localPart); await fs.mkdir(mailboxPath, { recursive: true }); console.log(`Created physical mailbox at ${mailboxPath}`); } catch (err) { console.error(`Error creating physical mailbox: ${err.message}`); // Continue anyway, as the mailbox might be created by the mail server } 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().findById(id); if (!mailbox) { return res.status(404).json({ error: 'Mailbox not found' }); } // Delete physical mailbox directory try { const mailboxPath = path.join('/var/mail', mailbox.domain, mailbox.local_part); await fs.rm(mailboxPath, { recursive: true, force: true }); console.log(`Deleted physical mailbox at ${mailboxPath}`); } catch (err) { console.error(`Error deleting physical mailbox: ${err.message}`); // Continue anyway to delete from database } // Delete from database await models.Mailbox.query().deleteById(id); 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();