A secure web application for generating personalized QSL cards for amateur radio operators.
- Multi-user authentication: Secure login with bcrypt password hashing
- Role-based access: Admin and callsign-owner roles with strict isolation
- Dynamic routing: Access via
/{callsign}(e.g.,/oe8yml) - Real-time preview: Canvas-based rendering with instant updates
- Custom backgrounds: Upload and select from multiple background images
- Admin interface: Manage callsigns, users, upload templates, configure text positions
- Audit logging: Track all sensitive operations
- Responsive design: Works on desktop and mobile
IMPORTANT: This application handles personal data (callsigns, QSL card content). The following safeguards are in place:
The .gitignore is configured to exclude all user data:
data/- All runtime data (callsigns.json, user uploads, generated cards)*.db- SQLite databases (user accounts, sessions, audit logs)- All image files (
*.png,*.jpg, etc.)
All user data is stored in the data/ directory which is:
- Created automatically on first run
- Mounted as a Docker volume for persistence
- Never committed to git
data/
├── callsigns.json # Callsign configurations (runtime only)
├── users.db # User accounts, sessions, audit log
└── cards/ # Uploaded card templates and backgrounds
└── {callsign}/
├── card.png
└── backgrounds/
- Console logs only show aggregate counts (e.g., "Loaded 17 callsigns")
- No personal data, callsigns, or card content is logged to stdout
- Audit logs are stored in the SQLite database, not in files
- Error messages never expose user data
- Passwords hashed with bcrypt (12 salt rounds)
- Session tokens are cryptographically random (32 bytes)
- Sessions expire after 7 days
- Rate limiting on authentication endpoints
- Card images served through authenticated API endpoints only
- 404 responses for unauthorized access (prevents callsign enumeration)
# Build and run
docker compose up -d
# Create initial admin user
docker exec -it qslcardgenerator node scripts/create-admin.js admin yourpassword
# Access at http://localhost:3400# Install dependencies
npm install
# Create admin user
node scripts/create-admin.js admin yourpassword
# Start server
npm startPOST /api/auth/login- Login with username/passwordPOST /api/auth/logout- Logout (invalidate session)GET /api/auth/me- Get current user infoPOST /api/auth/change-password- Change own password
GET /api/manage/callsign- Get own callsign configPUT /api/manage/callsign- Update own callsign configGET /api/manage/backgrounds- List own backgroundsPOST /api/manage/upload/:type- Upload card/backgroundDELETE /api/manage/backgrounds/:filename- Delete background
GET /api/admin/users- List all usersPOST /api/admin/users- Create userPUT /api/admin/users/:id- Update userDELETE /api/admin/users/:id- Delete userGET /api/admin/callsigns- List all callsignsPOST /api/admin/callsigns- Create callsignPUT /api/admin/callsigns/:callsign- Update callsignDELETE /api/admin/callsigns/:callsign- Delete callsignGET /api/admin/audit- View audit log
GET /api/cards/:callsign/card.png- Get card template (auth required)GET /api/cards/:callsign/backgrounds/:filename- Get background (auth required)
qslcardgenerator/
├── src/
│ └── server.js # Express backend
├── public/
│ ├── index.html # Login page
│ ├── generator.html # QSL card generator
│ ├── admin.html # Admin interface
│ ├── logo.svg # Application logo
│ └── 404.html # Not found page
├── scripts/
│ ├── create-admin.js # Create admin user
│ └── migrate.js # Migration from old system
├── data/ # Runtime data (NOT in git)
├── Dockerfile
├── docker-compose.yml
└── .gitignore # Excludes all user data
- Format: PNG with transparency
- Resolution: 4837 x 3078 pixels (recommended)
- Scale factor: 0.4x for display
| Variable | Default | Description |
|---|---|---|
PORT |
3400 | Server port |
When contributing, ensure:
- Never commit any files from
data/ - Never log personal data or callsign content
- Test with sample data, not real user data
- Run
git statusbefore committing to verify no data files are staged
MIT