mirror of
https://github.com/movixcorp/MovixOpenSource.git
synced 2026-08-04 21:22:19 +00:00
fix: add TMDB proxy endpoint and axios interceptor to hide API key
- Add GET /api/tmdb-proxy/* backend route that proxies TMDB API calls using the server-side TMDB_API_KEY (process.env), keeping it hidden from clients - Add frontend axios interceptor (registerTmdbProxyInterceptor) that automatically rewrites any request to api.themoviedb.org/3/ through the backend proxy, stripping the api_key parameter from the URL - Register interceptor on both default axios and api instances
This commit is contained in:
parent
1b41d85795
commit
d6e72a7e18
4 changed files with 105 additions and 1 deletions
|
|
@ -842,6 +842,54 @@ router.get('/SenpaiStream/tv/cache/:tmdbId', async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /tmdb-proxy/* -- Generic proxy to TMDB API (hides API key from client)
|
||||
// ---------------------------------------------------------------------------
|
||||
router.get('/tmdb-proxy/*', async (req, res) => {
|
||||
try {
|
||||
// Extract the TMDB API path from the wildcard
|
||||
// e.g. /api/tmdb-proxy/genre/movie/list -> genre/movie/list
|
||||
const tmdbPath = req.params[0] || req.url.replace(/^\/+tmdb-proxy\//, '');
|
||||
if (!tmdbPath) {
|
||||
return res.status(400).json({ error: 'TMDB path is required' });
|
||||
}
|
||||
|
||||
// Forward query parameters (except api_key which would be passed by the client)
|
||||
const { api_key, ...forwardParams } = req.query;
|
||||
const params = new URLSearchParams();
|
||||
params.set('api_key', TMDB_API_KEY);
|
||||
params.set('language', forwardParams.language || 'fr-FR');
|
||||
for (const [key, value] of Object.entries(forwardParams)) {
|
||||
if (key !== 'language') {
|
||||
params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
const url = `${TMDB_API_URL}/${tmdbPath}?${params.toString()}`;
|
||||
const response = await axios.get(url, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Movix/1.0',
|
||||
},
|
||||
});
|
||||
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||
res.setHeader('Cache-Control', 'public, max-age=300'); // 5min cache
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
return res.status(error.response.status).json({
|
||||
error: 'TMDB API error',
|
||||
status: error.response.status,
|
||||
});
|
||||
}
|
||||
console.error('[TMDB Proxy] Error:', error.message);
|
||||
res.status(502).json({ error: 'TMDB proxy failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Exports
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// TMDB Configuration
|
||||
export const TMDB_API_KEY = import.meta.env.VITE_TMDB_API_KEY || '';
|
||||
export const TMDB_API_URL = 'https://api.themoviedb.org/3';
|
||||
export const TMDB_IMAGE_URL = 'https://image.tmdb.org/t/p';
|
||||
export const TMDB_IMAGE_URL = 'https://image.tmdb.org/t/p';
|
||||
|
|
@ -7,6 +7,8 @@ import App from './App.tsx'
|
|||
import axios from 'axios'
|
||||
import { api } from './services/api'
|
||||
import { registerBlockDetection } from './services/blockDetection'
|
||||
import { registerTmdbProxyInterceptor } from './services/tmdbProxyInterceptor'
|
||||
import { MAIN_API } from './config/runtime'
|
||||
import './index.css'
|
||||
import './styles/light-mode.css'
|
||||
|
||||
|
|
@ -126,6 +128,10 @@ startMovixConsoleSafetyWarning();
|
|||
registerBlockDetection(axios)
|
||||
registerBlockDetection(api)
|
||||
|
||||
// Register TMDB proxy interceptor to hide API key from client
|
||||
registerTmdbProxyInterceptor(axios, MAIN_API)
|
||||
registerTmdbProxyInterceptor(api, MAIN_API)
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ErrorBoundary>
|
||||
|
|
|
|||
50
src/services/tmdbProxyInterceptor.ts
Normal file
50
src/services/tmdbProxyInterceptor.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
const TMDB_HOST = 'api.themoviedb.org';
|
||||
const TMDB_PATH_PREFIX = '/3/';
|
||||
|
||||
/**
|
||||
* Intercepteur axios qui redirige les appels TMDB vers le proxy backend
|
||||
* pour masquer la clé API côté client.
|
||||
*
|
||||
* Au lieu d'appeler https://api.themoviedb.org/3/genre/movie/list?api_key=XXX,
|
||||
* la requête est redirigée vers /api/tmdb-proxy/genre/movie/list?api_key=XXX
|
||||
* et le backend ajoute sa propre clé TMDB (process.env.TMDB_API_KEY).
|
||||
*
|
||||
* La clé API envoyée par le frontend est ignorée par le backend.
|
||||
*/
|
||||
export function registerTmdbProxyInterceptor(axiosInstance: AxiosInstance, mainApiUrl: string) {
|
||||
return axiosInstance.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
if (!config.url || !config.baseURL) return config;
|
||||
|
||||
let fullUrl: string;
|
||||
try {
|
||||
fullUrl = new URL(config.url, config.baseURL).toString();
|
||||
} catch {
|
||||
return config;
|
||||
}
|
||||
|
||||
// Ne toucher qu'aux requêtes vers api.themoviedb.org
|
||||
const parsed = new URL(fullUrl);
|
||||
if (parsed.hostname !== TMDB_HOST || !parsed.pathname.startsWith(TMDB_PATH_PREFIX)) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// Extraire le chemin TMDB (ex: /3/genre/movie/list -> genre/movie/list)
|
||||
const tmdbPath = parsed.pathname.replace(TMDB_PATH_PREFIX, '');
|
||||
const proxyUrl = `${mainApiUrl}/api/tmdb-proxy/${tmdbPath}`;
|
||||
|
||||
// Reconstruire les paramètres, en supprimant api_key (le backend ajoute la sienne)
|
||||
const params = new URLSearchParams(parsed.search);
|
||||
params.delete('api_key');
|
||||
|
||||
const paramsString = params.toString();
|
||||
config.url = paramsString ? `${proxyUrl}?${paramsString}` : proxyUrl;
|
||||
config.baseURL = ''; // Éviter que axios re-prépende baseURL
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue