commit 528acda3d918feba7fc6354c67b9636b48d3ba12
parent 7b1c28bca7ac5a4083df641565eac1286ba83f9f
Author: AMIT DUTTA <amitdutta4255@gmail.com>
Date: Mon, 16 Feb 2026 18:37:12 +0530
Add session management and authentication features
Diffstat:
| M | index.js | | | 125 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
1 file changed, 100 insertions(+), 25 deletions(-)
diff --git a/index.js b/index.js
@@ -10,6 +10,7 @@ const pino = require('pino');
const admin = require('firebase-admin');
const fs = require('fs');
const path = require('path');
+const crypto = require('crypto');
// --- CONFIGURATION ---
const PORT = process.env.PORT || 3000;
@@ -18,6 +19,7 @@ const AUTH_PASS = process.env.AUTH_PASS;
// Initialize Express
const app = express();
+app.use(express.urlencoded({ extended: true })); // Parse form data
// --- FIREBASE SETUP ---
let serviceAccount;
@@ -64,11 +66,11 @@ async function startWhatsApp() {
if (qr) {
qrCodeData = qr;
- isConnected = false; // Ensure we know we aren't logged in yet
+ isConnected = false;
}
if (connection === 'close') {
- isConnected = false; // Reset on disconnect
+ isConnected = false;
const shouldReconnect = (lastDisconnect.error)?.output?.statusCode !== DisconnectReason.loggedOut;
if (shouldReconnect) {
startWhatsApp();
@@ -76,25 +78,20 @@ async function startWhatsApp() {
} else if (connection === 'open') {
console.log("System: Connection Open and Authenticated");
qrCodeData = null;
- isConnected = true; // Only true when fully open
+ isConnected = true;
}
});
sock.ev.on('creds.update', saveCreds);
- // --- MESSAGE HANDLING ---
sock.ev.on('messages.upsert', async ({ messages, type }) => {
- // Allow 'notify' (incoming) and 'append' (sent from phone/history sync)
if (type !== 'notify' && type !== 'append') return;
for (const msg of messages) {
try {
if (!msg.message) continue;
- // For sent messages, remoteJid is the Recipient.
- // For received messages, remoteJid is the Sender.
const remoteJid = msg.key.remoteJid;
-
if (remoteJid === 'status@broadcast') continue;
const textContent =
@@ -111,17 +108,12 @@ async function startWhatsApp() {
: Math.floor(Date.now() / 1000);
const isFromMe = msg.key.fromMe || false;
-
- // Determine name: Use "Me" for outgoing, otherwise pushName or Unknown
const senderName = isFromMe ? "Me" : (msg.pushName || "Unknown");
- // --- SAVE TO FIRESTORE ---
- // UPDATE: Using .doc(msg.key.id).set(...) instead of .add(...)
- // This ensures IDEMPOTENCY. If the same message arrives twice, it won't duplicate.
await db.collection('Chats')
.doc(remoteJid)
.collection('Messages')
- .doc(msg.key.id) // <--- CRITICAL CHANGE: Use WhatsApp ID as Doc ID
+ .doc(msg.key.id)
.set({
text: textContent,
senderId: remoteJid,
@@ -129,7 +121,7 @@ async function startWhatsApp() {
timestamp: timestamp,
fromMe: isFromMe,
id: msg.key.id
- }, { merge: true }); // Merge ensures we don't accidentally wipe fields if we add more later
+ }, { merge: true });
} catch (err) {
// Silent error handling
@@ -138,39 +130,115 @@ async function startWhatsApp() {
});
}
+// --- AUTH UTILS ---
+// Generate a simple session token (in production use a real secret)
+const SESSION_SECRET = crypto.createHash('sha256').update(AUTH_PASS || 'default').digest('hex');
+
+function parseCookies(request) {
+ const list = {};
+ const rc = request.headers.cookie;
+ if (rc) {
+ rc.split(';').forEach((cookie) => {
+ const parts = cookie.split('=');
+ list[parts.shift().trim()] = decodeURI(parts.join('='));
+ });
+ }
+ return list;
+}
+
// --- EXPRESS ROUTES ---
-// 1. PUBLIC ROUTE: Ping (Must be before Auth middleware)
+// 1. PUBLIC ROUTE: Ping (UptimeRobot)
app.get('/ping', (req, res) => {
res.status(200).send('Pong');
});
-// --- MIDDLEWARE: BASIC AUTH ---
+// 2. LOGIN PAGE (GET)
+app.get('/login', (req, res) => {
+ res.send(`
+ <html>
+ <body style="font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f2f5;">
+ <form action="/login" method="POST" style="background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 300px;">
+ <h2 style="margin-top: 0; text-align: center;">WhatsApp Logger</h2>
+ <div style="margin-bottom: 1rem;">
+ <label style="display: block; margin-bottom: 0.5rem;">Username</label>
+ <input type="text" name="username" required style="width: 100%; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;">
+ </div>
+ <div style="margin-bottom: 1rem;">
+ <label style="display: block; margin-bottom: 0.5rem;">Password</label>
+ <input type="password" name="password" required style="width: 100%; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;">
+ </div>
+ <div style="margin-bottom: 1rem;">
+ <label style="display: flex; align-items: center; font-size: 0.9rem;">
+ <input type="checkbox" name="remember" value="yes" style="margin-right: 0.5rem;">
+ Keep me logged in for 5 mins
+ </label>
+ </div>
+ <button type="submit" style="width: 100%; padding: 0.75rem; background: #25D366; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer;">Login</button>
+ </form>
+ </body>
+ </html>
+ `);
+});
+
+// 3. LOGIN ACTION (POST)
+app.post('/login', (req, res) => {
+ const { username, password, remember } = req.body;
+
+ if (username === AUTH_USER && password === AUTH_PASS) {
+ let cookieSettings = 'HttpOnly; Path=/;'; // HttpOnly prevents JS access (Security)
+
+ if (remember === 'yes') {
+ // Add Max-Age = 300 seconds (5 minutes)
+ cookieSettings += ' Max-Age=300;';
+ }
+ // If not remembered, no Max-Age = Session Cookie (Expires when browser closes)
+
+ res.setHeader('Set-Cookie', `auth_session=${SESSION_SECRET}; ${cookieSettings}`);
+ return res.redirect('/');
+ }
+
+ res.status(401).send('Invalid credentials. <a href="/login">Try again</a>');
+});
+
+// 4. LOGOUT ACTION
+app.get('/logout', (req, res) => {
+ res.setHeader('Set-Cookie', 'auth_session=; Max-Age=0; Path=/;');
+ res.redirect('/login');
+});
+
+// --- MIDDLEWARE: FORM AUTH ---
const checkAuth = (req, res, next) => {
if (!AUTH_USER || !AUTH_PASS) return next();
- const b64auth = (req.headers.authorization || '').split(' ')[1] || '';
- const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':');
-
- if (login && password && login === AUTH_USER && password === AUTH_PASS) {
+ const cookies = parseCookies(req);
+ // Check if the cookie exists and matches our secret
+ if (cookies.auth_session === SESSION_SECRET) {
return next();
}
- res.set('WWW-Authenticate', 'Basic realm="401"');
- res.status(401).send('Authentication required to access WhatsApp Logger.');
+ // If API/Asset request, send 401, else redirect to login
+ if (req.path.startsWith('/api')) {
+ res.status(401).send('Unauthorized');
+ } else {
+ res.redirect('/login');
+ }
};
app.use(checkAuth);
-// 2. PROTECTED ROUTE: Serve Static Frontend
+// 5. PROTECTED ROUTE: Serve Static Frontend
app.use(express.static(path.join(__dirname, 'public')));
-// 3. PROTECTED ROUTE: Main Page (QR Code or Status)
+// 6. PROTECTED ROUTE: Main Page (QR Code or Status)
app.get('/', async (req, res) => {
+ const logoutBtn = `<a href="/logout" style="position: absolute; top: 10px; right: 10px; padding: 10px; background: #ff4444; color: white; text-decoration: none; border-radius: 4px; font-size: 14px;">Logout</a>`;
+
if (isConnected) {
return res.send(`
<html>
<body style="font-family: sans-serif; text-align: center; padding-top: 50px;">
+ ${logoutBtn}
<h2 style="color: green;">System Operational</h2>
<p>Connected to WhatsApp.</p>
</body>
@@ -185,6 +253,7 @@ app.get('/', async (req, res) => {
<html>
<head><meta http-equiv="refresh" content="5"></head>
<body style="font-family: sans-serif; text-align: center; padding-top: 50px;">
+ ${logoutBtn}
<h2>Scan to Link</h2>
<img src="${qrImage}" alt="QR Code" />
<p>Refreshes every 5 seconds...</p>
@@ -208,4 +277,10 @@ app.get('/', async (req, res) => {
app.listen(PORT, () => {
startWhatsApp();
console.log(`Server running on port ${PORT}`);
+
+ if (AUTH_USER && AUTH_PASS) {
+ console.log("Security: Form Authentication is ENABLED.");
+ } else {
+ console.log("Security: Form Authentication is DISABLED (Env vars missing).");
+ }
});