Kyle Thompson 814a0d6919 Implement multi-tenant documentation platform
This commit transforms the single-tenant Docusaurus POC into a fully-featured
multi-tenant documentation platform with per-client authentication.

Key Changes:
- Add query parameter-based authentication system using nginx
- Implement multi-client architecture with shared/client-specific content
- Create build system for managing multiple client instances
- Add secure token generation and management
- Update PDF service to support multi-tenant requests
- Configure production-ready nginx with SSL/HTTPS support
- Add rate limiting and security headers
- Create deployment scripts for automated builds

New Features:
- Multiple clients served from subdirectories (e.g., /cral/, /client2/)
- Unique auth keys per client stored in environment variables
- Shared content structure (~90% shared, 10% client-specific)
- Client-specific branding and customization
- Automated client creation script
- Custom 403 error page for unauthorized access

Architecture:
- Static builds served by nginx for performance
- Symlinks for shared content to avoid duplication
- Environment-based configuration for security
- Docker Compose setup for production deployment

Security:
- HTTPS enforcement with Let's Encrypt certificates
- Auth-key validation on every request
- Rate limiting to prevent abuse
- No direct access to backend services

Documentation:
- Added comprehensive MULTI_TENANT_PLAN.md
- Added detailed IMPLEMENTATION_GUIDE.md

This implementation provides a scalable, secure foundation for serving
multiple clients from a single docs.netdesk.ca domain while maintaining
client isolation and customization capabilities.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 18:08:34 -04:00

NetDesk Multi-Tenant Documentation System

Complete multi-tenant documentation platform with auth-key protection for docs.netdesk.ca.

Quick Start

# 1. Build CRAL client
cd clients/cral
npm install
npm run build

