some fixes to the auth and lockouts

This commit is contained in:
admin 2025-08-27 08:54:22 -06:00
parent a026af5b48
commit 24ce74d61c
13 changed files with 54 additions and 369 deletions

View File

@ -43,7 +43,6 @@ const checkTempUserExpiration = async (req, res) => {
}; };
const requireAuth = async (req, res, next) => { const requireAuth = async (req, res, next) => {
// Check for both authentication patterns used in your app
const isAuthenticated = (req.session && req.session.authenticated) || const isAuthenticated = (req.session && req.session.authenticated) ||
(req.session && req.session.userId && req.session.userEmail); (req.session && req.session.userId && req.session.userEmail);
@ -67,9 +66,22 @@ const requireAuth = async (req, res, next) => {
logger.warn('Unauthorized access attempt', { logger.warn('Unauthorized access attempt', {
ip: req.ip, ip: req.ip,
path: req.path, path: req.path,
userAgent: req.get('User-Agent') userAgent: req.get('User-Agent'),
referer: req.get('Referer'), // Add referer to see where requests come from
method: req.method,
timestamp: new Date().toISOString()
}); });
// Check if this is an auto-refresh request
if (req.headers['x-requested-with'] === 'XMLHttpRequest' ||
req.path.includes('/api/locations')) {
return res.status(401).json({
authenticated: false,
error: 'Session expired',
isAutoRefresh: true
});
}
if (req.xhr || req.headers.accept?.indexOf('json') > -1) { if (req.xhr || req.headers.accept?.indexOf('json') > -1) {
res.status(401).json({ res.status(401).json({
success: false, success: false,

View File

@ -11,8 +11,12 @@ const keyGenerator = (req) => {
// General API rate limiter // General API rate limiter
const apiLimiter = rateLimit({ const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes windowMs: 15 * 60 * 1000, // 15 minutes
max: 300, // Increased from 100 to 300 to accommodate auto-refresh and multiple users max: 300, // Already increased
keyGenerator, keyGenerator,
skip: (req) => {
// Skip rate limiting for authenticated users (or increase their limit)
return req.session?.authenticated === true;
},
standardHeaders: true, standardHeaders: true,
legacyHeaders: false, legacyHeaders: false,
trustProxy: true, // Explicitly trust proxy trustProxy: true, // Explicitly trust proxy

View File

View File

View File

View File

View File

View File

View File

View File

@ -96,6 +96,17 @@ function setupAutoRefresh() {
refreshInterval = setInterval(async () => { refreshInterval = setInterval(async () => {
try { try {
// Check if user is still authenticated
const authResponse = await fetch('/api/auth/check');
const authData = await authResponse.json();
if (!authData.authenticated) {
// Stop auto-refresh if not authenticated
clearInterval(refreshInterval);
console.log('Auto-refresh stopped - user not authenticated');
return;
}
await loadLocations(); await loadLocations();
consecutiveErrors = 0; // Reset error count on success consecutiveErrors = 0; // Reset error count on success
} catch (error) { } catch (error) {
@ -111,6 +122,17 @@ function setupAutoRefresh() {
const slowInterval = refreshInterval_ms * 2; // Double the interval const slowInterval = refreshInterval_ms * 2; // Double the interval
refreshInterval = setInterval(async () => { refreshInterval = setInterval(async () => {
try { try {
// Check if user is still authenticated
const authResponse = await fetch('/api/auth/check');
const authData = await authResponse.json();
if (!authData.authenticated) {
// Stop auto-refresh if not authenticated
clearInterval(refreshInterval);
console.log('Auto-refresh stopped - user not authenticated');
return;
}
await loadLocations(); await loadLocations();
consecutiveErrors = 0; consecutiveErrors = 0;
} catch (error) { } catch (error) {

View File

@ -126,3 +126,16 @@ export function setViewportDimensions() {
} }
} }
} }
// Add session expiry detection
function handleAuthError(error) {
if (error.status === 401) {
// Stop all intervals
if (typeof autoRefreshInterval !== 'undefined') {
clearInterval(autoRefreshInterval);
}
// Redirect to login with expiry message
window.location.href = '/login.html?expired=true';
}
}

View File

@ -1,293 +0,0 @@
#!/bin/bash
# Listmonk Integration Test Script
# This script validates the complete Listmonk integration
echo "🧪 Starting Listmonk Integration Tests..."
echo "======================================="
# Configuration
MAP_URL="http://localhost:3000"
LISTMONK_URL="http://localhost:9000"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Test counter
TESTS_PASSED=0
TESTS_FAILED=0
# Helper functions
pass_test() {
echo -e "${GREEN}✅ PASS:${NC} $1"
((TESTS_PASSED++))
}
fail_test() {
echo -e "${RED}❌ FAIL:${NC} $1"
((TESTS_FAILED++))
}
warn_test() {
echo -e "${YELLOW}⚠️ WARN:${NC} $1"
}
info_test() {
echo -e "${BLUE} INFO:${NC} $1"
}
# Test 1: Environment Configuration
echo
echo "📋 Test 1: Environment Configuration"
echo "------------------------------------"
if grep -q "LISTMONK_ENABLED=true" .env 2>/dev/null; then
pass_test "Listmonk is enabled in environment"
else
fail_test "LISTMONK_ENABLED not set to true in .env"
fi
if grep -q "LISTMONK_URL=" .env 2>/dev/null; then
LISTMONK_ENV_URL=$(grep "LISTMONK_URL=" .env | cut -d'=' -f2)
pass_test "Listmonk URL configured: $LISTMONK_ENV_URL"
else
fail_test "LISTMONK_URL not configured in .env"
fi
if grep -q "LISTMONK_USERNAME=" .env 2>/dev/null; then
pass_test "Listmonk username configured"
else
fail_test "LISTMONK_USERNAME not configured in .env"
fi
# Test 2: Service Connectivity
echo
echo "🌐 Test 2: Service Connectivity"
echo "------------------------------"
# Test Map application
if curl -s "$MAP_URL/health" > /dev/null 2>&1; then
pass_test "Map application is accessible at $MAP_URL"
else
fail_test "Map application not accessible at $MAP_URL"
fi
# Test Listmonk service
if curl -s "$LISTMONK_URL/api/health" > /dev/null 2>&1; then
pass_test "Listmonk service is accessible at $LISTMONK_URL"
else
fail_test "Listmonk service not accessible at $LISTMONK_URL"
fi
# Test 3: File Structure
echo
echo "📁 Test 3: Integration File Structure"
echo "------------------------------------"
REQUIRED_FILES=(
"app/services/listmonk.js"
"app/controllers/listmonkController.js"
"app/routes/listmonk.js"
"app/public/js/listmonk-status.js"
"app/public/js/listmonk-admin.js"
"app/public/css/modules/listmonk.css"
"listmonk-env-example.txt"
"instruct/LISTMONK_INTEGRATION_GUIDE.md"
)
for file in "${REQUIRED_FILES[@]}"; do
if [[ -f "$file" ]]; then
pass_test "Required file exists: $file"
else
fail_test "Missing required file: $file"
fi
done
# Test 4: Code Integration Points
echo
echo "🔗 Test 4: Code Integration Points"
echo "---------------------------------"
# Check if listmonk service is imported in server.js
if grep -q "listmonk" app/server.js 2>/dev/null; then
pass_test "Listmonk service integrated in server.js"
else
fail_test "Listmonk service not integrated in server.js"
fi
# Check if real-time sync hooks are in controllers
if grep -q "listmonkService" app/controllers/locationsController.js 2>/dev/null; then
pass_test "Real-time sync integrated in locations controller"
else
fail_test "Real-time sync missing from locations controller"
fi
if grep -q "listmonkService" app/controllers/usersController.js 2>/dev/null; then
pass_test "Real-time sync integrated in users controller"
else
fail_test "Real-time sync missing from users controller"
fi
# Check admin panel integration
if grep -q "listmonk" app/public/admin.html 2>/dev/null; then
pass_test "Admin panel includes Listmonk section"
else
fail_test "Admin panel missing Listmonk integration"
fi
# Check status indicator integration
if grep -q "listmonk-status" app/public/index.html 2>/dev/null; then
pass_test "Status indicator integrated in main page"
else
fail_test "Status indicator not integrated in main page"
fi
# Test 5: API Endpoints (if services are running)
echo
echo "🔌 Test 5: API Endpoints"
echo "-----------------------"
# Try to test connection endpoint (requires authentication)
STATUS_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "$MAP_URL/api/listmonk/status" 2>/dev/null)
if [[ "$STATUS_RESPONSE" == "401" ]]; then
pass_test "Listmonk status endpoint exists (requires auth)"
elif [[ "$STATUS_RESPONSE" == "200" ]]; then
pass_test "Listmonk status endpoint accessible"
else
warn_test "Listmonk status endpoint response: $STATUS_RESPONSE (may require auth)"
fi
# Test 6: Docker Configuration
echo
echo "🐳 Test 6: Docker Configuration"
echo "------------------------------"
# Check if services are running
if docker-compose ps | grep -q "map-viewer.*Up"; then
pass_test "Map application container is running"
else
fail_test "Map application container not running"
fi
if docker-compose ps | grep -q "listmonk.*Up"; then
pass_test "Listmonk container is running"
else
fail_test "Listmonk container not running"
fi
# Test 7: Documentation
echo
echo "📚 Test 7: Documentation"
echo "-----------------------"
if grep -q "Listmonk Integration" README.md 2>/dev/null; then
pass_test "README includes Listmonk integration documentation"
else
fail_test "README missing Listmonk integration documentation"
fi
if grep -q "files-explainer.md" app/ 2>/dev/null && grep -q "listmonk" files-explainer.md 2>/dev/null; then
pass_test "Files explainer includes Listmonk files"
else
warn_test "Files explainer may not include Listmonk files"
fi
# Test 8: JavaScript Module Tests
echo
echo "🟨 Test 8: JavaScript Modules"
echo "----------------------------"
# Check for syntax errors in JavaScript files
JS_FILES=(
"app/public/js/listmonk-status.js"
"app/public/js/listmonk-admin.js"
)
for js_file in "${JS_FILES[@]}"; do
if [[ -f "$js_file" ]]; then
# Basic syntax check (requires node)
if command -v node > /dev/null; then
if node -c "$js_file" 2>/dev/null; then
pass_test "JavaScript syntax valid: $js_file"
else
fail_test "JavaScript syntax error: $js_file"
fi
else
info_test "Node.js not available for syntax checking: $js_file"
fi
fi
done
# Test 9: CSS Validation
echo
echo "🎨 Test 9: CSS Validation"
echo "------------------------"
if [[ -f "app/public/css/modules/listmonk.css" ]]; then
# Check for basic CSS structure
if grep -q "@media" app/public/css/modules/listmonk.css; then
pass_test "CSS includes responsive design rules"
else
warn_test "CSS may not include responsive design"
fi
if grep -q "\.listmonk-" app/public/css/modules/listmonk.css; then
pass_test "CSS includes Listmonk-specific classes"
else
fail_test "CSS missing Listmonk-specific styling"
fi
fi
# Test 10: Log Analysis (if container is running)
echo
echo "📋 Test 10: Application Logs"
echo "---------------------------"
if docker-compose ps | grep -q "map-viewer.*Up"; then
# Check recent logs for Listmonk-related messages
RECENT_LOGS=$(docker-compose logs --tail=20 map-viewer 2>/dev/null | grep -i listmonk | head -5)
if [[ -n "$RECENT_LOGS" ]]; then
pass_test "Application logs contain Listmonk activity"
info_test "Recent Listmonk log entries:"
echo "$RECENT_LOGS" | sed 's/^/ /'
else
warn_test "No recent Listmonk activity in logs (may be normal)"
fi
# Check for error messages
ERROR_LOGS=$(docker-compose logs --tail=50 map-viewer 2>/dev/null | grep -i "error\|fail" | grep -i listmonk | head -3)
if [[ -n "$ERROR_LOGS" ]]; then
fail_test "Found Listmonk-related errors in logs:"
echo "$ERROR_LOGS" | sed 's/^/ /'
else
pass_test "No Listmonk-related errors in recent logs"
fi
else
warn_test "Cannot analyze logs - Map application container not running"
fi
# Summary
echo
echo "📊 Test Summary"
echo "=============="
echo -e "Tests Passed: ${GREEN}$TESTS_PASSED${NC}"
echo -e "Tests Failed: ${RED}$TESTS_FAILED${NC}"
echo -e "Total Tests: $((TESTS_PASSED + TESTS_FAILED))"
if [[ $TESTS_FAILED -eq 0 ]]; then
echo -e "${GREEN}🎉 All tests passed! Listmonk integration appears to be working correctly.${NC}"
exit 0
elif [[ $TESTS_FAILED -le 3 ]]; then
echo -e "${YELLOW}⚠️ Most tests passed, but some issues were found. Check the failures above.${NC}"
exit 1
else
echo -e "${RED}❌ Multiple test failures detected. Please review the integration setup.${NC}"
exit 2
fi

View File

@ -1,73 +0,0 @@
// Test NocoDB API directly to debug the issue
const axios = require('axios');
require('dotenv').config();
async function testNocoDB() {
const apiUrl = process.env.NOCODB_API_URL;
const apiToken = process.env.NOCODB_API_TOKEN;
const projectId = process.env.NOCODB_PROJECT_ID || 'pp1ijipzj121aqq';
const shiftSignupsSheetId = process.env.NOCODB_SHIFT_SIGNUPS_SHEET || 'mocxv7kzcvyo4aa';
const client = axios.create({
baseURL: apiUrl,
timeout: 10000,
headers: {
'xc-token': apiToken,
'Content-Type': 'application/json'
}
});
// Test 1: Try to get table info
console.log('Test 1: Getting table info...');
try {
const url = `/db/data/v1/${projectId}/${shiftSignupsSheetId}?limit=1`;
console.log('Request URL:', apiUrl + url);
const response = await client.get(url);
console.log('Table info response:', JSON.stringify(response.data, null, 2));
} catch (error) {
console.error('Error getting table info:', error.response?.data || error.message);
}
// Test 2: Try to create a minimal record
console.log('\nTest 2: Creating minimal record...');
try {
const testData = {
'Shift ID': 1,
'User Email': 'test@example.com',
'User Name': 'Test User',
'Status': 'Confirmed'
};
console.log('Test data:', JSON.stringify(testData, null, 2));
const url = `/db/data/v1/${projectId}/${shiftSignupsSheetId}`;
console.log('Create URL:', apiUrl + url);
const response = await client.post(url, testData);
console.log('Create response:', JSON.stringify(response.data, null, 2));
} catch (error) {
console.error('Error creating record:');
console.error('Status:', error.response?.status);
console.error('Data:', JSON.stringify(error.response?.data, null, 2));
console.error('Headers:', JSON.stringify(error.response?.headers, null, 2));
}
// Test 3: Check what table fields exist
console.log('\nTest 3: Getting table schema...');
try {
const schemaUrl = `/db/meta/projects/${projectId}/tables`;
const response = await client.get(schemaUrl);
const tables = response.data.list || [];
const signupsTable = tables.find(t => t.id === shiftSignupsSheetId);
if (signupsTable) {
console.log('Signups table columns:');
signupsTable.columns?.forEach(col => {
console.log(` - ${col.title} (${col.column_name}) - ${col.uidt} - PK: ${col.pk}`);
});
}
} catch (error) {
console.error('Error getting schema:', error.response?.data || error.message);
}
}
testNocoDB().catch(console.error);