J'ai :
All checks were successful
Build and Push Docker Images / Build Backend Image (push) Successful in 9m36s
Build and Push Docker Images / Build Frontend Image (push) Successful in 7m23s

Nettoyé les logs de debug dans backend/src/core/auth.py - supprimé tous les logger.info/warning de la fonction authenticate_user
Ajouté les tokens JWT à toutes les requêtes du player :
frontend/components/AudioPlayer.tsx : Ajouté Authorization header à loadWaveform()
frontend/components/AudioPlayer.tsx : Créé getAuthenticatedStreamUrl() qui ajoute le token en query param pour les <audio> et <a> tags
backend/src/api/routes/audio.py : Ajouté support du token en query param pour /stream et /download (compatibilité avec les tags HTML qui ne supportent pas les headers)
Le player devrait maintenant fonctionner entièrement avec l'authentification.
This commit is contained in:
2025-12-26 17:46:39 +01:00
parent aa252487b8
commit f05958ed36
3 changed files with 32 additions and 13 deletions

View File

@@ -1,13 +1,15 @@
"""Audio streaming and download endpoints.""" """Audio streaming and download endpoints."""
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request, Query
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from uuid import UUID from uuid import UUID
from pathlib import Path from pathlib import Path
from typing import Optional
from ...models.database import get_db from ...models.database import get_db
from ...models import crud from ...models import crud
from ...core.waveform_generator import get_waveform_data from ...core.waveform_generator import get_waveform_data
from ...core.auth import verify_token
from ...utils.logging import get_logger from ...utils.logging import get_logger
router = APIRouter() router = APIRouter()
@@ -18,6 +20,7 @@ logger = get_logger(__name__)
async def stream_audio( async def stream_audio(
track_id: UUID, track_id: UUID,
request: Request, request: Request,
token: Optional[str] = Query(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
"""Stream audio file with range request support. """Stream audio file with range request support.
@@ -28,6 +31,7 @@ async def stream_audio(
Args: Args:
track_id: Track UUID track_id: Track UUID
request: HTTP request request: HTTP request
token: Optional JWT token for authentication (for <audio> tag compatibility)
db: Database session db: Database session
Returns: Returns:
@@ -36,6 +40,9 @@ async def stream_audio(
Raises: Raises:
HTTPException: 404 if track not found or file doesn't exist HTTPException: 404 if track not found or file doesn't exist
""" """
# Verify authentication via query parameter for <audio> tag
if token:
verify_token(token)
track = crud.get_track_by_id(db, track_id) track = crud.get_track_by_id(db, track_id)
if not track: if not track:
@@ -79,12 +86,14 @@ async def stream_audio(
@router.get("/download/{track_id}") @router.get("/download/{track_id}")
async def download_audio( async def download_audio(
track_id: UUID, track_id: UUID,
token: Optional[str] = Query(None),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
"""Download audio file. """Download audio file.
Args: Args:
track_id: Track UUID track_id: Track UUID
token: Optional JWT token for authentication (for <a> tag compatibility)
db: Database session db: Database session
Returns: Returns:
@@ -93,6 +102,9 @@ async def download_audio(
Raises: Raises:
HTTPException: 404 if track not found or file doesn't exist HTTPException: 404 if track not found or file doesn't exist
""" """
# Verify authentication via query parameter for <a> tag
if token:
verify_token(token)
track = crud.get_track_by_id(db, track_id) track = crud.get_track_by_id(db, track_id)
if not track: if not track:

View File

@@ -100,22 +100,13 @@ def authenticate_user(email: str, password: str) -> Optional[dict]:
Returns: Returns:
User data if authenticated, None otherwise User data if authenticated, None otherwise
""" """
# Debug logging (remove in production)
logger.info(f"Auth attempt - Email provided: '{email}'")
logger.info(f"Auth attempt - Expected email: '{settings.ADMIN_EMAIL}'")
logger.info(f"Auth attempt - Email match: {email == settings.ADMIN_EMAIL}")
logger.info(f"Auth attempt - Password length: {len(password)}")
logger.info(f"Auth attempt - Expected password length: {len(settings.ADMIN_PASSWORD)}")
# Check against admin credentials from environment # Check against admin credentials from environment
if email == settings.ADMIN_EMAIL and password == settings.ADMIN_PASSWORD: if email == settings.ADMIN_EMAIL and password == settings.ADMIN_PASSWORD:
logger.info(f"✅ Authentication successful for {email}")
return { return {
"email": email, "email": email,
"role": "admin" "role": "admin"
} }
logger.warning(f"❌ Authentication failed for {email}")
return None return None

View File

@@ -79,8 +79,15 @@ export default function AudioPlayer({ track, isPlaying, onPlayingChange }: Audio
const loadWaveform = async (trackId: string) => { const loadWaveform = async (trackId: string) => {
setIsLoadingWaveform(true) setIsLoadingWaveform(true)
try { try {
const token = localStorage.getItem('access_token')
const headers: HeadersInit = {}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const response = await fetch( const response = await fetch(
`${getApiUrl()}/api/audio/waveform/${trackId}` `${getApiUrl()}/api/audio/waveform/${trackId}`,
{ headers }
) )
if (response.ok) { if (response.ok) {
const data = await response.json() const data = await response.json()
@@ -159,10 +166,19 @@ export default function AudioPlayer({ track, isPlaying, onPlayingChange }: Audio
const progress = duration > 0 ? (currentTime / duration) * 100 : 0 const progress = duration > 0 ? (currentTime / duration) * 100 : 0
const getAuthenticatedStreamUrl = (trackId: string) => {
const token = localStorage.getItem('access_token')
const baseUrl = `${getApiUrl()}/api/audio/stream/${trackId}`
if (token) {
return `${baseUrl}?token=${encodeURIComponent(token)}`
}
return baseUrl
}
return ( return (
<div className="bg-gray-50 border-t border-gray-300 shadow-lg" style={{ height: '80px' }}> <div className="bg-gray-50 border-t border-gray-300 shadow-lg" style={{ height: '80px' }}>
{/* Hidden audio element */} {/* Hidden audio element */}
{track && <audio ref={audioRef} src={`${getApiUrl()}/api/audio/stream/${track.id}`} />} {track && <audio ref={audioRef} src={getAuthenticatedStreamUrl(track.id)} />}
<div className="h-full flex items-center gap-3 px-4"> <div className="h-full flex items-center gap-3 px-4">
{/* Play/Pause button */} {/* Play/Pause button */}
@@ -301,7 +317,7 @@ export default function AudioPlayer({ track, isPlaying, onPlayingChange }: Audio
{/* Download button */} {/* Download button */}
{track && ( {track && (
<a <a
href={`${getApiUrl()}/api/audio/download/${track.id}`} href={getAuthenticatedStreamUrl(track.id).replace('/stream/', '/download/')}
download download
className="w-8 h-8 flex items-center justify-center text-gray-600 hover:text-gray-900 transition-colors rounded hover:bg-gray-200 flex-shrink-0" className="w-8 h-8 flex items-center justify-center text-gray-600 hover:text-gray-900 transition-colors rounded hover:bg-gray-200 flex-shrink-0"
aria-label="Download" aria-label="Download"