From e0c89b4f212f31984d15df22df2060773964dfbf Mon Sep 17 00:00:00 2001 From: Matt Hills Date: Tue, 15 Apr 2025 07:44:20 -0400 Subject: [PATCH] require client-key for uploads --- .env.example | 7 ++++ .gitignore | 1 + Dockerfile | 6 ++++ README.md | 84 +++++++++++++++++++++++++++++++++++++++++------- TASK.md | 6 ++++ requirements.txt | 1 + src/app.py | 18 ++++++++++- 7 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..20173e4 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Required for file uploads +CLIENT_KEY=your-secret-key-here + +# Optional configurations +UPLOAD_FOLDER=tmp/uploads +DEBUG=False +MAX_CONTENT_LENGTH=16777216 # 16MB in bytes \ No newline at end of file diff --git a/.gitignore b/.gitignore index 971855c..8e32e75 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ tmp/ uploads/ file_metadata.json +.env # Python venv/ diff --git a/Dockerfile b/Dockerfile index 140af89..231a107 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,10 @@ COPY ./src /app COPY requirements.txt /app/requirements.txt RUN pip install -r /app/requirements.txt EXPOSE 7777 + +# The actual values should be provided when running the container +ENV CLIENT_KEY=default-secret-key \ + UPLOAD_FOLDER=tmp/uploads \ + DEBUG=False + CMD ["gunicorn", "wsgi:app", "--bind", "0.0.0.0:7777"] \ No newline at end of file diff --git a/README.md b/README.md index 70beb71..2874cc3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ A lightweight Flask-based file upload and download server with a modern UI. Perf ## Features - Clean, modern UI for file downloads +- Secure API authentication for uploads - Mobile-friendly upload endpoint - Secure 6-digit alphanumeric code system - Automatic file cleanup @@ -20,25 +21,40 @@ Built using the MVC design pattern: ## Getting Started ### Local Development +1. Clone the repository +2. Create and activate a virtual environment: ```bash -# Create and activate virtual environment python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate +``` -# Install dependencies +3. Install dependencies: +```bash pip install -r requirements.txt +``` -# Run the server +4. Set up environment variables: + - Copy `.env.example` to `.env` + - Set your `CLIENT_KEY` in the `.env` file + - Adjust other settings as needed + +5. Run the server: +```bash python src/app.py ``` ### Docker Deployment +1. Build the image: ```bash -# Build the image docker build -t file-upload-server . +``` -# Run the container -docker run -p 7777:7777 -v $(pwd)/tmp:/app/tmp file-upload-server +2. Run the container: +```bash +docker run -p 7777:7777 \ + -e CLIENT_KEY=your-secret-key \ + -v $(pwd)/tmp:/app/tmp \ + file-upload-server ``` ## API Endpoints @@ -46,10 +62,13 @@ docker run -p 7777:7777 -v $(pwd)/tmp:/app/tmp file-upload-server ### POST /upload Upload a file and receive a download code. +**Authentication:** +- Required: `X-Client-Key` header with your API key + **Form Fields:** - `file`: File to upload (required) - `max_downloads`: Maximum download count (optional, default: 1) -- `expiry_hours`: Hours until expiry (optional, default: 24, max: 24) +- `expiry_hours`: Hours until expiry (optional, default: 24) **Response:** ```json @@ -58,34 +77,75 @@ Upload a file and receive a download code. } ``` +**Error Responses:** +- 401: Invalid or missing client key +- 400: Missing file or invalid request +```json +{ + "error": "Error message here" +} +``` + ### GET /download/{code} Download a file using a 6-character code. **Responses:** - 200: File download starts -- 404: File not found with specific error page for: +- 404: File not found with specific error pages for: - Invalid code - Expired file - Download limit reached - File no longer exists -## Security Notes -- Files are automatically deleted after: +### DELETE /delete/{code} +Delete a file using its code. + +**Responses:** +- 200: File deleted successfully +- 404: File not found or expired + +## Security Features +- **API Authentication**: Client key required for file uploads +- **Secure Downloads**: 6-digit alphanumeric codes +- **Auto Cleanup**: Files are automatically deleted after: - Reaching download limit - Exceeding expiry time - Server restart (temporary storage) -- No file execution permissions in upload directory -- Secure filename handling +- **Upload Security**: + - Secure filename handling + - No file execution permissions + - File size limits ## Configuration All configuration is handled through environment variables: + +### Required Settings +- `CLIENT_KEY`: API key for file uploads (required for production) + +### Optional Settings - `UPLOAD_FOLDER`: Upload directory path (default: tmp/uploads) - `MAX_CONTENT_LENGTH`: Maximum file size (default: 16MB) - `DEBUG`: Enable debug mode (default: False) +### Environment File +The application supports `.env` file for configuration: +```env +CLIENT_KEY=your-secret-key-here +UPLOAD_FOLDER=tmp/uploads +DEBUG=False +MAX_CONTENT_LENGTH=16777216 +``` + ## Contributing 1. Fork the repository 2. Create a feature branch 3. Commit your changes 4. Push to the branch 5. Create a Pull Request + +## Security Best Practices +- Always change the default client key +- Use HTTPS in production +- Regularly monitor disk usage +- Set appropriate file size limits +- Keep dependencies updated diff --git a/TASK.md b/TASK.md index 422d026..03247e9 100644 --- a/TASK.md +++ b/TASK.md @@ -7,6 +7,7 @@ - [x] Implement download tracking and limits - [x] Add file expiry system - [x] Create modern UI with error handling +- [x] Add API authentication for uploads ## 🎯 Core Features Implemented @@ -24,6 +25,7 @@ - [x] Metadata tracking - [x] Configurable limits - [x] API response format +- [x] API key authentication ### Download System - [x] Code validation @@ -47,6 +49,9 @@ - [ ] Implement file type restrictions - [ ] Add optional password protection - [ ] CSRF protection +- [ ] Add key rotation support +- [ ] Implement IP whitelisting +- [ ] Add audit logging ### Features - [ ] Multi-file upload support @@ -61,6 +66,7 @@ - [ ] Implement persistent storage - [ ] Add monitoring/metrics - [ ] Create backup system +- [ ] Add health check endpoints ### UI/UX - [ ] Dark mode support diff --git a/requirements.txt b/requirements.txt index 1d85079..726f4ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ itsdangerous==2.2.0 Jinja2==3.1.4 MarkupSafe==3.0.0 packaging==24.1 +python-dotenv==1.0.1 requests==2.32.3 urllib3==2.2.3 Werkzeug==3.0.4 diff --git a/src/app.py b/src/app.py index a13fe63..9ad6e8b 100644 --- a/src/app.py +++ b/src/app.py @@ -1,20 +1,36 @@ import os import uuid +from functools import wraps from flask import Flask, request, jsonify, send_from_directory from werkzeug.utils import secure_filename import requests +from dotenv import load_dotenv import utils.mLogger as log from controllers.file_controller import upload_file, download_file, delete_file from views import views +# Load environment variables +load_dotenv() + app = Flask(__name__) app.config['UPLOAD_FOLDER'] = 'tmp/uploads' +app.config['CLIENT_KEY'] = os.getenv('CLIENT_KEY', 'default-secret-key') if not os.path.exists(app.config['UPLOAD_FOLDER']): os.makedirs(app.config['UPLOAD_FOLDER']) +def require_client_key(f): + @wraps(f) + def decorated_function(*args, **kwargs): + client_key = request.headers.get('X-Client-Key') + if not client_key or client_key != app.config['CLIENT_KEY']: + log.e("Invalid or missing client key") + return jsonify({'error': 'Invalid or missing client key'}), 401 + return f(*args, **kwargs) + return decorated_function + # Route for file uploads -app.route('/upload', methods=['POST'])(upload_file) +app.route('/upload', methods=['POST'])(require_client_key(upload_file)) # Route for downloading the file app.route('/download/', methods=['GET'])(download_file)