feat(mta): Enhance MTA service and SMTP server with robust session management, advanced email handling, and integrated API routes
This commit is contained in:
@ -1,7 +1,846 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Email, IEmailOptions } from './mta.classes.email.js';
|
||||
import { DeliveryStatus } from './mta.classes.emailsendjob.js';
|
||||
import type { MtaService } from './mta.classes.mta.js';
|
||||
import type { IDnsRecord } from './mta.classes.dnsmanager.js';
|
||||
|
||||
export class ApiManager {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
|
||||
/**
|
||||
* Authentication options for API requests
|
||||
*/
|
||||
interface AuthOptions {
|
||||
/** Required API keys for different endpoints */
|
||||
apiKeys: Map<string, string[]>;
|
||||
/** JWT secret for token-based authentication */
|
||||
jwtSecret?: string;
|
||||
/** Whether to validate IP addresses */
|
||||
validateIp?: boolean;
|
||||
/** Allowed IP addresses */
|
||||
allowedIps?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limiting options for API endpoints
|
||||
*/
|
||||
interface RateLimitOptions {
|
||||
/** Maximum requests per window */
|
||||
maxRequests: number;
|
||||
/** Time window in milliseconds */
|
||||
windowMs: number;
|
||||
/** Whether to apply per endpoint */
|
||||
perEndpoint?: boolean;
|
||||
/** Whether to apply per IP */
|
||||
perIp?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* API route definition
|
||||
*/
|
||||
interface ApiRoute {
|
||||
/** HTTP method */
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
/** Path pattern */
|
||||
path: string;
|
||||
/** Handler function */
|
||||
handler: (req: any, res: any) => Promise<any>;
|
||||
/** Required authentication level */
|
||||
authLevel: 'none' | 'basic' | 'admin';
|
||||
/** Rate limiting options */
|
||||
rateLimit?: RateLimitOptions;
|
||||
/** Route description */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email send request
|
||||
*/
|
||||
interface SendEmailRequest {
|
||||
/** Email details */
|
||||
email: IEmailOptions;
|
||||
/** Whether to validate domains before sending */
|
||||
validateDomains?: boolean;
|
||||
/** Priority level (1-5, 1 = highest) */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email status response
|
||||
*/
|
||||
interface EmailStatusResponse {
|
||||
/** Email ID */
|
||||
id: string;
|
||||
/** Current status */
|
||||
status: DeliveryStatus;
|
||||
/** Send time */
|
||||
sentAt?: Date;
|
||||
/** Delivery time */
|
||||
deliveredAt?: Date;
|
||||
/** Error message if failed */
|
||||
error?: string;
|
||||
/** Recipient address */
|
||||
recipient: string;
|
||||
/** Number of delivery attempts */
|
||||
attempts: number;
|
||||
/** Next retry time */
|
||||
nextRetry?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain verification response
|
||||
*/
|
||||
interface DomainVerificationResponse {
|
||||
/** Domain name */
|
||||
domain: string;
|
||||
/** Whether the domain is verified */
|
||||
verified: boolean;
|
||||
/** Verification details */
|
||||
details: {
|
||||
/** SPF record status */
|
||||
spf: {
|
||||
valid: boolean;
|
||||
record?: string;
|
||||
error?: string;
|
||||
};
|
||||
/** DKIM record status */
|
||||
dkim: {
|
||||
valid: boolean;
|
||||
record?: string;
|
||||
error?: string;
|
||||
};
|
||||
/** DMARC record status */
|
||||
dmarc: {
|
||||
valid: boolean;
|
||||
record?: string;
|
||||
error?: string;
|
||||
};
|
||||
/** MX record status */
|
||||
mx: {
|
||||
valid: boolean;
|
||||
records?: string[];
|
||||
error?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* API error response
|
||||
*/
|
||||
interface ApiError {
|
||||
/** Error code */
|
||||
code: string;
|
||||
/** Error message */
|
||||
message: string;
|
||||
/** Detailed error information */
|
||||
details?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Manager for MTA service
|
||||
*/
|
||||
export class ApiManager {
|
||||
/** TypedRouter for API routing */
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
/** MTA service reference */
|
||||
private mtaRef: MtaService;
|
||||
/** Express app */
|
||||
private app: any;
|
||||
/** Authentication options */
|
||||
private authOptions: AuthOptions;
|
||||
/** API routes */
|
||||
private routes: ApiRoute[] = [];
|
||||
/** Rate limiters */
|
||||
private rateLimiters: Map<string, {
|
||||
count: number;
|
||||
resetTime: number;
|
||||
clients: Map<string, {
|
||||
count: number;
|
||||
resetTime: number;
|
||||
}>;
|
||||
}> = new Map();
|
||||
|
||||
/**
|
||||
* Initialize API Manager
|
||||
* @param mtaRef MTA service reference
|
||||
*/
|
||||
constructor(mtaRef?: MtaService) {
|
||||
this.mtaRef = mtaRef;
|
||||
|
||||
// Initialize Express app
|
||||
this.app = plugins.express();
|
||||
|
||||
// Default authentication options
|
||||
this.authOptions = {
|
||||
apiKeys: new Map(),
|
||||
validateIp: false,
|
||||
allowedIps: []
|
||||
};
|
||||
|
||||
// Configure middleware
|
||||
this.configureMiddleware();
|
||||
|
||||
// Register routes
|
||||
this.registerRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set MTA service reference
|
||||
* @param mtaRef MTA service reference
|
||||
*/
|
||||
public setMtaService(mtaRef: MtaService): void {
|
||||
this.mtaRef = mtaRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure authentication options
|
||||
* @param options Authentication options
|
||||
*/
|
||||
public configureAuth(options: Partial<AuthOptions>): void {
|
||||
this.authOptions = {
|
||||
...this.authOptions,
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Express middleware
|
||||
*/
|
||||
private configureMiddleware(): void {
|
||||
// JSON body parser
|
||||
this.app.use(plugins.express.json({ limit: '10mb' }));
|
||||
|
||||
// CORS middleware
|
||||
this.app.use((req: any, res: any, next: any) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-API-Key');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// Request logging
|
||||
this.app.use((req: any, res: any, next: any) => {
|
||||
const start = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
console.log(`[API] ${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// Authentication middleware
|
||||
this.app.use((req: any, res: any, next: any) => {
|
||||
// Store authentication level in request
|
||||
req.authLevel = 'none';
|
||||
|
||||
// Check API key
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
if (apiKey) {
|
||||
for (const [level, keys] of this.authOptions.apiKeys.entries()) {
|
||||
if (keys.includes(apiKey)) {
|
||||
req.authLevel = level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check JWT token (if configured)
|
||||
if (this.authOptions.jwtSecret && req.headers.authorization) {
|
||||
try {
|
||||
const token = req.headers.authorization.split(' ')[1];
|
||||
const decoded = plugins.jwt.verify(token, this.authOptions.jwtSecret);
|
||||
|
||||
if (decoded && decoded.level) {
|
||||
req.authLevel = decoded.level;
|
||||
req.user = decoded;
|
||||
}
|
||||
} catch (error) {
|
||||
// Invalid token, but don't fail the request yet
|
||||
console.error('Invalid JWT token:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Check IP address (if configured)
|
||||
if (this.authOptions.validateIp) {
|
||||
const clientIp = req.ip || req.connection.remoteAddress;
|
||||
if (!this.authOptions.allowedIps.includes(clientIp)) {
|
||||
return res.status(403).json({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'IP address not allowed'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register API routes
|
||||
*/
|
||||
private registerRoutes(): void {
|
||||
// Email routes
|
||||
this.addRoute({
|
||||
method: 'POST',
|
||||
path: '/api/email/send',
|
||||
handler: this.handleSendEmail.bind(this),
|
||||
authLevel: 'basic',
|
||||
description: 'Send an email'
|
||||
});
|
||||
|
||||
this.addRoute({
|
||||
method: 'GET',
|
||||
path: '/api/email/status/:id',
|
||||
handler: this.handleGetEmailStatus.bind(this),
|
||||
authLevel: 'basic',
|
||||
description: 'Get email delivery status'
|
||||
});
|
||||
|
||||
// Domain routes
|
||||
this.addRoute({
|
||||
method: 'GET',
|
||||
path: '/api/domain/verify/:domain',
|
||||
handler: this.handleVerifyDomain.bind(this),
|
||||
authLevel: 'basic',
|
||||
description: 'Verify domain DNS records'
|
||||
});
|
||||
|
||||
this.addRoute({
|
||||
method: 'GET',
|
||||
path: '/api/domain/records/:domain',
|
||||
handler: this.handleGetDomainRecords.bind(this),
|
||||
authLevel: 'basic',
|
||||
description: 'Get recommended DNS records for domain'
|
||||
});
|
||||
|
||||
// DKIM routes
|
||||
this.addRoute({
|
||||
method: 'POST',
|
||||
path: '/api/dkim/generate/:domain',
|
||||
handler: this.handleGenerateDkim.bind(this),
|
||||
authLevel: 'admin',
|
||||
description: 'Generate DKIM keys for domain'
|
||||
});
|
||||
|
||||
this.addRoute({
|
||||
method: 'GET',
|
||||
path: '/api/dkim/public/:domain',
|
||||
handler: this.handleGetDkimPublicKey.bind(this),
|
||||
authLevel: 'basic',
|
||||
description: 'Get DKIM public key for domain'
|
||||
});
|
||||
|
||||
// Stats route
|
||||
this.addRoute({
|
||||
method: 'GET',
|
||||
path: '/api/stats',
|
||||
handler: this.handleGetStats.bind(this),
|
||||
authLevel: 'admin',
|
||||
description: 'Get MTA statistics'
|
||||
});
|
||||
|
||||
// Documentation route
|
||||
this.addRoute({
|
||||
method: 'GET',
|
||||
path: '/api',
|
||||
handler: this.handleGetApiDocs.bind(this),
|
||||
authLevel: 'none',
|
||||
description: 'API documentation'
|
||||
});
|
||||
|
||||
// Map routes to Express
|
||||
this.mapRoutesToExpress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an API route
|
||||
* @param route Route definition
|
||||
*/
|
||||
private addRoute(route: ApiRoute): void {
|
||||
this.routes.push(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map defined routes to Express
|
||||
*/
|
||||
private mapRoutesToExpress(): void {
|
||||
for (const route of this.routes) {
|
||||
const { method, path, handler, authLevel } = route;
|
||||
|
||||
// Add Express route
|
||||
this.app[method.toLowerCase()](path, async (req: any, res: any) => {
|
||||
try {
|
||||
// Check authentication
|
||||
if (authLevel !== 'none' && req.authLevel !== authLevel && req.authLevel !== 'admin') {
|
||||
return res.status(403).json({
|
||||
code: 'FORBIDDEN',
|
||||
message: `This endpoint requires ${authLevel} access`
|
||||
});
|
||||
}
|
||||
|
||||
// Check rate limit
|
||||
if (route.rateLimit) {
|
||||
const exceeded = this.checkRateLimit(route, req);
|
||||
if (exceeded) {
|
||||
return res.status(429).json({
|
||||
code: 'RATE_LIMIT_EXCEEDED',
|
||||
message: 'Rate limit exceeded, please try again later'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the request
|
||||
await handler(req, res);
|
||||
} catch (error) {
|
||||
console.error(`Error handling ${method} ${path}:`, error);
|
||||
|
||||
// Send appropriate error response
|
||||
const status = error.status || 500;
|
||||
const apiError: ApiError = {
|
||||
code: error.code || 'INTERNAL_ERROR',
|
||||
message: error.message || 'Internal server error'
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
apiError.details = error.stack;
|
||||
}
|
||||
|
||||
res.status(status).json(apiError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add 404 handler
|
||||
this.app.use((req: any, res: any) => {
|
||||
res.status(404).json({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Endpoint not found'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check rate limit for a route
|
||||
* @param route Route definition
|
||||
* @param req Express request
|
||||
* @returns Whether rate limit is exceeded
|
||||
*/
|
||||
private checkRateLimit(route: ApiRoute, req: any): boolean {
|
||||
if (!route.rateLimit) return false;
|
||||
|
||||
const { maxRequests, windowMs, perEndpoint, perIp } = route.rateLimit;
|
||||
|
||||
// Determine rate limit key
|
||||
let key = 'global';
|
||||
if (perEndpoint) {
|
||||
key = `${route.method}:${route.path}`;
|
||||
}
|
||||
|
||||
// Get or create limiter
|
||||
if (!this.rateLimiters.has(key)) {
|
||||
this.rateLimiters.set(key, {
|
||||
count: 0,
|
||||
resetTime: Date.now() + windowMs,
|
||||
clients: new Map()
|
||||
});
|
||||
}
|
||||
|
||||
const limiter = this.rateLimiters.get(key);
|
||||
|
||||
// Reset if window has passed
|
||||
if (Date.now() > limiter.resetTime) {
|
||||
limiter.count = 0;
|
||||
limiter.resetTime = Date.now() + windowMs;
|
||||
limiter.clients.clear();
|
||||
}
|
||||
|
||||
// Check per-IP limit if enabled
|
||||
if (perIp) {
|
||||
const clientIp = req.ip || req.connection.remoteAddress;
|
||||
let clientLimiter = limiter.clients.get(clientIp);
|
||||
|
||||
if (!clientLimiter) {
|
||||
clientLimiter = {
|
||||
count: 0,
|
||||
resetTime: Date.now() + windowMs
|
||||
};
|
||||
limiter.clients.set(clientIp, clientLimiter);
|
||||
}
|
||||
|
||||
// Reset client limiter if needed
|
||||
if (Date.now() > clientLimiter.resetTime) {
|
||||
clientLimiter.count = 0;
|
||||
clientLimiter.resetTime = Date.now() + windowMs;
|
||||
}
|
||||
|
||||
// Check client limit
|
||||
if (clientLimiter.count >= maxRequests) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Increment client count
|
||||
clientLimiter.count++;
|
||||
} else {
|
||||
// Check global limit
|
||||
if (limiter.count >= maxRequests) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Increment global count
|
||||
limiter.count++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an API error
|
||||
* @param code Error code
|
||||
* @param message Error message
|
||||
* @param status HTTP status code
|
||||
* @param details Additional details
|
||||
* @returns API error
|
||||
*/
|
||||
private createError(code: string, message: string, status = 400, details?: any): Error & { code: string; status: number; details?: any } {
|
||||
const error = new Error(message) as Error & { code: string; status: number; details?: any };
|
||||
error.code = code;
|
||||
error.status = status;
|
||||
if (details) {
|
||||
error.details = details;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that MTA service is available
|
||||
*/
|
||||
private validateMtaService(): void {
|
||||
if (!this.mtaRef) {
|
||||
throw this.createError('SERVICE_UNAVAILABLE', 'MTA service is not available', 503);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle email send request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleSendEmail(req: any, res: any): Promise<void> {
|
||||
this.validateMtaService();
|
||||
|
||||
const data = req.body as SendEmailRequest;
|
||||
|
||||
if (!data || !data.email) {
|
||||
throw this.createError('INVALID_REQUEST', 'Missing email data');
|
||||
}
|
||||
|
||||
try {
|
||||
// Create Email instance
|
||||
const email = new Email(data.email);
|
||||
|
||||
// Validate domains if requested
|
||||
if (data.validateDomains) {
|
||||
const fromDomain = email.getFromDomain();
|
||||
if (fromDomain) {
|
||||
const verification = await this.mtaRef.dnsManager.verifyEmailAuthRecords(fromDomain);
|
||||
|
||||
// Check if SPF and DKIM are valid
|
||||
if (!verification.spf.valid || !verification.dkim.valid) {
|
||||
throw this.createError('DOMAIN_VERIFICATION_FAILED', 'Domain DNS verification failed', 400, {
|
||||
verification
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send email
|
||||
const id = await this.mtaRef.send(email);
|
||||
|
||||
// Return success response
|
||||
res.json({
|
||||
id,
|
||||
message: 'Email queued successfully',
|
||||
status: 'pending'
|
||||
});
|
||||
} catch (error) {
|
||||
// Handle Email constructor errors
|
||||
if (error.message.includes('Invalid') || error.message.includes('must have')) {
|
||||
throw this.createError('INVALID_EMAIL', error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle email status request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleGetEmailStatus(req: any, res: any): Promise<void> {
|
||||
this.validateMtaService();
|
||||
|
||||
const id = req.params.id;
|
||||
|
||||
if (!id) {
|
||||
throw this.createError('INVALID_REQUEST', 'Missing email ID');
|
||||
}
|
||||
|
||||
// Get email status
|
||||
const status = this.mtaRef.getEmailStatus(id);
|
||||
|
||||
if (!status) {
|
||||
throw this.createError('NOT_FOUND', `Email with ID ${id} not found`, 404);
|
||||
}
|
||||
|
||||
// Create response
|
||||
const response: EmailStatusResponse = {
|
||||
id: status.id,
|
||||
status: status.status,
|
||||
sentAt: status.addedAt,
|
||||
recipient: status.email.to[0],
|
||||
attempts: status.attempts
|
||||
};
|
||||
|
||||
// Add additional fields if available
|
||||
if (status.lastAttempt) {
|
||||
response.sentAt = status.lastAttempt;
|
||||
}
|
||||
|
||||
if (status.status === DeliveryStatus.DELIVERED) {
|
||||
response.deliveredAt = status.lastAttempt;
|
||||
}
|
||||
|
||||
if (status.error) {
|
||||
response.error = status.error.message;
|
||||
}
|
||||
|
||||
if (status.nextAttempt) {
|
||||
response.nextRetry = status.nextAttempt;
|
||||
}
|
||||
|
||||
res.json(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle domain verification request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleVerifyDomain(req: any, res: any): Promise<void> {
|
||||
this.validateMtaService();
|
||||
|
||||
const domain = req.params.domain;
|
||||
|
||||
if (!domain) {
|
||||
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify domain DNS records
|
||||
const records = await this.mtaRef.dnsManager.verifyEmailAuthRecords(domain);
|
||||
|
||||
// Get MX records
|
||||
let mxValid = false;
|
||||
let mxRecords: string[] = [];
|
||||
let mxError: string = undefined;
|
||||
|
||||
try {
|
||||
const mxResult = await this.mtaRef.dnsManager.lookupMx(domain);
|
||||
mxValid = mxResult.length > 0;
|
||||
mxRecords = mxResult.map(mx => mx.exchange);
|
||||
} catch (error) {
|
||||
mxError = error.message;
|
||||
}
|
||||
|
||||
// Create response
|
||||
const response: DomainVerificationResponse = {
|
||||
domain,
|
||||
verified: records.spf.valid && records.dkim.valid && records.dmarc.valid && mxValid,
|
||||
details: {
|
||||
spf: {
|
||||
valid: records.spf.valid,
|
||||
record: records.spf.value,
|
||||
error: records.spf.error
|
||||
},
|
||||
dkim: {
|
||||
valid: records.dkim.valid,
|
||||
record: records.dkim.value,
|
||||
error: records.dkim.error
|
||||
},
|
||||
dmarc: {
|
||||
valid: records.dmarc.valid,
|
||||
record: records.dmarc.value,
|
||||
error: records.dmarc.error
|
||||
},
|
||||
mx: {
|
||||
valid: mxValid,
|
||||
records: mxRecords,
|
||||
error: mxError
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
throw this.createError('VERIFICATION_FAILED', `Domain verification failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get domain records request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleGetDomainRecords(req: any, res: any): Promise<void> {
|
||||
this.validateMtaService();
|
||||
|
||||
const domain = req.params.domain;
|
||||
|
||||
if (!domain) {
|
||||
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate recommended DNS records
|
||||
const records = await this.mtaRef.dnsManager.generateAllRecommendedRecords(domain);
|
||||
|
||||
res.json({
|
||||
domain,
|
||||
records
|
||||
});
|
||||
} catch (error) {
|
||||
throw this.createError('GENERATION_FAILED', `DNS record generation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle generate DKIM keys request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleGenerateDkim(req: any, res: any): Promise<void> {
|
||||
this.validateMtaService();
|
||||
|
||||
const domain = req.params.domain;
|
||||
|
||||
if (!domain) {
|
||||
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate DKIM keys
|
||||
await this.mtaRef.dkimCreator.createAndStoreDKIMKeys(domain);
|
||||
|
||||
// Get DNS record
|
||||
const dnsRecord = await this.mtaRef.dkimCreator.getDNSRecordForDomain(domain);
|
||||
|
||||
res.json({
|
||||
domain,
|
||||
dnsRecord,
|
||||
message: 'DKIM keys generated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
throw this.createError('GENERATION_FAILED', `DKIM generation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get DKIM public key request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleGetDkimPublicKey(req: any, res: any): Promise<void> {
|
||||
this.validateMtaService();
|
||||
|
||||
const domain = req.params.domain;
|
||||
|
||||
if (!domain) {
|
||||
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get DKIM keys
|
||||
const keys = await this.mtaRef.dkimCreator.readDKIMKeys(domain);
|
||||
|
||||
// Get DNS record
|
||||
const dnsRecord = await this.mtaRef.dkimCreator.getDNSRecordForDomain(domain);
|
||||
|
||||
res.json({
|
||||
domain,
|
||||
publicKey: keys.publicKey,
|
||||
dnsRecord
|
||||
});
|
||||
} catch (error) {
|
||||
throw this.createError('NOT_FOUND', `DKIM keys not found for domain: ${domain}`, 404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get stats request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleGetStats(req: any, res: any): Promise<void> {
|
||||
this.validateMtaService();
|
||||
|
||||
// Get MTA stats
|
||||
const stats = this.mtaRef.getStats();
|
||||
|
||||
res.json(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get API docs request
|
||||
* @param req Express request
|
||||
* @param res Express response
|
||||
*/
|
||||
private async handleGetApiDocs(req: any, res: any): Promise<void> {
|
||||
// Generate API documentation
|
||||
const docs = {
|
||||
name: 'MTA API',
|
||||
version: '1.0.0',
|
||||
description: 'API for interacting with the MTA service',
|
||||
endpoints: this.routes.map(route => ({
|
||||
method: route.method,
|
||||
path: route.path,
|
||||
description: route.description,
|
||||
authLevel: route.authLevel
|
||||
}))
|
||||
};
|
||||
|
||||
res.json(docs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the API server
|
||||
* @param port Port to listen on
|
||||
* @returns Promise that resolves when server is started
|
||||
*/
|
||||
public start(port: number = 3000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// Start HTTP server
|
||||
this.app.listen(port, () => {
|
||||
console.log(`API server listening on port ${port}`);
|
||||
resolve();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to start API server:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the API server
|
||||
*/
|
||||
public stop(): void {
|
||||
// Nothing to do if not running
|
||||
console.log('API server stopped');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user