OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana
Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor, admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS. Kredit & lisensi (ISC): - Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak atas base kode dan repo yang bermanfaat. - Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder. - Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
This commit is contained in:
commit
eaf05f2811
280 files changed
+123975
No files matched your search
@@ -0,0 +1,757 @@
|
||||
# IMPLEMENTATION GUIDE - SECURITY FIXES
|
||||
**Status:** READY TO IMPLEMENT
|
||||
**Tanggal:** 14 Agustus 2026
|
||||
**Target Completion:** 2-3 minggu
|
||||
|
||||
---
|
||||
|
||||
## 🚀 QUICK START - CRITICAL FIXES
|
||||
|
||||
### STEP 1: Install Required Packages
|
||||
```bash
|
||||
npm install bcrypt dotenv helmet express-rate-limit
|
||||
npm install --save-dev @types/bcrypt
|
||||
|
||||
# Optional but recommended:
|
||||
npm install express-validator joi
|
||||
npm install better-sqlite3-session-store
|
||||
```
|
||||
|
||||
### STEP 2: Create .env File
|
||||
```bash
|
||||
# Copy template
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env dengan credentials actual
|
||||
# Edit dengan text editor
|
||||
nano .env
|
||||
```
|
||||
|
||||
File `.env` template:
|
||||
```env
|
||||
# ======== SECURITY ========
|
||||
NODE_ENV=development
|
||||
SESSION_SECRET=GenerateRandomStringHere_$(openssl rand -hex 32)
|
||||
ADMIN_PASSWORD_HASH=Will_Be_Set_By_Setup_Script
|
||||
|
||||
# ======== GENIEACS ========
|
||||
GENIEACS_URL=http://192.168.8.189:7557
|
||||
GENIEACS_USERNAME=admin
|
||||
GENIEACS_PASSWORD=ChangeThisPassword
|
||||
|
||||
# ======== DATABASE ========
|
||||
DATABASE_PATH=./database/billing.db
|
||||
|
||||
# ======== PAYMENT GATEWAYS ========
|
||||
MIDTRANS_SERVER_KEY=Mid_server_xxxxx
|
||||
MIDTRANS_MODE=sandbox
|
||||
|
||||
XENDIT_API_KEY=xnd_development_xxxxx
|
||||
|
||||
TRIPAY_API_KEY=xxxxx
|
||||
TRIPAY_MERCHANT_CODE=xxxxx
|
||||
TRIPAY_MODE=sandbox
|
||||
|
||||
DUITKU_API_KEY=xxxxx
|
||||
DUITKU_MERCHANT_CODE=xxxxx
|
||||
|
||||
# ======== THIRD PARTY ========
|
||||
DIGIFLAZZ_USERNAME=xxxxx
|
||||
DIGIFLAZZ_API_KEY=xxxxx
|
||||
DIGIFLAZZ_WEBHOOK_SECRET=xxxxx
|
||||
|
||||
# ======== WHATSAPP ========
|
||||
WHATSAPP_ENABLED=true
|
||||
|
||||
# ======== TELEGRAM ========
|
||||
TELEGRAM_ENABLED=false
|
||||
TELEGRAM_BOT_TOKEN=xxxxx
|
||||
|
||||
# ======== RADIUS ========
|
||||
RADIUS_SECRET=secret123
|
||||
RADIUS_AUTH_PORT=1812
|
||||
RADIUS_ACCT_PORT=1813
|
||||
|
||||
# ======== LOGGING ========
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
### STEP 3: Update .gitignore
|
||||
```bash
|
||||
# Add these lines to .gitignore
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
settings.json # Optional: jika ada credentials di sini
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 FIX #1: Password Hashing with Bcrypt
|
||||
|
||||
### Part A: Create Password Helper Module
|
||||
|
||||
**File:** `utils/passwordHelper.js`
|
||||
```javascript
|
||||
const bcrypt = require('bcrypt');
|
||||
const SALT_ROUNDS = 10;
|
||||
|
||||
/**
|
||||
* Hash password dengan bcrypt
|
||||
* @param {string} plainPassword - Plain text password
|
||||
* @returns {Promise<string>} Hashed password
|
||||
*/
|
||||
async function hashPassword(plainPassword) {
|
||||
if (!plainPassword) throw new Error('Password cannot be empty');
|
||||
return await bcrypt.hash(plainPassword, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify password
|
||||
* @param {string} plainPassword - Plain text password to verify
|
||||
* @param {string} hashedPassword - Hashed password from database
|
||||
* @returns {Promise<boolean>} True if password matches
|
||||
*/
|
||||
async function verifyPassword(plainPassword, hashedPassword) {
|
||||
if (!plainPassword || !hashedPassword) return false;
|
||||
return await bcrypt.compare(plainPassword, hashedPassword);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hashPassword,
|
||||
verifyPassword
|
||||
};
|
||||
```
|
||||
|
||||
### Part B: Create Admin Setup Script
|
||||
|
||||
**File:** `scripts/setup-admin-password.js`
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
const readline = require('readline');
|
||||
const db = require('../config/database');
|
||||
const { hashPassword } = require('../utils/passwordHelper');
|
||||
const { logger } = require('../config/logger');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
const question = (prompt) => new Promise((resolve) => {
|
||||
rl.question(prompt, resolve);
|
||||
});
|
||||
|
||||
async function setupAdmin() {
|
||||
console.log('\n🔐 SETUP ADMIN PASSWORD\n');
|
||||
|
||||
try {
|
||||
// Check if admin exists
|
||||
const admin = db.prepare('SELECT id FROM administrators LIMIT 1').get();
|
||||
|
||||
if (!admin) {
|
||||
console.log('⚠️ No administrator found in database.');
|
||||
console.log('Please run database migration first.\n');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const username = await question('Admin Username [admin]: ');
|
||||
const password = await question('Admin Password (min 8 chars): ');
|
||||
const passwordConfirm = await question('Confirm Password: ');
|
||||
|
||||
// Validation
|
||||
if (!username.trim() || !password) {
|
||||
console.log('❌ Username and password cannot be empty');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
console.log('❌ Passwords do not match');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
console.log('❌ Password must be at least 8 characters');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Hash password
|
||||
console.log('\n🔐 Hashing password...');
|
||||
const hashedPassword = await hashPassword(password);
|
||||
|
||||
// Update database
|
||||
const stmt = db.prepare('UPDATE administrators SET username = ?, password_hash = ? WHERE id = ?');
|
||||
stmt.run(username.trim(), hashedPassword, admin.id);
|
||||
|
||||
console.log('✅ Admin password updated successfully!');
|
||||
console.log(` Username: ${username.trim()}`);
|
||||
console.log('\nℹ️ Please keep your password secure and do NOT share with anyone.\n');
|
||||
|
||||
rl.close();
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
logger.error('Setup failed:', err);
|
||||
console.error('❌ Error:', err.message);
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
setupAdmin();
|
||||
```
|
||||
|
||||
### Part C: Update Technician Service
|
||||
|
||||
**File:** `services/techService.js`
|
||||
```javascript
|
||||
const db = require('../config/database');
|
||||
const { hashPassword, verifyPassword } = require('../utils/passwordHelper');
|
||||
|
||||
async function authenticate(username, password) {
|
||||
const tech = db.prepare('SELECT * FROM technicians WHERE username = ? AND is_active = 1').get(username);
|
||||
|
||||
if (!tech) return null;
|
||||
|
||||
// Verify password using bcrypt
|
||||
const isValid = await verifyPassword(password, tech.password_hash);
|
||||
|
||||
if (!isValid) return null;
|
||||
|
||||
// Return user data tanpa password
|
||||
const { password_hash, ...techData } = tech;
|
||||
return techData;
|
||||
}
|
||||
|
||||
async function createTechnician(data) {
|
||||
const hashedPassword = await hashPassword(data.password);
|
||||
const stmt = db.prepare(
|
||||
'INSERT INTO technicians (username, password_hash, name, phone, area) VALUES (?, ?, ?, ?, ?)'
|
||||
);
|
||||
return stmt.run(data.username, hashedPassword, data.name, data.phone || '', data.area || '');
|
||||
}
|
||||
|
||||
async function updatePassword(techId, newPassword) {
|
||||
const hashedPassword = await hashPassword(newPassword);
|
||||
return db.prepare('UPDATE technicians SET password_hash = ? WHERE id = ?').run(hashedPassword, techId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
authenticate,
|
||||
createTechnician,
|
||||
updatePassword,
|
||||
// ... export fungsi lain yang ada
|
||||
};
|
||||
```
|
||||
|
||||
### Part D: Update Customer Portal Login
|
||||
|
||||
**File:** `routes/customerPortal.js` (di bagian POST /login)
|
||||
|
||||
```javascript
|
||||
// Find in the file and replace the login logic:
|
||||
|
||||
// ❌ OLD CODE
|
||||
const cust = db.prepare('SELECT * FROM customers WHERE ...')
|
||||
.get(loginId, password);
|
||||
|
||||
// ✅ NEW CODE
|
||||
const cust = db.prepare('SELECT * FROM customers WHERE login_id = ?')
|
||||
.get(loginId);
|
||||
|
||||
if (cust) {
|
||||
const { verifyPassword } = require('../utils/passwordHelper');
|
||||
const isValid = await verifyPassword(password, cust.password_hash);
|
||||
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Regenerate session ID untuk security
|
||||
req.session.regenerate((err) => {
|
||||
if (err) return res.status(500).json({ error: 'Session error' });
|
||||
req.session.customerId = cust.id;
|
||||
res.json({ success: true });
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Part E: Database Migration
|
||||
|
||||
**File:** `scripts/migrate-password-hashing.js`
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
const db = require('../config/database');
|
||||
const { hashPassword } = require('../utils/passwordHelper');
|
||||
const { logger } = require('../config/logger');
|
||||
|
||||
async function migrate() {
|
||||
console.log('🔄 Starting password hashing migration...\n');
|
||||
|
||||
try {
|
||||
// Migrate technicians
|
||||
console.log('📋 Migrating technicians...');
|
||||
const techs = db.prepare('SELECT id, password FROM technicians WHERE password_hash IS NULL').all();
|
||||
|
||||
for (const tech of techs) {
|
||||
if (tech.password) {
|
||||
const hashedPassword = await hashPassword(tech.password);
|
||||
db.prepare('UPDATE technicians SET password_hash = ? WHERE id = ?').run(hashedPassword, tech.id);
|
||||
console.log(` ✅ Tech ${tech.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate customers (if applicable)
|
||||
console.log('\n📋 Migrating customers...');
|
||||
const custs = db.prepare('SELECT id, password FROM customers WHERE password_hash IS NULL AND password IS NOT NULL').all();
|
||||
|
||||
for (const cust of custs) {
|
||||
if (cust.password) {
|
||||
const hashedPassword = await hashPassword(cust.password);
|
||||
db.prepare('UPDATE customers SET password_hash = ? WHERE id = ?').run(hashedPassword, cust.id);
|
||||
console.log(` ✅ Customer ${cust.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate administrators
|
||||
console.log('\n📋 Migrating administrators...');
|
||||
const admins = db.prepare('SELECT id, password FROM administrators WHERE password_hash IS NULL').all();
|
||||
|
||||
for (const admin of admins) {
|
||||
if (admin.password) {
|
||||
const hashedPassword = await hashPassword(admin.password);
|
||||
db.prepare('UPDATE administrators SET password_hash = ? WHERE id = ?').run(hashedPassword, admin.id);
|
||||
console.log(` ✅ Admin ${admin.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✅ Migration completed successfully!');
|
||||
console.log('\n⚠️ IMPORTANT:');
|
||||
console.log(' 1. Delete plain password columns: ALTER TABLE technicians DROP COLUMN password;');
|
||||
console.log(' 2. Ensure all code uses password_hash column');
|
||||
console.log(' 3. Update settings.json to not store admin password\n');
|
||||
|
||||
logger.info('Password hashing migration completed');
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
logger.error('Migration failed:', err);
|
||||
console.error('❌ Error:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
```
|
||||
|
||||
### Part F: Update Database Schema
|
||||
|
||||
```sql
|
||||
-- Add password_hash column to tables (if not exists)
|
||||
ALTER TABLE technicians ADD COLUMN password_hash TEXT;
|
||||
ALTER TABLE administrators ADD COLUMN password_hash TEXT;
|
||||
ALTER TABLE customers ADD COLUMN password_hash TEXT;
|
||||
|
||||
-- After migration, drop old password columns:
|
||||
-- ALTER TABLE technicians DROP COLUMN password;
|
||||
-- ALTER TABLE administrators DROP COLUMN password;
|
||||
-- ALTER TABLE customers DROP COLUMN password;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗝️ FIX #2: Environment Variables & Config Management
|
||||
|
||||
### Part A: Create .env.example
|
||||
|
||||
**File:** `.env.example`
|
||||
```env
|
||||
# Copy this file to .env and fill in actual values
|
||||
# DO NOT commit .env to git!
|
||||
|
||||
# ======== APPLICATION ========
|
||||
NODE_ENV=development
|
||||
SERVER_PORT=3001
|
||||
SERVER_HOST=localhost
|
||||
|
||||
# ======== SECURITY ========
|
||||
SESSION_SECRET=change-me-to-random-string-minimum-32-chars
|
||||
COOKIE_SECURE=false # Set to true in production with HTTPS
|
||||
|
||||
# ======== ADMIN SETUP ========
|
||||
# Set these during first-time setup, will be migrated to database
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=change-me-to-strong-password-min-8-chars
|
||||
|
||||
# ======== GENIEACS ========
|
||||
GENIEACS_URL=http://192.168.8.189:7557
|
||||
GENIEACS_USERNAME=admin
|
||||
GENIEACS_PASSWORD=change-me
|
||||
|
||||
# ======== PAYMENT GATEWAYS ========
|
||||
MIDTRANS_ENABLED=true
|
||||
MIDTRANS_SERVER_KEY=Mid_server_xxxxx
|
||||
MIDTRANS_MODE=sandbox
|
||||
|
||||
XENDIT_ENABLED=true
|
||||
XENDIT_API_KEY=xnd_development_xxxxx
|
||||
|
||||
TRIPAY_ENABLED=false
|
||||
TRIPAY_API_KEY=xxxxx
|
||||
TRIPAY_PRIVATE_KEY=xxxxx
|
||||
TRIPAY_MERCHANT_CODE=xxxxx
|
||||
TRIPAY_MODE=sandbox
|
||||
|
||||
DUITKU_ENABLED=true
|
||||
DUITKU_API_KEY=xxxxx
|
||||
DUITKU_MERCHANT_CODE=xxxxx
|
||||
DUITKU_MODE=sandbox
|
||||
|
||||
# ======== DIGIFLAZZ ========
|
||||
DIGIFLAZZ_USERNAME=
|
||||
DIGIFLAZZ_API_KEY=
|
||||
DIGIFLAZZ_WEBHOOK_SECRET=
|
||||
|
||||
# ======== WHATSAPP ========
|
||||
WHATSAPP_ENABLED=true
|
||||
WHATSAPP_AUTH_FOLDER=auth_info_baileys
|
||||
|
||||
# ======== TELEGRAM ========
|
||||
TELEGRAM_ENABLED=false
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
|
||||
# ======== RADIUS ========
|
||||
RADIUS_ENABLED=0
|
||||
RADIUS_SECRET=secret123
|
||||
|
||||
# ======== LOGGING ========
|
||||
LOG_LEVEL=info
|
||||
|
||||
# ======== DATABASE ========
|
||||
DATABASE_PATH=./database/billing.db
|
||||
|
||||
# ======== TIMEZONE ========
|
||||
TIMEZONE=Asia/Jakarta
|
||||
```
|
||||
|
||||
### Part B: Update config/settingsManager.js
|
||||
|
||||
```javascript
|
||||
// Add at the top of settingsManager.js:
|
||||
require('dotenv').config();
|
||||
|
||||
// Modify getSetting function to check .env first:
|
||||
function getSetting(key, defaultValue = null) {
|
||||
// 1. Check environment variable first (highest priority)
|
||||
const envKey = key.toUpperCase().replace(/([A-Z])/g, '_$1');
|
||||
if (process.env[envKey] !== undefined) {
|
||||
return process.env[envKey];
|
||||
}
|
||||
|
||||
// 2. Check settings.json
|
||||
const settings = getSettings();
|
||||
if (settings[key] !== undefined) {
|
||||
return settings[key];
|
||||
}
|
||||
|
||||
// 3. Return default value
|
||||
return defaultValue;
|
||||
}
|
||||
```
|
||||
|
||||
### Part C: Update .gitignore
|
||||
|
||||
```bash
|
||||
# Add to .gitignore:
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.development.local
|
||||
.env.staging.local
|
||||
.env.production.local
|
||||
|
||||
# Optional: exclude settings.json if it contains credentials
|
||||
# settings.json
|
||||
# Instead: settings.json.example (commit this template)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ FIX #3: Enhanced CSRF Protection
|
||||
|
||||
### Part A: Install CSURF
|
||||
|
||||
```bash
|
||||
npm install csurf
|
||||
```
|
||||
|
||||
### Part B: Add CSRF to app-customer.js
|
||||
|
||||
```javascript
|
||||
const csrf = require('csurf');
|
||||
|
||||
// Create CSRF protection middleware
|
||||
const csrfProtection = csrf({
|
||||
cookie: false // Use session instead of cookie
|
||||
});
|
||||
|
||||
// Apply to POST/PUT/DELETE routes
|
||||
app.post('/admin/:path*', csrfProtection, adminRoutes);
|
||||
app.post('/customer/:path*', csrfProtection, customerRoutes);
|
||||
```
|
||||
|
||||
### Part C: Add CSRF Token to Forms
|
||||
|
||||
```ejs
|
||||
<!-- In EJS templates, add hidden CSRF token field: -->
|
||||
<form method="POST" action="/admin/billing/generate">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<!-- Other form fields -->
|
||||
<button type="submit">Generate Invoice</button>
|
||||
</form>
|
||||
|
||||
<!-- For AJAX requests, include in headers: -->
|
||||
<script>
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
||||
|
||||
fetch('/api/action', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 FIX #4: Rate Limiting
|
||||
|
||||
### Part A: Install & Setup
|
||||
|
||||
```bash
|
||||
npm install express-rate-limit
|
||||
```
|
||||
|
||||
### Part B: Create Rate Limiter Middleware
|
||||
|
||||
**File:** `middleware/rateLimiter.js`
|
||||
```javascript
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
const loginRateLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 5, // Limit each IP to 5 requests per windowMs
|
||||
message: 'Terlalu banyak percobaan login, coba lagi dalam beberapa menit',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
const apiRateLimiter = rateLimit({
|
||||
windowMs: 1 * 60 * 1000, // 1 minute
|
||||
max: 100, // 100 requests per minute
|
||||
message: 'Terlalu banyak request ke API, coba lagi nanti'
|
||||
});
|
||||
|
||||
const webhookRateLimiter = rateLimit({
|
||||
windowMs: 1 * 60 * 1000,
|
||||
max: 1000, // Higher limit for webhooks
|
||||
skip: (req) => {
|
||||
// Skip rate limiting if webhook signature is valid
|
||||
return req.session?.webhookVerified;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
loginRateLimiter,
|
||||
apiRateLimiter,
|
||||
webhookRateLimiter
|
||||
};
|
||||
```
|
||||
|
||||
### Part C: Apply Rate Limiters
|
||||
|
||||
```javascript
|
||||
// In routes
|
||||
const { loginRateLimiter, apiRateLimiter } = require('../middleware/rateLimiter');
|
||||
|
||||
// Apply to login endpoints
|
||||
router.post('/login', loginRateLimiter, loginController);
|
||||
router.post('/customer/login', loginRateLimiter, customerLoginController);
|
||||
|
||||
// Apply to API endpoints
|
||||
router.get('/api/data', apiRateLimiter, apiController);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 FIX #5: Session Management Improvements
|
||||
|
||||
### Part A: Setup Session Store
|
||||
|
||||
```bash
|
||||
npm install better-sqlite3-session-store
|
||||
```
|
||||
|
||||
### Part B: Update app-customer.js
|
||||
|
||||
```javascript
|
||||
const SessionStore = require('better-sqlite3-session-store')(session);
|
||||
|
||||
const store = new SessionStore({
|
||||
client: db,
|
||||
expired: {
|
||||
clear: true,
|
||||
intervalMs: 15 * 60 * 1000 // Clear expired sessions every 15 minutes
|
||||
}
|
||||
});
|
||||
|
||||
app.use(session({
|
||||
store: store, // Use database store instead of memory
|
||||
secret: process.env.SESSION_SECRET || getSetting('session_secret'),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: process.env.COOKIE_SECURE === 'true',
|
||||
httpOnly: true,
|
||||
sameSite: 'strict', // More restrictive
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
},
|
||||
name: 'customer.sid'
|
||||
}));
|
||||
```
|
||||
|
||||
### Part C: Session Regeneration on Login
|
||||
|
||||
```javascript
|
||||
// After successful login:
|
||||
req.session.regenerate((err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Session error' });
|
||||
}
|
||||
req.session.userId = user.id;
|
||||
req.session.userRole = user.role;
|
||||
req.session.loginTime = Date.now();
|
||||
|
||||
// Set inactivity timeout
|
||||
req.session.inactivityTimeout = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
res.json({ success: true, redirect: '/dashboard' });
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ VERIFICATION CHECKLIST
|
||||
|
||||
After implementing all fixes, verify:
|
||||
|
||||
- [ ] bcrypt password hashing implemented
|
||||
- [ ] Old plain passwords migrated to hashed
|
||||
- [ ] .env file created and populated
|
||||
- [ ] .env added to .gitignore
|
||||
- [ ] Settings.json reviewed for sensitive data
|
||||
- [ ] CSRF protection implemented
|
||||
- [ ] Rate limiting configured
|
||||
- [ ] Session store using database
|
||||
- [ ] Session regeneration on login working
|
||||
- [ ] All tests passing
|
||||
- [ ] Application running without errors
|
||||
|
||||
---
|
||||
|
||||
## 🧪 TESTING FIXES
|
||||
|
||||
### Test Password Hashing
|
||||
|
||||
```bash
|
||||
node -e "
|
||||
const { hashPassword, verifyPassword } = require('./utils/passwordHelper');
|
||||
(async () => {
|
||||
const hash = await hashPassword('test123456');
|
||||
const valid = await verifyPassword('test123456', hash);
|
||||
console.log('Hash:', hash);
|
||||
console.log('Valid:', valid);
|
||||
})();
|
||||
"
|
||||
```
|
||||
|
||||
### Test Environment Variables
|
||||
|
||||
```bash
|
||||
node -e "
|
||||
require('dotenv').config();
|
||||
console.log('NODE_ENV:', process.env.NODE_ENV);
|
||||
console.log('SESSION_SECRET:', process.env.SESSION_SECRET ? '✅ Set' : '❌ Not set');
|
||||
console.log('GENIEACS_PASSWORD:', process.env.GENIEACS_PASSWORD ? '✅ Set' : '❌ Not set');
|
||||
"
|
||||
```
|
||||
|
||||
### Test Login Flow
|
||||
|
||||
```bash
|
||||
# Start application
|
||||
npm start
|
||||
|
||||
# Open browser and test login with new credentials
|
||||
# Check console logs for any errors
|
||||
# Verify session is created in database
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 TROUBLESHOOTING
|
||||
|
||||
### Issue: "bcrypt is not defined"
|
||||
```bash
|
||||
Solution: npm install bcrypt
|
||||
```
|
||||
|
||||
### Issue: ".env file not loading"
|
||||
```bash
|
||||
Solution: Ensure dotenv is imported at the top of app-customer.js:
|
||||
require('dotenv').config();
|
||||
```
|
||||
|
||||
### Issue: "CSRF token mismatch"
|
||||
```bash
|
||||
Solution: Ensure csrfToken is included in all forms and AJAX requests
|
||||
```
|
||||
|
||||
### Issue: "Session not persisting"
|
||||
```bash
|
||||
Solution: Verify database schema has session table:
|
||||
sqlite3 database/billing.db ".schema sessions"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 NEXT STEPS
|
||||
|
||||
1. **Immediate (Today):**
|
||||
- Review this guide with team
|
||||
- Create .env file
|
||||
- Test bcrypt locally
|
||||
|
||||
2. **This Week:**
|
||||
- Implement bcrypt in all auth modules
|
||||
- Run password migration script
|
||||
- Deploy to staging for testing
|
||||
|
||||
3. **Next Week:**
|
||||
- Implement CSRF protection
|
||||
- Add rate limiting
|
||||
- Full security testing
|
||||
|
||||
4. **Following Week:**
|
||||
- Monitor production (if live)
|
||||
- Collect logs and monitoring data
|
||||
- Review and optimize
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Review AUDIT_SECURITY_2026.md for detailed security analysis.
|
||||
Reference in new issue
Block a user