This document outlines all security measures implemented in the Interactive Product Analytics Dashboard.
- Implementation: All passwords are hashed using bcrypt with 10 salt rounds
- Location:
backend/routes/auth.js - Code:
const password_hash = await bcrypt.hash(password, 10);
- Storage: Only
password_hashis stored in database, never plaintext passwords - Verification: Uses
bcrypt.compare()for secure password comparison
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL, -- Only hashed passwords stored
...
);- Algorithm: HS256 (HMAC with SHA-256)
- Secret: Stored in environment variable
JWT_SECRET - Expiration: 7 days
- Payload: Contains only
userIdandusername(no sensitive data)
- Location:
backend/middleware/authMiddleware.js - Checks:
- Token presence in Authorization header
- Token format (Bearer TOKEN)
- Token validity and signature
- Token expiration
- Error Handling:
- Invalid token: 401 Unauthorized
- Expired token: 401 Unauthorized with specific message
- Missing token: 401 Unauthorized
All database queries use parameterized placeholders ($1, $2, etc.) to prevent SQL injection.
Examples:
// ✓ SECURE - Parameterized query
await query(
'SELECT * FROM users WHERE username = $1',
[username]
);
// ✗ INSECURE - String concatenation (NOT USED)
// await query(`SELECT * FROM users WHERE username = '${username}'`);backend/routes/auth.js- All user queriesbackend/routes/track.js- Event tracking queriesbackend/routes/analytics.js- Analytics queriesbackend/db.js- Database initialization
- Package:
express-validator@^7.0.1 - Location:
backend/validation/validators.js
- username: 3-50 chars, alphanumeric + underscore only
- password: minimum 6 characters
- age: 13-120 (optional)
- gender: Male, Female, or Other (optional)- dates: YYYY-MM-DD format, start_date <= end_date
- age: 13-120 range
- gender: Male, Female, or Other enum
- feature_name: alphanumeric + underscore/hyphen, max 255 chars- Trim whitespace from inputs
- Validate format before processing
- Reject invalid characters
- Length limits enforced
const corsOptions = {
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
};- Restricts cross-origin requests to specified frontend URL
- Prevents unauthorized domains from accessing API
- Allows credentials (cookies, authorization headers)
- Limits HTTP methods to necessary ones only
- Limit: 100 requests per 15 minutes per IP
- Purpose: Prevent abuse of event tracking
- Response: 429 Too Many Requests with retry-after header
- Limit: 5 requests per 15 minutes per IP
- Purpose: Prevent brute force attacks
- Skip: Successful requests don't count toward limit
- Response: 429 Too Many Requests
- Limit: 1000 requests per 15 minutes per IP
- Purpose: Prevent API abuse
- Scope: All /api/* routes
import rateLimit from 'express-rate-limit';
export const trackRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false
});X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains- JSON payload: 10MB maximum
- URL-encoded payload: 10MB maximum
- Prevents memory exhaustion attacks
- Sensitive data stored in
.envfile - Never committed to version control
- Required variables validated on startup
- JWT_SECRET minimum length warning (32 chars recommended)
- Production mode hides internal error details
- Generic error messages prevent information leakage
- Detailed errors only in development mode
- All errors logged server-side
- SSL/TLS connection to database (Neon compatible)
- Connection pooling with proper cleanup
- Prepared statements for all queries
- Foreign key constraints for data integrity
- Indexes for performance (not security, but prevents DoS via slow queries)
- Bcrypt password hashing (10 rounds)
- JWT authentication with expiration
- SQL injection protection (parameterized queries)
- Input validation (express-validator)
- CORS configuration (restricted origins)
- Rate limiting (/track endpoint: 100/15min)
- Rate limiting (auth endpoints: 5/15min)
- Rate limiting (general API: 1000/15min)
- Passwords never stored in plaintext
- Security headers set
- Payload size limits
- Environment variable validation
- Error message sanitization
- HTTPS enforcement (Strict-Transport-Security header)
- XSS protection headers
- Clickjacking protection (X-Frame-Options)
# Test track endpoint rate limit
for i in {1..101}; do
curl -X POST -H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"feature_name":"test"}' \
http://localhost:5000/track
done# This should be safely handled (returns error, not SQL injection)
curl -X POST -H "Content-Type: application/json" \
-d '{"username":"admin'\'' OR '\''1'\''='\''1","password":"test"}' \
http://localhost:5000/logincurl -H "Authorization: Bearer invalid_token" \
http://localhost:5000/analytics- Set strong JWT_SECRET (min 32 characters, random)
- Set FRONTEND_URL to production domain
- Enable HTTPS/TLS
- Set NODE_ENV=production
- Use strong database password
- Enable database SSL
- Configure firewall rules
- Set up monitoring and alerting
- Regular security updates
- Backup strategy in place
If you discover a security vulnerability, please email security@example.com. Do not create public GitHub issues for security vulnerabilities.