146 lines
4.5 KiB
JavaScript
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}`);
|
|
});
|
|
|