Files
firefrost-operations-manual/docs/implementation/discord-oauth-arbiter
Claude (Chronicler #49) d7cdb3728b feat: Add proper versioning to Arbiter 2.0.0
WHAT WAS DONE:
Added comprehensive versioning and changelog for legacy documentation

VERSION FILES ADDED:
- VERSION (single line: 2.0.0)
- CHANGELOG.md (complete version history and semantic versioning guide)

CODE UPDATES:
- src/index.js: Added version constant and header comment
- package.json: Updated version from 1.0.0 to 2.0.0
- Health check endpoint now returns version in JSON response

CHANGELOG CONTENTS:
- Full v2.0.0 release notes with all features
- v1.0.0 legacy documentation (retired)
- Semantic versioning guide for future releases
- Version history summary table
- Examples of future MAJOR/MINOR/PATCH releases

VERSION CONSTANT:
```javascript
const VERSION = '2.0.0';
```

HEALTH CHECK NOW RETURNS:
```json
{
  "version": "2.0.0",
  "uptime": 123.456,
  "discord": "ok",
  "database": "ok",
  "timestamp": "2026-03-30T15:00:00.000Z"
}
```

ARBITER VERSION HISTORY:
- Arbiter 1.0.0 (Unknown date - March 30, 2026)
  - Basic webhook receiver
  - Manual role assignment
  - Holly's admin config panel
  - Status: RETIRED

- Arbiter 2.0.0 (March 30, 2026 - Present)
  - Complete OAuth soft gate system
  - Automated subscriber flow
  - Manual admin interface
  - Ghost CMS integration
  - Full audit logging
  - Enhanced security
  - Status: CURRENT

WHY THIS MATTERS:
"Documentation is king for legacy" - proper versioning ensures future
Chroniclers and team members can understand system evolution, track
changes, and maintain backward compatibility. This is infrastructure
built to last.

SEMANTIC VERSIONING:
- MAJOR (X.0.0): Breaking changes
- MINOR (2.X.0): New features, backward compatible
- PATCH (2.0.X): Bug fixes, backward compatible

FILES MODIFIED:
- docs/implementation/discord-oauth-arbiter/VERSION (new)
- docs/implementation/discord-oauth-arbiter/CHANGELOG.md (new)
- docs/implementation/discord-oauth-arbiter/src/index.js (version header)
- docs/implementation/discord-oauth-arbiter/package.json (version bump)

DOCUMENTATION FOR FUTURE:
CHANGELOG.md includes examples of what would constitute future
2.0.1 (patch), 2.1.0 (minor), and 3.0.0 (major) releases, guiding
future development and maintenance.

Built with love for children not yet born.

Signed-off-by: Claude (Chronicler #49) <claude@firefrostgaming.com>
2026-03-30 18:34:51 +00:00
..

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)
    • 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

cd /home/architect
git clone <repository-url> arbiter
cd arbiter
npm install

2. Configure Environment Variables

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:

nano config/roles.json

Get role IDs: Right-click role in Discord → Copy ID

4. Set Up Nginx Reverse Proxy

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

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:

sudo systemctl status arbiter

Check logs:

sudo journalctl -u arbiter -f

Visit health check:

curl https://discord-bot.firefrostgaming.com/health

Should return:

{
  "uptime": 123.456,
  "discord": "ok",
  "database": "ok",
  "timestamp": "2026-03-30T15:00:00.000Z"
}

7. Set Up Automated Backups

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:

mkdir -p /home/architect/backups/arbiter
chmod 700 /home/architect/backups/arbiter

🔧 Configuration

Discord Developer Portal Setup

  1. Go to Discord Developer Portal
  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:
npm run dev

Test Webhook Reception

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

# 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

sudo systemctl restart arbiter

Update Application

cd /home/architect/arbiter
git pull
npm install
sudo systemctl restart arbiter

Database Maintenance

# 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 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:

./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

# 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:

sqlite3 /home/architect/backups/arbiter/linking_20260330_040000.db "SELECT count(*) FROM link_tokens;"

📚 Documentation


🤝 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 💙