Add API security with token authentication and localStorage management
- Add API token authentication middleware (X-API-Key header) - Add CORS configuration with ALLOWED_ORIGINS - Add security HTTP headers (X-Frame-Options, CSP, etc.) - Add web interface for API token configuration with localStorage - Add toggle visibility for token input - Add connection status indicator - Add auto-save token functionality - Update API documentation with authentication examples - Add deployment guides (OVH specific and general) - Add local testing guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ce32ae3134
commit
c0cfc4c28e
297
TEST_LOCAL.md
Normal file
297
TEST_LOCAL.md
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
# Test Local du Système de Sécurité
|
||||||
|
|
||||||
|
Ce guide te permet de tester la sécurité de l'API localement avant de déployer sur le serveur.
|
||||||
|
|
||||||
|
## Étape 1 : Générer un token de test
|
||||||
|
|
||||||
|
**Windows (PowerShell) :**
|
||||||
|
```powershell
|
||||||
|
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 64 | % {[char]$_})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac :**
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 32
|
||||||
|
```
|
||||||
|
|
||||||
|
**Copie le token généré**. Exemple : `a1b2c3d4e5f6...`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Étape 2 : Configurer le .env
|
||||||
|
|
||||||
|
Édite ton fichier `.env` et ajoute (ou modifie) ces lignes :
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Token API (colle ton token généré)
|
||||||
|
API_TOKEN=ton_token_genere_ici
|
||||||
|
|
||||||
|
# CORS en développement (tout autoriser)
|
||||||
|
ALLOWED_ORIGINS=*
|
||||||
|
|
||||||
|
# Port
|
||||||
|
PORT=8888
|
||||||
|
|
||||||
|
# Ta clé OpenAI (tu l'as déjà normalement)
|
||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Étape 3 : Démarrer le serveur
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run server
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu :**
|
||||||
|
```
|
||||||
|
Server running on http://localhost:8888
|
||||||
|
|
||||||
|
Endpoints:
|
||||||
|
GET /health - Health check
|
||||||
|
GET /info?url= - Get video/playlist info
|
||||||
|
POST /download - Download as MP3
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Étape 4 : Tester l'authentification
|
||||||
|
|
||||||
|
### Test 1 : Endpoint public (sans token) ✅
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/health
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu :**
|
||||||
|
```json
|
||||||
|
{"status":"ok","timestamp":"2025-..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 2 : Endpoint protégé SANS token ❌
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/info?url=https://youtube.com/watch?v=test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu (ERREUR 401) :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Unauthorized",
|
||||||
|
"message": "API key required. Provide X-API-Key header or Authorization: Bearer <token>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Si tu vois cette erreur, c'est PARFAIT !** Ça veut dire que la sécurité fonctionne.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 3 : Endpoint protégé AVEC token ✅
|
||||||
|
|
||||||
|
**Remplace `ton_token_ici` par le token que tu as généré :**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "X-API-Key: ton_token_ici" \
|
||||||
|
"http://localhost:8888/info?url=https://youtube.com/watch?v=dQw4w9WgXcQ"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu (informations sur la vidéo) :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"title": "Rick Astley - Never Gonna Give You Up",
|
||||||
|
"type": "video",
|
||||||
|
"duration": 212,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Si tu vois les infos de la vidéo, parfait !**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 4 : Avec un MAUVAIS token ❌
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "X-API-Key: mauvais_token_123" \
|
||||||
|
"http://localhost:8888/info?url=https://youtube.com/watch?v=test"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu (ERREUR 403) :**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Forbidden",
|
||||||
|
"message": "Invalid API key"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Si tu vois cette erreur, c'est bon !**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Étape 5 : Tester l'interface web
|
||||||
|
|
||||||
|
### 1. Ouvre ton navigateur
|
||||||
|
|
||||||
|
Va sur : **http://localhost:8888**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Configure le token
|
||||||
|
|
||||||
|
1. **Clique sur le panneau "🔐 API Configuration"**
|
||||||
|
2. **Colle ton token** dans le champ "API Token"
|
||||||
|
3. **Clique sur "Save & Test"**
|
||||||
|
|
||||||
|
**Résultat attendu :**
|
||||||
|
- Le statut passe en vert : **"Connected ✓"**
|
||||||
|
- Notification verte : **"✓ API token saved successfully!"**
|
||||||
|
- Le panneau se referme automatiquement
|
||||||
|
|
||||||
|
![Configuration réussie]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Teste un téléchargement
|
||||||
|
|
||||||
|
1. Va dans l'onglet **"Download"**
|
||||||
|
2. Entre cette URL de test : `https://www.youtube.com/watch?v=dQw4w9WgXcQ`
|
||||||
|
3. Clique sur **"Download MP3"**
|
||||||
|
|
||||||
|
**Résultat attendu :**
|
||||||
|
- La barre de progression apparaît
|
||||||
|
- Le téléchargement démarre
|
||||||
|
- Un fichier MP3 est créé dans `./output/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Teste sans token (localStorage)
|
||||||
|
|
||||||
|
1. **Ouvre la console du navigateur** (F12)
|
||||||
|
2. **Efface le localStorage** :
|
||||||
|
```javascript
|
||||||
|
localStorage.clear()
|
||||||
|
```
|
||||||
|
3. **Rafraîchis la page** (F5)
|
||||||
|
4. **Essaie de télécharger à nouveau** la même vidéo
|
||||||
|
|
||||||
|
**Résultat attendu :**
|
||||||
|
- Une erreur apparaît : **"⚠️ Authentication Error"**
|
||||||
|
- Message : **"API token required"**
|
||||||
|
|
||||||
|
✅ **C'est parfait !** Sans token dans localStorage, l'API refuse la requête.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Re-configure le token
|
||||||
|
|
||||||
|
1. **Rouvre le panneau de configuration**
|
||||||
|
2. **Entre ton token à nouveau**
|
||||||
|
3. **Clique "Save & Test"**
|
||||||
|
4. **Le téléchargement devrait maintenant fonctionner**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Étape 6 : Vérifier le localStorage
|
||||||
|
|
||||||
|
**Dans la console du navigateur (F12) :**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
localStorage.getItem('video_transcriptor_api_token')
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu :**
|
||||||
|
```
|
||||||
|
"ton_token_genere_ici"
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Le token est bien sauvegardé !**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Récapitulatif des Tests
|
||||||
|
|
||||||
|
| Test | Attendu | Statut |
|
||||||
|
|------|---------|--------|
|
||||||
|
| `/health` sans token | ✅ 200 OK | ☐ |
|
||||||
|
| `/info` sans token | ❌ 401 Unauthorized | ☐ |
|
||||||
|
| `/info` avec bon token | ✅ 200 OK + données | ☐ |
|
||||||
|
| `/info` avec mauvais token | ❌ 403 Forbidden | ☐ |
|
||||||
|
| Interface web - config token | ✅ "Connected ✓" | ☐ |
|
||||||
|
| Interface web - download avec token | ✅ Fonctionne | ☐ |
|
||||||
|
| Interface web - download sans token | ❌ Erreur auth | ☐ |
|
||||||
|
| localStorage sauvegarde token | ✅ Token présent | ☐ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
### ❌ Le serveur ne démarre pas
|
||||||
|
|
||||||
|
**Erreur possible :** `API_TOKEN not configured in .env`
|
||||||
|
|
||||||
|
**Solution :** Vérifie que `.env` contient bien `API_TOKEN=...`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ "Authentication Error" même avec token
|
||||||
|
|
||||||
|
**Vérifications :**
|
||||||
|
|
||||||
|
1. **Le token dans `.env` est identique à celui dans l'interface ?**
|
||||||
|
```bash
|
||||||
|
cat .env | grep API_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Le serveur a bien redémarré après modification de `.env` ?**
|
||||||
|
- Arrête le serveur (Ctrl+C)
|
||||||
|
- Relance : `npm run server`
|
||||||
|
|
||||||
|
3. **Le token ne contient pas d'espaces ?**
|
||||||
|
```env
|
||||||
|
# ❌ MAUVAIS
|
||||||
|
API_TOKEN= mon_token
|
||||||
|
|
||||||
|
# ✅ BON
|
||||||
|
API_TOKEN=mon_token
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ "CORS error" dans la console
|
||||||
|
|
||||||
|
**Problème :** La requête est bloquée par CORS
|
||||||
|
|
||||||
|
**Solution :** Vérifie dans `.env` :
|
||||||
|
```env
|
||||||
|
ALLOWED_ORIGINS=*
|
||||||
|
```
|
||||||
|
|
||||||
|
Redémarre le serveur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ Le localStorage ne sauvegarde pas
|
||||||
|
|
||||||
|
**Problème :** Le navigateur bloque le localStorage
|
||||||
|
|
||||||
|
**Solutions :**
|
||||||
|
1. Désactive les extensions qui bloquent (uBlock, Privacy Badger)
|
||||||
|
2. Vérifie que tu n'es pas en mode navigation privée
|
||||||
|
3. Autorise le stockage local pour `localhost`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prochaine Étape
|
||||||
|
|
||||||
|
Si tous les tests sont ✅ **OK**, tu peux déployer sur le serveur OVH !
|
||||||
|
|
||||||
|
📄 **Suis le guide** : [`docs/DEPLOIEMENT_OVH.md`](./docs/DEPLOIEMENT_OVH.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Bonne chance ! 🚀**
|
||||||
162
docs/API.md
162
docs/API.md
@ -5,13 +5,75 @@
|
|||||||
http://localhost:8888
|
http://localhost:8888
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 🔐 Authentication
|
||||||
|
|
||||||
|
**⚠️ IMPORTANT**: All API endpoints (except `/health` and `/api`) require authentication using an API token.
|
||||||
|
|
||||||
|
### How to Authenticate
|
||||||
|
|
||||||
|
Include your API token in **one** of these ways:
|
||||||
|
|
||||||
|
**Option 1: X-API-Key header (Recommended)**
|
||||||
|
```bash
|
||||||
|
curl -H "X-API-Key: your_api_token_here" http://localhost:8888/endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Authorization Bearer header**
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer your_api_token_here" http://localhost:8888/endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
1. Set your API token in `.env`:
|
||||||
|
```env
|
||||||
|
API_TOKEN=your_secure_token_here
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Generate a secure token for production:
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
|
# Or use any secure random string generator
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Notes
|
||||||
|
|
||||||
|
- **Public endpoints** (no auth required): `/health`, `/api`
|
||||||
|
- **Protected endpoints**: All other endpoints require authentication
|
||||||
|
- In **development**: If `API_TOKEN` is not set, the API will work without authentication (with a warning)
|
||||||
|
- In **production**: Always set a strong `API_TOKEN`
|
||||||
|
|
||||||
|
### Error Responses
|
||||||
|
|
||||||
|
**401 Unauthorized** - No API key provided:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Unauthorized",
|
||||||
|
"message": "API key required. Provide X-API-Key header or Authorization: Bearer <token>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**403 Forbidden** - Invalid API key:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Forbidden",
|
||||||
|
"message": "Invalid API key"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
- [Authentication](#-authentication)
|
||||||
- [Health & Info](#health--info)
|
- [Health & Info](#health--info)
|
||||||
- [Download Endpoints](#download-endpoints)
|
- [Download Endpoints](#download-endpoints)
|
||||||
- [Transcription Endpoints](#transcription-endpoints)
|
- [Transcription Endpoints](#transcription-endpoints)
|
||||||
- [Translation Endpoints](#translation-endpoints)
|
- [Translation Endpoints](#translation-endpoints)
|
||||||
- [Summarization Endpoints](#summarization-endpoints)
|
- [Summarization Endpoints](#summarization-endpoints)
|
||||||
- [File Management](#file-management)
|
- [File Management](#file-management)
|
||||||
|
- [Security Configuration](#security-configuration)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -48,7 +110,8 @@ Get information about a YouTube video or playlist.
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```bash
|
```bash
|
||||||
curl "http://localhost:8888/info?url=https://www.youtube.com/watch?v=VIDEO_ID"
|
curl -H "X-API-Key: your_token" \
|
||||||
|
"http://localhost:8888/info?url=https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
@ -76,7 +139,8 @@ Download YouTube video(s) to MP3 with Server-Sent Events (SSE) progress updates.
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```bash
|
```bash
|
||||||
curl "http://localhost:8888/download-stream?url=https://www.youtube.com/watch?v=VIDEO_ID"
|
curl -H "X-API-Key: your_token" \
|
||||||
|
"http://localhost:8888/download-stream?url=https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
```
|
```
|
||||||
|
|
||||||
**SSE Events:**
|
**SSE Events:**
|
||||||
@ -99,7 +163,8 @@ Download YouTube video(s) to MP3 (non-streaming).
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8888/download \
|
curl -H "X-API-Key: your_token" \
|
||||||
|
-X POST http://localhost:8888/download \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"url":"https://www.youtube.com/watch?v=VIDEO_ID"}'
|
-d '{"url":"https://www.youtube.com/watch?v=VIDEO_ID"}'
|
||||||
```
|
```
|
||||||
@ -148,7 +213,8 @@ Transcribe an existing audio file.
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8888/transcribe \
|
curl -H "X-API-Key: your_token" \
|
||||||
|
-X POST http://localhost:8888/transcribe \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"filePath": "./output/audio.mp3",
|
"filePath": "./output/audio.mp3",
|
||||||
@ -559,3 +625,91 @@ All endpoints that support `outputPath` parameter:
|
|||||||
|
|
||||||
### API Key
|
### API Key
|
||||||
Ensure `OPENAI_API_KEY` is set in your `.env` file for transcription, translation, and summarization features to work.
|
Ensure `OPENAI_API_KEY` is set in your `.env` file for transcription, translation, and summarization features to work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Required security variables in `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# API Authentication Token
|
||||||
|
API_TOKEN=your_secure_random_token_here
|
||||||
|
|
||||||
|
# CORS - Allowed Origins (comma-separated)
|
||||||
|
# Development: * (all origins)
|
||||||
|
# Production: https://yourdomain.com,https://app.yourdomain.com
|
||||||
|
ALLOWED_ORIGINS=*
|
||||||
|
|
||||||
|
# Server Port
|
||||||
|
PORT=8888
|
||||||
|
|
||||||
|
# Output Directory
|
||||||
|
OUTPUT_DIR=./output
|
||||||
|
|
||||||
|
# OpenAI API Key (required for AI features)
|
||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Features
|
||||||
|
|
||||||
|
The API implements the following security measures:
|
||||||
|
|
||||||
|
1. **API Token Authentication**
|
||||||
|
- All endpoints (except `/health` and `/api`) require authentication
|
||||||
|
- Supports both `X-API-Key` and `Authorization: Bearer` headers
|
||||||
|
|
||||||
|
2. **CORS Protection**
|
||||||
|
- Configurable allowed origins via `ALLOWED_ORIGINS`
|
||||||
|
- Restricts cross-origin requests to trusted domains
|
||||||
|
|
||||||
|
3. **HTTP Security Headers**
|
||||||
|
- `X-Content-Type-Options: nosniff`
|
||||||
|
- `X-Frame-Options: DENY`
|
||||||
|
- `X-XSS-Protection: 1; mode=block`
|
||||||
|
- `Strict-Transport-Security: max-age=31536000; includeSubDomains`
|
||||||
|
- `Content-Security-Policy` with strict policies
|
||||||
|
|
||||||
|
4. **Input Validation**
|
||||||
|
- File type validation for uploads
|
||||||
|
- Parameter validation on all endpoints
|
||||||
|
|
||||||
|
### Production Deployment Checklist
|
||||||
|
|
||||||
|
Before deploying to production:
|
||||||
|
|
||||||
|
- [ ] Generate a strong, unique `API_TOKEN` (min 32 characters)
|
||||||
|
- [ ] Set `ALLOWED_ORIGINS` to your specific domains (remove `*`)
|
||||||
|
- [ ] Ensure `OPENAI_API_KEY` is properly set
|
||||||
|
- [ ] Use HTTPS (not HTTP) for all connections
|
||||||
|
- [ ] Set up rate limiting (recommended via reverse proxy)
|
||||||
|
- [ ] Configure firewall rules
|
||||||
|
- [ ] Set up monitoring and logging
|
||||||
|
- [ ] Review and secure file upload limits
|
||||||
|
|
||||||
|
### Example Authenticated Requests
|
||||||
|
|
||||||
|
**Using X-API-Key header:**
|
||||||
|
```bash
|
||||||
|
# Download endpoint
|
||||||
|
curl -H "X-API-Key: your_token" \
|
||||||
|
-X POST http://localhost:8888/download \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url":"https://www.youtube.com/watch?v=VIDEO_ID"}'
|
||||||
|
|
||||||
|
# Transcribe endpoint
|
||||||
|
curl -H "X-API-Key: your_token" \
|
||||||
|
-X POST http://localhost:8888/transcribe \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"filePath":"./output/audio.mp3"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using Authorization Bearer:**
|
||||||
|
```bash
|
||||||
|
curl -H "Authorization: Bearer your_token" \
|
||||||
|
-X POST http://localhost:8888/summarize \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"text":"Long text to summarize..."}'
|
||||||
|
```
|
||||||
|
|||||||
395
docs/DEPLOIEMENT_OVH.md
Normal file
395
docs/DEPLOIEMENT_OVH.md
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
# Guide de Mise à Jour - Serveur OVH Existant
|
||||||
|
|
||||||
|
Ce guide explique comment mettre à jour ton serveur OVH existant avec le nouveau système de sécurité.
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
Tu as déjà :
|
||||||
|
- ✅ Un VPS chez OVH
|
||||||
|
- ✅ Git configuré
|
||||||
|
- ✅ Un service qui tourne (PM2/systemd)
|
||||||
|
|
||||||
|
## Étapes de Mise à Jour
|
||||||
|
|
||||||
|
### 1. Générer un token API sécurisé
|
||||||
|
|
||||||
|
**Sur ton serveur OVH (via SSH):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Générer un token aléatoire de 64 caractères
|
||||||
|
openssl rand -hex 32
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ou sur Windows (PowerShell):**
|
||||||
|
```powershell
|
||||||
|
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 64 | % {[char]$_})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Copie ce token**, tu vas en avoir besoin maintenant.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Configurer les variables d'environnement
|
||||||
|
|
||||||
|
Connecte-toi en SSH à ton serveur :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh user@ton-serveur-ovh.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigue vers le dossier du projet :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /chemin/vers/videotoMP3Transcriptor
|
||||||
|
```
|
||||||
|
|
||||||
|
Édite le fichier `.env` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ajoute ces lignes** (ou modifie si elles existent déjà) :
|
||||||
|
|
||||||
|
```env
|
||||||
|
# ========================================
|
||||||
|
# SÉCURITÉ API
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# Remplace par le token que tu viens de générer
|
||||||
|
API_TOKEN=ton_token_de_64_caracteres_ici
|
||||||
|
|
||||||
|
# Domaines autorisés (séparés par des virgules)
|
||||||
|
# En développement: * (tout le monde)
|
||||||
|
# En production: https://ton-domaine.com,https://api.ton-domaine.com
|
||||||
|
ALLOWED_ORIGINS=*
|
||||||
|
|
||||||
|
# Port (optionnel, défaut: 8888)
|
||||||
|
PORT=8888
|
||||||
|
|
||||||
|
# OpenAI API Key (tu dois déjà l'avoir)
|
||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sauvegarde** : `Ctrl + X`, puis `Y`, puis `Enter`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Pull les dernières modifications
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sauvegarder les modifications locales si nécessaire
|
||||||
|
git stash
|
||||||
|
|
||||||
|
# Récupérer les dernières modifications
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# Restaurer tes modifications si tu avais stashé
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Redémarrer le service
|
||||||
|
|
||||||
|
**Si tu utilises PM2:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Redémarrer l'application
|
||||||
|
pm2 restart video-transcriptor
|
||||||
|
|
||||||
|
# Vérifier que ça tourne
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# Voir les logs en temps réel
|
||||||
|
pm2 logs video-transcriptor
|
||||||
|
```
|
||||||
|
|
||||||
|
**Si tu utilises systemd:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Redémarrer le service
|
||||||
|
sudo systemctl restart video-transcriptor
|
||||||
|
|
||||||
|
# Vérifier le statut
|
||||||
|
sudo systemctl status video-transcriptor
|
||||||
|
|
||||||
|
# Voir les logs
|
||||||
|
sudo journalctl -u video-transcriptor -f
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Tester l'API
|
||||||
|
|
||||||
|
**Test de santé (sans token - devrait marcher):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/health
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu:**
|
||||||
|
```json
|
||||||
|
{"status":"ok","timestamp":"2025-..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test avec authentification (devrait échouer sans token):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8888/info?url=https://youtube.com/watch?v=test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu:**
|
||||||
|
```json
|
||||||
|
{"error":"Unauthorized","message":"API key required..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test avec token (devrait marcher):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "X-API-Key: ton_token_ici" \
|
||||||
|
"http://localhost:8888/info?url=https://youtube.com/watch?v=dQw4w9WgXcQ"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat attendu:** Informations sur la vidéo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Configurer le DNS (si pas déjà fait)
|
||||||
|
|
||||||
|
**Chez OVH, dans l'espace client:**
|
||||||
|
|
||||||
|
1. Va dans **Web Cloud** → **Domaines** → **Ton domaine**
|
||||||
|
2. Clique sur **Zone DNS**
|
||||||
|
3. Ajoute un enregistrement **A** :
|
||||||
|
- Sous-domaine: `api` (ou `@` pour le domaine principal)
|
||||||
|
- Cible: **L'IP de ton VPS OVH**
|
||||||
|
- TTL: 3600
|
||||||
|
|
||||||
|
**Exemple:**
|
||||||
|
```
|
||||||
|
Type: A
|
||||||
|
Nom: api
|
||||||
|
Cible: 51.195.XXX.XXX (ton IP OVH)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Attends 5-10 minutes** pour la propagation DNS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Tester depuis l'interface web
|
||||||
|
|
||||||
|
1. **Ouvre ton navigateur** et va sur : `http://ton-domaine.com` (ou `http://ip-du-serveur:8888`)
|
||||||
|
|
||||||
|
2. **Clique sur le panneau "🔐 API Configuration"**
|
||||||
|
|
||||||
|
3. **Colle ton token** dans le champ
|
||||||
|
|
||||||
|
4. **Clique sur "Save & Test"**
|
||||||
|
|
||||||
|
5. **Résultat attendu :**
|
||||||
|
- Statut passe en vert "Connected ✓"
|
||||||
|
- Notification de succès
|
||||||
|
- Le token est sauvegardé dans le navigateur
|
||||||
|
|
||||||
|
6. **Teste un téléchargement** dans l'onglet "Download"
|
||||||
|
- Entre une URL YouTube
|
||||||
|
- Le token sera automatiquement ajouté aux requêtes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sécurité en Production
|
||||||
|
|
||||||
|
### Option 1 : Limiter les origines CORS
|
||||||
|
|
||||||
|
Si tu veux que SEUL ton domaine puisse utiliser l'API :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Change :
|
||||||
|
```env
|
||||||
|
ALLOWED_ORIGINS=https://ton-domaine.com,https://api.ton-domaine.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2 : HTTPS avec Nginx + Let's Encrypt
|
||||||
|
|
||||||
|
**Si pas déjà configuré**, installe Nginx et SSL :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer Nginx
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y nginx certbot python3-certbot-nginx
|
||||||
|
|
||||||
|
# Créer la configuration Nginx
|
||||||
|
sudo nano /etc/nginx/sites-available/video-transcriptor
|
||||||
|
```
|
||||||
|
|
||||||
|
**Colle cette configuration :**
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name api.ton-domaine.com;
|
||||||
|
|
||||||
|
# Redirection vers HTTPS (sera configuré après)
|
||||||
|
# return 301 https://$server_name$request_uri;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8888;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Activer et tester:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Activer le site
|
||||||
|
sudo ln -s /etc/nginx/sites-available/video-transcriptor /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
# Tester la config
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# Redémarrer Nginx
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
|
||||||
|
# Obtenir un certificat SSL GRATUIT
|
||||||
|
sudo certbot --nginx -d api.ton-domaine.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Certbot va automatiquement configurer HTTPS et les redirections.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
### ❌ "API token required"
|
||||||
|
|
||||||
|
**Problème:** Le token n'est pas envoyé ou invalide
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Vérifie que le token est bien configuré dans l'interface web
|
||||||
|
2. Rafraîchis la page et entre le token à nouveau
|
||||||
|
3. Vérifie que le token dans `.env` est le même que dans l'interface
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ Le service ne démarre pas
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Voir les logs
|
||||||
|
pm2 logs video-transcriptor --lines 50
|
||||||
|
|
||||||
|
# ou pour systemd
|
||||||
|
sudo journalctl -u video-transcriptor -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vérifications:**
|
||||||
|
- La variable `API_TOKEN` est bien dans `.env`
|
||||||
|
- Pas d'erreurs de syntaxe dans `.env`
|
||||||
|
- Node modules à jour : `npm ci`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ CORS errors dans le navigateur
|
||||||
|
|
||||||
|
**Problème:** "Access to fetch at ... has been blocked by CORS policy"
|
||||||
|
|
||||||
|
**Solution 1:** En développement
|
||||||
|
```env
|
||||||
|
ALLOWED_ORIGINS=*
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution 2:** En production
|
||||||
|
```env
|
||||||
|
ALLOWED_ORIGINS=https://ton-domaine.com,https://www.ton-domaine.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Redémarre après modification : `pm2 restart video-transcriptor`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ DNS ne fonctionne pas
|
||||||
|
|
||||||
|
**Vérifier la propagation DNS:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Depuis ton serveur
|
||||||
|
dig api.ton-domaine.com
|
||||||
|
|
||||||
|
# Ou depuis Windows
|
||||||
|
nslookup api.ton-domaine.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Si ça ne fonctionne pas:**
|
||||||
|
- Attends 10-30 minutes
|
||||||
|
- Vérifie dans l'interface OVH que l'enregistrement A pointe vers la bonne IP
|
||||||
|
- Vide le cache DNS : `ipconfig /flushdns` (Windows) ou `sudo systemd-resolve --flush-caches` (Linux)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist Finale
|
||||||
|
|
||||||
|
Avant de considérer le déploiement comme terminé :
|
||||||
|
|
||||||
|
- [ ] `.env` configuré avec un `API_TOKEN` fort
|
||||||
|
- [ ] Service redémarré et en cours d'exécution
|
||||||
|
- [ ] Test `/health` fonctionne
|
||||||
|
- [ ] Test avec token fonctionne
|
||||||
|
- [ ] Interface web accessible
|
||||||
|
- [ ] Token sauvegardé dans l'interface web
|
||||||
|
- [ ] Test de téléchargement YouTube réussi
|
||||||
|
- [ ] DNS configuré (si applicable)
|
||||||
|
- [ ] HTTPS configuré (recommandé pour production)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commandes Utiles
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Voir les logs en temps réel
|
||||||
|
pm2 logs video-transcriptor
|
||||||
|
|
||||||
|
# Statut du service
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# Redémarrer
|
||||||
|
pm2 restart video-transcriptor
|
||||||
|
|
||||||
|
# Vérifier les ports ouverts
|
||||||
|
sudo netstat -tlnp | grep 8888
|
||||||
|
|
||||||
|
# Vérifier l'utilisation des ressources
|
||||||
|
htop
|
||||||
|
|
||||||
|
# Espace disque
|
||||||
|
df -h
|
||||||
|
|
||||||
|
# Tester l'API locale
|
||||||
|
curl -H "X-API-Key: ton_token" http://localhost:8888/health
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Si tu rencontres des problèmes :
|
||||||
|
|
||||||
|
1. **Vérifie les logs** : `pm2 logs`
|
||||||
|
2. **Vérifie le `.env`** : `cat .env | grep API_TOKEN`
|
||||||
|
3. **Teste en local** : `curl http://localhost:8888/health`
|
||||||
|
4. **Vérifie le firewall** : `sudo ufw status`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Bon déploiement ! 🚀**
|
||||||
|
|
||||||
|
Si tout fonctionne, tu devrais pouvoir utiliser l'interface web avec le token sauvegardé, et ne plus avoir à le copier-coller à chaque fois !
|
||||||
699
docs/DEPLOYMENT.md
Normal file
699
docs/DEPLOYMENT.md
Normal file
@ -0,0 +1,699 @@
|
|||||||
|
# Guide de Déploiement - Video to MP3 Transcriptor
|
||||||
|
|
||||||
|
Ce guide vous accompagne pour déployer l'API de manière sécurisée sur un serveur de production.
|
||||||
|
|
||||||
|
## Table des matières
|
||||||
|
1. [Prérequis](#prérequis)
|
||||||
|
2. [Configuration de sécurité](#configuration-de-sécurité)
|
||||||
|
3. [Déploiement sur VPS/Serveur](#déploiement-sur-vpsserveur)
|
||||||
|
4. [Déploiement avec Docker](#déploiement-avec-docker)
|
||||||
|
5. [Nginx Reverse Proxy](#nginx-reverse-proxy)
|
||||||
|
6. [SSL/HTTPS avec Let's Encrypt](#sslhttps-avec-lets-encrypt)
|
||||||
|
7. [Surveillance et logs](#surveillance-et-logs)
|
||||||
|
8. [Sécurité avancée](#sécurité-avancée)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
### Serveur
|
||||||
|
- Linux (Ubuntu 20.04+ / Debian 11+ recommandé)
|
||||||
|
- Minimum 2 GB RAM
|
||||||
|
- 10 GB espace disque
|
||||||
|
- Node.js 18+ ou Docker
|
||||||
|
|
||||||
|
### Dépendances système
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y ffmpeg python3
|
||||||
|
|
||||||
|
# Pour téléchargement YouTube
|
||||||
|
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
|
||||||
|
sudo chmod a+rx /usr/local/bin/yt-dlp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Domaine et DNS
|
||||||
|
- Un nom de domaine pointant vers votre serveur
|
||||||
|
- Accès aux paramètres DNS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration de sécurité
|
||||||
|
|
||||||
|
### 1. Générer un token API sécurisé
|
||||||
|
|
||||||
|
**Sur votre serveur:**
|
||||||
|
```bash
|
||||||
|
# Générer un token de 64 caractères
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
|
# Ou utiliser cette commande alternative
|
||||||
|
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 64
|
||||||
|
```
|
||||||
|
|
||||||
|
Copiez le token généré, vous en aurez besoin pour le `.env`.
|
||||||
|
|
||||||
|
### 2. Configurer les variables d'environnement
|
||||||
|
|
||||||
|
Créez/éditez le fichier `.env` sur le serveur:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/videotoMP3Transcriptor
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration minimale de production:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# ========================================
|
||||||
|
# SÉCURITÉ - PRODUCTION
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# Token API (REMPLACEZ PAR VOTRE TOKEN GÉNÉRÉ)
|
||||||
|
API_TOKEN=votre_token_securise_de_64_caracteres
|
||||||
|
|
||||||
|
# Origines CORS autorisées (vos domaines uniquement)
|
||||||
|
ALLOWED_ORIGINS=https://yourdomain.com,https://api.yourdomain.com
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# CONFIGURATION SERVEUR
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# Port interne (Nginx fera le reverse proxy)
|
||||||
|
PORT=8888
|
||||||
|
|
||||||
|
# Répertoire de sortie
|
||||||
|
OUTPUT_DIR=/var/www/videotoMP3Transcriptor/output
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# API KEYS
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# OpenAI API Key (OBLIGATOIRE)
|
||||||
|
OPENAI_API_KEY=sk-...
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# ENVIRONNEMENT
|
||||||
|
# ========================================
|
||||||
|
NODE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Permissions du fichier .env
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sécuriser le fichier .env
|
||||||
|
chmod 600 .env
|
||||||
|
chown www-data:www-data .env # ou votre utilisateur système
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Déploiement sur VPS/Serveur
|
||||||
|
|
||||||
|
### 1. Installation de Node.js
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installation de Node.js 20 LTS
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||||
|
sudo apt install -y nodejs
|
||||||
|
|
||||||
|
# Vérification
|
||||||
|
node --version # devrait afficher v20.x
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Cloner et installer l'application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Créer le répertoire
|
||||||
|
sudo mkdir -p /var/www/videotoMP3Transcriptor
|
||||||
|
sudo chown $USER:$USER /var/www/videotoMP3Transcriptor
|
||||||
|
|
||||||
|
# Cloner (ou copier) votre code
|
||||||
|
cd /var/www/videotoMP3Transcriptor
|
||||||
|
# git clone ... ou upload manuel
|
||||||
|
|
||||||
|
# Installer les dépendances
|
||||||
|
npm ci --only=production
|
||||||
|
|
||||||
|
# Créer le répertoire de sortie
|
||||||
|
mkdir -p output
|
||||||
|
chmod 755 output
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Utiliser PM2 pour la gestion des processus
|
||||||
|
|
||||||
|
PM2 est un gestionnaire de processus pour Node.js qui redémarre automatiquement votre app en cas de crash.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer PM2 globalement
|
||||||
|
sudo npm install -g pm2
|
||||||
|
|
||||||
|
# Démarrer l'application
|
||||||
|
pm2 start src/server.js --name "video-transcriptor"
|
||||||
|
|
||||||
|
# Configurer PM2 pour démarrer au boot
|
||||||
|
pm2 startup systemd
|
||||||
|
pm2 save
|
||||||
|
|
||||||
|
# Commandes utiles
|
||||||
|
pm2 status # Voir le statut
|
||||||
|
pm2 logs video-transcriptor # Voir les logs
|
||||||
|
pm2 restart video-transcriptor # Redémarrer
|
||||||
|
pm2 stop video-transcriptor # Arrêter
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Configuration PM2 avancée (optionnelle)
|
||||||
|
|
||||||
|
Créez un fichier `ecosystem.config.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
apps: [{
|
||||||
|
name: 'video-transcriptor',
|
||||||
|
script: './src/server.js',
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: '1G',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 8888
|
||||||
|
},
|
||||||
|
error_file: '/var/log/pm2/video-transcriptor-error.log',
|
||||||
|
out_file: '/var/log/pm2/video-transcriptor-out.log',
|
||||||
|
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Démarrer avec:
|
||||||
|
```bash
|
||||||
|
pm2 start ecosystem.config.js
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Déploiement avec Docker
|
||||||
|
|
||||||
|
### 1. Créer un Dockerfile
|
||||||
|
|
||||||
|
Créez `Dockerfile` à la racine du projet:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Installer les dépendances système
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
ffmpeg \
|
||||||
|
python3 \
|
||||||
|
curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Installer yt-dlp
|
||||||
|
RUN curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp \
|
||||||
|
&& chmod a+rx /usr/local/bin/yt-dlp
|
||||||
|
|
||||||
|
# Créer le répertoire de l'app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copier package.json et installer les dépendances
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Copier le code source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Créer le répertoire de sortie
|
||||||
|
RUN mkdir -p /app/output && chmod 755 /app/output
|
||||||
|
|
||||||
|
# Exposer le port
|
||||||
|
EXPOSE 8888
|
||||||
|
|
||||||
|
# Variables d'environnement par défaut
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=8888
|
||||||
|
ENV OUTPUT_DIR=/app/output
|
||||||
|
|
||||||
|
# Démarrer l'application
|
||||||
|
CMD ["node", "src/server.js"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Créer docker-compose.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
video-transcriptor:
|
||||||
|
build: .
|
||||||
|
container_name: video-transcriptor
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8888:8888"
|
||||||
|
volumes:
|
||||||
|
- ./output:/app/output
|
||||||
|
- ./.env:/app/.env:ro
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
networks:
|
||||||
|
- transcriptor-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
transcriptor-network:
|
||||||
|
driver: bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Lancer avec Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build et démarrer
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Voir les logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Arrêter
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Reconstruire après modification
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nginx Reverse Proxy
|
||||||
|
|
||||||
|
### 1. Installer Nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configuration Nginx
|
||||||
|
|
||||||
|
Créez `/etc/nginx/sites-available/video-transcriptor`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Rate limiting
|
||||||
|
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name api.yourdomain.com;
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
access_log /var/log/nginx/video-transcriptor-access.log;
|
||||||
|
error_log /var/log/nginx/video-transcriptor-error.log;
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
limit_req zone=api_limit burst=20 nodelay;
|
||||||
|
|
||||||
|
# Augmenter les timeouts pour les longs traitements
|
||||||
|
proxy_connect_timeout 600;
|
||||||
|
proxy_send_timeout 600;
|
||||||
|
proxy_read_timeout 600;
|
||||||
|
send_timeout 600;
|
||||||
|
|
||||||
|
# Augmenter la taille max des uploads
|
||||||
|
client_max_body_size 500M;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8888;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
# Headers
|
||||||
|
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;
|
||||||
|
|
||||||
|
# Pour Server-Sent Events (SSE)
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Headers de sécurité supplémentaires
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Activer le site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Créer un lien symbolique
|
||||||
|
sudo ln -s /etc/nginx/sites-available/video-transcriptor /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
# Tester la configuration
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# Recharger Nginx
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SSL/HTTPS avec Let's Encrypt
|
||||||
|
|
||||||
|
### 1. Installer Certbot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install -y certbot python3-certbot-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Obtenir un certificat SSL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Obtenir et installer automatiquement le certificat
|
||||||
|
sudo certbot --nginx -d api.yourdomain.com
|
||||||
|
|
||||||
|
# Suivez les instructions à l'écran
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Renouvellement automatique
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tester le renouvellement
|
||||||
|
sudo certbot renew --dry-run
|
||||||
|
|
||||||
|
# Le renouvellement automatique est configuré via cron
|
||||||
|
# Vérifier: sudo systemctl status certbot.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
Après SSL, votre configuration Nginx sera automatiquement mise à jour pour HTTPS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Surveillance et logs
|
||||||
|
|
||||||
|
### 1. Logs de l'application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Avec PM2
|
||||||
|
pm2 logs video-transcriptor
|
||||||
|
|
||||||
|
# Avec Docker
|
||||||
|
docker-compose logs -f video-transcriptor
|
||||||
|
|
||||||
|
# Logs Nginx
|
||||||
|
sudo tail -f /var/log/nginx/video-transcriptor-access.log
|
||||||
|
sudo tail -f /var/log/nginx/video-transcriptor-error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Monitoring avec PM2 (optionnel)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer PM2 monitoring
|
||||||
|
pm2 install pm2-logrotate
|
||||||
|
|
||||||
|
# Configurer la rotation des logs
|
||||||
|
pm2 set pm2-logrotate:max_size 10M
|
||||||
|
pm2 set pm2-logrotate:retain 7
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Monitoring système
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer htop pour surveiller les ressources
|
||||||
|
sudo apt install -y htop
|
||||||
|
|
||||||
|
# Lancer htop
|
||||||
|
htop
|
||||||
|
|
||||||
|
# Voir l'utilisation disque
|
||||||
|
df -h
|
||||||
|
|
||||||
|
# Voir l'utilisation mémoire
|
||||||
|
free -h
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sécurité avancée
|
||||||
|
|
||||||
|
### 1. Firewall (UFW)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer UFW
|
||||||
|
sudo apt install -y ufw
|
||||||
|
|
||||||
|
# Autoriser SSH (IMPORTANT AVANT D'ACTIVER!)
|
||||||
|
sudo ufw allow ssh
|
||||||
|
sudo ufw allow 22/tcp
|
||||||
|
|
||||||
|
# Autoriser HTTP et HTTPS
|
||||||
|
sudo ufw allow 'Nginx Full'
|
||||||
|
|
||||||
|
# Activer le firewall
|
||||||
|
sudo ufw enable
|
||||||
|
|
||||||
|
# Vérifier le statut
|
||||||
|
sudo ufw status
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Fail2Ban (protection contre brute force)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Installer Fail2Ban
|
||||||
|
sudo apt install -y fail2ban
|
||||||
|
|
||||||
|
# Créer une configuration pour Nginx
|
||||||
|
sudo nano /etc/fail2ban/jail.local
|
||||||
|
```
|
||||||
|
|
||||||
|
Ajouter:
|
||||||
|
```ini
|
||||||
|
[nginx-limit-req]
|
||||||
|
enabled = true
|
||||||
|
filter = nginx-limit-req
|
||||||
|
port = http,https
|
||||||
|
logpath = /var/log/nginx/video-transcriptor-error.log
|
||||||
|
maxretry = 5
|
||||||
|
findtime = 600
|
||||||
|
bantime = 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Redémarrer Fail2Ban
|
||||||
|
sudo systemctl restart fail2ban
|
||||||
|
|
||||||
|
# Vérifier le statut
|
||||||
|
sudo fail2ban-client status nginx-limit-req
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Limitations supplémentaires
|
||||||
|
|
||||||
|
**Limiter les tailles de fichiers uploadés** - Déjà configuré dans Nginx (`client_max_body_size 500M`)
|
||||||
|
|
||||||
|
**Rate limiting par IP** - Déjà configuré dans Nginx (`limit_req_zone`)
|
||||||
|
|
||||||
|
### 4. Sauvegardes automatiques
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Créer un script de backup
|
||||||
|
sudo nano /usr/local/bin/backup-video-transcriptor.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
BACKUP_DIR="/backup/video-transcriptor"
|
||||||
|
APP_DIR="/var/www/videotoMP3Transcriptor"
|
||||||
|
DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
|
# Backup de la configuration
|
||||||
|
tar -czf $BACKUP_DIR/config_$DATE.tar.gz \
|
||||||
|
$APP_DIR/.env \
|
||||||
|
$APP_DIR/ecosystem.config.js
|
||||||
|
|
||||||
|
# Backup des fichiers de sortie (optionnel, peut être volumineux)
|
||||||
|
# tar -czf $BACKUP_DIR/output_$DATE.tar.gz $APP_DIR/output
|
||||||
|
|
||||||
|
# Garder seulement les 7 derniers backups
|
||||||
|
find $BACKUP_DIR -name "config_*.tar.gz" -mtime +7 -delete
|
||||||
|
|
||||||
|
echo "Backup completed: $DATE"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rendre exécutable
|
||||||
|
sudo chmod +x /usr/local/bin/backup-video-transcriptor.sh
|
||||||
|
|
||||||
|
# Ajouter au crontab (backup quotidien à 2h du matin)
|
||||||
|
sudo crontab -e
|
||||||
|
# Ajouter: 0 2 * * * /usr/local/bin/backup-video-transcriptor.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist finale de déploiement
|
||||||
|
|
||||||
|
Avant de mettre en production, vérifiez:
|
||||||
|
|
||||||
|
- [ ] **Sécurité**
|
||||||
|
- [ ] Token API fort généré (`API_TOKEN`)
|
||||||
|
- [ ] CORS configuré avec vos domaines (`ALLOWED_ORIGINS`)
|
||||||
|
- [ ] Fichier `.env` avec permissions 600
|
||||||
|
- [ ] HTTPS configuré et fonctionnel
|
||||||
|
- [ ] Firewall UFW activé
|
||||||
|
|
||||||
|
- [ ] **Configuration**
|
||||||
|
- [ ] `OPENAI_API_KEY` valide et fonctionnelle
|
||||||
|
- [ ] `NODE_ENV=production`
|
||||||
|
- [ ] Répertoire `output/` créé et accessible
|
||||||
|
- [ ] FFmpeg et yt-dlp installés
|
||||||
|
|
||||||
|
- [ ] **Infrastructure**
|
||||||
|
- [ ] PM2 ou Docker en cours d'exécution
|
||||||
|
- [ ] Nginx reverse proxy configuré
|
||||||
|
- [ ] SSL/TLS actif (Let's Encrypt)
|
||||||
|
- [ ] Rate limiting activé
|
||||||
|
|
||||||
|
- [ ] **Monitoring**
|
||||||
|
- [ ] Logs accessibles
|
||||||
|
- [ ] PM2 startup configuré (redémarrage auto)
|
||||||
|
- [ ] Fail2Ban actif
|
||||||
|
- [ ] Backups automatiques configurés
|
||||||
|
|
||||||
|
- [ ] **Tests**
|
||||||
|
- [ ] Endpoint `/health` accessible
|
||||||
|
- [ ] Test d'authentification (avec et sans token)
|
||||||
|
- [ ] Test d'upload de fichier
|
||||||
|
- [ ] Test de téléchargement YouTube
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests post-déploiement
|
||||||
|
|
||||||
|
### 1. Test de santé
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://api.yourdomain.com/health
|
||||||
|
# Devrait retourner: {"status":"ok","timestamp":"..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Test d'authentification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sans token (devrait échouer avec 401)
|
||||||
|
curl https://api.yourdomain.com/info?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||||
|
|
||||||
|
# Avec token (devrait réussir)
|
||||||
|
curl -H "X-API-Key: VOTRE_TOKEN" \
|
||||||
|
"https://api.yourdomain.com/info?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Test de download
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -H "X-API-Key: VOTRE_TOKEN" \
|
||||||
|
-X POST https://api.yourdomain.com/download \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dépannage
|
||||||
|
|
||||||
|
### L'API ne démarre pas
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier les logs PM2
|
||||||
|
pm2 logs video-transcriptor
|
||||||
|
|
||||||
|
# Vérifier les variables d'environnement
|
||||||
|
pm2 env video-transcriptor
|
||||||
|
|
||||||
|
# Redémarrer
|
||||||
|
pm2 restart video-transcriptor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Erreurs 502 Bad Gateway (Nginx)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier que l'app tourne
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# Vérifier les logs Nginx
|
||||||
|
sudo tail -f /var/log/nginx/error.log
|
||||||
|
|
||||||
|
# Vérifier que le port 8888 est ouvert
|
||||||
|
sudo netstat -tlnp | grep 8888
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problèmes SSL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier le certificat
|
||||||
|
sudo certbot certificates
|
||||||
|
|
||||||
|
# Renouveler manuellement
|
||||||
|
sudo certbot renew --force-renewal
|
||||||
|
|
||||||
|
# Tester la configuration Nginx
|
||||||
|
sudo nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mémoire insuffisante
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vérifier l'utilisation mémoire
|
||||||
|
free -h
|
||||||
|
|
||||||
|
# Créer un swap file (si nécessaire)
|
||||||
|
sudo fallocate -l 2G /swapfile
|
||||||
|
sudo chmod 600 /swapfile
|
||||||
|
sudo mkswap /swapfile
|
||||||
|
sudo swapon /swapfile
|
||||||
|
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mises à jour
|
||||||
|
|
||||||
|
### Mise à jour de l'application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /var/www/videotoMP3Transcriptor
|
||||||
|
|
||||||
|
# Sauvegarder la config
|
||||||
|
cp .env .env.backup
|
||||||
|
|
||||||
|
# Pull des nouvelles versions (git)
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Mettre à jour les dépendances
|
||||||
|
npm ci --only=production
|
||||||
|
|
||||||
|
# Redémarrer
|
||||||
|
pm2 restart video-transcriptor
|
||||||
|
|
||||||
|
# Ou avec Docker
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support et ressources
|
||||||
|
|
||||||
|
- **Documentation API**: [docs/API.md](./API.md)
|
||||||
|
- **CLAUDE.md**: [CLAUDE.md](../CLAUDE.md) - Instructions pour Claude
|
||||||
|
- **PM2 Documentation**: https://pm2.keymetrics.io/
|
||||||
|
- **Nginx Documentation**: https://nginx.org/en/docs/
|
||||||
|
- **Let's Encrypt**: https://letsencrypt.org/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Bon déploiement ! 🚀**
|
||||||
219
public/app.js
219
public/app.js
@ -1,5 +1,222 @@
|
|||||||
// API Base URL
|
// ==================== API CONFIGURATION ====================
|
||||||
const API_URL = '';
|
const API_URL = '';
|
||||||
|
const TOKEN_STORAGE_KEY = 'video_transcriptor_api_token';
|
||||||
|
|
||||||
|
// Token management
|
||||||
|
let apiToken = localStorage.getItem(TOKEN_STORAGE_KEY) || '';
|
||||||
|
|
||||||
|
// Helper: Get API headers with token
|
||||||
|
function getHeaders(additionalHeaders = {}) {
|
||||||
|
const headers = { ...additionalHeaders };
|
||||||
|
if (apiToken) {
|
||||||
|
headers['X-API-Key'] = apiToken;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Authenticated fetch
|
||||||
|
async function apiFetch(url, options = {}) {
|
||||||
|
const defaultHeaders = getHeaders(options.headers || {});
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: defaultHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for auth errors
|
||||||
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
showResult('api-error', false, `
|
||||||
|
<h3>⚠️ Authentication Error</h3>
|
||||||
|
<p>${response.status === 401 ? 'API token required' : 'Invalid API token'}</p>
|
||||||
|
<p>Please configure your API token in the configuration panel above.</p>
|
||||||
|
`);
|
||||||
|
throw new Error('Authentication failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Configuration Panel
|
||||||
|
const configPanel = document.getElementById('api-config-panel');
|
||||||
|
const configHeader = document.querySelector('.config-header');
|
||||||
|
const configContent = document.getElementById('config-content');
|
||||||
|
const toggleConfigBtn = document.getElementById('toggle-config');
|
||||||
|
const apiTokenInput = document.getElementById('api-token');
|
||||||
|
const toggleVisibilityBtn = document.getElementById('toggle-token-visibility');
|
||||||
|
const apiConfigForm = document.getElementById('api-config-form');
|
||||||
|
const clearTokenBtn = document.getElementById('clear-token');
|
||||||
|
const configStatus = document.getElementById('config-status');
|
||||||
|
|
||||||
|
// Load token on page load
|
||||||
|
if (apiToken) {
|
||||||
|
apiTokenInput.value = apiToken;
|
||||||
|
updateConnectionStatus(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle configuration panel
|
||||||
|
function toggleConfigPanel() {
|
||||||
|
const isExpanded = configContent.style.display !== 'none';
|
||||||
|
configContent.style.display = isExpanded ? 'none' : 'block';
|
||||||
|
configHeader.classList.toggle('expanded', !isExpanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
configHeader.addEventListener('click', (e) => {
|
||||||
|
if (e.target.closest('.btn-toggle') || e.target === configHeader) {
|
||||||
|
toggleConfigPanel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle token visibility
|
||||||
|
toggleVisibilityBtn.addEventListener('click', () => {
|
||||||
|
const isPassword = apiTokenInput.type === 'password';
|
||||||
|
apiTokenInput.type = isPassword ? 'text' : 'password';
|
||||||
|
|
||||||
|
const eyeIcon = document.getElementById('eye-icon');
|
||||||
|
if (isPassword) {
|
||||||
|
eyeIcon.innerHTML = `
|
||||||
|
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
|
||||||
|
<line x1="1" y1="1" x2="23" y2="23"></line>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
eyeIcon.innerHTML = `
|
||||||
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update connection status UI
|
||||||
|
function updateConnectionStatus(connected, message = '') {
|
||||||
|
const indicator = configStatus.querySelector('.status-indicator');
|
||||||
|
const text = configStatus.querySelector('.status-text');
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
indicator.className = 'status-indicator status-connected';
|
||||||
|
text.textContent = message || 'Connected ✓';
|
||||||
|
} else {
|
||||||
|
indicator.className = 'status-indicator status-disconnected';
|
||||||
|
text.textContent = message || 'Not configured';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test API connection
|
||||||
|
async function testApiConnection(token) {
|
||||||
|
try {
|
||||||
|
const tempToken = apiToken;
|
||||||
|
apiToken = token; // Temporarily set token for test
|
||||||
|
|
||||||
|
const response = await apiFetch(`${API_URL}/health`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'ok') {
|
||||||
|
apiToken = tempToken; // Restore original
|
||||||
|
return { success: true, message: 'Connected ✓' };
|
||||||
|
} else {
|
||||||
|
apiToken = tempToken;
|
||||||
|
return { success: false, message: 'API error' };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save and test token
|
||||||
|
apiConfigForm.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const token = apiTokenInput.value.trim();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
updateConnectionStatus(false, 'Please enter a token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitBtn = apiConfigForm.querySelector('button[type="submit"]');
|
||||||
|
const originalText = submitBtn.innerHTML;
|
||||||
|
submitBtn.innerHTML = '<span>Testing...</span>';
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await testApiConnection(token);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Save token
|
||||||
|
localStorage.setItem(TOKEN_STORAGE_KEY, token);
|
||||||
|
apiToken = token;
|
||||||
|
updateConnectionStatus(true, result.message);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showNotification('✓ API token saved successfully!', 'success');
|
||||||
|
|
||||||
|
// Collapse panel after 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
toggleConfigPanel();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
updateConnectionStatus(false, 'Connection failed');
|
||||||
|
showNotification('✗ Failed to connect to API. Check your token.', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
updateConnectionStatus(false, 'Connection error');
|
||||||
|
showNotification('✗ Error testing connection: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
submitBtn.innerHTML = originalText;
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear token
|
||||||
|
clearTokenBtn.addEventListener('click', () => {
|
||||||
|
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||||
|
apiToken = '';
|
||||||
|
apiTokenInput.value = '';
|
||||||
|
updateConnectionStatus(false, 'Token cleared');
|
||||||
|
showNotification('Token removed', 'info');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper: Show notification toast
|
||||||
|
function showNotification(message, type = 'info') {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = `notification notification-${type}`;
|
||||||
|
toast.textContent = message;
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
background: ${type === 'success' ? 'rgba(16, 185, 129, 0.9)' : type === 'error' ? 'rgba(239, 68, 68, 0.9)' : 'rgba(59, 130, 246, 0.9)'};
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 10000;
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.style.animation = 'slideOut 0.3s ease';
|
||||||
|
setTimeout(() => toast.remove(), 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add animations to document
|
||||||
|
if (!document.getElementById('notification-styles')) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'notification-styles';
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes slideIn {
|
||||||
|
from { transform: translateX(400px); opacity: 0; }
|
||||||
|
to { transform: translateX(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
@keyframes slideOut {
|
||||||
|
from { transform: translateX(0); opacity: 1; }
|
||||||
|
to { transform: translateX(400px); opacity: 0; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== TAB SWITCHING ====================
|
||||||
|
|
||||||
// Tab switching
|
// Tab switching
|
||||||
document.querySelectorAll('.tab').forEach(tab => {
|
document.querySelectorAll('.tab').forEach(tab => {
|
||||||
|
|||||||
@ -13,6 +13,61 @@
|
|||||||
<p class="subtitle">Download YouTube videos, transcribe and translate them</p>
|
<p class="subtitle">Download YouTube videos, transcribe and translate them</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- API Configuration Panel -->
|
||||||
|
<div class="api-config-panel" id="api-config-panel">
|
||||||
|
<div class="config-header">
|
||||||
|
<div class="config-title">
|
||||||
|
<span class="config-icon">🔐</span>
|
||||||
|
<h3>API Configuration</h3>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="toggle-config" class="btn-toggle" aria-label="Toggle configuration">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="config-content" id="config-content" style="display: none;">
|
||||||
|
<div class="config-status" id="config-status">
|
||||||
|
<span class="status-indicator status-disconnected"></span>
|
||||||
|
<span class="status-text">Not configured</span>
|
||||||
|
</div>
|
||||||
|
<form id="api-config-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="api-token">API Token</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="api-token"
|
||||||
|
placeholder="Enter your API token..."
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<button type="button" id="toggle-token-visibility" class="btn-icon" aria-label="Toggle visibility">
|
||||||
|
<svg id="eye-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="config-actions">
|
||||||
|
<button type="submit" class="btn btn-primary btn-small">
|
||||||
|
<span>Save & Test</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" id="clear-token" class="btn btn-secondary btn-small">
|
||||||
|
<span>Clear</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="config-help">
|
||||||
|
<p><strong>Where to get your token?</strong></p>
|
||||||
|
<p>Your API token is configured in the server's <code>.env</code> file (<code>API_TOKEN</code>). Contact your administrator if you don't have it.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Global API error display -->
|
||||||
|
<div id="api-error" class="result"></div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<button class="tab active" data-tab="download">Download</button>
|
<button class="tab active" data-tab="download">Download</button>
|
||||||
|
|||||||
179
public/style.css
179
public/style.css
@ -714,3 +714,182 @@ textarea::placeholder {
|
|||||||
color: #ccd6f6;
|
color: #ccd6f6;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==================== API CONFIGURATION PANEL ==================== */
|
||||||
|
.api-config-panel {
|
||||||
|
background: rgba(233, 69, 96, 0.1);
|
||||||
|
border: 1px solid rgba(233, 69, 96, 0.3);
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header:hover {
|
||||||
|
background: rgba(233, 69, 96, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-title h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #ccd6f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #8892b0;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toggle:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #e94560;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-toggle svg {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header.expanded .btn-toggle svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-content {
|
||||||
|
padding: 0 1.5rem 1.5rem;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-connected {
|
||||||
|
background: #10b981;
|
||||||
|
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-disconnected {
|
||||||
|
background: #ef4444;
|
||||||
|
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: #ccd6f6;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group input[type="password"],
|
||||||
|
.input-group input[type="text"] {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid #233554;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: #8892b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-color: #e94560;
|
||||||
|
color: #e94560;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="password"],
|
||||||
|
input[type="password"]:focus {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-help {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #8892b0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-help p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-help strong {
|
||||||
|
color: #ccd6f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-help code {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
color: #e94560;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|||||||
@ -74,11 +74,23 @@ const uploadVideo = multer({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(cors());
|
// CORS configuration - restrictive for production
|
||||||
|
const corsOptions = {
|
||||||
|
origin: process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : '*',
|
||||||
|
methods: ['GET', 'POST', 'OPTIONS'],
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
|
||||||
|
credentials: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use(cors(corsOptions));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
// Set permissive CSP for development
|
// Security headers
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
res.setHeader('X-Frame-Options', 'DENY');
|
||||||
|
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||||
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
'Content-Security-Policy',
|
'Content-Security-Policy',
|
||||||
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'"
|
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'"
|
||||||
@ -86,6 +98,42 @@ app.use((req, res, next) => {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// API Authentication Middleware
|
||||||
|
const authenticate = (req, res, next) => {
|
||||||
|
// Skip authentication for public endpoints
|
||||||
|
const publicEndpoints = ['/health', '/api'];
|
||||||
|
if (publicEndpoints.includes(req.path)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = req.headers['x-api-key'] || req.headers['authorization']?.replace('Bearer ', '');
|
||||||
|
const configuredKey = process.env.API_TOKEN;
|
||||||
|
|
||||||
|
if (!configuredKey) {
|
||||||
|
console.warn('⚠️ WARNING: API_TOKEN not configured in .env - API is UNSECURED!');
|
||||||
|
return next(); // Allow in development if not configured
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return res.status(401).json({
|
||||||
|
error: 'Unauthorized',
|
||||||
|
message: 'API key required. Provide X-API-Key header or Authorization: Bearer <token>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiKey !== configuredKey) {
|
||||||
|
return res.status(403).json({
|
||||||
|
error: 'Forbidden',
|
||||||
|
message: 'Invalid API key'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply authentication to all routes
|
||||||
|
app.use(authenticate);
|
||||||
|
|
||||||
// Serve static files (HTML interface)
|
// Serve static files (HTML interface)
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user