354 lines
11 KiB
JavaScript
354 lines
11 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');
|
|
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,
|
|
password: password.password,
|
|
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();
|