# 2. Deploy build
sudo mkdir -p /var/www/docs/cral
sudo cp -r build/* /var/www/docs/cral/

# 3. Start services
cd ../..
docker-compose up -d

# 4. Access with auth-key
# https://docs.netdesk.ca/cral/?auth-key=a6c2eeef9587fb3c06b8a2b772fd50973a0781983247a78d

Structure

docusaurus-multi-tenant/
├── shared/                     # Shared content (90%)
│   ├── docs/                  # Shared documentation
│   ├── src/components/        # Shared React components
│   └── src/css/              # Shared CSS
│
├── clients/
│   └── cral/                  # CRAL client instance
│       ├── docs/
│       │   ├── intro.md      # Client-specific homepage
│       │   └── netdesk-setup.md -> symlink to shared
│       ├── src/              # Symlinks to shared components
│       └── docusaurus.config.ts
│
├── nginx/
│   ├── nginx.conf            # Auth + routing config
│   ├── .env                  # Auth keys (SECRET!)
│   └── 403.html             # Access denied page
│
├── pdf-service/              # PDF generation service
│   ├── server.js
│   └── Dockerfile
│
└── scripts/
    ├── build-all.sh          # Build all clients
    ├── new-client.sh         # Create new client
    └── generate-token.sh     # Generate auth key

Authentication

Auth Keys

Each client has a unique 48-character auth key stored in nginx/.env:

AUTH_KEY_CRAL=a6c2eeef9587fb3c06b8a2b772fd50973a0781983247a78d
AUTH_KEY_CLIENT2=1fba0277e60cec7201ddb756a2d26ed21a99c2b26d1c5d43

IMPORTANT: nginx/.env is in .gitignore and must NEVER be committed to git!

Access URLs

https://docs.netdesk.ca/cral/?auth-key=YOUR_KEY
https://docs.netdesk.ca/client2/?auth-key=YOUR_KEY

Current Auth Keys

CRAL:

https://docs.netdesk.ca/cral/?auth-key=a6c2eeef9587fb3c06b8a2b772fd50973a0781983247a78d

Managing Clients

Build All Clients

./scripts/build-all.sh

Create New Client

./scripts/new-client.sh acme-corp

This will:

  1. Copy template from CRAL
  2. Generate unique auth key
  3. Create client directory
  4. Provide next steps

Add Client to Nginx

After running new-client.sh, update nginx/nginx.conf:

# Add to map $request_uri $expected_key
~^/acme-corp/ "${AUTH_KEY_ACME_CORP}";

# Add to location regex
location ~ ^/(cral|client2|client3|acme-corp)/ {

And add to nginx/.env:

AUTH_KEY_ACME_CORP=generated_key_here

Shared Content

Adding Shared Documentation

  1. Create markdown file in shared/docs/:
vim shared/docs/new-guide.md
  1. Symlink from each client:
cd clients/cral/docs
ln -s ../../../shared/docs/new-guide.md new-guide.md
  1. Rebuild all clients:
./scripts/build-all.sh

All clients now have the new documentation!

Client-Specific Content

Create files directly in clients/CLIENT/docs/:

vim clients/cral/docs/cral-specific-process.md

This appears only for CRAL, not other clients.

PDF Generation

PDFs are generated via the PDF service with auth-key pass-through:

https://docs.netdesk.ca/api/pdf/cral/netdesk-setup?auth-key=YOUR_KEY

The PDFDownload component automatically:

  • Detects the current client
  • Passes through the auth-key
  • Generates the correct PDF URL

Production Deployment

Prerequisites

  1. Domain: docs.netdesk.ca DNS configured
  2. Server: Ubuntu 22.04 with Docker
  3. Ports: 80, 443 open

Initial Setup

# 1. Clone repository
git clone <repo> /opt/docusaurus-multi-tenant
cd /opt/docusaurus-multi-tenant

# 2. Set up auth keys
cp nginx/.env.example nginx/.env
vim nginx/.env  # Add real auth keys

# 3. Build all clients
./scripts/build-all.sh

# 4. Set up SSL
sudo certbot --nginx -d docs.netdesk.ca

# 5. Start services
docker-compose up -d

# 6. Verify
curl -I "https://docs.netdesk.ca/cral/?auth-key=YOUR_KEY"

SSL Certificate (Let's Encrypt)

# Install certbot
sudo apt install certbot

# Get certificate
sudo certbot certonly --webroot \
  -w /var/www/certbot \
  -d docs.netdesk.ca

# Auto-renewal (already configured in docker-compose)
docker-compose --profile production up -d certbot

Update Deployment

# 1. Pull latest changes
git pull

# 2. Rebuild clients
./scripts/build-all.sh

# 3. Restart nginx
docker-compose restart nginx

Development

Local Development

# Start a client in dev mode
cd clients/cral
npm start

# Access at http://localhost:3000
# (No auth-key needed in dev mode)

Testing Auth

# Test valid auth
curl -I "http://localhost/cral/?auth-key=a6c2eeef9587fb3c06b8a2b772fd50973a0781983247a78d"
# Should return: 200 OK

# Test invalid auth
curl -I "http://localhost/cral/?auth-key=wrong"
# Should return: 403 Forbidden

# Test missing auth
curl -I "http://localhost/cral/"
# Should return: 403 Forbidden

Maintenance

Rotate Auth Keys (Quarterly)

# 1. Generate new key
./scripts/generate-token.sh

# 2. Update nginx/.env
vim nginx/.env

# 3. Restart nginx
docker-compose restart nginx

# 4. Notify client of new key
# Send via secure channel

Monitor Access

# View nginx logs
docker-compose logs -f nginx

# Count 403 errors
docker-compose logs nginx | grep "403" | wc -l

# Most accessed docs
docker-compose logs nginx | grep "200" | \
  awk '{print $7}' | sort | uniq -c | sort -rn | head -10

Backup

# Backup builds
tar -czf backup-$(date +%Y%m%d).tar.gz /var/www/docs/

# Backup source
tar -czf source-$(date +%Y%m%d).tar.gz \
  --exclude=node_modules \
  --exclude=build \
  --exclude=.docusaurus \
  .

Troubleshooting

403 Forbidden

Problem: Always getting 403 even with correct auth-key

Solution:

  1. Check auth-key in URL matches .env exactly
  2. Verify nginx loaded env vars: docker-compose logs nginx | grep AUTH
  3. Check nginx config syntax: docker-compose exec nginx nginx -t
  4. Restart nginx: docker-compose restart nginx

PDF Generation Fails

Problem: PDF download shows 500 error

Solution:

  1. Check PDF service logs: docker-compose logs pdf-service
  2. Verify auth-key is passed: Check URL has ?auth-key=...
  3. Test direct access: docker-compose exec pdf-service curl http://nginx/cral/?auth-key=KEY
  4. Restart PDF service: docker-compose restart pdf-service

Build Fails

Problem: npm run build fails

Solution:

  1. Clear cache: rm -rf node_modules .docusaurus build
  2. Reinstall: npm install
  3. Check symlinks: ls -la docs/
  4. Rebuild: npm run build

Security

Best Practices

  • Auth keys are 48 characters (24 bytes hex)
  • Auth keys stored in .env (not in git)
  • HTTPS enforced (HTTP redirects)
  • HSTS header enabled
  • Rate limiting configured
  • 403 page doesn't leak info
  • Each client has unique token

Token Storage

DO:

  • Store in nginx/.env
  • Use password manager
  • Rotate quarterly
  • Send via encrypted channel

DON'T:

  • Commit to git
  • Email in plaintext
  • Share between clients
  • Hardcode in configs

Support

Documentation

  • Architecture: See docs/MULTI_TENANT_PLAN.md
  • Implementation: See docs/IMPLEMENTATION_GUIDE.md

Contact

Thompson Hall Digital Inc.


Version: 1.0.0
Last Updated: December 2025

Description
Docusaurus POC project with PDF service and documentation
Readme 341 KiB
Languages
TypeScript 24.5%
JavaScript 20%
CSS 19.7%
Shell 19.7%
HTML 11.1%
Other 5%