A secure, business-focused video sharing platform built with Next.js, AWS S3, and HLS streaming. Share sensitive videos with specific users via email-based permissions, with automatic HLS transcoding and pre-signed URL protection.
🔒 Security First: Version 1.6.0 includes comprehensive security hardening with CSRF protection, rate limiting, path traversal prevention, and 70+ automated security tests.
- Secure Authentication: JWT-based authentication with NextAuth.js and hardened security
- User-Owned Storage: Each user brings their own AWS S3 bucket (credentials encrypted at rest)
- HLS Video Streaming: Automatic transcoding to HLS format using AWS MediaConvert
- Email-Based Sharing: Share videos with specific users (Google Docs-style permissions)
- Pre-Signed URLs: Short-lived, secure video URLs that expire after 3 hours
- Email Notifications: Bring your own email provider (Resend, AWS SES, or SMTP)
- Dashboard: Clean UI with "My Videos" and "Shared with Me" sections
- TypeScript: Fully typed for better development experience
- CSRF Protection: Automatic protection against Cross-Site Request Forgery attacks
- Path Traversal Prevention: Comprehensive validation blocking directory traversal attempts
- Rate Limiting: Brute force protection on authentication endpoints (5 attempts / 15 min)
- No Credential Exposure: AWS and email credentials never returned in API responses
- User Enumeration Prevention: Generic error messages prevent account discovery
- Edge Runtime Compatible: Web Crypto API implementation for Next.js Edge
- 70+ Security Tests: Comprehensive test coverage preventing regressions
- Frontend: Next.js 16 (App Router), React, TypeScript, Tailwind CSS
- Backend: Next.js API Routes with Edge Runtime support
- Database: PostgreSQL with Prisma ORM
- Authentication: NextAuth.js with JWT
- Security: CSRF protection, rate limiting (Upstash Redis), path validation
- Video Storage: AWS S3 (user-provided)
- Video Transcoding: AWS MediaConvert (HLS)
- Email: User-configured (Resend, AWS SES, or SMTP)
- Testing: Jest with 70+ security tests
- Hosting: Vercel (app) + Railway (database)
- Node.js 18+ and npm
- PostgreSQL database (Railway recommended)
- AWS Account with:
- S3 bucket
- IAM credentials with specific permissions (see below)
- Email provider account (Resend, AWS SES, or SMTP - configured in Settings)
Each user needs their own AWS account with:
- An S3 bucket for video storage
- IAM user credentials with permissions
- A MediaConvert IAM role for video transcoding
One-Click CloudFormation Template - Automatically creates all AWS resources in ~2 minutes:
- After deploying your app, visit
/help/aws-setupin your browser - Click the "Launch Stack in AWS" button
- Enter a unique bucket name (e.g.,
my-cheesebox-videos-123) - Check the IAM acknowledgment box
- Click "Create Stack" and wait ~2 minutes
- Copy all 5 credentials from the Outputs tab
- Paste them into your Settings page
What gets created automatically:
- ✅ S3 bucket (with CORS configured)
- ✅ IAM user with proper permissions
- ✅ Access keys
- ✅ MediaConvert role
Benefits:
- ⚡ 90% faster than manual setup
- ✅ Zero configuration errors
- 🔒 Security best practices built-in
Template Location: /public/cloudformation/private-video-setup.yaml
Note: The CloudFormation template is hosted on GitHub. After pushing your code, the launch button will work automatically.
If you prefer to configure AWS manually or want to learn how each component works, follow these detailed steps:
- Go to AWS Console → S3
- Click "Create bucket"
- Bucket name: Choose a unique name (e.g.,
my-cheesebox-videos) - Region: Choose your preferred region (e.g.,
us-east-1) - Block Public Access: Keep all boxes CHECKED (videos should be private)
- Click "Create bucket"
- Save the bucket name - you'll need this later
After creating the bucket, you need to configure CORS to allow your browser to stream videos:
- Click on your newly created bucket
- Go to the "Permissions" tab
- Scroll down to "Cross-origin resource sharing (CORS)"
- Click "Edit"
- Paste this JSON configuration:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": [
"http://localhost:3000",
"https://your-production-domain.com"
],
"ExposeHeaders": ["Content-Length", "Content-Range", "ETag"],
"MaxAgeSeconds": 3000
}
]- Important: Replace
https://your-production-domain.comwith your actual production URL when you deploy - Click "Save changes"
- Go to AWS Console → IAM → Users
- Click "Create user"
- User name: e.g.,
cheesebox-user - Click "Next"
- Select "Attach policies directly"
- Click "Create policy" (opens in new tab)
- Click the JSON tab
- Paste this policy (replace
YOUR-BUCKET-NAMEwith your bucket):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::YOUR-BUCKET-NAME",
"arn:aws:s3:::YOUR-BUCKET-NAME/*"
]
},
{
"Effect": "Allow",
"Action": [
"mediaconvert:CreateJob",
"mediaconvert:GetJob",
"mediaconvert:DescribeEndpoints"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::YOUR-ACCOUNT-ID:role/MediaConvertRole",
"Condition": {
"StringEquals": {
"iam:PassedToService": "mediaconvert.amazonaws.com"
}
}
}
]
}Note: Replace YOUR-ACCOUNT-ID with your AWS account ID (the 12-digit number, e.g., 123456789012). You can find this in the top-right of the AWS Console.
- Click "Next"
- Policy name:
CheeseboxUserPolicy - Click "Create policy"
- Go back to the user creation tab, refresh the policies list
- Search for
CheeseboxUserPolicyand check the box - Click "Next" → "Create user"
- Click on your newly created user
- Go to "Security credentials" tab
- Scroll to "Access keys" → Click "Create access key"
- Select "Other" → Click "Next"
- Click "Create access key"
- IMPORTANT: Copy and save:
- Access key ID (looks like:
AKIAIOSFODNN7EXAMPLE) - Secret access key (looks like:
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY) - You won't be able to see the secret key again!
- Access key ID (looks like:
This role allows MediaConvert to access your S3 bucket for transcoding.
- Go to AWS Console → IAM → Roles
- Click "Create role"
- Trusted entity type: Select "AWS service"
- Use case: Scroll down and select "MediaConvert" from the dropdown
- Click "Next"
- Click "Next" (skip permissions for now - we'll add them in a moment)
- Role name:
MediaConvertRole - Click "Create role"
- Click on the role you just created
- Go to the "Permissions" tab
- Click "Add permissions" → "Create inline policy"
- Click the "JSON" tab
- Paste this JSON (replace
YOUR-BUCKET-NAMEwith your actual bucket name):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::YOUR-BUCKET-NAME",
"arn:aws:s3:::YOUR-BUCKET-NAME/*"
]
}
]
}- Click "Next"
- Policy name:
S3BucketAccess - Click "Create policy"
- Go back to the role summary page
- COPY THE ROLE ARN from the top of the page
- It looks like:
arn:aws:iam::123456789012:role/MediaConvertRole - IMPORTANT: Make sure it says
role/notpolicy/ - This is what you'll paste in the app settings!
- It looks like:
You should now have:
- ✅ S3 bucket name (e.g.,
my-cheesebox-videos) - ✅ IAM Access Key ID (e.g.,
AKIAIOSFODNN7EXAMPLE) - ✅ IAM Secret Access Key (e.g.,
wJalrXUtnFEMI/K7MDENG...) - ✅ AWS Region (e.g.,
us-east-1) - ✅ MediaConvert Role ARN (e.g.,
arn:aws:iam::123456789012:role/MediaConvertRole)
Keep these handy - you'll enter them in the app's Settings page!
git clone https://github.com/onamfc/cheesebox.git
cd cheeseboxnpm installcp .env.example .envEdit .env and configure the following:
- Create a PostgreSQL database on Railway or locally
- Copy the connection string to
DATABASE_URL
Generate required secrets:
# Generate NEXTAUTH_SECRET
openssl rand -base64 32
# Generate JWT_SECRET (REQUIRED - app will fail without this)
openssl rand -base64 64
# Generate CSRF_SECRET (for CSRF protection)
openssl rand -base64 32
# Generate ENCRYPTION_KEY (for AWS credentials)
openssl rand -hex 32Important: The application will not start without JWT_SECRET configured. This prevents authentication bypass vulnerabilities.
✅ With Upstash: Full protection against brute force, credential stuffing, and abuse.
Configure Upstash Redis for distributed rate limiting:
# Get FREE tier at https://console.upstash.com/
# Free tier: 10,000 requests/day - sufficient for most apps
UPSTASH_REDIS_REST_URL="your-url"
UPSTASH_REDIS_REST_TOKEN="your-token"What you get with Upstash:
- ✅ Login: 5 attempts per 15 minutes per email
- ✅ Registration: 3 attempts per hour per IP address
- ✅ Protection against brute force attacks
- ✅ Distributed rate limiting across all instances
Without Upstash:
⚠️ No rate limiting at all⚠️ Unlimited login/registration attempts⚠️ Vulnerable to credential stuffing and brute force⚠️ Warning logged in production mode
Cost: FREE for typical usage (500k requests/month), ~$0.20 per 100k beyond that.
Cheesebox supports multiple email providers. Configure your preferred provider in Settings after deployment.
Supported Providers:
- Resend - Simple API (recommended for getting started)
- AWS SES - Low cost, high volume
- SMTP - Universal (Gmail, Outlook, custom servers)
Setup:
- Deploy the application
- Log in and go to Settings
- Scroll to Email Settings
- Choose your provider and enter credentials
- Click Send Test Email to verify
For detailed setup instructions for each provider, see: Email Provider Setup Guide
Note: For local development, you can use Gmail SMTP or Resend's free tier. Email credentials are encrypted before being stored in the database.
# Generate Prisma client
npx prisma generate
# Run migrations (creates tables)
npx prisma migrate dev --name initnpm run devOpen http://localhost:3000 in your browser.
- Create an account: Navigate to
/auth/signupand create your account - Configure AWS credentials:
- Go to Settings
- Enter all the information you gathered from AWS Setup:
- AWS Access Key ID
- AWS Secret Access Key
- S3 Bucket Name
- AWS Region
- MediaConvert IAM Role ARN (the
arn:aws:iam::...you copied)
- Click "Save Credentials"
- Your credentials are encrypted before being stored
- Upload a video:
- Return to the Dashboard
- Click "Upload Video"
- Select a video file and add a title
- The video will be uploaded to your S3 bucket and automatically transcoded to HLS
- Transcoding takes a few minutes depending on video size
- Click "Share" on any of your videos
- Enter the recipient's email address
- They'll receive an email notification
- The recipient can log in and view the video in "Shared with Me"
- Click "Watch" on any completed video
- A pre-signed URL is generated on-demand
- URLs expire after 3 hours for security
- If expired, clicking "Watch" again generates a new URL
-
Database Setup:
- Create a PostgreSQL database on Railway
- Copy the connection string
-
Deploy to Vercel:
# Install Vercel CLI npm i -g vercel # Deploy vercel
-
Configure Environment Variables:
- Go to your Vercel project settings
- Add all environment variables from
.env - Update
NEXTAUTH_URLandNEXT_PUBLIC_APP_URLto your production URL
-
Run Database Migrations:
# Set DATABASE_URL to your Railway connection string npx prisma migrate deploy
- Strong JWT Secrets: Application fails fast if JWT_SECRET not configured (prevents authentication bypass)
- Secure Password Hashing: bcrypt with salt rounds of 12
- Session Security: NextAuth.js with httpOnly cookies
- Team & Group Access Control: Complete authorization checks for video access
- User Enumeration Prevention: Generic error messages prevent account discovery
- CSRF Protection: Double-submit cookie pattern with Edge Runtime compatibility
- Automatic protection for all API routes
- Timing-safe token comparison
- Client utilities for easy integration
- See CSRF Protection Guide
- Path Traversal Prevention: Comprehensive validation blocking
.., null bytes, absolute paths - Rate Limiting: Progressive delays on failed authentication attempts
- Login: 5 attempts per 15 minutes
- Registration: 3 attempts per hour
- Graceful degradation when Redis not configured
- XSS Protection: Content Security Policy headers and sanitized inputs
- Encrypted AWS Credentials: AES-256-GCM encryption at rest
- No Credential Exposure: Masked values in API responses (e.g.,
***ABCD) - Pre-Signed URLs: Videos only accessible via short-lived URLs (3 hours)
- Email-Based Permissions: Only authorized users can view shared videos
- Secure Cookies: httpOnly, Secure, SameSite attributes
- 70+ Automated Security Tests: Comprehensive test coverage
- JWT secret validation tests
- Path traversal prevention tests
- Credential exposure tests
- Rate limiting tests
- Authentication tests
- CSRF protection tests
- Continuous Verification: Tests run on every commit
For detailed security information, see:
- CSRF Protection Guide - Implementation details
- Security Policy - Best practices and reporting
-
Video Upload:
- User uploads video → Next.js API → S3 (original)
- MediaConvert job created → Transcodes to HLS
- HLS files saved to S3 → Database updated with manifest path
-
Video Playback:
- User clicks "Watch" → API checks permissions
- If authorized → Generate pre-signed URL for HLS manifest
- HLS.js player streams video from S3
-
Video Sharing:
- Owner shares video → Database record created
- Email sent to recipient → Recipient logs in
- Video appears in "Shared with Me"
- users: User accounts (email, password hash)
- aws_credentials: Encrypted AWS credentials per user
- videos: Video metadata (title, transcoding status, S3 keys)
- video_shares: Sharing permissions (video ID + email)
- Troubleshooting Guide - Common issues and solutions
- Email Provider Setup - Configure Resend, AWS SES, or SMTP
- Deployment Guide - How to deploy to production
- CSRF Protection Guide - Implementation and usage
- CSRF Testing Guide - Automated and manual testing
- Security Policy - Best practices and vulnerability reporting
- Contributing Guidelines - How to contribute
- Code of Conduct - Community guidelines
- Changelog - Version history and updates
- Security Test README - Running security tests
Having issues? Check these first:
- 400 Error on Upload: Check browser console and server logs for details
- Verify file type (MP4, MOV, AVI, WebM, MKV)
- Check file size (max 5GB)
- Ensure title is entered
- Transcoding Fails: Verify AWS MediaConvert role is configured
- CORS Errors: Configure S3 bucket CORS (see README AWS Setup)
- 403 Forbidden: Update to latest code (uses streaming proxy)
- CSRF token validation failed: Refresh the page to get a new token
- Too many login attempts: Wait 15 minutes or check rate limit configuration
- App won't start: Verify
JWT_SECRETis set in.env- Generate with:
openssl rand -base64 64
- Generate with:
# Run all security tests
npm run test:security
# Test CSRF protection
bash test-csrf.sh
# Run security checks (tests + type check)
npm run security-checkFor detailed solutions, see TROUBLESHOOTING.md
We're actively working on improving Cheesebox. Here are some features we'd love to add:
- Video upload progress indicator
- Video thumbnails and previews
- Batch video operations
- Search and filtering
- Video analytics (view counts, watch time)
- Mobile app (React Native)
- Dark mode
- Internationalization (i18n)
- Video comments and annotations
- Webhook support for MediaConvert completion
- Admin dashboard
See something you'd like to work on? Check out our Contributing Guide!
We welcome contributions from the community! Here's how you can help:
- Report bugs: Open an issue with detailed information
- Suggest features: Share your ideas in the discussions
- Submit PRs: Fix bugs or add features (see CONTRIBUTING.md)
- Improve docs: Help make the documentation better
- Share: Star the repo and share with others
Please read our Contributing Guidelines before submitting a PR.
Security is a top priority for Cheesebox. Version 1.6.0 includes comprehensive security hardening:
- ✅ 8 out of 8 high-priority vulnerabilities fixed
- ✅ 70 automated security tests passing
- ✅ Complete CSRF protection with Edge Runtime support
- ✅ Path traversal prevention blocking all attack vectors
- ✅ Rate limiting protecting authentication endpoints
- ✅ No credential exposure in API responses
- ✅ User enumeration prevention
- Security Policy - Vulnerability reporting and best practices
- CSRF Protection Guide - Implementation details
Found a security vulnerability? Please see our Security Policy for responsible disclosure guidelines.
- Documentation: Check the README and SECURITY.md
- Issues: Open a GitHub issue
- Discussions: Join GitHub discussions
- Bugs: Use the bug report template
- Features: Use the feature request template
MIT License - see LICENSE file for details.
Copyright (c) 2025 Cheesebox Contributors
Built with:
- Next.js - React framework
- Prisma - Database ORM
- NextAuth.js - Authentication
- AWS SDK - Cloud services
- HLS.js - Video streaming
- Tailwind CSS - Styling
Special thanks to all contributors who help make this project better!
⭐ If you find this project useful, please consider giving it a star!