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}`));