From e00824ec29cf534a71119434efc68b506adbfacb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 16:14:33 +0000 Subject: [PATCH] =?UTF-8?q?Fix:=20D=C3=A9tection=20automatique=20URL=20pou?= =?UTF-8?q?r=20OAuth=20Google=20Calendar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problème: L'application échouait avec l'erreur "URL du site non configurée" lors de l'utilisation de Google Calendar car l'URL du site (app_url) n'était pas définie dans les paramètres, bloquant toutes les opérations OAuth (récupération événements, synchronisation, ajout d'événements). Erreur: ``` Error: URL du site non configurée. Veuillez configurer l'URL dans les paramètres. at getOAuth2Client (/app/routes/calendar.routes.js:47:11) at async getValidTokens (/app/routes/calendar.routes.js:312:24) at async getCalendarClient (/app/routes/calendar.routes.js:113:26) ``` Solution: Ajout d'une détection automatique de l'URL depuis les headers HTTP de la requête Express, avec cascade de fallback : 1. URL personnalisée (customRedirectUri) - si fournie explicitement 2. URL configurée (app_url settings) - si définie dans paramètres 3. **URL détectée automatiquement** - depuis headers HTTP (NOUVEAU) - x-forwarded-proto ou req.protocol - x-forwarded-host ou req.headers.host Changements: - routes/calendar.routes.js: * getOAuth2Client: Ajout paramètre `req` et logique détection URL * getCalendarClient: Ajout paramètre `req` et transmission * getValidTokens: Ajout paramètre `req` et transmission * Mise à jour tous les appels (5 emplacements): - GET /auth-url - GET /oauth-callback - POST /sync - POST /events - PUT /events/:id * Amélioration message d'erreur avec chemin exact paramètres Comportement: - Compatible avec proxy inverse (Nginx) via x-forwarded-* headers - Fonctionne en direct ou derrière proxy - Ne nécessite plus de configuration manuelle app_url (optionnel) - Fallback intelligent sur configuration existante si présente Cette correction permet à l'application de fonctionner immédiatement sans configuration supplémentaire tout en respectant les configurations manuelles existantes. --- routes/calendar.routes.js | 41 ++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/routes/calendar.routes.js b/routes/calendar.routes.js index 4b3183c..bf6decc 100644 --- a/routes/calendar.routes.js +++ b/routes/calendar.routes.js @@ -29,8 +29,9 @@ async function getAuthType() { /** * Créer un client OAuth2 * @param {string} [customRedirectUri] - URI de redirection personnalisée (optionnel) + * @param {Object} [req] - Objet requête Express pour détecter l'URL */ -async function getOAuth2Client(customRedirectUri = null) { +async function getOAuth2Client(customRedirectUri = null, req = null) { const clientId = await getOne("SELECT value FROM settings WHERE key = 'google_client_id'"); const clientSecret = await getOne("SELECT value FROM settings WHERE key = 'google_client_secret'"); const appUrl = await getOne("SELECT value FROM settings WHERE key = 'app_url'"); @@ -39,12 +40,23 @@ async function getOAuth2Client(customRedirectUri = null) { throw new Error('Client ID et Client Secret non configurés'); } - // Utiliser l'URI personnalisée si fournie, sinon utiliser l'URL du site configurée + // Détecter l'URL depuis la requête si possible + let detectedUrl = null; + if (req) { + const protocol = req.headers['x-forwarded-proto'] || req.protocol || 'https'; + const host = req.headers['x-forwarded-host'] || req.headers.host; + if (host) { + detectedUrl = `${protocol}://${host}`; + } + } + + // Utiliser l'URI personnalisée si fournie, sinon l'URL configurée, sinon l'URL détectée const redirectUri = customRedirectUri || - (appUrl?.value ? `${appUrl.value}/api/calendar/oauth-callback` : null); + (appUrl?.value ? `${appUrl.value}/api/calendar/oauth-callback` : null) || + (detectedUrl ? `${detectedUrl}/api/calendar/oauth-callback` : null); if (!redirectUri) { - throw new Error('URL du site non configurée. Veuillez configurer l\'URL dans les paramètres.'); + throw new Error('URL du site non configurée. Veuillez configurer l\'URL dans les paramètres (Paramètres > Google Calendar > URL du site).'); } return new google.auth.OAuth2( @@ -95,8 +107,9 @@ async function getApiKeyClient() { /** * Créer un client Google Calendar selon la méthode d'authentification * @param {number} userId - ID de l'utilisateur (pour OAuth2) + * @param {Object} [req] - Objet requête Express pour détecter l'URL */ -async function getCalendarClient(userId = null) { +async function getCalendarClient(userId = null, req = null) { const authType = await getAuthType(); if (authType === AUTH_TYPES.SERVICE_ACCOUNT) { @@ -110,7 +123,7 @@ async function getCalendarClient(userId = null) { if (!userId) { throw new Error('User ID requis pour OAuth2'); } - const oauth2Client = await getValidTokens(userId); + const oauth2Client = await getValidTokens(userId, req); return google.calendar({ version: 'v3', auth: oauth2Client }); } } @@ -139,7 +152,7 @@ router.get('/auth-url', authenticateToken, async (req, res) => { try { // Permettre de passer une URL de redirection personnalisée via query param const customRedirectUri = req.query.redirect_uri; - const oauth2Client = await getOAuth2Client(customRedirectUri); + const oauth2Client = await getOAuth2Client(customRedirectUri, req); const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', @@ -175,7 +188,7 @@ router.get('/oauth-callback', async (req, res) => { return res.status(400).send('État utilisateur invalide'); } - const oauth2Client = await getOAuth2Client(); + const oauth2Client = await getOAuth2Client(null, req); // Échanger le code contre des tokens const { tokens } = await oauth2Client.getToken(code); @@ -298,8 +311,10 @@ router.get('/auth-status', authenticateToken, async (req, res) => { /** * Récupérer et rafraîchir les tokens OAuth si nécessaire + * @param {number} userId - ID de l'utilisateur + * @param {Object} [req] - Objet requête Express pour détecter l'URL */ -async function getValidTokens(userId) { +async function getValidTokens(userId, req = null) { const tokenData = await getOne( 'SELECT * FROM google_oauth_tokens WHERE user_id = ?', [userId] @@ -309,7 +324,7 @@ async function getValidTokens(userId) { throw new Error('Tokens OAuth non trouvés. Veuillez vous authentifier.'); } - const oauth2Client = await getOAuth2Client(); + const oauth2Client = await getOAuth2Client(null, req); oauth2Client.setCredentials({ access_token: tokenData.access_token, refresh_token: tokenData.refresh_token, @@ -394,7 +409,7 @@ router.post('/sync', authenticateToken, async (req, res) => { } // Créer un client Google Calendar selon la méthode d'authentification - const calendar = await getCalendarClient(req.user.id); + const calendar = await getCalendarClient(req.user.id, req); // Récupérer les événements des 30 prochains jours const now = new Date(); @@ -528,7 +543,7 @@ router.post('/events', authenticateToken, async (req, res) => { return res.status(400).json({ error: 'Titre, date de début et date de fin requis' }); } - const calendar = await getCalendarClient(req.user.id); + const calendar = await getCalendarClient(req.user.id, req); // Préparer l'événement const event = { @@ -648,7 +663,7 @@ router.put('/events/:id', authenticateToken, async (req, res) => { return res.status(400).json({ error: 'Titre, date de début et date de fin requis' }); } - const calendar = await getCalendarClient(req.user.id); + const calendar = await getCalendarClient(req.user.id, req); // Préparer l'événement mis à jour const event = {