Add public download endpoint without authentication
Features: - New GET /public/download/:filename endpoint for public file access - No authentication required for downloading processed files - Directory traversal protection with path.basename() - Proper Content-Disposition headers for browser downloads Documentation: - Updated docs/API.md with public endpoint section - Added to table of contents - Usage examples and security notes Use cases: - Share download links via email/chat/social media - Embed in web applications without auth - Direct browser downloads - Public file sharing Security: - Path sanitization prevents directory traversal attacks - Only files in OUTPUT_DIR are accessible - Returns proper 404 for non-existent files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
751a382ccd
commit
eb69e8b063
66
docs/API.md
66
docs/API.md
@ -68,6 +68,7 @@ curl -H "Authorization: Bearer your_api_token_here" http://localhost:8888/endpoi
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Authentication](#-authentication)
|
- [Authentication](#-authentication)
|
||||||
- [Health & Info](#health--info)
|
- [Health & Info](#health--info)
|
||||||
|
- [Public Download Endpoint](#public-download-endpoint)
|
||||||
- [Download Endpoints](#download-endpoints)
|
- [Download Endpoints](#download-endpoints)
|
||||||
- [Transcription Endpoints](#transcription-endpoints)
|
- [Transcription Endpoints](#transcription-endpoints)
|
||||||
- [Conversion Endpoints](#conversion-endpoints)
|
- [Conversion Endpoints](#conversion-endpoints)
|
||||||
@ -84,6 +85,8 @@ curl -H "Authorization: Bearer your_api_token_here" http://localhost:8888/endpoi
|
|||||||
### GET /health
|
### GET /health
|
||||||
Health check endpoint.
|
Health check endpoint.
|
||||||
|
|
||||||
|
**Authentication**: Not required (public)
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -95,6 +98,8 @@ Health check endpoint.
|
|||||||
### GET /api
|
### GET /api
|
||||||
Get API information and available endpoints.
|
Get API information and available endpoints.
|
||||||
|
|
||||||
|
**Authentication**: Not required (public)
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -104,6 +109,67 @@ Get API information and available endpoints.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Public Download Endpoint
|
||||||
|
|
||||||
|
### GET /public/download/:filename
|
||||||
|
Public endpoint to download files without authentication.
|
||||||
|
|
||||||
|
**Authentication**: Not required (public)
|
||||||
|
|
||||||
|
**Purpose**: Share direct download links for generated files (MP3, transcriptions, translations, summaries) without requiring API authentication.
|
||||||
|
|
||||||
|
**URL Parameters:**
|
||||||
|
- `filename` (required): Name of the file to download
|
||||||
|
|
||||||
|
**Security**:
|
||||||
|
- Directory traversal protection enabled (uses `path.basename()`)
|
||||||
|
- Only files in the configured OUTPUT_DIR are accessible
|
||||||
|
- No authentication required
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Direct download (no auth needed)
|
||||||
|
curl -O http://localhost:8888/public/download/my_video.mp3
|
||||||
|
|
||||||
|
# Or simply open in browser
|
||||||
|
http://localhost:8888/public/download/my_video.mp3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Success):**
|
||||||
|
- File download with proper Content-Disposition headers
|
||||||
|
- Browser will prompt to download the file
|
||||||
|
|
||||||
|
**Response (Error - 404):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "File not found",
|
||||||
|
"message": "File 'my_video.mp3' does not exist"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Error - 500):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Download failed",
|
||||||
|
"message": "Error details..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Share download links via email/chat
|
||||||
|
- Embed in web applications
|
||||||
|
- Direct browser downloads
|
||||||
|
- Public file sharing
|
||||||
|
|
||||||
|
**Note**: After processing (download, transcription, etc.), use the returned `filePath` or `fileUrl` from authenticated endpoints, then construct public URL:
|
||||||
|
```
|
||||||
|
/public/download/{basename_of_filePath}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### GET /info
|
### GET /info
|
||||||
Get information about a YouTube video or playlist.
|
Get information about a YouTube video or playlist.
|
||||||
|
|
||||||
|
|||||||
@ -1,135 +1,135 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Extract and Upload Cookies - All-in-one script
|
# Extract and Upload Cookies - All-in-one script
|
||||||
# This script extracts YouTube cookies from your browser and uploads them to the API
|
# This script extracts YouTube cookies from your browser and uploads them to the API
|
||||||
|
|
||||||
set -e # Exit on error
|
set -e # Exit on error
|
||||||
|
|
||||||
# Colors for pretty output
|
# Colors for pretty output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
BLUE='\033[0;34m'
|
BLUE='\033[0;34m'
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
API_URL="${API_URL:-http://localhost:8888}"
|
API_URL="${API_URL:-http://localhost:8888}"
|
||||||
API_TOKEN="${API_TOKEN:-}"
|
API_TOKEN="${API_TOKEN:-}"
|
||||||
TEMP_COOKIES="/tmp/youtube-cookies-temp.txt"
|
TEMP_COOKIES="/tmp/youtube-cookies-temp.txt"
|
||||||
|
|
||||||
echo -e "${BLUE}========================================${NC}"
|
echo -e "${BLUE}========================================${NC}"
|
||||||
echo -e "${BLUE}YouTube Cookies Extractor & Uploader${NC}"
|
echo -e "${BLUE}YouTube Cookies Extractor & Uploader${NC}"
|
||||||
echo -e "${BLUE}========================================${NC}"
|
echo -e "${BLUE}========================================${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Step 1: Check if API token is set
|
# Step 1: Check if API token is set
|
||||||
if [ -z "$API_TOKEN" ]; then
|
if [ -z "$API_TOKEN" ]; then
|
||||||
echo -e "${YELLOW}⚠️ API_TOKEN not set in environment${NC}"
|
echo -e "${YELLOW}⚠️ API_TOKEN not set in environment${NC}"
|
||||||
echo -e "Please enter your API token:"
|
echo -e "Please enter your API token:"
|
||||||
read -s API_TOKEN
|
read -s API_TOKEN
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 2: Detect available browsers
|
# Step 2: Detect available browsers
|
||||||
echo -e "${BLUE}🔍 Detecting browsers...${NC}"
|
echo -e "${BLUE}🔍 Detecting browsers...${NC}"
|
||||||
AVAILABLE_BROWSERS=()
|
AVAILABLE_BROWSERS=()
|
||||||
|
|
||||||
if command -v google-chrome &> /dev/null || command -v chromium &> /dev/null || [ -d "$HOME/.config/google-chrome" ] || [ -d "$HOME/.config/chromium" ]; then
|
if command -v google-chrome &> /dev/null || command -v chromium &> /dev/null || [ -d "$HOME/.config/google-chrome" ] || [ -d "$HOME/.config/chromium" ]; then
|
||||||
AVAILABLE_BROWSERS+=("chrome")
|
AVAILABLE_BROWSERS+=("chrome")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v firefox &> /dev/null || [ -d "$HOME/.mozilla/firefox" ]; then
|
if command -v firefox &> /dev/null || [ -d "$HOME/.mozilla/firefox" ]; then
|
||||||
AVAILABLE_BROWSERS+=("firefox")
|
AVAILABLE_BROWSERS+=("firefox")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v microsoft-edge &> /dev/null || [ -d "$HOME/.config/microsoft-edge" ]; then
|
if command -v microsoft-edge &> /dev/null || [ -d "$HOME/.config/microsoft-edge" ]; then
|
||||||
AVAILABLE_BROWSERS+=("edge")
|
AVAILABLE_BROWSERS+=("edge")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ${#AVAILABLE_BROWSERS[@]} -eq 0 ]; then
|
if [ ${#AVAILABLE_BROWSERS[@]} -eq 0 ]; then
|
||||||
echo -e "${RED}❌ No supported browsers found${NC}"
|
echo -e "${RED}❌ No supported browsers found${NC}"
|
||||||
echo -e "${YELLOW}Supported browsers: Chrome, Firefox, Edge${NC}"
|
echo -e "${YELLOW}Supported browsers: Chrome, Firefox, Edge${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${YELLOW}Alternative: Manually export cookies${NC}"
|
echo -e "${YELLOW}Alternative: Manually export cookies${NC}"
|
||||||
echo -e "1. Install browser extension 'Get cookies.txt LOCALLY'"
|
echo -e "1. Install browser extension 'Get cookies.txt LOCALLY'"
|
||||||
echo -e "2. Visit youtube.com and login"
|
echo -e "2. Visit youtube.com and login"
|
||||||
echo -e "3. Export cookies to a file"
|
echo -e "3. Export cookies to a file"
|
||||||
echo -e "4. Run: curl -X POST -H \"X-API-Key: \$API_TOKEN\" -F \"cookies=@youtube-cookies.txt\" $API_URL/admin/upload-cookies"
|
echo -e "4. Run: curl -X POST -H \"X-API-Key: \$API_TOKEN\" -F \"cookies=@youtube-cookies.txt\" $API_URL/admin/upload-cookies"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}✓ Found browsers:${NC} ${AVAILABLE_BROWSERS[*]}"
|
echo -e "${GREEN}✓ Found browsers:${NC} ${AVAILABLE_BROWSERS[*]}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Step 3: Let user choose browser
|
# Step 3: Let user choose browser
|
||||||
if [ ${#AVAILABLE_BROWSERS[@]} -eq 1 ]; then
|
if [ ${#AVAILABLE_BROWSERS[@]} -eq 1 ]; then
|
||||||
BROWSER="${AVAILABLE_BROWSERS[0]}"
|
BROWSER="${AVAILABLE_BROWSERS[0]}"
|
||||||
echo -e "${GREEN}Using browser: $BROWSER${NC}"
|
echo -e "${GREEN}Using browser: $BROWSER${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Multiple browsers found. Choose one:${NC}"
|
echo -e "${YELLOW}Multiple browsers found. Choose one:${NC}"
|
||||||
select BROWSER in "${AVAILABLE_BROWSERS[@]}"; do
|
select BROWSER in "${AVAILABLE_BROWSERS[@]}"; do
|
||||||
if [ -n "$BROWSER" ]; then
|
if [ -n "$BROWSER" ]; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Step 4: Check if yt-dlp is installed
|
# Step 4: Check if yt-dlp is installed
|
||||||
if ! command -v yt-dlp &> /dev/null; then
|
if ! command -v yt-dlp &> /dev/null; then
|
||||||
echo -e "${RED}❌ yt-dlp is not installed${NC}"
|
echo -e "${RED}❌ yt-dlp is not installed${NC}"
|
||||||
echo -e "${YELLOW}Install with: pip install yt-dlp${NC}"
|
echo -e "${YELLOW}Install with: pip install yt-dlp${NC}"
|
||||||
echo -e "${YELLOW}Or: sudo apt install yt-dlp${NC}"
|
echo -e "${YELLOW}Or: sudo apt install yt-dlp${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 5: Extract cookies using yt-dlp
|
# Step 5: Extract cookies using yt-dlp
|
||||||
echo -e "${BLUE}🍪 Extracting cookies from $BROWSER...${NC}"
|
echo -e "${BLUE}🍪 Extracting cookies from $BROWSER...${NC}"
|
||||||
|
|
||||||
if yt-dlp --cookies-from-browser "$BROWSER" --cookies "$TEMP_COOKIES" --no-download "https://www.youtube.com" > /dev/null 2>&1; then
|
if yt-dlp --cookies-from-browser "$BROWSER" --cookies "$TEMP_COOKIES" --no-download "https://www.youtube.com" > /dev/null 2>&1; then
|
||||||
echo -e "${GREEN}✓ Cookies extracted successfully${NC}"
|
echo -e "${GREEN}✓ Cookies extracted successfully${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${RED}❌ Failed to extract cookies${NC}"
|
echo -e "${RED}❌ Failed to extract cookies${NC}"
|
||||||
echo -e "${YELLOW}Make sure:${NC}"
|
echo -e "${YELLOW}Make sure:${NC}"
|
||||||
echo -e " 1. You're logged into YouTube in $BROWSER"
|
echo -e " 1. You're logged into YouTube in $BROWSER"
|
||||||
echo -e " 2. $BROWSER is closed (some browsers lock the cookies database)"
|
echo -e " 2. $BROWSER is closed (some browsers lock the cookies database)"
|
||||||
echo -e " 3. You have the necessary permissions"
|
echo -e " 3. You have the necessary permissions"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 6: Upload cookies to API
|
# Step 6: Upload cookies to API
|
||||||
echo -e "${BLUE}📤 Uploading cookies to API...${NC}"
|
echo -e "${BLUE}📤 Uploading cookies to API...${NC}"
|
||||||
|
|
||||||
UPLOAD_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
UPLOAD_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||||
-H "X-API-Key: $API_TOKEN" \
|
-H "X-API-Key: $API_TOKEN" \
|
||||||
-F "cookies=@$TEMP_COOKIES" \
|
-F "cookies=@$TEMP_COOKIES" \
|
||||||
"$API_URL/admin/upload-cookies")
|
"$API_URL/admin/upload-cookies")
|
||||||
|
|
||||||
HTTP_CODE=$(echo "$UPLOAD_RESPONSE" | tail -n1)
|
HTTP_CODE=$(echo "$UPLOAD_RESPONSE" | tail -n1)
|
||||||
RESPONSE_BODY=$(echo "$UPLOAD_RESPONSE" | head -n-1)
|
RESPONSE_BODY=$(echo "$UPLOAD_RESPONSE" | head -n-1)
|
||||||
|
|
||||||
if [ "$HTTP_CODE" = "200" ]; then
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
echo -e "${GREEN}✓ Cookies uploaded successfully!${NC}"
|
echo -e "${GREEN}✓ Cookies uploaded successfully!${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}═══════════════════════════════════════${NC}"
|
echo -e "${GREEN}═══════════════════════════════════════${NC}"
|
||||||
echo -e "${GREEN}SUCCESS! You're all set!${NC}"
|
echo -e "${GREEN}SUCCESS! You're all set!${NC}"
|
||||||
echo -e "${GREEN}═══════════════════════════════════════${NC}"
|
echo -e "${GREEN}═══════════════════════════════════════${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "$RESPONSE_BODY" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE_BODY"
|
echo "$RESPONSE_BODY" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE_BODY"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BLUE}ℹ️ Your cookies are now active${NC}"
|
echo -e "${BLUE}ℹ️ Your cookies are now active${NC}"
|
||||||
echo -e "${BLUE}ℹ️ YouTube downloads should work without bot detection${NC}"
|
echo -e "${BLUE}ℹ️ YouTube downloads should work without bot detection${NC}"
|
||||||
echo -e "${BLUE}ℹ️ Cookies expire after a few weeks - run this script again if needed${NC}"
|
echo -e "${BLUE}ℹ️ Cookies expire after a few weeks - run this script again if needed${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${RED}❌ Failed to upload cookies${NC}"
|
echo -e "${RED}❌ Failed to upload cookies${NC}"
|
||||||
echo -e "${RED}HTTP Status: $HTTP_CODE${NC}"
|
echo -e "${RED}HTTP Status: $HTTP_CODE${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "$RESPONSE_BODY"
|
echo "$RESPONSE_BODY"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 7: Cleanup
|
# Step 7: Cleanup
|
||||||
rm -f "$TEMP_COOKIES"
|
rm -f "$TEMP_COOKIES"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}🎉 All done!${NC}"
|
echo -e "${GREEN}🎉 All done!${NC}"
|
||||||
|
|||||||
14
refresh-cookies.sh
Executable file
14
refresh-cookies.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Auto-refresh cookies from /tmp/share if they exist and are newer
|
||||||
|
|
||||||
|
SHARE_COOKIES="/tmp/share/youtube-cookies.txt"
|
||||||
|
LOCAL_COOKIES="/home/debian/videotomp3transcriptor/youtube-cookies.txt"
|
||||||
|
|
||||||
|
if [ -f "$SHARE_COOKIES" ]; then
|
||||||
|
# Copy if share is newer or local doesn't exist
|
||||||
|
if [ ! -f "$LOCAL_COOKIES" ] || [ "$SHARE_COOKIES" -nt "$LOCAL_COOKIES" ]; then
|
||||||
|
cp "$SHARE_COOKIES" "$LOCAL_COOKIES"
|
||||||
|
chmod 600 "$LOCAL_COOKIES"
|
||||||
|
echo "[$(date)] ✓ Cookies refreshed from /tmp/share"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@ -102,7 +102,8 @@ app.use((req, res, next) => {
|
|||||||
const authenticate = (req, res, next) => {
|
const authenticate = (req, res, next) => {
|
||||||
// Skip authentication for public endpoints
|
// Skip authentication for public endpoints
|
||||||
const publicEndpoints = ['/health', '/api'];
|
const publicEndpoints = ['/health', '/api'];
|
||||||
if (publicEndpoints.includes(req.path)) {
|
// Allow public download endpoint
|
||||||
|
if (publicEndpoints.includes(req.path) || req.path.startsWith('/public/download/')) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +174,47 @@ app.get('/health', (req, res) => {
|
|||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /public/download/:filename
|
||||||
|
* Public endpoint to download files without authentication
|
||||||
|
*/
|
||||||
|
app.get('/public/download/:filename', (req, res) => {
|
||||||
|
try {
|
||||||
|
const { filename } = req.params;
|
||||||
|
|
||||||
|
// Security: prevent directory traversal
|
||||||
|
const safeName = path.basename(filename);
|
||||||
|
const filePath = path.join(OUTPUT_DIR, safeName);
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return res.status(404).json({
|
||||||
|
error: 'File not found',
|
||||||
|
message: `File '${safeName}' does not exist`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send file with proper headers
|
||||||
|
res.download(filePath, safeName, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`[Public Download] Error: ${err.message}`);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Download failed',
|
||||||
|
message: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[Public Download] Error: ${error.message}`);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Server error',
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /info?url=<youtube_url>
|
* GET /info?url=<youtube_url>
|
||||||
* Get info about a video or playlist
|
* Get info about a video or playlist
|
||||||
@ -1429,6 +1471,7 @@ app.listen(PORT, () => {
|
|||||||
console.log(`Server running on http://localhost:${PORT}`);
|
console.log(`Server running on http://localhost:${PORT}`);
|
||||||
console.log('\nEndpoints:');
|
console.log('\nEndpoints:');
|
||||||
console.log(' GET /health - Health check');
|
console.log(' GET /health - Health check');
|
||||||
|
console.log(' GET /public/download/:filename - Public file download (no auth)');
|
||||||
console.log(' GET /info?url= - Get video/playlist info');
|
console.log(' GET /info?url= - Get video/playlist info');
|
||||||
console.log(' POST /download - Download as MP3');
|
console.log(' POST /download - Download as MP3');
|
||||||
console.log(' POST /transcribe - Transcribe audio file');
|
console.log(' POST /transcribe - Transcribe audio file');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user