rm unnec. stuff
This commit is contained in:
@@ -6,9 +6,7 @@ const morgan = require('morgan');
|
||||
const crypto = require('crypto');
|
||||
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
|
||||
const { DynamoDBDocumentClient, GetCommand, PutCommand, DeleteCommand, ScanCommand } = require('@aws-sdk/lib-dynamodb');
|
||||
const { body, param, validationResult, query } = require('express-validator');
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const { body, param, validationResult } = require('express-validator');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
@@ -16,22 +14,13 @@ const TOKEN_SECRET = process.env.TOKEN_SECRET_KEY;
|
||||
|
||||
// Middleware
|
||||
app.use(helmet());
|
||||
//app.use(cors());
|
||||
const corsOptions = {
|
||||
origin: [
|
||||
'https://config.email-bayarea.com',
|
||||
],
|
||||
origin: ['https://config.email-bayarea.com'],
|
||||
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
|
||||
allowedHeaders: [
|
||||
'Content-Type',
|
||||
'Accept',
|
||||
'Authorization',
|
||||
'x-hide-loading',
|
||||
],
|
||||
credentials: false, // true nur wenn Cookies / Auth-Headers mit credentials genutzt werden
|
||||
allowedHeaders: ['Content-Type', 'Accept', 'Authorization', 'x-hide-loading'],
|
||||
credentials: false,
|
||||
};
|
||||
app.use(cors(corsOptions));
|
||||
app.options('*', cors(corsOptions));
|
||||
app.use(express.json());
|
||||
app.use(morgan('dev'));
|
||||
|
||||
@@ -43,318 +32,73 @@ const client = new DynamoDBClient({
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const docClient = DynamoDBDocumentClient.from(client);
|
||||
const TABLE_NAME = process.env.DYNAMODB_TABLE || 'email-rules';
|
||||
|
||||
// Validation Middleware
|
||||
const validateEmailRule = [
|
||||
body('email_address').isEmail().withMessage('Valid email address is required'),
|
||||
body('ooo_active').optional().isBoolean().withMessage('ooo_active must be a boolean'),
|
||||
body('ooo_message').optional().isString().withMessage('ooo_message must be a string'),
|
||||
body('ooo_content_type').optional().isIn(['text', 'html']).withMessage('ooo_content_type must be "text" or "html"'),
|
||||
body('forwards').optional().isArray().withMessage('forwards must be an array'),
|
||||
body('forwards.*').optional().isEmail().withMessage('All forward addresses must be valid emails'),
|
||||
];
|
||||
|
||||
const validateEmail = [
|
||||
param('email').isEmail().withMessage('Valid email address is required'),
|
||||
];
|
||||
|
||||
// Error Handler Middleware
|
||||
// Validation
|
||||
const handleValidationErrors = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
error: 'Validation failed',
|
||||
details: errors.array(),
|
||||
});
|
||||
}
|
||||
if (!errors.isEmpty()) return res.status(400).json({ error: 'Validation failed', details: errors.array() });
|
||||
next();
|
||||
};
|
||||
|
||||
// Token Validation Function
|
||||
const validateToken = (email, expires, signature) => {
|
||||
// Check expiry
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (now > parseInt(expires)) {
|
||||
return { valid: false, error: 'Token expired' };
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
if (now > parseInt(expires)) return { valid: false, error: 'Token expired' };
|
||||
const data = `${email}|${expires}`;
|
||||
const expected = crypto
|
||||
.createHmac('sha256', TOKEN_SECRET)
|
||||
.update(data)
|
||||
.digest('hex');
|
||||
|
||||
if (signature !== expected) {
|
||||
return { valid: false, error: 'Invalid signature' };
|
||||
}
|
||||
|
||||
return { valid: true, email };
|
||||
const expected = crypto.createHmac('sha256', TOKEN_SECRET).update(data).digest('hex');
|
||||
return signature === expected ? { valid: true, email } : { valid: false, error: 'Invalid signature' };
|
||||
};
|
||||
|
||||
// Trigger Email Rules Synchronization
|
||||
const triggerSync = () => {
|
||||
const syncScriptPath = path.join(__dirname, '../sync/sync.js');
|
||||
// --- API Routes ---
|
||||
|
||||
console.log('🔄 Triggering email rules synchronization...');
|
||||
|
||||
const syncProcess = spawn('sudo', ['node', syncScriptPath], {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
});
|
||||
|
||||
syncProcess.unref(); // Allow the parent to exit independently
|
||||
|
||||
console.log('✅ Sync triggered (running in background)');
|
||||
};
|
||||
|
||||
// POST /api/auth/validate-token - Validate authentication token from Roundcube
|
||||
// 1. Auth check
|
||||
app.post('/api/auth/validate-token', [
|
||||
body('email').isEmail().withMessage('Valid email is required'),
|
||||
body('expires').isNumeric().withMessage('Expires must be a number'),
|
||||
body('signature').notEmpty().withMessage('Signature is required'),
|
||||
body('email').isEmail(),
|
||||
body('expires').isNumeric(),
|
||||
body('signature').notEmpty(),
|
||||
], handleValidationErrors, (req, res) => {
|
||||
try {
|
||||
const { email, expires, signature } = req.body;
|
||||
const result = validateToken(email, expires, signature);
|
||||
|
||||
if (!result.valid) {
|
||||
return res.status(401).json({
|
||||
error: 'Invalid token',
|
||||
message: result.error,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
email: result.email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Token validation error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Token validation failed',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
const { email, expires, signature } = req.body;
|
||||
const result = validateToken(email, expires, signature);
|
||||
if (!result.valid) return res.status(401).json({ error: result.error });
|
||||
res.json({ success: true, email: result.email });
|
||||
});
|
||||
|
||||
// Health Check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'OK', timestamp: new Date().toISOString() });
|
||||
});
|
||||
// 2. Health check
|
||||
app.get('/health', (req, res) => res.json({ status: 'OK' }));
|
||||
|
||||
// GET /api/rules - Get all rules
|
||||
app.get('/api/rules', async (req, res) => {
|
||||
// 3. Rules: Get One
|
||||
app.get('/api/rules/:email', [
|
||||
param('email').isEmail()
|
||||
], handleValidationErrors, async (req, res) => {
|
||||
try {
|
||||
const command = new ScanCommand({
|
||||
const response = await docClient.send(new GetCommand({
|
||||
TableName: TABLE_NAME,
|
||||
});
|
||||
|
||||
const response = await docClient.send(command);
|
||||
|
||||
res.json({
|
||||
rules: response.Items || [],
|
||||
count: response.Count || 0,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching rules:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch rules',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/rules/:email - Get rule for specific email
|
||||
app.get('/api/rules/:email', validateEmail, handleValidationErrors, async (req, res) => {
|
||||
try {
|
||||
const email = decodeURIComponent(req.params.email);
|
||||
|
||||
const command = new GetCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Key: {
|
||||
email_address: email,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await docClient.send(command);
|
||||
|
||||
if (!response.Item) {
|
||||
return res.status(404).json({
|
||||
error: 'Rule not found',
|
||||
message: `No rule exists for email: ${email}`,
|
||||
});
|
||||
}
|
||||
|
||||
Key: { email_address: decodeURIComponent(req.params.email) }
|
||||
}));
|
||||
if (!response.Item) return res.status(404).json({ error: 'No rule found' });
|
||||
res.json(response.Item);
|
||||
} catch (error) {
|
||||
console.error('Error fetching rule:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch rule',
|
||||
message: error.message,
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/rules - Create or update rule
|
||||
app.post('/api/rules', validateEmailRule, handleValidationErrors, async (req, res) => {
|
||||
// 4. Rules: Save/Update (OOO & Forwards)
|
||||
app.post('/api/rules', [
|
||||
body('email_address').isEmail(),
|
||||
body('ooo_active').isBoolean(),
|
||||
body('forwards').isArray()
|
||||
], handleValidationErrors, async (req, res) => {
|
||||
try {
|
||||
const { email_address, ooo_active, ooo_message, ooo_content_type, forwards } = req.body;
|
||||
|
||||
const item = {
|
||||
email_address,
|
||||
ooo_active: ooo_active || false,
|
||||
ooo_message: ooo_message || '',
|
||||
ooo_content_type: ooo_content_type || 'text',
|
||||
forwards: forwards || [],
|
||||
...req.body,
|
||||
last_updated: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const command = new PutCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Item: item,
|
||||
});
|
||||
|
||||
await docClient.send(command);
|
||||
|
||||
// Trigger immediate synchronization
|
||||
triggerSync();
|
||||
|
||||
res.status(201).json({
|
||||
message: 'Rule created/updated successfully',
|
||||
rule: item,
|
||||
});
|
||||
await docClient.send(new PutCommand({ TableName: TABLE_NAME, Item: item }));
|
||||
res.json({ success: true, rule: item });
|
||||
} catch (error) {
|
||||
console.error('Error creating/updating rule:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to create/update rule',
|
||||
message: error.message,
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/rules/:email - Update existing rule
|
||||
app.put('/api/rules/:email', validateEmail, validateEmailRule, handleValidationErrors, async (req, res) => {
|
||||
try {
|
||||
const email = decodeURIComponent(req.params.email);
|
||||
const { ooo_active, ooo_message, ooo_content_type, forwards } = req.body;
|
||||
|
||||
// First check if rule exists
|
||||
const getCommand = new GetCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Key: { email_address: email },
|
||||
});
|
||||
|
||||
const existingRule = await docClient.send(getCommand);
|
||||
|
||||
if (!existingRule.Item) {
|
||||
return res.status(404).json({
|
||||
error: 'Rule not found',
|
||||
message: `No rule exists for email: ${email}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Merge with existing data
|
||||
const item = {
|
||||
...existingRule.Item,
|
||||
ooo_active: ooo_active !== undefined ? ooo_active : existingRule.Item.ooo_active,
|
||||
ooo_message: ooo_message !== undefined ? ooo_message : existingRule.Item.ooo_message,
|
||||
ooo_content_type: ooo_content_type !== undefined ? ooo_content_type : existingRule.Item.ooo_content_type,
|
||||
forwards: forwards !== undefined ? forwards : existingRule.Item.forwards,
|
||||
last_updated: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const putCommand = new PutCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Item: item,
|
||||
});
|
||||
|
||||
await docClient.send(putCommand);
|
||||
|
||||
// Trigger immediate synchronization
|
||||
triggerSync();
|
||||
|
||||
res.json({
|
||||
message: 'Rule updated successfully',
|
||||
rule: item,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating rule:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to update rule',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/rules/:email - Delete rule
|
||||
app.delete('/api/rules/:email', validateEmail, handleValidationErrors, async (req, res) => {
|
||||
try {
|
||||
const email = decodeURIComponent(req.params.email);
|
||||
|
||||
// First check if rule exists
|
||||
const getCommand = new GetCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Key: { email_address: email },
|
||||
});
|
||||
|
||||
const existingRule = await docClient.send(getCommand);
|
||||
|
||||
if (!existingRule.Item) {
|
||||
return res.status(404).json({
|
||||
error: 'Rule not found',
|
||||
message: `No rule exists for email: ${email}`,
|
||||
});
|
||||
}
|
||||
|
||||
const deleteCommand = new DeleteCommand({
|
||||
TableName: TABLE_NAME,
|
||||
Key: {
|
||||
email_address: email,
|
||||
},
|
||||
});
|
||||
|
||||
await docClient.send(deleteCommand);
|
||||
|
||||
// Trigger immediate synchronization
|
||||
triggerSync();
|
||||
|
||||
res.json({
|
||||
message: 'Rule deleted successfully',
|
||||
email_address: email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error deleting rule:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to delete rule',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 404 Handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
error: 'Not Found',
|
||||
message: `Cannot ${req.method} ${req.path}`,
|
||||
});
|
||||
});
|
||||
|
||||
// Global Error Handler
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Unhandled error:', err);
|
||||
res.status(500).json({
|
||||
error: 'Internal Server Error',
|
||||
message: err.message,
|
||||
});
|
||||
});
|
||||
|
||||
// Start Server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Email Config API running on port ${PORT}`);
|
||||
console.log(`📊 DynamoDB Table: ${TABLE_NAME}`);
|
||||
console.log(`🌍 Region: ${process.env.AWS_REGION || 'us-east-2'}`);
|
||||
});
|
||||
app.listen(PORT, () => console.log(`🚀 API active on port ${PORT}`));
|
||||
Reference in New Issue
Block a user