2weekmail/api/controllers/MailboxController.js
2025-03-19 19:56:57 -05:00

320 lines
9.9 KiB
JavaScript

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();