# 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 💙**
