Overview
The audit logging system tracks all significant actions in the voting system for security, compliance, and troubleshooting purposes. All audit logs are stored in the database and can be viewed via the admin interface.
What Gets Logged
Logged Actions
The system logs the following actions:
election.created: When an election is createdelection.deleted: When an election is deletedelection.closed: When an election is manually closedelection.viewed: When someone views an election pagetokens.generated: When tokens are generated for an electionvote.submitted: When a vote is successfully submittedresults.viewed: When election results are viewedadmin.access: When admin routes are accessedtoken.invalid: When an invalid token is usedrate_limit.exceeded: When rate limits are exceeded
Logged Information
Each audit log entry includes:
- ID: Unique identifier (UUID)
- Action: Action type (see above)
- IP Address: Client IP address (if available)
- User Agent: Browser/client user agent (if available)
- Election ID: Associated election (if applicable)
- Timestamp: When the action occurred (Unix timestamp in milliseconds)
- Metadata: Additional context (JSON string)
Database Schema
audit_logs Table
CREATE TABLE audit_logs (
id TEXT PRIMARY KEY, -- UUID
action TEXT NOT NULL, -- Action type
ip_address TEXT, -- Client IP
user_agent TEXT, -- User agent string
election_id TEXT, -- Associated election
timestamp INTEGER NOT NULL, -- Unix timestamp (ms)
metadata TEXT NOT NULL, -- JSON string of additional data
FOREIGN KEY (election_id) REFERENCES elections(id)
);Indexes
- Index on
action(for filtering) - Index on
election_id(for election-specific queries) - Index on
timestamp(for time-based queries)
Metadata Structure
Election Created
{
"election_id": "elec_123",
"title": "Board President 2024",
"ballot_type": "RANKED_CONDORCET"
}Vote Submitted
{
"election_id": "elec_123",
"ballot_type": "SIMPLE_TRIPLE",
"choice": "YES"
}Note: Token information is never logged for security.
Admin Access
{
"endpoint": "/admin/elections",
"method": "GET",
"action": "list_elections"
}Rate Limit Exceeded
{
"limit_type": "vote",
"ip_address": "192.168.1.1",
"endpoint": "/e/elec_123/vote"
}Viewing Audit Logs
Via Admin UI
- Navigate to Admin → Audit Logs
- View logs in chronological order (newest first)
- Filter by:
- Action: Select specific action type
- Election: Filter by election ID
- Pagination: 100 logs per page
Via API
Endpoint: GET /admin/audit-logs
Query Parameters:
limit: Number of logs to return (default: 100)offset: Pagination offset (default: 0)action: Filter by action typeelection_id: Filter by election ID
Response:
{
"logs": [
{
"id": "log_abc123",
"action": "vote.submitted",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"election_id": "elec_123",
"timestamp": 1704067200000,
"metadata": "{\"ballot_type\":\"SIMPLE_TRIPLE\"}"
}
],
"total": 150,
"limit": 100,
"offset": 0
}Security Considerations
Privacy
- IP Addresses: Logged for security but should be handled carefully
- User Agents: May contain identifying information
- No Tokens: Token information is never logged
- No Personal Data: Email addresses not logged in metadata
Retention
- No Automatic Deletion: Logs are kept indefinitely
- Manual Cleanup: Can be deleted via database queries
- Compliance: Consider retention policies for your jurisdiction
Access Control
- Admin Only: Audit logs are only accessible to admins
- Rate Limited: Audit log viewing is rate-limited (50 req/min)
- No Public Access: Logs are never exposed to public
Creating Audit Logs
In Code
import { createAuditLog, AUDIT_ACTIONS } from "../utils/audit";
await createAuditLog(db, AUDIT_ACTIONS.VOTE_SUBMITTED, {
ipAddress: c.req.header("CF-Connecting-IP"),
userAgent: c.req.header("User-Agent"),
electionId: election.id,
metadata: {
ballot_type: "SIMPLE_TRIPLE",
choice: "YES"
}
});Error Handling
Audit logging never fails the request. If logging fails:
- Error is logged to console
- Request continues normally
- No user-facing error
This ensures audit logging doesn’t impact system reliability.
Use Cases
Security Monitoring
Track suspicious activity:
- Multiple invalid token attempts
- Rate limit violations
- Unusual access patterns
Compliance
Maintain audit trail for:
- Regulatory compliance
- Internal audits
- Legal requirements
Troubleshooting
Debug issues:
- Track when actions occurred
- Identify problematic requests
- Correlate errors with actions
Analytics
Analyze usage:
- Election participation
- Admin activity
- System usage patterns
Querying Audit Logs
Common Queries
All votes for an election:
SELECT * FROM audit_logs
WHERE action = 'vote.submitted'
AND election_id = 'elec_123'
ORDER BY timestamp DESC;Admin access in last 24 hours:
SELECT * FROM audit_logs
WHERE action = 'admin.access'
AND timestamp > (unixepoch() * 1000 - 86400000)
ORDER BY timestamp DESC;Rate limit violations:
SELECT * FROM audit_logs
WHERE action = 'rate_limit.exceeded'
ORDER BY timestamp DESC
LIMIT 100;Invalid token attempts:
SELECT ip_address, COUNT(*) as attempts
FROM audit_logs
WHERE action = 'token.invalid'
GROUP BY ip_address
ORDER BY attempts DESC;Best Practices
For Administrators
- Regular Review: Periodically review audit logs
- Monitor Suspicious Activity: Watch for patterns
- Retention Policy: Establish log retention policy
- Access Control: Limit who can view audit logs
For Developers
- Log Important Actions: Log all significant actions
- Include Context: Add relevant metadata
- Never Log Secrets: Never log tokens, API keys, etc.
- Handle Errors Gracefully: Don’t fail requests if logging fails
Limitations
Current Limitations
- No Automatic Cleanup: Logs accumulate indefinitely
- No Export: Must query database directly
- No Real-Time Alerts: No automatic alerting on suspicious activity
- Limited Filtering: Basic filtering only
Future Enhancements
Potential improvements:
- Automatic log rotation/archival
- CSV/JSON export
- Real-time alerting
- Advanced filtering/search
- Log aggregation/analytics
Performance Considerations
Indexing
Audit logs are indexed on:
action: For filtering by action typeelection_id: For election-specific queriestimestamp: For time-based queries
Query Performance
- Recent Logs: Fast (indexed by timestamp)
- Filtered Queries: Fast (indexed columns)
- Full Table Scans: Slow (avoid for large datasets)
Scaling
For high-volume systems:
- Consider log rotation
- Archive old logs
- Use time-based partitioning
- Implement log aggregation
Migration
Audit logs were added in migration 002_audit_logs.sql. The table is created automatically during migration.
See Also
- ops - General infrastructure documentation
- migrations - Database migration documentation
- api-reference - API endpoint documentation