146 lines
4.5 KiB
JavaScript

const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const rateLimit = require("express-rate-limit");
const { Client, LocalAuth } = require("whatsapp-web.js");
const qrcode = require("qrcode-terminal");
const { loadEnvFile } = require("./src/utils/env");
const { initSchema } = require("./src/db");
const { requestLogger } = require("./src/utils/logger");
loadEnvFile();
const app = express();
const PORT = process.env.PORT || 3000;
const dashboardOrigin = process.env.VERIFLO_DASHBOARD_ORIGIN || 'http://localhost:5174';
const whatsappEnabled = process.env.VERIFLO_ENABLE_WHATSAPP_CLIENT !== 'false';
// Initialize SQLite Schema
initSchema();
// --- Global Security & Middleware ---
app.use(helmet());
app.use(cors({
origin: [dashboardOrigin, 'http://localhost:5173'],
credentials: false,
}));
app.use(express.json());
// Request logging middleware - logs all requests with timestamps to file
app.use(requestLogger);
// Import Routers
const authRoutes = require('./src/routes/auth');
const userRoutes = require('./src/routes/user');
const otpRoutes = require('./src/routes/otp');
const analyticsRoutes = require('./src/routes/analytics');
const adminRoutes = require('./src/routes/admin');
// Trust proxy if running behind Nginx/Vercel to get real IPs for analytics
app.set('trust proxy', 1);
// Global Rate Limiter — 5000 reqs / 15 mins per IP (development-friendly, still prevents abuse)
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5000,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Too many requests from this IP. Please try again later.' },
skip: (req) => req.ip === '127.0.0.1' || req.ip === '::1', // Skip rate limiting for localhost
});
app.use(globalLimiter);
// --- WhatsApp Client Wrapper ---
// We attach the client and its state to `app.locals` so routes can access it
app.locals.waReady = false;
app.locals.waInitError = null;
app.locals.waClient = null;
if (whatsappEnabled) {
const client = new Client({
authStrategy: new LocalAuth(),
puppeteer: {
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-features=site-per-process',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list'
]
}
});
client.on("qr", (qr) => {
console.log("Scan this QR code with WhatsApp:");
qrcode.generate(qr, { small: true });
});
client.on("ready", () => {
app.locals.waReady = true;
app.locals.waInitError = null;
console.log("WhatsApp client is ready!");
});
client.on("authenticated", () => {
console.log("Authenticated successfully.");
});
client.on("auth_failure", (msg) => {
app.locals.waInitError = typeof msg === 'string' ? msg : 'WhatsApp authentication failed.';
console.error("Authentication failed:", msg);
});
client.on("disconnected", (reason) => {
app.locals.waReady = false;
app.locals.waInitError = typeof reason === 'string' ? reason : 'WhatsApp client disconnected.';
console.log("Client disconnected:", reason);
});
client.initialize().catch((error) => {
app.locals.waReady = false;
app.locals.waInitError = error.message;
console.error("WhatsApp client failed to initialize:", error.message);
});
app.locals.waClient = client;
} else {
app.locals.waInitError = 'WhatsApp client disabled by configuration.';
console.log('WhatsApp client initialization skipped because VERIFLO_ENABLE_WHATSAPP_CLIENT=false');
}
// --- Express Routes ---
app.get("/status", (req, res) => {
res.json({
connected: app.locals.waReady,
whatsapp_enabled: whatsappEnabled,
whatsapp_error: app.locals.waInitError,
timestamp: new Date(),
port: PORT,
dashboard_origin: dashboardOrigin,
});
});
// --- Register Internal Dashboard APIs ---
app.use('/api/auth', authRoutes);
app.use('/api/user', userRoutes);
app.use('/api/user/analytics', analyticsRoutes);
app.use('/api/admin', adminRoutes);
// --- Register Public Facing Edge SDK APIs ---
app.use('/v1/otp', otpRoutes);
// Legacy /send route temporarily preserved for reference,
// but it will be replaced by the secured /v1/otp/send SDK endpoint later
app.post("/send", async (req, res) => {
if (!app.locals.waReady) return res.status(503).json({ error: "WhatsApp not ready" });
res.status(410).json({ error: "Use /v1/otp/send instead" });
});
// --- Server Startup ---
app.listen(PORT, () => {
console.log(`Veriflo API server running on http://localhost:${PORT}`);
});