This guide documents all security features implemented in the IBR Tournament System and how to use them effectively.
- Overview
- Authentication Security
- Multi-Factor Authentication (MFA)
- Session Management
- Rate Limiting
- Input Validation
- Encryption
- Audit Logging
- OAuth Security
- Security Headers
- Environment Configuration
- CI/CD Security
- Monitoring and Alerting
The IBR Tournament System implements defense-in-depth security with multiple layers of protection:
- Authentication: Password hashing, MFA, OAuth integration
- Session Security: Encrypted sessions, secure token storage
- Rate Limiting: Sliding window algorithm with IP and user tracking
- Input Validation: Zod schemas with sanitization
- Encryption: AES-256-GCM for sensitive data at rest
- Audit Logging: Comprehensive security event tracking
- Infrastructure: CI/CD security scanning, environment validation
All security features are designed to be:
- Transparent: Don't impact legitimate user experience
- Monitored: Generate audit trails for compliance
- Tested: Full test coverage for all security components
Passwords must meet the following criteria:
- Minimum 8 characters
- At least one uppercase letter (A-Z)
- At least one lowercase letter (a-z)
- At least one number (0-9)
- At least one special character (!@#$%^&*)
Implementation: See apps/api/src/middleware/validation.ts - commonSchemas.password
- Passwords are hashed using bcrypt with 10 salt rounds
- Plain-text passwords are never stored or logged
- Password change events are logged to
password_change_eventstable
Code Reference: apps/api/src/handlers/auth.ts - handleRegister()
- User submits username/password
- System validates input format
- Rate limiting is checked (3 attempts per 5 minutes)
- Credentials are verified against bcrypt hash
- Successful login is logged to
login_attemptstable - Failed login is logged with failure reason
- Session is created if MFA is not enabled
- MFA verification is required if enabled
Security Checks:
- Rate limiting prevents brute force attacks
- Account lockout after multiple failed attempts (feature can be enabled)
- Audit trail of all login attempts
MFA adds an additional layer of security using TOTP (Time-based One-Time Password) compatible with:
- Google Authenticator
- Authy
- Microsoft Authenticator
- Any TOTP-compatible app
Users can enable MFA via:
- API Endpoint:
POST /api/auth/mfa/setup - Frontend Settings page (when implemented)
Flow:
- User requests MFA setup
- System generates TOTP secret
- System returns QR code for scanning
- User scans QR code with authenticator app
- User submits 6-digit code to verify
- MFA is enabled for the account
// Setup MFA - returns QR code
POST /api/auth/mfa/setup
Authorization: Bearer <session_token>
// Verify and enable MFA
POST /api/auth/mfa/enable
Body: { code: "123456" }
Authorization: Bearer <session_token>
// Disable MFA (requires password confirmation)
POST /api/auth/mfa/disable
Body: { password: "current_password" }
Authorization: Bearer <session_token>
// Verify MFA during login
POST /api/auth/mfa/verify
Body: { code: "123456" }
Authorization: Bearer <session_token>- TOTP secrets are encrypted using AES-256-GCM before storage
- Encryption key:
SESSION_ENCRYPTION_KEYenvironment variable - Encrypted secrets stored in
users.mfa_secretcolumn
All MFA events are logged to mfa_events table:
- Setup initiated
- Setup completed
- Verification attempts (success/failure)
- MFA disabled
Sessions are stored in Cloudflare KV with the following structure:
Key: session:<token>
Value: {
userId: string,
username: string,
role: string,
mfaEnabled: boolean,
mfaVerified: boolean,
createdAt: string,
expiresAt: string
}
TTL: 7 days (configurable)
- Encryption: Session data is encrypted before storage
- Token Generation: Cryptographically secure random tokens
- Expiration: Automatic expiration after 7 days
- MFA Tracking: Separate MFA verification flag
- Audit Trail: All session events logged
Create → Verify (with MFA if enabled) → Refresh → Destroy/Expire
Events Logged:
session.create: New session createdsession.destroy: User logged outsession.refresh: Session extendedsession.expire: Session timed out
// Create session (after successful login)
POST /api/auth/login
Body: { username, password }
// Refresh session
POST /api/auth/refresh
Authorization: Bearer <session_token>
// Destroy session (logout)
POST /api/auth/logout
Authorization: Bearer <session_token>Rate limiting prevents abuse and brute force attacks using a sliding window algorithm.
| Endpoint Type | Limit | Window | Description |
|---|---|---|---|
| Authentication | 5 requests | 60 seconds | Login, register, password reset |
| Login | 3 requests | 300 seconds | Strict limit on login attempts |
| MFA Setup | 3 requests | 3600 seconds | MFA configuration |
| MFA Verify | 10 requests | 300 seconds | MFA code verification |
| Password Reset | 3 requests | 3600 seconds | Password reset requests |
| API Read | 100 requests | 60 seconds | GET requests |
| API Write | 30 requests | 60 seconds | POST/PUT/PATCH requests |
| API Delete | 10 requests | 60 seconds | DELETE requests |
| Admin Read | 200 requests | 60 seconds | Admin GET requests |
| Admin Write | 50 requests | 60 seconds | Admin POST/PUT/PATCH |
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1708800000
Retry-After: 30
Sliding Window:
- Tracks timestamps of all requests
- Counts requests within the time window
- More accurate than fixed window algorithms
- Prevents burst attacks at window boundaries
Admin users can bypass rate limits:
- Automatic bypass for users with
role: 'admin' - Useful for bulk operations
- Logged for audit purposes
Rate limits are configured in apps/api/src/middleware/enhanced-rate-limit.ts:
export const RATE_LIMIT_CONFIGS = {
login: { limit: 3, window: 300, keyPrefix: 'rl:login' },
apiRead: { limit: 100, window: 60, keyPrefix: 'rl:api:read' },
// ... more configs
};All user input is validated using Zod schemas with the following layers:
- Schema Validation: Type checking, format validation
- Sanitization: HTML tag stripping, SQL injection detection
- Length Limits: Prevent DoS via large inputs
- Pattern Matching: Email, UUID, username formats
// Email validation
email: z.string().email('Invalid email format')
// Username validation (3-20 chars, alphanumeric + underscore + hyphen)
username: z.string()
.min(3, 'Username must be at least 3 characters')
.max(20, 'Username must not exceed 20 characters')
.regex(/^[a-zA-Z0-9_-]+$/, 'Username can only contain letters, numbers, underscores, and hyphens')
// Password validation (8+ chars, uppercase, lowercase, number, special)
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number')
.regex(/[^a-zA-Z0-9]/, 'Password must contain at least one special character')
// UUID validation
uuid: z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)SQL Injection Detection:
const sqlPatterns = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b)/i,
/(--|;|\/\*|\*\/)/,
/\b(OR|AND)\s+\w+\s*(=|<|>|\!=)/i,
/(\bUNION\s+SELECT\b)/i,
];
sanitizers.hasSqlInjection(input); // Returns true if detectedXSS Detection:
const xssPatterns = [
/<script[^>]*>.*?<\/script>/gi,
/<iframe[^>]*>.*?<\/iframe>/gi,
/javascript:/i,
/on\w+\s*=/i, // onclick=, onload=, etc.
];
sanitizers.hasXss(input); // Returns true if detectedimport { withValidation, requestSchemas } from '../middleware/validation';
// Automatic validation
export const handleLogin = withValidation(
requestSchemas.login,
async (request) => {
const { username, password } = request.validatedData;
// Process login...
}
);All encryption uses AES-256-GCM (Galois/Counter Mode) which provides:
- Confidentiality: Data cannot be read without the key
- Integrity: Tampering is detected
- Authenticated Encryption: Combines encryption and authentication
Primary Key: SESSION_ENCRYPTION_KEY environment variable
- Must be 32+ characters
- Used for all encryption operations
- Never expose in logs or error messages
Key Rotation (future enhancement):
- Implement key versioning
- Re-encrypt data with new keys
- Support multiple key versions
The following data is encrypted at rest:
- MFA secrets (
users.mfa_secret) - OAuth refresh tokens (
oauth_accounts.refresh_token) - Session data (in KV storage)
- Any future sensitive user data
// Encrypt string data
const encrypted = await encryptData("sensitive data", env);
// Decrypt string data
const decrypted = await decryptData(encrypted, env);
// Encrypt objects
const encryptedObj = await encryptObject({ key: "value" }, env);
const decryptedObj = await decryptObject<MyType>(encryptedObj, env);
// Hash for integrity verification
const hash = await hashData("data");
const isValid = await verifyHash("data", hash);The following tables track security-relevant events:
| Table | Purpose |
|---|---|
login_attempts |
All login attempts (success/failure) |
admin_actions |
Admin operations for accountability |
password_change_events |
Password changes and resets |
oauth_events |
OAuth linking/unlinking/login |
session_events |
Session lifecycle |
mfa_events |
MFA setup and verification |
rate_limit_events |
Rate limit violations |
data_export_events |
GDPR data export requests |
All audit events include:
id: Unique event identifiercreated_at: Event timestampip_address: Client IP addressuser_id: Affected user (if applicable)- Additional fields specific to event type
import { createAuditService } from '../services/audit-service';
const auditService = createAuditService(env);
// Get failed login attempts for a user
const failedLogins = await auditService.getFailedLoginAttempts('username', 60);
// Get recent admin actions
const adminActions = await auditService.getRecentAdminActions(100);
// Get user activity history
const activity = await auditService.getUserActivity('user-id');
// Get security metrics for dashboard
const metrics = await auditService.getSecurityMetrics(24);The audit log system supports:
- GDPR: Complete user activity tracking
- SOC2: Comprehensive security monitoring
- Forensic Analysis: Incident response data
- Accountability: Admin action tracking
- Google OAuth 2.0
- GitHub OAuth 2.0
- User clicks "Sign in with [Provider]"
- Redirect to provider's authorization page
- User grants permissions
- Provider redirects back with authorization code
- Server exchanges code for access token
- Server fetches user profile
- Account is linked or created
- Session is created
- OAuth event is logged
- Access tokens are stored in
oauth_accountstable - Refresh tokens are encrypted before storage
- Tokens are never logged or exposed in errors
- Tokens can be revoked via OAuth provider
- State Parameter: CSRF protection during OAuth flow
- PKCE (future): Proof Key for Code Exchange
- Token Encryption: Refresh tokens encrypted at rest
- Audit Logging: All OAuth events logged
- Revocation: Users can unlink OAuth accounts
The following security headers are automatically added to all API responses:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()Headers are defined in apps/api/src/middleware/security-headers.ts:
export const SECURITY_HEADERS = {
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
// ... more headers
};| Variable | Purpose | Requirements |
|---|---|---|
SESSION_ENCRYPTION_KEY |
Session encryption | 32+ characters |
JWT_SECRET |
JWT signing | 32+ characters |
PUBG_API_KEY |
PUBG API access | Valid API key |
ENVIRONMENT |
Environment name | development/staging/production |
CORS_ORIGIN |
Frontend origin | Valid URL |
The application validates all environment variables at startup:
import { validateOnStartup } from './services/environment-validation';
// In src/index.ts
validateOnStartup(env);Development: Warns about issues but continues Production: Fails startup if validation fails
Control features via environment variables:
# Enable/disable MFA
ENABLE_MFA=true
# Enable/disable OAuth
ENABLE_OAUTH=true
# Enable/disable new user registration
ENABLE_REGISTRATION=falseSecurity scans run automatically on:
- Every push to main/master/develop branches
- Every pull request
- Daily schedule (2 AM UTC)
- Dependency Vulnerability Scan: npm audit, pnpm audit
- Secret Detection: TruffleHog scans full git history
- Code Security: ESLint with security rules, TypeScript type checking
- SAST: Semgrep, CodeQL for vulnerability patterns
- License Compliance: Checks for GPL/AGPL/SSPL licenses
Run security checks locally before committing:
# Full security scan
npm run security:check
# Install pre-commit hooks
npm run security:install-hooksAutomatically runs before every commit:
- Secret detection
- Large file warnings
- Dangerous pattern detection
- Linting staged files
Track these metrics for security monitoring:
const metrics = await auditService.getSecurityMetrics(24);
console.log({
failedLogins: metrics.failedLogins,
successfulLogins: metrics.successfulLogins,
adminActions: metrics.adminActions,
passwordChanges: metrics.passwordChanges,
mfaEvents: metrics.mfaEvents,
rateLimitExceeded: metrics.rateLimitExceeded,
});Recommended alerts for monitoring systems:
- High Failed Login Rate: >10 failed logins per minute
- Rate Limit Violations: >100 per hour
- Admin Actions: All admin actions (for review)
- MFA Disable Events: When users disable MFA
- Password Reset: Multiple resets for same user
Create dashboards showing:
- Failed vs successful login attempts
- Rate limit violations over time
- MFA adoption rate
- Active sessions
- Recent admin actions
-
Never commit secrets
- Use environment variables
- Add sensitive files to .gitignore
- Use pre-commit hooks
-
Validate all input
- Use Zod schemas
- Sanitize user input
- Check for SQL injection/XSS
-
Log security events
- Use audit service
- Include context (IP, user, timestamp)
- Never log sensitive data
-
Encrypt sensitive data
- Use encryption service
- Never store plain-text secrets
- Use strong encryption keys
-
Follow principle of least privilege
- Minimize permissions
- Use read-only when possible
- Validate admin actions
- Rotate encryption keys quarterly
- Review audit logs weekly
- Update dependencies regularly
- Monitor security metrics daily
- Test security features monthly
- All required environment variables set
- Encryption keys are strong (32+ chars)
- CORS origin configured correctly
- Rate limiting enabled
- MFA enabled for production
- Audit logging configured
- Security headers in place
- Dependency scan passing
- No secrets in code
- Database migrations applied
- SSL/TLS configured
- Monitoring/alerting configured
- Monitor failed login rate
- Review audit logs for issues
- Test rate limiting
- Verify MFA flow
- Check OAuth integration
- Validate session management
- Review security metrics
Problem: Users seeing rate limit errors Solution:
- Check rate limit configuration
- Verify admin bypass working
- Review rate limit metrics
Problem: MFA verification failing Solution:
- Check system time synchronization
- Verify TOTP secret encryption
- Test with manual TOTP code
Problem: Login failures increasing Solution:
- Review failed login logs
- Check for brute force attacks
- Consider lowering rate limits
Problem: Session expiration issues Solution:
- Verify KV storage working
- Check session TTL configuration
- Review session event logs
For questions or security concerns, contact the security team or open a security issue in the repository.