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>
466 lines
12 KiB
Markdown
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 💙**
|