104 lines
3.5 KiB
JavaScript
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}`)); |