Files
firefrost-services/services/arbiter/README.md
Claude (The Golden Chronicler #50) 04e9b407d5 feat: Migrate Arbiter and Modpack Version Checker to monorepo
WHAT WAS DONE:
- Migrated Arbiter (discord-oauth-arbiter) code to services/arbiter/
- Migrated Modpack Version Checker code to services/modpack-version-checker/
- Created .env.example for Arbiter with all required environment variables
- Moved systemd service file to services/arbiter/deploy/
- Organized directory structure per Gemini monorepo recommendations

WHY:
- Consolidate all service code in one repository
- Prepare for Gemini code review (Panel v1.12 compatibility check)
- Enable service-prefixed Git tagging (arbiter-v2.1.0, modpack-v1.0.0)
- Support npm workspaces for shared dependencies

SERVICES MIGRATED:
1. Arbiter (Discord OAuth bot) - Originally written by Gemini + Claude
   - Full source code from ops-manual docs/implementation/
   - Created comprehensive .env.example
   - Ready for Panel v1.12 compatibility verification

2. Modpack Version Checker (Python CLI tool)
   - Full source code from ops-manual docs/tasks/
   - Written for Panel v1.11, needs Gemini review for v1.12
   - Never had code review before

STILL TODO:
- Whitelist Manager - Pull from Billing VPS (38.68.14.188)
  - Currently deployed and running
  - Needs Panel v1.12 API compatibility fix (Task #86)
  - Requires SSH access to pull code

NEXT STEPS:
- Gemini code review for Panel v1.12 API compatibility
- Create package.json for each service
- Test npm workspaces integration
- Deploy after verification

FILES:
- services/arbiter/ (25 new files, full application)
- services/modpack-version-checker/ (21 new files, full application)

Signed-off-by: The Golden Chronicler <claude@firefrostgaming.com>
2026-03-31 21:52:42 +00:00

466 lines
12 KiB
Markdown

# Firefrost Arbiter
**Discord Role Management and Subscription OAuth Gateway**
A centralized Node.js/Express service for managing Discord community roles, authenticating users via OAuth2, and processing subscription webhooks from Paymenter billing platform.
---
## 🎯 Purpose
Firefrost Arbiter automates the entire subscription-to-Discord-role workflow:
1. **User subscribes** via Paymenter (billing system)
2. **Webhook fires** to Arbiter
3. **Email sent** with secure 24-hour linking URL
4. **User clicks link** → Discord OAuth → Role assigned automatically
5. **Ghost CMS updated** with Discord ID for future reference
**Admin Interface** allows Trinity members to manually assign/remove roles, search subscribers, and view audit logs.
---
## 🏗️ Architecture Overview
### Components
- **Webhook Gateway**: Receives subscription events from Paymenter, generates secure single-use tokens, dispatches notification emails via SMTP
- **OAuth2 Linking**: Authenticates users via Discord, updates Ghost CMS member metadata, automatically assigns Discord server roles based on subscription tiers
- **Admin Dashboard**: Protected by Discord OAuth (restricted to specific User IDs), allows staff to manually assign roles, view audit logs, and search CMS records
- **State Management**: Utilizes local SQLite databases (`linking.db` and `sessions.db`) for lightweight, persistent data storage
### Tech Stack
- **Runtime**: Node.js 18.x+
- **Framework**: Express 4.x
- **Database**: SQLite (better-sqlite3)
- **Discord**: discord.js 14.x
- **CMS**: Ghost 5.x (Admin API)
- **Email**: Nodemailer (SMTP)
- **Session**: express-session + connect-sqlite3
- **Security**: express-rate-limit, Zod validation, HMAC webhook verification
---
## 📋 Prerequisites
Before installation, ensure you have:
- **Node.js 18.x or higher** installed
- **A Discord Application** with Bot User created ([Discord Developer Portal](https://discord.com/developers/applications))
- Server Members Intent enabled
- Bot invited to your Discord server with "Manage Roles" permission
- Bot role positioned ABOVE all subscription tier roles in role hierarchy
- **Ghost CMS 5.x** with Admin API access
- Custom field `discord_id` created in Ghost Admin
- Integration created for Admin API key
- **SMTP Server** for outgoing mail (Mailcow, Gmail, SendGrid, etc.)
- **Nginx** for reverse proxy and SSL termination
- **SSL Certificate** (Let's Encrypt recommended)
---
## 🚀 Installation
### 1. Clone and Install Dependencies
```bash
cd /home/architect
git clone <repository-url> arbiter
cd arbiter
npm install
```
### 2. Configure Environment Variables
```bash
cp .env.example .env
nano .env
```
Fill in all required values:
- Discord credentials (bot token, client ID/secret, guild ID)
- Ghost CMS URL and Admin API key
- SMTP server details
- Admin Discord IDs (comma-separated, no spaces)
- Generate SESSION_SECRET: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
- Webhook secret from Paymenter
### 3. Configure Discord Role Mapping
Edit `config/roles.json` with your Discord role IDs:
```bash
nano config/roles.json
```
Get role IDs: Right-click role in Discord → Copy ID
### 4. Set Up Nginx Reverse Proxy
```bash
sudo cp nginx.conf /etc/nginx/sites-available/arbiter
sudo ln -s /etc/nginx/sites-available/arbiter /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
### 5. Set Up Systemd Service
```bash
sudo cp arbiter.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable arbiter
sudo systemctl start arbiter
```
### 6. Verify Installation
Check service status:
```bash
sudo systemctl status arbiter
```
Check logs:
```bash
sudo journalctl -u arbiter -f
```
Visit health check:
```bash
curl https://discord-bot.firefrostgaming.com/health
```
Should return:
```json
{
"uptime": 123.456,
"discord": "ok",
"database": "ok",
"timestamp": "2026-03-30T15:00:00.000Z"
}
```
### 7. Set Up Automated Backups
```bash
chmod +x backup.sh
crontab -e
```
Add this line (runs daily at 4:00 AM):
```
0 4 * * * /home/architect/arbiter/backup.sh >> /home/architect/backups/arbiter/cron_error.log 2>&1
```
Create backup directory:
```bash
mkdir -p /home/architect/backups/arbiter
chmod 700 /home/architect/backups/arbiter
```
---
## 🔧 Configuration
### Discord Developer Portal Setup
1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
2. Create New Application (or select existing)
3. **Bot Tab**:
- Generate bot token (save to `.env` as `DISCORD_BOT_TOKEN`)
- Enable "Server Members Intent"
4. **OAuth2 → General**:
- Add Redirect URIs:
- `http://localhost:3500/auth/callback` (testing)
- `https://discord-bot.firefrostgaming.com/auth/callback` (production)
- `https://discord-bot.firefrostgaming.com/admin/callback` (admin login)
5. **OAuth2 → URL Generator**:
- Scopes: `bot`
- Permissions: `Manage Roles`
- Copy generated URL and invite bot to server
**CRITICAL**: In Discord Server Settings → Roles, drag the bot's role ABOVE all subscription tier roles!
### Ghost CMS Setup
1. **Create Custom Field**:
- Navigate to: Settings → Membership → Custom Fields
- Add field: `discord_id` (type: Text)
2. **Generate Admin API Key**:
- Navigate to: Settings → Integrations → Add Custom Integration
- Name: "Firefrost Arbiter"
- Copy Admin API Key (format: `key_id:secret`)
- Save to `.env` as `CMS_ADMIN_KEY`
### Paymenter Webhook Configuration
1. In Paymenter admin panel, navigate to Webhooks
2. Add new webhook:
- URL: `https://discord-bot.firefrostgaming.com/webhook/billing`
- Secret: (generate secure random string, save to `.env` as `WEBHOOK_SECRET`)
- Events: `subscription.created`, `subscription.upgraded`, `subscription.downgraded`, `subscription.cancelled`
---
## 📖 Usage
### For Subscribers (Automated Flow)
1. User subscribes via Paymenter
2. User receives email with secure linking URL
3. User clicks link → redirected to Discord OAuth
4. User authorizes → role automatically assigned
5. User sees new channels in Discord immediately
### For Admins (Manual Assignment)
1. Visit `https://discord-bot.firefrostgaming.com/admin/login`
2. Authenticate via Discord
3. **Search user** by email (from Ghost CMS)
4. **Assign role** or remove all roles
5. **Provide reason** (logged to audit trail)
6. View **audit log** of all manual actions
---
## 🧪 Testing
### Local Testing Setup
1. Set `APP_URL=http://localhost:3500` in `.env`
2. Add `http://localhost:3500/auth/callback` to Discord redirect URIs
3. Run in development mode:
```bash
npm run dev
```
### Test Webhook Reception
```bash
curl -X POST http://localhost:3500/webhook/billing \
-H "Content-Type: application/json" \
-H "x-signature: test_signature" \
-d '{
"event": "subscription.created",
"customer_email": "test@example.com",
"customer_name": "Test User",
"tier": "awakened",
"subscription_id": "test_sub_123"
}'
```
### Test OAuth Flow
1. Trigger webhook (above) to generate token
2. Check database: `sqlite3 linking.db "SELECT * FROM link_tokens;"`
3. Check email sent (Mailcow logs)
4. Click link in email
5. Complete Discord OAuth
6. Verify role assigned in Discord
7. Verify Ghost CMS updated: Ghost Admin → Members → search email
### Test Admin Panel
1. Visit `/admin/login`
2. Authenticate with Discord
3. Search for test user by email
4. Assign/remove role
5. Check audit log displays action
---
## 📁 Project Structure
```
arbiter/
├── src/
│ ├── routes/
│ │ ├── webhook.js # Paymenter webhook handler
│ │ ├── oauth.js # User Discord linking flow
│ │ ├── admin.js # Admin panel routes
│ │ └── adminAuth.js # Admin OAuth login
│ ├── middleware/
│ │ ├── auth.js # Admin access control
│ │ ├── verifyWebhook.js # HMAC signature verification
│ │ └── validateWebhook.js # Zod schema validation
│ ├── utils/
│ │ └── templates.js # HTML success/error pages
│ ├── views/
│ │ └── admin.html # Admin panel UI
│ ├── database.js # SQLite initialization
│ ├── email.js # Nodemailer SMTP
│ ├── discordService.js # Bot client + role management
│ ├── cmsService.js # Ghost CMS integration
│ └── index.js # Main application entry
├── config/
│ └── roles.json # Tier → Discord Role ID mapping
├── .env # Environment variables (not in git)
├── .env.example # Template for .env
├── package.json # Dependencies
├── backup.sh # Automated backup script
├── arbiter.service # Systemd service file
└── nginx.conf # Nginx reverse proxy config
```
---
## 🔐 Security
- **Webhook Verification**: HMAC SHA256 signature validation
- **Input Validation**: Zod schemas for all webhook payloads
- **Rate Limiting**: 100 requests per 15 minutes per IP
- **Session Security**: httpOnly, SameSite cookies
- **Admin Access Control**: Discord ID whitelist via environment variable
- **HTTPS Enforcement**: Nginx SSL termination with HSTS headers
- **Secure Tokens**: 32-byte cryptographically random tokens (64 hex chars)
- **Token Expiration**: 24-hour automatic expiry
- **Database Permissions**: `chmod 700` on backup directory
---
## 🛠️ Maintenance
### View Logs
```bash
# Application logs
sudo journalctl -u arbiter -f
# Nginx access logs
sudo tail -f /var/log/nginx/arbiter-access.log
# Nginx error logs
sudo tail -f /var/log/nginx/arbiter-error.log
# Backup logs
tail -f /home/architect/backups/arbiter/backup_log.txt
```
### Restart Service
```bash
sudo systemctl restart arbiter
```
### Update Application
```bash
cd /home/architect/arbiter
git pull
npm install
sudo systemctl restart arbiter
```
### Database Maintenance
```bash
# View link tokens
sqlite3 linking.db "SELECT * FROM link_tokens WHERE used = 0;"
# View audit logs
sqlite3 linking.db "SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT 10;"
# Manual cleanup of expired tokens
sqlite3 linking.db "DELETE FROM link_tokens WHERE created_at < datetime('now', '-1 day');"
```
---
## 🚨 Troubleshooting
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for detailed solutions to common issues.
Quick reference:
- Invalid redirect URI → Check Discord Developer Portal OAuth settings
- Bot missing permissions → Check role hierarchy in Discord
- Session not persisting → Check `trust proxy` setting in code
- Ghost API 401 → Verify Admin API key format
- Database locked → Increase timeout in database.js
- Email not sending → Check SMTP credentials and port 587 firewall rule
---
## 📦 Backup & Restore
### Backup Procedure
Automated daily at 4:00 AM via cron. Manual backup:
```bash
./backup.sh
```
Backs up:
- `linking.db` (tokens and audit logs)
- `sessions.db` (admin sessions)
- `.env` (configuration with secrets)
- `config/roles.json` (tier mappings)
Retention: 7 days
### Restore Procedure
```bash
# Stop service
sudo systemctl stop arbiter
# Move corrupted database
mv linking.db linking.db.corrupt
# Restore from backup
cp /home/architect/backups/arbiter/linking_YYYYMMDD_HHMMSS.db linking.db
# Start service
sudo systemctl start arbiter
```
Verify restored backup:
```bash
sqlite3 /home/architect/backups/arbiter/linking_20260330_040000.db "SELECT count(*) FROM link_tokens;"
```
---
## 📚 Documentation
- [DEPLOYMENT.md](DEPLOYMENT.md) - Complete deployment guide
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Common issues and solutions
- [API.md](API.md) - API endpoint documentation (if created)
---
## 🤝 Contributing
This is a private system for Firefrost Gaming. For internal team members:
1. Create feature branch
2. Test locally
3. Commit with detailed messages
4. Deploy to staging first
5. Monitor logs before production rollout
---
## 📝 License
Private - Firefrost Gaming Internal Use Only
---
## 👥 Team
**Built by:**
- Michael "Frostystyle" Krause (The Wizard) - Technical Lead
- Claude (Chronicler #49) - Implementation Partner
- Gemini AI - Architecture Consultant
**For:** Firefrost Gaming Community
**Date:** March 30, 2026
---
**🔥❄️ Fire + Frost + Foundation = Where Love Builds Legacy 💙**