diff --git a/config.sh b/config.sh index d00651f..2b88c1a 100755 --- a/config.sh +++ b/config.sh @@ -185,6 +185,9 @@ initialize_available_ports() { ["MAP_PORT"]=3000 ["INFLUENCE_PORT"]=3333 ["MINI_QR_PORT"]=8089 + ["REDIS_PORT"]=6379 + ["PROMETHEUS_PORT"]=9090 + ["GRAFANA_PORT"]=3001 ) # Find available ports for each service @@ -252,6 +255,11 @@ MAP_PORT=${MAP_PORT:-3000} INFLUENCE_PORT=${INFLUENCE_PORT:-3333} MINI_QR_PORT=${MINI_QR_PORT:-8089} +# Centralized Services Ports +REDIS_PORT=${REDIS_PORT:-6379} +PROMETHEUS_PORT=${PROMETHEUS_PORT:-9090} +GRAFANA_PORT=${GRAFANA_PORT:-3001} + # Domain Configuration BASE_DOMAIN=https://changeme.org DOMAIN=changeme.org @@ -301,6 +309,21 @@ NOCODB_DB_PASSWORD=changeMe # Gitea Database Configuration GITEA_DB_PASSWD=changeMe GITEA_DB_ROOT_PASSWORD=changeMe + +# Centralized Services Configuration +# Redis (used by all applications for caching, sessions, queues) +REDIS_HOST=redis-changemaker +REDIS_PORT=6379 +REDIS_PASSWORD= + +# Prometheus (metrics collection) +PROMETHEUS_PORT=9090 +PROMETHEUS_RETENTION_TIME=30d + +# Grafana (monitoring dashboards) +GRAFANA_PORT=3001 +GRAFANA_ADMIN_USER=admin +GRAFANA_ADMIN_PASSWORD=changeMe EOL echo "New .env file created with conflict-free port assignments." @@ -321,6 +344,11 @@ EOL echo "Map: ${MAP_PORT:-3000}" echo "Influence: ${INFLUENCE_PORT:-3333}" echo "Mini QR: ${MINI_QR_PORT:-8089}" + echo "" + echo "=== Centralized Services ===" + echo "Redis: ${REDIS_PORT:-6379}" + echo "Prometheus: ${PROMETHEUS_PORT:-9090}" + echo "Grafana: ${GRAFANA_PORT:-3001}" echo "================================" } @@ -383,6 +411,8 @@ update_services_yaml() { ["Mini QR"]="qr.$new_domain" ["n8n"]="n8n.$new_domain" ["Gitea"]="git.$new_domain" + ["Prometheus"]="prometheus.$new_domain" + ["Grafana"]="grafana.$new_domain" ) # Process each service mapping @@ -537,6 +567,12 @@ ingress: - hostname: qr.$new_domain service: http://localhost:${MINI_QR_PORT:-8089} + - hostname: prometheus.$new_domain + service: http://localhost:${PROMETHEUS_PORT:-9090} + + - hostname: grafana.$new_domain + service: http://localhost:${GRAFANA_PORT:-3001} + # Catch-all rule (required) - service: http_status:404 EOL @@ -1173,6 +1209,10 @@ update_env_var "GITEA_DB_PASSWD" "$gitea_db_password" gitea_db_root_password=$(generate_password 20) update_env_var "GITEA_DB_ROOT_PASSWORD" "$gitea_db_root_password" +# Generate and update Grafana admin password +grafana_admin_password=$(generate_password 20) +update_env_var "GRAFANA_ADMIN_PASSWORD" "$grafana_admin_password" + echo "Secure passwords generated and updated." echo -e "\n✅ Configuration completed successfully!" @@ -1185,6 +1225,8 @@ echo "- Map .env updated with domain settings" echo "- Listmonk Admin: $listmonk_user" echo "- N8N Admin Email: $n8n_email" echo "- Secure random passwords for database, encryption, and NocoDB" +echo "- Grafana Admin Password: Generated (see .env file)" +echo "- Centralized services: Redis, Prometheus, Grafana" echo "- Tunnel configuration updated at: $TUNNEL_CONFIG_FILE" echo -e "\nYour .env file is located at: $ENV_FILE" echo "A backup of your original .env file was created before modifications." @@ -1213,6 +1255,13 @@ echo " - Map: http://localhost:${MAP_PORT:-3000}" echo " - Influence: http://localhost:${INFLUENCE_PORT:-3333}" echo " - Mini QR: http://localhost:${MINI_QR_PORT:-8089}" echo "" +echo " Centralized Services (optional monitoring profile):" +echo " - Prometheus: http://localhost:${PROMETHEUS_PORT:-9090}" +echo " - Grafana: http://localhost:${GRAFANA_PORT:-3001} (admin/${GRAFANA_ADMIN_PASSWORD})" +echo "" +echo " To start with monitoring:" +echo " docker compose --profile monitoring up -d" +echo "" echo "3. When ready for production:" echo " ./start-production.sh" echo "" diff --git a/configs/grafana/dashboards.yml b/configs/grafana/dashboards.yml new file mode 100644 index 0000000..be165c4 --- /dev/null +++ b/configs/grafana/dashboards.yml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + updateIntervalSeconds: 10 + allowUiUpdates: true + options: + path: /etc/grafana/provisioning/dashboards diff --git a/configs/grafana/datasources.yml b/configs/grafana/datasources.yml new file mode 100644 index 0000000..0384e31 --- /dev/null +++ b/configs/grafana/datasources.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus-changemaker:9090 + isDefault: true + editable: true + jsonData: + timeInterval: 15s diff --git a/configs/prometheus/alerts.yml b/configs/prometheus/alerts.yml new file mode 100644 index 0000000..f742e2e --- /dev/null +++ b/configs/prometheus/alerts.yml @@ -0,0 +1,93 @@ +groups: + - name: influence_app_alerts + interval: 30s + rules: + # Application availability + - alert: ApplicationDown + expr: up{job="influence-app"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: "Influence application is down" + description: "The Influence application has been down for more than 2 minutes." + + # High error rate + - alert: HighErrorRate + expr: rate(influence_http_requests_total{status_code=~"5.."}[5m]) > 0.1 + for: 5m + labels: + severity: warning + annotations: + summary: "High error rate detected" + description: "Application is experiencing {{ $value }} errors per second." + + # Email queue backing up + - alert: EmailQueueBacklog + expr: influence_email_queue_size > 100 + for: 10m + labels: + severity: warning + annotations: + summary: "Email queue has significant backlog" + description: "Email queue size is {{ $value }}, emails may be delayed." + + # High email failure rate + - alert: HighEmailFailureRate + expr: rate(influence_emails_failed_total[5m]) / rate(influence_emails_sent_total[5m]) > 0.2 + for: 10m + labels: + severity: warning + annotations: + summary: "High email failure rate" + description: "{{ $value | humanizePercentage }} of emails are failing to send." + + # Rate limiting being hit frequently + - alert: FrequentRateLimiting + expr: rate(influence_rate_limit_hits_total[5m]) > 1 + for: 5m + labels: + severity: info + annotations: + summary: "Rate limiting triggered frequently" + description: "Rate limits are being hit {{ $value }} times per second." + + # Memory usage high + - alert: HighMemoryUsage + expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > 0.85 + for: 10m + labels: + severity: warning + annotations: + summary: "High memory usage" + description: "Memory usage is above 85% ({{ $value | humanizePercentage }})." + + # Failed login attempts spike + - alert: SuspiciousLoginActivity + expr: rate(influence_login_attempts_total{status="failed"}[5m]) > 5 + for: 2m + labels: + severity: warning + annotations: + summary: "Suspicious login activity detected" + description: "{{ $value }} failed login attempts per second detected." + + # External service failures + - alert: ExternalServiceFailures + expr: rate(influence_external_service_requests_total{status="failed"}[5m]) > 0.5 + for: 5m + labels: + severity: warning + annotations: + summary: "External service failures detected" + description: "{{ $labels.service }} is failing at {{ $value }} requests per second." + + # High API latency + - alert: HighAPILatency + expr: histogram_quantile(0.95, rate(influence_http_request_duration_seconds_bucket[5m])) > 2 + for: 5m + labels: + severity: warning + annotations: + summary: "High API latency" + description: "95th percentile latency is {{ $value }}s for {{ $labels.route }}." diff --git a/configs/prometheus/prometheus.yml b/configs/prometheus/prometheus.yml new file mode 100644 index 0000000..2b6770a --- /dev/null +++ b/configs/prometheus/prometheus.yml @@ -0,0 +1,54 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + external_labels: + monitor: 'changemaker-lite' + +# Alertmanager configuration (optional) +alerting: + alertmanagers: + - static_configs: + - targets: [] + +# Load rules once and periodically evaluate them +rule_files: + - "alerts.yml" + +# Scrape configurations +scrape_configs: + # Influence Application Metrics + - job_name: 'influence-app' + static_configs: + - targets: ['influence-app:3333'] + metrics_path: '/api/metrics' + scrape_interval: 10s + scrape_timeout: 5s + + # N8N Metrics (if available) + - job_name: 'n8n' + static_configs: + - targets: ['n8n-changemaker:5678'] + metrics_path: '/metrics' + scrape_interval: 30s + + # Redis Metrics (requires redis_exporter - optional) + # Uncomment and add redis_exporter service to enable + # - job_name: 'redis' + # static_configs: + # - targets: ['redis-exporter:9121'] + + # Listmonk Metrics (if available) + # - job_name: 'listmonk' + # static_configs: + # - targets: ['listmonk-app:9000'] + # metrics_path: '/metrics' + + # Prometheus self-monitoring + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + # Docker container metrics (requires cAdvisor - optional) + # - job_name: 'cadvisor' + # static_configs: + # - targets: ['cadvisor:8080'] diff --git a/docker-compose.yml b/docker-compose.yml index 2bbf807..e8ae12b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -240,6 +240,96 @@ services: networks: - changemaker-lite + # Shared Redis - Used by all services for caching, queues, sessions + redis: + image: redis:7-alpine + container_name: redis-changemaker + command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru + ports: + - "6379:6379" + volumes: + - redis-data:/data + restart: always + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + deploy: + resources: + limits: + cpus: '1' + memory: 512M + reservations: + cpus: '0.25' + memory: 256M + networks: + - changemaker-lite + logging: + driver: "json-file" + options: + max-size: "5m" + max-file: "2" + + # Prometheus - Metrics collection for all services + prometheus: + image: prom/prometheus:latest + container_name: prometheus-changemaker + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--storage.tsdb.retention.time=30d' + ports: + - "${PROMETHEUS_PORT:-9090}:9090" + volumes: + - ./configs/prometheus:/etc/prometheus + - prometheus-data:/prometheus + restart: always + networks: + - changemaker-lite + profiles: + - monitoring + + # Grafana - Metrics visualization for all services + grafana: + image: grafana/grafana:latest + container_name: grafana-changemaker + ports: + - "${GRAFANA_PORT:-3001}:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin} + - GF_USERS_ALLOW_SIGN_UP=false + - GF_SERVER_ROOT_URL=${GRAFANA_ROOT_URL:-http://localhost:3001} + volumes: + - grafana-data:/var/lib/grafana + - ./configs/grafana:/etc/grafana/provisioning + restart: always + depends_on: + - prometheus + networks: + - changemaker-lite + profiles: + - monitoring + + # MailHog - Shared email testing service for all applications + # Captures all emails sent by any service for development/testing + # Web UI: http://localhost:8025 + # SMTP: mailhog-changemaker:1025 + mailhog: + image: mailhog/mailhog:latest + container_name: mailhog-changemaker + ports: + - "1025:1025" # SMTP server + - "8025:8025" # Web UI + restart: unless-stopped + networks: + - changemaker-lite + logging: + driver: "json-file" + options: + max-size: "5m" + max-file: "2" + networks: changemaker-lite: driver: bridge @@ -250,4 +340,7 @@ volumes: nc_data: db_data: gitea_data: - mysql_data: \ No newline at end of file + mysql_data: + redis-data: + prometheus-data: + grafana-data: \ No newline at end of file diff --git a/influence/BUGFIX_VERIFICATION_FIELDS.md b/influence/BUGFIX_VERIFICATION_FIELDS.md deleted file mode 100644 index 8fee3f6..0000000 --- a/influence/BUGFIX_VERIFICATION_FIELDS.md +++ /dev/null @@ -1,139 +0,0 @@ -# Bug Fix: Response Verification Data Not Populating - -## Issue Description -Verification fields were not being saved to the database when submitting responses through the Response Wall. The fields (`Representative Email`, `Verification Token`, etc.) were being created as `null` values. - -## Root Causes - -### 1. Missing Field Mapping in NocoDB Service -**File:** `app/services/nocodb.js` - -**Problem:** The `createRepresentativeResponse()` and `updateRepresentativeResponse()` functions were missing the mappings for the new verification fields. - -**Solution:** Added proper Column Title mappings following NocoDB conventions: -```javascript -// Added to createRepresentativeResponse -'Representative Email': responseData.representative_email, -'Verification Token': responseData.verification_token, -'Verification Sent At': responseData.verification_sent_at, -'Verified At': responseData.verified_at, -'Verified By': responseData.verified_by, - -// Added to updateRepresentativeResponse -if (updates.representative_email !== undefined) data['Representative Email'] = updates.representative_email; -if (updates.verification_token !== undefined) data['Verification Token'] = updates.verification_token; -if (updates.verification_sent_at !== undefined) data['Verification Sent At'] = updates.verification_sent_at; -if (updates.verified_at !== undefined) data['Verified At'] = updates.verified_at; -if (updates.verified_by !== undefined) data['Verified By'] = updates.verified_by; -``` - -### 2. Representative Email Coming as Array -**File:** `app/public/js/response-wall.js` - -**Problem:** The Represent API sometimes returns email as an array `["email@example.com"]` instead of a string. This caused the form to submit an array value. - -**Solution:** Added array handling in `handleRepresentativeSelect()`: -```javascript -// Handle email being either string or array -const emailValue = Array.isArray(rep.email) ? rep.email[0] : rep.email; -document.getElementById('representative-email').value = emailValue; -``` - -### 3. Anonymous Checkbox Value as String -**File:** `app/controllers/responses.js` - -**Problem:** HTML checkboxes send the value "on" when checked, not boolean `true`. This was being stored as the string "on" in the database. - -**Solution:** Added proper checkbox normalization: -```javascript -// Normalize is_anonymous checkbox value -const isAnonymous = responseData.is_anonymous === true || - responseData.is_anonymous === 'true' || - responseData.is_anonymous === 'on'; -``` - -### 4. Backend Email Handling -**File:** `app/controllers/responses.js` - -**Problem:** The backend wasn't handling the case where `representative_email` might come as an array from the form. - -**Solution:** Added array handling in backend: -```javascript -// Handle representative_email - could be string or array from form -let representativeEmail = responseData.representative_email; -if (Array.isArray(representativeEmail)) { - representativeEmail = representativeEmail[0]; // Take first email if array -} -representativeEmail = representativeEmail || null; -``` - -Also added support for "on" value in verification checkbox: -```javascript -const sendVerification = responseData.send_verification === 'true' || - responseData.send_verification === true || - responseData.send_verification === 'on'; -``` - -## Files Modified - -1. ✅ `app/services/nocodb.js` - Added verification field mappings -2. ✅ `app/public/js/response-wall.js` - Fixed email array handling -3. ✅ `app/controllers/responses.js` - Fixed checkbox values and email array handling - -## Testing Performed - -### Before Fix -- Representative Email: `null` in database -- Verification Token: `null` in database -- Is Anonymous: String `"on"` instead of boolean -- No verification emails sent - -### After Fix -- ✅ Representative Email: Correctly stored as string -- ✅ Verification Token: 64-character hex string generated -- ✅ Verification Sent At: ISO timestamp -- ✅ Is Anonymous: Boolean `true` or `false` -- ✅ Verification email sent successfully - -## NocoDB Best Practices Applied - -Following the guidelines from `instruct.md`: - -1. **Use Column Titles, Not Column Names:** All field mappings use NocoDB Column Titles (e.g., "Representative Email" not "representative_email") -2. **Consistent Mapping:** Service layer properly maps between application field names and NocoDB column titles -3. **System Field Awareness:** Avoided conflicts with NocoDB system fields - -## Deployment - -No database schema changes required - the columns already exist from the previous deployment. Only code changes needed: - -```bash -# Rebuild Docker container -docker compose build && docker compose up -d -``` - -## Verification Checklist - -After deployment, verify: -- [ ] Submit a response with postal code lookup -- [ ] Select representative with email address -- [ ] Check "Send verification request" checkbox -- [ ] Submit form -- [ ] Verify in database: - - [ ] Representative Email is populated (string, not array) - - [ ] Verification Token is 64-char hex string - - [ ] Verification Sent At has ISO timestamp - - [ ] Is Anonymous is boolean -- [ ] Check MailHog/email for verification email -- [ ] Click verification link and confirm it works - -## Related Documentation - -- `IMPLEMENTATION_SUMMARY.md` - Full feature implementation -- `DEPLOYMENT_GUIDE.md` - Deployment instructions -- `instruct.md` - NocoDB best practices - ---- - -**Date Fixed:** October 16, 2025 -**Status:** ✅ Resolved diff --git a/influence/DEPLOYMENT_GUIDE.md b/influence/DEPLOYMENT_GUIDE.md deleted file mode 100644 index ec87822..0000000 --- a/influence/DEPLOYMENT_GUIDE.md +++ /dev/null @@ -1,210 +0,0 @@ -# Response Wall Verification Feature - Deployment Guide - -## Overview -This guide walks you through deploying the new Response Wall verification features that were added to the Influence Campaign Tool. - -## Features Implemented - -### 1. Postal Code Lookup for Response Submission -- Users can search by postal code to find their representatives -- Auto-fills representative details when selected -- Validates Alberta postal codes (T prefix) -- Fallback to manual entry if needed - -### 2. Representative Verification System -- Optional email verification for submitted responses -- Representatives receive verification emails with unique tokens -- Representatives can verify or report responses -- Verified responses display with special badge -- Disputed responses are hidden from public view - -## Deployment Steps - -### Step 1: Update Database Schema - -Run the NocoDB setup script to create/update tables with new verification fields: - -```bash -cd /path/to/influence -./scripts/build-nocodb.sh -``` - -**If you already have existing tables**, you'll need to manually add the new columns through NocoDB UI: - -1. Log into your NocoDB instance -2. Open the `influence_representative_responses` table -3. Add these columns: - - `representative_email` - Type: Email, Required: No - - `verification_token` - Type: SingleLineText, Required: No - - `verification_sent_at` - Type: DateTime, Required: No - - `verified_at` - Type: DateTime, Required: No - - `verified_by` - Type: SingleLineText, Required: No - -### Step 2: Update Environment Variables - -Add these variables to your `.env` file: - -```bash -# Application Name (used in emails) -APP_NAME="BNKops Influence" - -# Base URL for verification links -BASE_URL=https://yourdomain.com - -# Existing variables to verify: -SMTP_HOST=your-smtp-host -SMTP_PORT=587 -SMTP_USER=your-email@domain.com -SMTP_PASS=your-password -SMTP_FROM_EMAIL=your-email@domain.com -SMTP_FROM_NAME="Your Campaign Name" -``` - -⚠️ **Important:** The `BASE_URL` must be your production domain for verification links to work correctly. - -### Step 3: Rebuild Docker Container (if using Docker) - -```bash -cd /path/to/influence -docker compose build -docker compose up -d -``` - -### Step 4: Verify Email Templates - -Ensure the email templates are in place: - -```bash -ls -la app/templates/email/ -``` - -You should see: -- `response-verification.html` -- `response-verification.txt` - -### Step 5: Test the Feature - -#### Test Postal Code Lookup: -1. Go to any campaign's Response Wall -2. Click "Share a Response" -3. Enter postal code (e.g., T5K 2J1) -4. Click Search -5. Verify representatives appear -6. Select a representative -7. Confirm form auto-fills - -#### Test Verification Email: -1. Complete the form with all required fields -2. Check "Send verification request to representative" -3. Submit the response -4. Check that confirmation message mentions email sent -5. Check representative's email inbox for verification email - -#### Test Verification Flow: -1. Open verification email -2. Click "Verify This Response" button -3. Should see green success page -4. Check Response Wall - response should have verified badge -5. Check admin panel - response should be auto-approved - -#### Test Report Flow: -1. Open verification email for a different response -2. Click "Report as Invalid" button -3. Should see warning page -4. Check Response Wall - response should be hidden -5. Check admin panel - response should be marked as rejected - -## Production Checklist - -- [ ] Database schema updated with new verification fields -- [ ] Environment variables configured (APP_NAME, BASE_URL) -- [ ] Email templates exist and are readable -- [ ] SMTP settings are correct and tested -- [ ] Docker container rebuilt and running -- [ ] Postal code search tested successfully -- [ ] Verification email sent and received -- [ ] Verification link works and updates database -- [ ] Report link works and hides response -- [ ] Verified badge displays on Response Wall -- [ ] Admin panel shows verification status correctly - -## Security Notes - -1. **Token Security**: Verification tokens are 32-byte cryptographically secure random strings -2. **Token Expiry**: Consider implementing token expiration (currently no expiry - tokens work indefinitely) -3. **Rate Limiting**: Existing rate limiting applies to submission endpoint -4. **Email Validation**: Representative emails are validated on backend -5. **XSS Prevention**: All user inputs are sanitized before display - -## Troubleshooting - -### Verification Emails Not Sending -- Check SMTP settings in `.env` -- Verify SMTP credentials are correct -- Check application logs: `docker logs influence-app -f` -- Test email connection: Use email test page at `/email-test.html` - -### Postal Code Search Returns No Results -- Verify Represent API is accessible -- Check `REPRESENT_API_BASE` in `.env` -- Ensure postal code is Alberta format (starts with T) -- Check browser console for errors - -### Verification Links Don't Work -- Verify `BASE_URL` in `.env` matches your domain -- Check that verification token was saved to database -- Ensure response ID is correct -- Check application logs for errors - -### Representative Dropdown Not Populating -- Check browser console for JavaScript errors -- Verify `api-client.js` is loaded in HTML -- Ensure API endpoint `/api/representatives/by-postal/:code` is accessible -- Check network tab for API response - -## Rollback Plan - -If you need to rollback this feature: - -1. **Frontend Only Rollback**: - ```bash - # Restore old files - git checkout HEAD~1 -- app/public/response-wall.html - git checkout HEAD~1 -- app/public/js/response-wall.js - git checkout HEAD~1 -- app/public/css/response-wall.css - ``` - -2. **Full Rollback** (including backend): - ```bash - # Restore all files - git checkout HEAD~1 - docker compose build - docker compose up -d - ``` - -3. **Database Cleanup** (optional): - - The new columns don't hurt anything if left in place - - You can manually remove them through NocoDB UI if desired - -## Support - -For issues or questions: -- Check application logs: `docker logs influence-app -f` -- Review `RESPONSE_WALL_UPDATES.md` for implementation details -- Check `files-explainer.md` for file structure information - -## Next Steps - -### Recommended Enhancements: -1. **Token Expiration**: Implement 30-day expiration for verification tokens -2. **Email Notifications**: Notify submitter when representative verifies -3. **Analytics Dashboard**: Track verification rates and response authenticity -4. **Bulk Verification**: Allow representatives to verify multiple responses at once -5. **Representative Dashboard**: Create dedicated portal for representatives to manage responses - -### Future Features: -1. Support for other provinces beyond Alberta -2. SMS verification option -3. Representative accounts for ongoing engagement -4. Response comment system for public discussion -5. Export verified responses for accountability reporting diff --git a/influence/IMPLEMENTATION_SUMMARY.md b/influence/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index cb8cb1f..0000000 --- a/influence/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,455 +0,0 @@ -# Response Wall Verification Feature - Implementation Summary - -## ✅ COMPLETED - October 16, 2025 - -## Overview -Successfully implemented a comprehensive response verification system for the BNKops Influence Campaign Tool's Response Wall feature. The system allows constituents to submit representative responses with optional email verification, enabling representatives to authenticate submissions. - -## Features Implemented - -### 1. Postal Code Lookup Integration ✅ -**Location:** Frontend (response-wall.html, response-wall.js, response-wall.css) - -**Features:** -- Search by Alberta postal code (T prefix validation) -- Fetches representatives from Represent API -- Interactive dropdown selection list -- Auto-fills form fields when representative selected: - - Representative name - - Title/office position - - Government level (Federal/Provincial/Municipal/School Board) - - Email address (hidden field) -- Fallback to manual entry if search fails -- Format validation and user-friendly error messages - -**Implementation Details:** -- Reuses existing API client (`api-client.js`) -- Validates Canadian postal code format (A1A 1A1) -- Government level auto-detection from office type -- Responsive UI with clear user feedback - -### 2. Response Verification System ✅ -**Location:** Backend (controllers/responses.js, services/email.js, templates/email/) - -**Features:** -- Optional verification checkbox on submission form -- Checkbox auto-disabled if no representative email available -- Generates cryptographically secure verification tokens -- Sends professional HTML/text email to representative -- Unique verification and report URLs for each submission -- Styled confirmation pages for both actions - -**Email Template Features:** -- Professional design with gradient backgrounds -- Clear call-to-action buttons -- Response preview in email body -- Campaign and submitter details -- Explanation of verification purpose -- Mobile-responsive design - -### 3. Verification Endpoints ✅ -**Location:** Backend (controllers/responses.js, routes/api.js) - -**Endpoints:** -- `GET /api/responses/:id/verify/:token` - Verify response as authentic -- `GET /api/responses/:id/report/:token` - Report response as invalid - -**Security Features:** -- Token validation before any action -- Protection against duplicate verification -- Clear error messages for invalid/expired tokens -- Styled HTML pages instead of JSON responses -- Auto-approval of verified responses - -**Actions on Verification:** -- Sets `is_verified: true` -- Records `verified_at` timestamp -- Records `verified_by` (representative email) -- Auto-approves response (`status: 'approved'`) -- Response displays with verification badge - -**Actions on Report:** -- Sets `status: 'rejected'` -- Sets `is_verified: false` -- Records dispute in `verified_by` field -- Hides response from public view -- Queues for admin review - -## Files Created - -### Frontend Files -1. **Modified:** `app/public/response-wall.html` - - Added postal code search input and button - - Added representative selection dropdown - - Added hidden representative email field - - Added verification checkbox with description - - Included api-client.js dependency - -2. **Modified:** `app/public/js/response-wall.js` - - Added postal lookup functions - - Added representative selection handling - - Added form auto-fill logic - - Added government level detection - - Updated form submission to include verification data - - Updated modal reset to clear new fields - -3. **Modified:** `app/public/css/response-wall.css` - - Added postal lookup container styles - - Added representative dropdown styles - - Added checkbox styling improvements - - Added responsive design for mobile - -### Backend Files -4. **Modified:** `app/controllers/responses.js` - - Updated `submitResponse()` to handle verification - - Added verification token generation (crypto) - - Added verification email sending logic - - Created `verifyResponse()` endpoint function - - Created `reportResponse()` endpoint function - - Added styled HTML response pages - -5. **Modified:** `app/services/email.js` - - Added `sendResponseVerification()` method - - Configured template variables for verification emails - - Added error handling for email failures - -6. **Created:** `app/templates/email/response-verification.html` - - Professional HTML email template - - Gradient header design - - Clear verify/report buttons - - Response preview section - - Mobile-responsive layout - -7. **Created:** `app/templates/email/response-verification.txt` - - Plain text email version - - All essential information included - - Accessible format for all email clients - -8. **Modified:** `app/routes/api.js` - - Added verification endpoint routes - - Public access (no authentication required) - - Proper route ordering - -### Database Files -9. **Modified:** `scripts/build-nocodb.sh` - - Added `representative_email` column (Email type) - - Added `verification_token` column (SingleLineText) - - Added `verification_sent_at` column (DateTime) - - Added `verified_at` column (DateTime) - - Added `verified_by` column (SingleLineText) - -### Documentation Files -10. **Created:** `RESPONSE_WALL_UPDATES.md` - - Complete feature documentation - - Frontend implementation details - - Backend requirements and implementation - - User flow descriptions - - Testing checklist - - Security considerations - -11. **Created:** `DEPLOYMENT_GUIDE.md` - - Step-by-step deployment instructions - - Database schema update procedures - - Environment variable configuration - - Testing procedures for all features - - Production checklist - - Troubleshooting guide - - Rollback plan - -12. **Modified:** `example.env` - - Added `APP_NAME` variable - - Added `BASE_URL` variable for verification links - - Updated documentation - -13. **Created:** `IMPLEMENTATION_SUMMARY.md` (this file) - -## Database Schema Changes - -### Table: influence_representative_responses -**New Columns Added:** - -| Column Name | Type | Required | Description | -|-------------|------|----------|-------------| -| representative_email | Email | No | Email address of the representative | -| verification_token | SingleLineText | No | Unique token for verification (32-byte hex) | -| verification_sent_at | DateTime | No | Timestamp when verification email was sent | -| verified_at | DateTime | No | Timestamp when response was verified | -| verified_by | SingleLineText | No | Who verified (email or "Disputed by...") | - -## API Changes - -### Modified Endpoints - -#### POST `/api/campaigns/:slug/responses` -**New Request Fields:** -```javascript -{ - // ... existing fields ... - representative_email: String, // Optional: Representative's email - send_verification: Boolean|String, // Optional: 'true' to send email -} -``` - -**New Response Fields:** -```javascript -{ - success: Boolean, - message: String, // Updated to mention verification - response: Object, - verificationEmailSent: Boolean // New: indicates if email sent -} -``` - -### New Endpoints - -#### GET `/api/responses/:id/verify/:token` -**Purpose:** Verify a response as authentic -**Authentication:** None required (public link) -**Response:** Styled HTML page with success/error message -**Side Effects:** -- Updates `is_verified` to true -- Records `verified_at` timestamp -- Records `verified_by` field -- Auto-approves response - -#### GET `/api/responses/:id/report/:token` -**Purpose:** Report a response as invalid -**Authentication:** None required (public link) -**Response:** Styled HTML page with confirmation -**Side Effects:** -- Updates `status` to 'rejected' -- Sets `is_verified` to false -- Records dispute in `verified_by` -- Hides from public view - -## Environment Variables Required - -```bash -# Required for verification feature -APP_NAME="BNKops Influence" # App name for emails -BASE_URL=https://yourdomain.com # Base URL for verification links - -# Required for email sending -SMTP_HOST=smtp.provider.com -SMTP_PORT=587 -SMTP_USER=email@domain.com -SMTP_PASS=password -SMTP_FROM_EMAIL=sender@domain.com -SMTP_FROM_NAME="Campaign Name" -``` - -## User Flow - -### Submission Flow with Verification -1. User opens Response Wall for a campaign -2. User clicks "Share a Response" button -3. **[NEW]** User enters postal code and clicks Search -4. **[NEW]** System displays list of representatives -5. **[NEW]** User selects their representative -6. **[NEW]** Form auto-fills with rep details -7. User completes response details (type, text, comment, screenshot) -8. **[NEW]** User optionally checks "Send verification request" -9. User submits form -10. **[NEW]** System generates verification token (if opted in) -11. System saves response with pending status -12. **[NEW]** System sends verification email (if opted in) -13. User sees success message - -### Verification Flow -1. Representative receives verification email -2. Representative reviews response content -3. Representative clicks "Verify This Response" -4. System validates token -5. System updates response to verified -6. System auto-approves response -7. Representative sees styled success page -8. Response appears on Response Wall with verified badge - -### Report Flow -1. Representative receives verification email -2. Representative identifies invalid response -3. Representative clicks "Report as Invalid" -4. System validates token -5. System marks response as rejected/disputed -6. System hides response from public view -7. Representative sees styled confirmation page -8. Admin can review disputed responses - -## Security Implementation - -### Token Security -- **Generation:** Crypto.randomBytes(32) - 256-bit entropy -- **Storage:** Plain text in database (tokens are one-time use) -- **Validation:** Exact string match required -- **Expiration:** Currently no expiration (recommend 30-day TTL) - -### Input Validation -- **Postal Codes:** Regex validation for Canadian format -- **Emails:** Email type validation in NocoDB -- **Response Data:** Required field validation -- **Tokens:** URL parameter validation - -### Rate Limiting -- Existing rate limiters apply to submission endpoint -- No rate limiting on verification endpoints (public, one-time use) - -### XSS Prevention -- All user inputs escaped before email inclusion -- HTML entities encoded in styled pages -- Template variable substitution prevents injection - -## Testing Results - -### Manual Testing Completed -✅ Postal code search with valid Alberta codes -✅ Validation rejection of non-Alberta codes -✅ Representative dropdown population -✅ Representative selection auto-fill -✅ Verification checkbox disabled without email -✅ Verification checkbox enabled with email -✅ Form submission with verification flag -✅ Backend verification parameter handling -✅ Verification email delivery -✅ Verification link functionality -✅ Report link functionality -✅ Styled HTML page rendering -✅ Token validation -✅ Duplicate verification handling -✅ Invalid token rejection -✅ Manual entry without postal lookup -✅ Modal reset clearing new fields - -### Integration Testing Required -⚠️ End-to-end flow with real representative email -⚠️ Email deliverability to various providers -⚠️ Mobile responsive testing -⚠️ Browser compatibility (Chrome, Firefox, Safari, Edge) -⚠️ Load testing for concurrent submissions -⚠️ Token collision testing (extremely unlikely but should verify) - -## Performance Considerations - -### Frontend -- Postal lookup adds one API call per search -- Representative list rendering: O(n) where n = representatives count -- No significant performance impact expected - -### Backend -- Token generation: Negligible CPU impact -- Email sending: Asynchronous, doesn't block response -- Database writes: 5 additional columns per response -- No new indexes required (token not queried frequently) - -### Email -- Email sending happens after response saved -- Failures don't affect submission success -- Consider queue system for high-volume deployments - -## Known Limitations - -1. **Postal Code Validation:** Only supports Alberta (T prefix) - - **Recommendation:** Extend to other provinces - -2. **Token Expiration:** Tokens don't expire - - **Recommendation:** Implement 30-day expiration - -3. **Email Required:** Verification requires email address - - **Recommendation:** Support phone verification for reps without email - -4. **No Notification:** Submitter not notified when verified - - **Recommendation:** Add email notification to submitter - -5. **Single Verification:** Can only verify once per token - - **Recommendation:** Consider revocation system - -## Future Enhancements - -### Short Term (1-3 months) -1. Token expiration (30 days) -2. Submitter notification emails -3. Verification analytics dashboard -4. Support for other Canadian provinces -5. Admin verification override - -### Medium Term (3-6 months) -1. Representative dashboard for bulk verification -2. SMS verification option -3. Response comment system -4. Verification badge prominence settings -5. Export verified responses - -### Long Term (6-12 months) -1. Full representative portal with authentication -2. Two-way communication system -3. Automated verification reminders -4. Public verification statistics -5. API for third-party integrations - -## Rollback Procedures - -### If Issues Arise - -**Level 1 - Frontend Only:** -```bash -git checkout HEAD~1 -- app/public/response-wall.* -docker compose restart -``` - -**Level 2 - Backend Only:** -```bash -git checkout HEAD~1 -- app/controllers/responses.js app/services/email.js -docker compose restart -``` - -**Level 3 - Full Rollback:** -```bash -git checkout HEAD~1 -docker compose build && docker compose up -d -``` - -**Database Cleanup (optional):** -- New columns can remain without causing issues -- Remove via NocoDB UI if desired - -## Maintenance Notes - -### Regular Tasks -- Monitor verification email delivery rates -- Review disputed responses in admin panel -- Check for expired tokens (when expiration implemented) -- Monitor token collision (extremely unlikely) - -### Monitoring Metrics -- Verification email success/failure rate -- Verification vs. report ratio -- Time to verification (submission → verification) -- Disputed response resolution time - -## Support Information - -### Log Locations -```bash -# Application logs -docker logs influence-app -f - -# Email service logs -grep "verification email" logs/app.log -``` - -### Common Issues -See `DEPLOYMENT_GUIDE.md` troubleshooting section - -### Contact -- Technical Issues: Check application logs -- Feature Requests: Document in project issues -- Security Concerns: Report to security team immediately - -## Conclusion - -The Response Wall verification feature has been successfully implemented with comprehensive frontend and backend support. The system provides a secure, user-friendly way for constituents to submit representative responses with optional verification, enhancing transparency and accountability in political engagement. - -All code is production-ready, well-documented, and follows the project's architectural patterns. The feature integrates seamlessly with existing functionality while adding significant value to the platform. - -**Status:** ✅ Ready for Production Deployment -**Date Completed:** October 16, 2025 -**Version:** 1.0.0 diff --git a/influence/QUICK_REFERENCE.md b/influence/QUICK_REFERENCE.md deleted file mode 100644 index ceaf5ca..0000000 --- a/influence/QUICK_REFERENCE.md +++ /dev/null @@ -1,177 +0,0 @@ -# Quick Reference: Response Verification Feature - -## Quick Start - -### For Developers -```bash -# 1. Update database -./scripts/build-nocodb.sh - -# 2. Update .env -echo 'APP_NAME="BNKops Influence"' >> .env -echo 'BASE_URL=http://localhost:3333' >> .env - -# 3. Rebuild -docker compose build && docker compose up -d - -# 4. Test at -open http://localhost:3333/response-wall.html?campaign=your-campaign-slug -``` - -### For Testers -1. Navigate to any campaign Response Wall -2. Click "Share a Response" -3. Enter postal code: **T5K 2J1** -4. Click Search -5. Select a representative -6. Fill in response details -7. Check "Send verification request" -8. Submit - -## File Locations - -### Frontend -- `app/public/response-wall.html` - Main page -- `app/public/js/response-wall.js` - Logic -- `app/public/css/response-wall.css` - Styles - -### Backend -- `app/controllers/responses.js` - Main controller -- `app/services/email.js` - Email service -- `app/templates/email/response-verification.*` - Email templates -- `app/routes/api.js` - Route definitions - -### Database -- `scripts/build-nocodb.sh` - Schema definitions - -### Documentation -- `IMPLEMENTATION_SUMMARY.md` - Full implementation details -- `DEPLOYMENT_GUIDE.md` - Deployment instructions -- `RESPONSE_WALL_UPDATES.md` - Feature documentation - -## Key Functions - -### Frontend (`response-wall.js`) -```javascript -handlePostalLookup() // Searches by postal code -displayRepresentativeOptions() // Shows rep dropdown -handleRepresentativeSelect() // Auto-fills form -handleSubmitResponse() // Submits with verification -``` - -### Backend (`responses.js`) -```javascript -submitResponse() // Handles submission + verification -verifyResponse() // Verifies via token -reportResponse() // Reports as invalid -``` - -### Email Service (`email.js`) -```javascript -sendResponseVerification() // Sends verification email -``` - -## API Endpoints - -``` -POST /api/campaigns/:slug/responses # Submit response -GET /api/responses/:id/verify/:token # Verify response -GET /api/responses/:id/report/:token # Report response -``` - -## Database Fields - -**Table:** influence_representative_responses - -| Field | Type | Purpose | -|-------|------|---------| -| representative_email | Email | Rep's email address | -| verification_token | Text | 32-byte random hex | -| verification_sent_at | DateTime | When email sent | -| verified_at | DateTime | When verified | -| verified_by | Text | Who verified | - -## Environment Variables - -```bash -APP_NAME="BNKops Influence" -BASE_URL=https://yourdomain.com -SMTP_HOST=smtp.provider.com -SMTP_PORT=587 -SMTP_USER=email@domain.com -SMTP_PASS=password -SMTP_FROM_EMAIL=sender@domain.com -SMTP_FROM_NAME="Campaign Name" -``` - -## Testing Checklist - -**Frontend:** -- [ ] Postal search works -- [ ] Rep dropdown populates -- [ ] Form auto-fills -- [ ] Checkbox enables/disables -- [ ] Submission succeeds - -**Backend:** -- [ ] Token generated -- [ ] Email sent -- [ ] Verification works -- [ ] Report works -- [ ] HTML pages display - -**Security:** -- [ ] Invalid tokens rejected -- [ ] Duplicate verification handled -- [ ] XSS prevention working - -## Common Issues - -### Email Not Sending -- Check SMTP settings in `.env` -- Test at `/email-test.html` -- Check logs: `docker logs influence-app -f` - -### Postal Search Fails -- Verify Represent API accessible -- Check postal code format (T5K 2J1) -- Check browser console for errors - -### Verification Link Fails -- Verify BASE_URL is correct -- Check token in database -- Check application logs - -## URLs for Testing - -``` -# Main page -http://localhost:3333/response-wall.html?campaign=test-campaign - -# Verification (replace ID and TOKEN) -http://localhost:3333/api/responses/123/verify/abc123... - -# Report (replace ID and TOKEN) -http://localhost:3333/api/responses/123/report/abc123... -``` - -## Support - -- **Logs:** `docker logs influence-app -f` -- **Docs:** See markdown files in project root -- **Email Test:** http://localhost:3333/email-test.html - -## Quick Troubleshooting - -| Problem | Solution | -|---------|----------| -| No representatives found | Check postal code format (T5K 2J1) | -| Email not received | Check SMTP settings, spam folder | -| Verification fails | Check BASE_URL, token validity | -| Checkbox disabled | Representative has no email | -| Form won't submit | Check required fields, validation | - ---- - -**Last Updated:** October 16, 2025 -**Version:** 1.0.0 diff --git a/influence/RESPONSE_WALL_UPDATES.md b/influence/RESPONSE_WALL_UPDATES.md deleted file mode 100644 index f50834b..0000000 --- a/influence/RESPONSE_WALL_UPDATES.md +++ /dev/null @@ -1,202 +0,0 @@ -# Response Wall Feature Updates - -## Overview -Updated the Response Wall submission modal to include two new features: -1. **Postal Code Lookup** - Auto-fill representative details by searching postal codes -2. **Response Verification** - Option to send verification email to representatives - -## Frontend Changes Completed - -### 1. HTML Updates (`response-wall.html`) -- Added postal code search input field with search button -- Added representative selection dropdown (hidden by default, shows after search) -- Added hidden field for storing representative email -- Added "Send verification request" checkbox option -- Included `api-client.js` script dependency - -### 2. JavaScript Updates (`response-wall.js`) -- Added `loadedRepresentatives` array to store search results -- Implemented `formatPostalCodeInput()` - formats postal code as "A1A 1A1" -- Implemented `validatePostalCode()` - validates Canadian (Alberta) postal codes -- Implemented `handlePostalLookup()` - fetches representatives from API -- Implemented `displayRepresentativeOptions()` - populates dropdown with results -- Implemented `handleRepresentativeSelect()` - auto-fills form when rep selected -- Implemented `determineGovernmentLevel()` - maps office type to government level -- Updated `handleSubmitResponse()` - includes verification flag and rep email -- Updated `closeSubmitModal()` - resets postal lookup fields - -### 3. CSS Updates (`response-wall.css`) -- Added `.postal-lookup-container` styles for search UI -- Added `#rep-select` and `#rep-select-group` styles for dropdown -- Added checkbox styling improvements -- Added disabled state styling for verification checkbox - -## Backend Implementation - ✅ COMPLETED - -### 1. API Endpoint Updates - ✅ COMPLETED - -#### Update: `POST /api/campaigns/:slug/responses` - ✅ COMPLETED -The endpoint now handles new fields: - -**New Request Fields:** -```javascript -{ - // ... existing fields ... - representative_email: String, // Email address of the representative - send_verification: Boolean // Whether to send verification email -} -``` - -**Implementation Requirements:** -1. Accept and validate `representative_email` field -2. Accept `send_verification` boolean flag -3. When `send_verification === true` AND `representative_email` is present: - - Generate a unique verification token - - Store token with the response record - - Send verification email to representative - -### 2. Database Schema Updates - ✅ COMPLETED - -**responses table additions:** ✅ Implemented in `scripts/build-nocodb.sh` -- `representative_email` - Email field for storing rep email -- `verification_token` - SingleLineText for unique verification token -- `verification_sent_at` - DateTime for tracking when email was sent -- `verified_at` - DateTime for tracking verification timestamp -- `verified_by` - SingleLineText for tracking who verified - -### 3. Verification Email Template - ✅ COMPLETED - -Created email templates in `app/templates/email/`: - -**Subject:** "Verification Request: Response Submission on BNKops Influence" - -**Body:** -``` -Dear [Representative Name], - -A constituent has submitted a response they received from you on the BNKops Influence platform. - -Campaign: [Campaign Name] -Response Type: [Email/Letter/etc.] -Submitted: [Date] - -To verify this response is authentic, please click the link below: -[Verification Link] - -If you did not send this response, please click here to report it: -[Report Link] - -This helps maintain transparency and accountability in constituent communications. - -Best regards, -BNKops Influence Team -``` - -### 4. Verification Endpoints (New) - ✅ COMPLETED - -#### `GET /api/responses/:id/verify/:token` - ✅ COMPLETED -Implemented in `app/controllers/responses.js`: -- Verifies response using unique token -- Updates `verified_at` timestamp -- Marks response as verified (`is_verified: true`) -- Auto-approves response (`status: 'approved'`) -- Returns styled HTML success page - -#### `GET /api/responses/:id/report/:token` - ✅ COMPLETED -Implemented in `app/controllers/responses.js`: -- Marks response as disputed by representative -- Updates response status to 'rejected' -- Sets `is_verified: false` -- Hides from public view (rejected status) -- Returns styled HTML confirmation page - -### 5. Email Service Integration - ✅ COMPLETED - -Updated email service with verification support: - -**File:** `app/services/email.js` - -```javascript -async function sendVerificationEmail(responseId, representativeEmail, representativeName, verificationToken) { - const verificationUrl = `${process.env.BASE_URL}/api/responses/${responseId}/verify/${verificationToken}`; - const reportUrl = `${process.env.BASE_URL}/api/responses/${responseId}/report/${verificationToken}`; - - // Send email using your email service - // Include verification and report links -} -``` - -### 6. Environment Variables - ✅ COMPLETED - -Added to `example.env`: -```env -APP_NAME="BNKops Influence" -BASE_URL=http://localhost:3333 -``` - -**Note:** Update your `.env` file with these values for production deployment. - -## User Flow - -### Submitting with Verification -1. User clicks "Share a Response" -2. User enters postal code and clicks search -3. System fetches representatives from Represent API -4. User selects their representative from dropdown -5. Form auto-fills: name, title, level, email (hidden) -6. User completes response details -7. User checks "Send verification request" -8. User submits form -9. **Backend**: Response saved as pending/unverified -10. **Backend**: Verification email sent to representative -11. User sees success message - -### Representative Verification -1. Representative receives email -2. Clicks verification link -3. Redirects to verification endpoint -4. Response marked as verified -5. Response becomes visible with "Verified" badge - -## Testing Checklist - -### Frontend Testing -- [ ] Postal code search works with valid Alberta codes -- [ ] Validation rejects non-Alberta codes -- [ ] Representative dropdown populates correctly -- [ ] Selecting a rep auto-fills form fields -- [ ] Verification checkbox is disabled when no email -- [ ] Verification checkbox is enabled when rep has email -- [ ] Form submits successfully with verification flag -- [ ] Manual entry still works without postal lookup -- [ ] Modal resets properly when closed - -### Backend Testing -- [ ] Backend receives verification parameters correctly -- [ ] Verification token is generated and stored -- [ ] Verification email is sent when opted in -- [ ] Email contains correct verification and report URLs -- [ ] Verification endpoint validates token correctly -- [ ] Verification endpoint updates database correctly -- [ ] Report endpoint marks response as disputed -- [ ] Styled HTML pages display correctly on verify/report -- [ ] Security: Invalid tokens are rejected -- [ ] Security: Already verified responses show appropriate message - -## Security Considerations - -1. **Token Security**: Use cryptographically secure random tokens -2. **Token Expiry**: Verification tokens should expire (e.g., 30 days) -3. **Rate Limiting**: Limit verification emails per IP/session -4. **Email Validation**: Validate representative email format -5. **XSS Prevention**: Sanitize all form inputs on backend -6. **CSRF Protection**: Ensure CSRF tokens on form submission - -## Future Enhancements - -1. Add notification when representative verifies -2. Show verification status prominently on response cards -3. Add statistics: "X% of responses verified" -4. Allow representatives to add comments during verification -5. Add representative dashboard to manage verifications -6. Support for multiple verification methods (SMS, etc.) diff --git a/influence/app/Dockerfile b/influence/app/Dockerfile index c1f84c0..63793fc 100644 --- a/influence/app/Dockerfile +++ b/influence/app/Dockerfile @@ -2,6 +2,9 @@ FROM node:18-alpine WORKDIR /usr/src/app +# Install curl for healthcheck +RUN apk add --no-cache curl + # Copy package files COPY package*.json ./ diff --git a/influence/app/middleware/csrf.js b/influence/app/middleware/csrf.js new file mode 100644 index 0000000..abeb03f --- /dev/null +++ b/influence/app/middleware/csrf.js @@ -0,0 +1,104 @@ +const csrf = require('csurf'); +const logger = require('../utils/logger'); + +// Create CSRF protection middleware +const csrfProtection = csrf({ + cookie: { + httpOnly: true, + secure: process.env.NODE_ENV === 'production' && process.env.HTTPS === 'true', + sameSite: 'strict', + maxAge: 3600000 // 1 hour + } +}); + +/** + * Middleware to handle CSRF token errors + */ +const csrfErrorHandler = (err, req, res, next) => { + if (err.code === 'EBADCSRFTOKEN') { + logger.warn('CSRF token validation failed', { + ip: req.ip, + path: req.path, + method: req.method, + userAgent: req.get('user-agent') + }); + + return res.status(403).json({ + success: false, + error: 'Invalid CSRF token', + message: 'Your session has expired or the request is invalid. Please refresh the page and try again.' + }); + } + + next(err); +}; + +/** + * Middleware to inject CSRF token into response + * Adds csrfToken to all JSON responses and as a header + */ +const injectCsrfToken = (req, res, next) => { + // Add token to response locals for template rendering + res.locals.csrfToken = req.csrfToken(); + + // Override json method to automatically include CSRF token + const originalJson = res.json.bind(res); + res.json = function(data) { + if (data && typeof data === 'object' && !data.csrfToken) { + data.csrfToken = res.locals.csrfToken; + } + return originalJson(data); + }; + + next(); +}; + +/** + * Skip CSRF protection for specific routes (e.g., webhooks, public APIs) + */ +const csrfExemptRoutes = [ + '/api/health', + '/api/metrics', + '/api/config', + '/api/auth/login', // Login uses credentials for authentication + '/api/auth/session', // Session check is read-only + '/api/representatives/postal/', // Read-only operation + '/api/campaigns/public' // Public read operations +]; + +const conditionalCsrfProtection = (req, res, next) => { + // Skip CSRF for exempt routes + const isExempt = csrfExemptRoutes.some(route => req.path.startsWith(route)); + + // Skip CSRF for GET, HEAD, OPTIONS (safe methods) + const isSafeMethod = ['GET', 'HEAD', 'OPTIONS'].includes(req.method); + + if (isExempt || isSafeMethod) { + return next(); + } + + // Apply CSRF protection for state-changing operations + csrfProtection(req, res, (err) => { + if (err) { + return csrfErrorHandler(err, req, res, next); + } + injectCsrfToken(req, res, next); + }); +}; + +/** + * Helper to get CSRF token for client-side use + */ +const getCsrfToken = (req, res) => { + res.json({ + csrfToken: req.csrfToken() + }); +}; + +module.exports = { + csrfProtection, + csrfErrorHandler, + injectCsrfToken, + conditionalCsrfProtection, + getCsrfToken +}; diff --git a/influence/app/package.json b/influence/app/package.json index 9fcfa89..fe5e07f 100644 --- a/influence/app/package.json +++ b/influence/app/package.json @@ -29,7 +29,16 @@ "express-session": "^1.17.3", "bcryptjs": "^2.4.3", "multer": "^1.4.5-lts.1", - "qrcode": "^1.5.3" + "qrcode": "^1.5.3", + "winston": "^3.11.0", + "winston-daily-rotate-file": "^4.7.1", + "compression": "^1.7.4", + "csurf": "^1.11.0", + "cookie-parser": "^1.4.6", + "bull": "^4.12.0", + "prom-client": "^15.1.0", + "sharp": "^0.33.0", + "ioredis": "^5.3.2" }, "devDependencies": { "nodemon": "^3.0.1", diff --git a/influence/app/public/campaign.html b/influence/app/public/campaign.html index 9bd0af2..fe4f5c0 100644 --- a/influence/app/public/campaign.html +++ b/influence/app/public/campaign.html @@ -130,6 +130,75 @@ border-color: rgba(40, 167, 69, 1); } + .share-more-container { + position: relative; + display: inline-block; + } + + .share-dropdown { + display: none; + position: absolute; + top: 100%; + right: 0; + margin-top: 0.5rem; + background: white; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + padding: 0.5rem; + z-index: 1000; + min-width: 200px; + } + + .share-dropdown.show { + display: block; + } + + .share-dropdown-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.5rem; + } + + .share-dropdown-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 0.75rem 0.5rem; + border-radius: 6px; + background: rgba(52, 152, 219, 0.1); + cursor: pointer; + transition: all 0.2s; + text-decoration: none; + color: #2c3e50; + } + + .share-dropdown-item:hover { + background: rgba(52, 152, 219, 0.2); + transform: translateY(-2px); + } + + .share-dropdown-item svg { + width: 24px; + height: 24px; + margin-bottom: 0.25rem; + fill: #2c3e50; + } + + .share-dropdown-item span { + font-size: 0.7rem; + text-align: center; + line-height: 1.2; + } + + .share-btn-small.more-btn { + position: relative; + } + + .share-btn-small.more-btn.active { + background: rgba(255, 255, 255, 0.4); + } + .campaign-content { max-width: 800px; margin: 0 auto; @@ -463,40 +532,110 @@
@@ -544,15 +683,6 @@ - - - + + +