diff --git a/map/app/middleware/auth.js b/map/app/middleware/auth.js index 0c45e19..6ea731f 100644 --- a/map/app/middleware/auth.js +++ b/map/app/middleware/auth.js @@ -43,7 +43,6 @@ const checkTempUserExpiration = async (req, res) => { }; const requireAuth = async (req, res, next) => { - // Check for both authentication patterns used in your app const isAuthenticated = (req.session && req.session.authenticated) || (req.session && req.session.userId && req.session.userEmail); @@ -67,9 +66,22 @@ const requireAuth = async (req, res, next) => { logger.warn('Unauthorized access attempt', { ip: req.ip, 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) { res.status(401).json({ success: false, diff --git a/map/app/middleware/rateLimiter.js b/map/app/middleware/rateLimiter.js index e9fb3a7..88ae1db 100644 --- a/map/app/middleware/rateLimiter.js +++ b/map/app/middleware/rateLimiter.js @@ -11,8 +11,12 @@ const keyGenerator = (req) => { // General API rate limiter const apiLimiter = rateLimit({ 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, + skip: (req) => { + // Skip rate limiting for authenticated users (or increase their limit) + return req.session?.authenticated === true; + }, standardHeaders: true, legacyHeaders: false, trustProxy: true, // Explicitly trust proxy diff --git a/map/app/public/js/admin/auth.js b/map/app/public/js/admin/auth.js new file mode 100644 index 0000000..e69de29 diff --git a/map/app/public/js/admin/navigation.js b/map/app/public/js/admin/navigation.js new file mode 100644 index 0000000..e69de29 diff --git a/map/app/public/js/admin/shifts.js b/map/app/public/js/admin/shifts.js new file mode 100644 index 0000000..e69de29 diff --git a/map/app/public/js/admin/startLocation.js b/map/app/public/js/admin/startLocation.js new file mode 100644 index 0000000..e69de29 diff --git a/map/app/public/js/admin/users.js b/map/app/public/js/admin/users.js new file mode 100644 index 0000000..e69de29 diff --git a/map/app/public/js/admin/utils.js b/map/app/public/js/admin/utils.js new file mode 100644 index 0000000..e69de29 diff --git a/map/app/public/js/admin/walkSheet.js b/map/app/public/js/admin/walkSheet.js new file mode 100644 index 0000000..e69de29 diff --git a/map/app/public/js/main.js b/map/app/public/js/main.js index b2002b6..bfe285b 100644 --- a/map/app/public/js/main.js +++ b/map/app/public/js/main.js @@ -96,6 +96,17 @@ function setupAutoRefresh() { refreshInterval = setInterval(async () => { 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(); consecutiveErrors = 0; // Reset error count on success } catch (error) { @@ -111,6 +122,17 @@ function setupAutoRefresh() { const slowInterval = refreshInterval_ms * 2; // Double the interval refreshInterval = setInterval(async () => { 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(); consecutiveErrors = 0; } catch (error) { diff --git a/map/app/public/js/utils.js b/map/app/public/js/utils.js index 80f194d..b932b1b 100644 --- a/map/app/public/js/utils.js +++ b/map/app/public/js/utils.js @@ -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'; + } +} diff --git a/map/test-listmonk-integration.sh b/map/test-listmonk-integration.sh deleted file mode 100755 index 87357d4..0000000 --- a/map/test-listmonk-integration.sh +++ /dev/null @@ -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 diff --git a/map/test-nocodb.js b/map/test-nocodb.js deleted file mode 100644 index a85bf38..0000000 --- a/map/test-nocodb.js +++ /dev/null @@ -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);