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.
Note: This is an open-source project. Feel free to use, modify, and contribute!
- Secure Authentication: JWT-based authentication with NextAuth.js
- 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
- Frontend: Next.js 14 (App Router), React, TypeScript, Tailwind CSS
- Backend: Next.js API Routes (Node.js)
- Database: PostgreSQL with Prisma ORM
- Authentication: NextAuth.js with JWT
- Video Storage: AWS S3 (user-provided)
- Video Transcoding: AWS MediaConvert (HLS)
- Email: User-configured (Resend, AWS SES, or SMTP)
- 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 secrets:
# Generate NEXTAUTH_SECRET
openssl rand -base64 32
# Generate ENCRYPTION_KEY
openssl rand -hex 32Cheesebox 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
- Encrypted AWS Credentials: User AWS credentials are encrypted using AES-256-GCM before storage
- JWT Authentication: Stateless, secure session management
- Pre-Signed URLs: Videos are only accessible via short-lived URLs
- Email-Based Permissions: Only authorized users can view shared videos
- Secure Password Hashing: bcrypt with salt rounds of 12
-
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
- Deployment Guide - How to deploy to production
- Contributing Guidelines - How to contribute
- Security Policy - Security best practices
- Code of Conduct - Community guidelines
- Changelog - Version history
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)
For 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
- Rate limiting and DDoS protection
- 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. Please review our Security Policy for:
- Reporting vulnerabilities
- Security best practices
- Known limitations
- Production deployment checklist
- 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!