config-email/backend/server.js

104 lines
3.5 KiB
JavaScript

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
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 } = require('express-validator');
const app = express();
const PORT = process.env.PORT || 3001;
const TOKEN_SECRET = process.env.TOKEN_SECRET_KEY;
// Middleware
app.use(helmet());
const corsOptions = {
origin: ['https://config.email-bayarea.com'],
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
allowedHeaders: ['Content-Type', 'Accept', 'Authorization', 'x-hide-loading'],
credentials: false,
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(morgan('dev'));
// AWS DynamoDB Configuration
const client = new DynamoDBClient({
region: process.env.AWS_REGION || 'us-east-2',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.DYNAMODB_TABLE || 'email-rules';
// Validation
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ error: 'Validation failed', details: errors.array() });
next();
};
const validateToken = (email, expires, signature) => {
const now = Math.floor(Date.now() / 1000);
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');
return signature === expected ? { valid: true, email } : { valid: false, error: 'Invalid signature' };
};
// --- API Routes ---
// 1. Auth check
app.post('/api/auth/validate-token', [
body('email').isEmail(),
body('expires').isNumeric(),
body('signature').notEmpty(),
], handleValidationErrors, (req, res) => {
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 });
});
// 2. Health check
app.get('/health', (req, res) => res.json({ status: 'OK' }));
// 3. Rules: Get One
app.get('/api/rules/:email', [
param('email').isEmail()
], handleValidationErrors, async (req, res) => {
try {
const response = await docClient.send(new GetCommand({
TableName: TABLE_NAME,
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) {
res.status(500).json({ error: error.message });
}
});
// 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 item = {
...req.body,
last_updated: new Date().toISOString(),
};
await docClient.send(new PutCommand({ TableName: TABLE_NAME, Item: item }));
res.json({ success: true, rule: item });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(PORT, () => console.log(`🚀 API active on port ${PORT}`));