# 🔴 Redlight

> ⚠️ **Warning:**  This project is entirely *vibe coded* and meant to be a fun/hobby project. Use at your own risk!

A modern, self-hosted BigBlueButton frontend with 25+ themes, federation, calendar, CalDAV, OAuth/OIDC, and powerful room management.

![Node.js](https://img.shields.io/badge/Node.js-20+-green)
![React](https://img.shields.io/badge/React-18+-blue)
![License](https://img.shields.io/badge/License-GPL-pink)
![BigBlueButton](https://img.shields.io/badge/BigBlueButton-Compatible-red)

## 🖼️ Pictures

<details>
<summary>Click to expand screenshots</summary>

![Home](screenshots/home.png)
![Dashboard](screenshots/dashboard.png)
![Rooms](screenshots/rooms.png)

</details>

## ✨ Features

### Core Features
- 🎥 **Video Conferencing** - Integrated BigBlueButton support for professional video meetings
- 🎨 **25+ Themes** - Dracula, Nord, Catppuccin, Rosé Pine, Gruvbox, Tokyo Night, Solarized, Everforest, Ayu, Kanagawa, Moonlight, Cyberpunk, Cotton Candy, and more
- 📝 **Room Management** - Create unlimited rooms with custom settings, access codes, and moderator codes
- 🔐 **User Management** - Registration, login, role-based access control (Admin/User)
- 📹 **Recording Management** - View, publish, and delete meeting recordings per room
- 📊 **Learning Analytics** - Collect and view per-room participant engagement data (talk time, messages, reactions) via BBB callbacks, secured with HMAC tokens
- 📅 **Calendar** - Built-in calendar with event creation, sharing, customizable reminders, and room linking
- 📆 **CalDAV Server** - Full CalDAV support for syncing calendars with Thunderbird, Apple Calendar, GNOME Calendar, DAVx⁵ (Android), and other standard clients
- 🌍 **Multi-Language Support** - German (Deutsch) and English built-in, easily extensible
- 🔔 **In-App Notifications** - Real-time notifications for room shares, federation invites, and calendar reminders
- ✉️ **Email Verification** - Optional SMTP-based email verification for user registration
- 🔑 **OAuth / OIDC** - Login via OpenID Connect providers (Keycloak, Authentik, etc.) with PKCE
- 👤 **User Profiles** - Customizable display names, avatars, themes, and language preferences
- 📱 **Responsive Design** - Works seamlessly on mobile, tablet, and desktop
- 🌐 **Federation** - Invite users from remote Redlight instances via Ed25519-signed messages
- 🐉 **DragonflyDB / Redis** - JWT blacklisting for secure token revocation on logout

### Admin Features
- 👥 **User Administration** - Manage users and roles
- 🏢 **Branding Customization** - Custom app name, logos, and default theme
- 📊 **Dashboard** - Overview of system statistics
- 🔧 **Settings Management** - System-wide configuration
- ✉️ **Invite-Only Registration** - Generate invite tokens for controlled user signup

### Room Features
- 🔑 **Access Codes** - Restrict room access with optional passwords
- 🔐 **Moderator Codes** - Separate code to grant moderator privileges
- 🚪 **Guest Access** - Allow unauthenticated users to join meetings (rate-limited)
- ⏱️ **Max Participants** - Set limits on concurrent participants
- 🎤 **Mute on Join** - Automatically mute new participants
- ✅ **Approval Mode** - Require moderator approval for participants
- 🎙️ **Anyone Can Start** - Allow participants to start the meeting
- 📹 **Recording Settings** - Control whether meetings are recorded
- 📊 **Learning Analytics** - Toggle per-room to collect participant engagement data after each meeting
- 📑 **Presentation Upload** - Upload PDF, PPTX, ODP, DOC, DOCX as default slides
- 🤝 **Room Sharing** - Share rooms with other registered users

### Security
- 🛡️ **Comprehensive Rate Limiting** - Login, register, profile, avatar, guest-join, OAuth, and federation endpoints
- 🔒 **Input Validation** - Email format, field length limits, ID format checks, color format validation
- 🕐 **Timing-Safe Comparisons** - Access codes and moderator codes compared with `crypto.timingSafeEqual`
- 📏 **Streaming Upload Limits** - Avatar (5 MB) and presentation (50 MB) uploads reject early without buffering
- 🧹 **XSS Prevention** - HTML-escaped emails, XML-escaped BBB parameters, SVG sanitization
- 🔐 **JWT Blacklist** - Token revocation via DragonflyDB/Redis on logout
- 🌐 **CORS Restriction** - Locked to `APP_URL` in production
- ⚙️ **Configurable Trust Proxy** - `TRUST_PROXY` env var for reverse proxy setups
- 🔏 **HMAC-Secured Callbacks** - Learning analytics callback URLs signed with HMAC-SHA256

### Developer Features
- 🐳 **Docker Support** - Easy deployment with Docker Compose (includes PostgreSQL + DragonflyDB)
- 🗄️ **Database Flexibility** - SQLite (default) or PostgreSQL support
- 🔌 **REST API** - Comprehensive API for custom integrations
- 📦 **Open Source** - Full source code transparency
- 🛠️ **Self-Hosted** - Complete data privacy and control

---

## 📊 Comparison: Redlight vs Greenlight

| Feature | Redlight | Greenlight |
|---------|----------|-----------|
| **Theme System** | 25+ customizable themes | Limited theming |
| **Learning Analytics** | ✅ Per-room engagement data | ❌ Not supported |
| **Calendar / CalDAV** | ✅ Built-in calendar + CalDAV sync | ❌ Not supported |
| **OAuth / OIDC** | ✅ OpenID Connect (PKCE) | ✅ Supported |
| **Federation** | ✅ Cross-instance invites | ❌ Not supported |
| **Notifications** | ✅ In-app + calendar reminders | ❌ Not supported |
| **Language Support** | Multi-language ready | Multi-language ready |
| **UI Framework** | React + Tailwind (Modern) | Rails-based (Traditional) |
| **User Preferences** | Theme, language, avatar, display name | Limited customization |
| **Database Options** | SQLite / PostgreSQL | PostgreSQL only |
| **Docker** | ✅ Supported | ✅ Supported |
| **Admin Dashboard** | Modern React UI | Legacy Rails interface |
| **Room Sharing** | ✅ Share rooms with users | ✅ Supported |
| **Recording Management** | Full control per room | Standard management |
| **Presentation Upload** | ✅ Supported | ✅ Supported |
| **API** | RESTful JSON API | RESTful API |
| **Setup Complexity** | Simple (5 min) | Moderate (10-15 min) |
| **Customization** | Easy (Tailwind CSS) | Requires Ruby/Rails |

---

## 🚀 Quick Start

### Prerequisites
- Docker & Docker Compose
- BigBlueButton server (with API access)
- SMTP server (optional, for email verification)

### Installation

1. **Clone the repository**
   ```bash
   git clone https://git.scrunkly.cat/Michelle/redlight
   cd redlight
   ```

2. **Configure environment**
   ```bash
   cp .env.example .env
   ```
   Edit `.env` with your settings:
   ```env
   BBB_URL=https://your-bbb-server.com/bigbluebutton/api/
   BBB_SECRET=your-bbb-shared-secret
   JWT_SECRET=your-secret-key          # REQUIRED - app won't start without this
   APP_URL=https://your-domain.com     # Used for CORS and email links
   DATABASE_URL=postgres://redlight:redlight@postgres:5432/redlight

   POSTGRES_USER=redlight
   POSTGRES_PASSWORD=redlight
   POSTGRES_DB=redlight

   # DragonflyDB / Redis (JWT blacklist for logout)
   REDIS_URL=redis://dragonfly:6379

   # Reverse proxy trust (default: loopback)
   # TRUST_PROXY=loopback

   # Optional: Email verification
   # SMTP_HOST=smtp.gmail.com
   # SMTP_PORT=587
   # SMTP_USER=your-email@gmail.com
   # SMTP_PASS=your-app-password

   # Optional: Federation (cross-instance room invites)
   # FEDERATION_DOMAIN=your-domain.com

   # Optional: OAuth / OIDC login
   # OAUTH_ISSUER=https://auth.your-domain.com/realms/your-realm
   # OAUTH_CLIENT_ID=redlight
   # OAUTH_CLIENT_SECRET=your-client-secret
   ```

3. **Start the application**
   ```bash
   docker compose up -d
   ```

4. **Access the application**
   - Open `http://localhost:3001` in your browser
   - Default admin: `admin@example.com` / `admin123`
   - Change password immediately!

---

## 🛠️ Development

### Local Setup

1. **Install dependencies**
   ```bash
   npm install
   ```

2. **Start development server**
   ```bash
   npm run dev
   ```
   - Frontend: http://localhost:5173
   - Backend: http://localhost:3001

3. **Build for production**
   ```bash
   npm run build
   npm run preview
   ```

### Tech Stack
- **Frontend**: React 18, Tailwind CSS, React Router, Lucide Icons
- **Backend**: Node.js 20, Express, JWT, Bcrypt
- **Database**: SQLite / PostgreSQL with better-sqlite3 / pg
- **Cache**: DragonflyDB / Redis (ioredis) - JWT blacklisting
- **Email**: Nodemailer
- **CalDAV**: xml2js-based WebDAV/CalDAV server
- **Auth**: JWT + OAuth/OIDC (PKCE)
- **Build**: Vite

---

## 📁 Project Structure

```
redlight/
├── server/                 # Node.js/Express backend
│   ├── config/            # Database, Redis, mailer, BBB, federation, OAuth & notification config
│   ├── i18n/              # Server-side translations (email templates)
│   ├── jobs/              # Background jobs (federation sync, calendar reminders)
│   ├── middleware/        # JWT authentication, logging & token blacklisting
│   ├── routes/            # API endpoints (auth, rooms, recordings, admin, branding,
│   │                      #   federation, calendar, caldav, notifications, oauth, analytics)
│   └── index.js           # Server entry point
├── src/                   # React frontend
│   ├── components/        # Reusable components (RecordingList, AnalyticsList, etc.)
│   ├── contexts/          # React context (Auth, Language, Theme, Branding, Notification)
│   ├── i18n/              # Translations (DE, EN)
│   ├── pages/             # Page components
│   ├── services/          # API client
│   ├── themes/            # 25+ theme definitions
│   └── main.jsx           # Frontend entry point
├── public/                # Static assets
├── uploads/               # User avatars, branding & presentations (runtime)
├── keys/                  # Federation Ed25519 key pair (auto-generated)
├── compose.yml            # Docker Compose (Redlight + PostgreSQL + DragonflyDB)
├── Dockerfile             # Multi-stage container image
└── package.json           # Dependencies
```

---

## 🔐 Security

- **JWT Authentication** - Secure token-based auth with 7-day expiration and `jti`-based blacklisting via DragonflyDB/Redis
- **Mandatory JWT Secret** - Server refuses to start without a `JWT_SECRET` env var
- **OAuth / OIDC** - OpenID Connect with PKCE (S256) and cryptographic state tokens
- **HTTPS Ready** - Configure behind reverse proxy (nginx, Caddy); trust proxy via `TRUST_PROXY` env
- **Password Hashing** - bcryptjs with salt rounds 12, minimum 8-character passwords
- **Email Verification** - Optional SMTP-based email verification with resend support
- **CORS Protection** - Restricted to `APP_URL` in production, open in development
- **Rate Limiting** - Login, register, profile, password, avatar, guest-join, OAuth, and federation endpoints
- **Input Validation** - Email regex, field length limits, ID format checks, hex-color format checks
- **Timing-Safe Comparisons** - Access codes and moderator codes compared via `crypto.timingSafeEqual`
- **Upload Safety** - Streaming body size limits (avatar 5 MB, presentation 50 MB) abort early without buffering
- **XSS / Injection Prevention** - HTML-escaped emails, XML-escaped BBB API parameters, SVG logos served as `attachment`
- **HMAC-Secured Callbacks** - Learning analytics callback URLs signed with HMAC-SHA256 derived from BBB_SECRET
- **Admin Isolation** - Role-based access control with strict admin checks
- **Network Isolation** - Docker Compose uses an internal backend network for DB and cache

---

## 📦 API Endpoints

### Authentication
- `POST /api/auth/register` - Register new user
- `POST /api/auth/login` - Login user
- `POST /api/auth/logout` - Logout (blacklists JWT)
- `GET /api/auth/verify-email?token=...` - Verify email with token
- `POST /api/auth/resend-verification` - Resend verification email
- `GET /api/auth/me` - Get current user info
- `PUT /api/auth/profile` - Update profile (theme, language, display name)
- `PUT /api/auth/password` - Change password
- `POST /api/auth/avatar` - Upload avatar image
- `DELETE /api/auth/avatar` - Remove avatar image

### Rooms
- `GET /api/rooms` - List user's rooms (owned + shared)
- `POST /api/rooms` - Create new room
- `GET /api/rooms/:uid` - Get room details
- `PUT /api/rooms/:uid` - Update room (incl. learning analytics toggle)
- `DELETE /api/rooms/:uid` - Delete room
- `POST /api/rooms/:uid/start` - Start meeting
- `POST /api/rooms/:uid/join` - Join meeting as authenticated user
- `POST /api/rooms/:uid/guest-join` - Join meeting as guest (rate-limited)
- `POST /api/rooms/:uid/end` - End meeting
- `GET /api/rooms/:uid/status` - Check if meeting is running
- `GET /api/rooms/:uid/shares` - List shared users
- `POST /api/rooms/:uid/shares` - Share room with user
- `DELETE /api/rooms/:uid/shares/:userId` - Remove share
- `POST /api/rooms/:uid/presentation` - Upload default presentation
- `DELETE /api/rooms/:uid/presentation` - Remove presentation

### Recordings
- `GET /api/recordings/room/:uid` - List room recordings
- `PUT /api/recordings/:recordID/publish` - Publish/unpublish recording
- `DELETE /api/recordings/:recordID` - Delete recording

### Learning Analytics
- `POST /api/analytics/callback/:uid?token=...` - BBB callback (HMAC-secured)
- `GET /api/analytics/room/:uid` - Get analytics for a room
- `DELETE /api/analytics/:id` - Delete analytics entry

### Calendar
- `GET /api/calendar` - List calendar events
- `POST /api/calendar` - Create event
- `PUT /api/calendar/:uid` - Update event
- `DELETE /api/calendar/:uid` - Delete event
- `GET /api/calendar/caldav-tokens` - List CalDAV tokens
- `POST /api/calendar/caldav-tokens` - Create CalDAV token
- `DELETE /api/calendar/caldav-tokens/:id` - Delete CalDAV token

### Notifications
- `GET /api/notifications` - List notifications
- `PUT /api/notifications/:id/read` - Mark as read
- `POST /api/notifications/read-all` - Mark all as read
- `DELETE /api/notifications/:id` - Delete notification

### Admin
- `GET /api/admin/users` - List all users
- `GET /api/admin/stats` - System statistics
- `POST /api/admin/users` - Create user (admin)
- `PUT /api/admin/users/:id` - Update user
- `DELETE /api/admin/users/:id` - Delete user

### Branding
- `GET /api/branding` - Get branding settings
- `PUT /api/branding` - Update branding (admin only)
- `POST /api/branding/logo` - Upload custom logo
- `DELETE /api/branding/logo` - Remove custom logo

### OAuth
- `GET /api/oauth/url` - Get OAuth authorization URL
- `GET /api/oauth/callback` - OAuth callback (PKCE exchange)

### Federation
- `GET /.well-known/redlight` - Instance discovery (domain, public key)
- `POST /api/federation/invite` - Send invitation to remote user
- `POST /api/federation/receive` - Receive invitation from remote instance (rate-limited)
- `GET /api/federation/invitations` - List received invitations
- `PUT /api/federation/invitations/:id` - Accept / decline invitation
- `DELETE /api/federation/invitations/:id` - Delete invitation

### CalDAV
- `PROPFIND /caldav/` - CalDAV discovery
- `REPORT /caldav/:user/calendar/` - Calendar query
- `GET/PUT/DELETE /caldav/:user/calendar/:uid.ics` - Event CRUD

---

## 🌍 Internationalization (i18n)

Redlight comes with built-in support for multiple languages. Currently supported:
- 🇩🇪 Deutsch (German)
- 🇬🇧 English

### Adding a new language

1. Create `src/i18n/xx.json` (e.g., `fr.json` for French)
2. Copy structure from `de.json` or `en.json`
3. Translate all strings
4. Update `src/i18n/index.js` to include the new language

---

## 🎨 Themes

Redlight includes 25+ themes:
- ☀️ Light / 🌙 Dark (default)
- 🐱 Catppuccin Mocha / Latte
- 🧛 Dracula
- ❄️ Nord
- 🌊 Tokyo Night
- 💜 One Dark
- 🐙 GitHub Dark
- 🌹 Rosé Pine / Rosé Pine Dawn
- 🍂 Gruvbox Dark / Gruvbox Light
- ☀️ Solarized Dark / Solarized Light
- 🌲 Everforest Dark / Everforest Light
- 🌊 Kanagawa
- 🌙 Moonlight
- 🎮 Cyberpunk
- 🌸 Ayu Dark
- 🔴 Red Modular Light
- 🍬 Cotton Candy Light
- 🐱 scrunkly.cat Dark

Themes are fully customizable by editing `src/themes/index.js`.

---

## 🐳 Docker Deployment

### Using Docker Compose (Recommended)

```bash
docker compose up -d
```

Services:
- **redlight** - Node.js application (port 3001)
- **postgres** - PostgreSQL 17 database
- **dragonfly** - DragonflyDB (Redis-compatible) for JWT blacklisting

The `compose.yml` uses isolated networks: `frontend` (public) and `backend` (internal, no external access). Data is persisted via named volumes (`pgdata`, `uploads`, `dragonflydata`). Federation keys are mounted from `./keys`.

### Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `BBB_URL` | Yes | - | BigBlueButton API URL |
| `BBB_SECRET` | Yes | - | BigBlueButton shared secret |
| `JWT_SECRET` | Yes | - | Secret for signing JWTs (server won't start without it) |
| `APP_URL` | Recommended | - | Public URL of the app (used for CORS + email links) |
| `DATABASE_URL` | No | SQLite | PostgreSQL connection string |
| `REDIS_URL` | No | `redis://localhost:6379` | DragonflyDB / Redis URL |
| `TRUST_PROXY` | No | `loopback` | Express trust proxy setting (number or string) |
| `SMTP_HOST` | No | - | SMTP server for email verification |
| `SMTP_PORT` | No | `587` | SMTP port |
| `SMTP_USER` | No | - | SMTP username |
| `SMTP_PASS` | No | - | SMTP password |
| `FEDERATION_DOMAIN` | No | - | Domain for federation (enables cross-instance invites) |
| `OAUTH_ISSUER` | No | - | OIDC issuer URL (enables OAuth login) |
| `OAUTH_CLIENT_ID` | No | - | OIDC client ID |
| `OAUTH_CLIENT_SECRET` | No | - | OIDC client secret |
| `ADMIN_EMAIL` | No | `admin@example.com` | Default admin email (first start only) |
| `ADMIN_PASSWORD` | No | `admin123` | Default admin password (first start only) |

### Production Deployment

Behind a reverse proxy (nginx example):

```nginx
upstream redlight {
  server localhost:3001;
}

server {
  listen 443 ssl http2;
  server_name your-domain.com;
  
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
  
  client_max_body_size 5M;

  location / {
    proxy_pass http://redlight;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection upgrade;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
```

> **Note:** When running behind a reverse proxy, set `TRUST_PROXY=1` (or the appropriate value) in `.env` so Express reads the correct client IP for rate limiting.

---

## 🌐 Federation

Federation allows users on different Redlight instances to invite each other into rooms.

### Setup

1. Set `FEDERATION_DOMAIN=your-domain.com` in `.env`.
2. On first start, an Ed25519 key pair is generated automatically and stored in `keys/federation_key.pem`.
3. In Docker, mount `./keys:/app/keys` (already configured in `compose.yml`).
4. Other instances discover your public key via `GET /.well-known/redlight`.

### How it works

1. **User A** on `instance-a.com` sends an invite to `userB@instance-b.com`.
2. Redlight looks up `instance-b.com/.well-known/redlight` to discover the federation API.
3. The invite payload is signed with instance A's private key and POSTed to instance B's `/api/federation/receive`.
4. Instance B verifies the Ed25519 signature against instance A's public key.
5. **User B** sees the invitation and can accept or decline. Accepting provides a join link to the remote room.

---

## 🐛 Troubleshooting

### Issue: "ERR_ERL_PERMISSIVE_TRUST_PROXY"
**Solution**: Set `TRUST_PROXY` in `.env`. Use `loopback` (default) or `1` when behind a single reverse proxy.

### Issue: "JWT_SECRET is not set"
**Solution**: The server requires a `JWT_SECRET` environment variable and will refuse to start without one. Add it to your `.env` file.

### Issue: "Email verification not working"
**Solution**: Ensure SMTP is configured in `.env`. If `SMTP_HOST` is not set, email verification is disabled.

### Issue: "BigBlueButton API error"
**Solution**: Verify `BBB_URL` and `BBB_SECRET` are correct. Test the connection with:
```bash
curl "https://your-bbb-server/bigbluebutton/api/getMeetings?checksum=..."
```

### Issue: "Database connection failed"
**Solution**: Check `DATABASE_URL` format. For PostgreSQL: `postgres://user:password@host:5432/redlight`

### Issue: "Theme not applying"
**Solution**: Clear browser cache (Ctrl+Shift+Del) or restart dev server with `npm run dev`.

### Issue: "DragonflyDB connection error"
**Solution**: Ensure DragonflyDB (or Redis) is running and `REDIS_URL` is correct. If unavailable, the app still works - JWT blacklisting degrades gracefully (logout won't revoke tokens immediately).

---

## 📝 License

This project is licensed under the GNU GPL v3 (or later) - see [LICENSE](LICENSE) file for details.

---

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit changes (`git commit -m 'Add amazing feature'`)
4. Push to branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
