Fix: Détection automatique URL pour OAuth Google Calendar

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.
This commit is contained in:
Claude committed 2025-11-13 16:14:33 +00:00
1 parent 9df41f51f1
commit e00824ec29
1 file changed
+28 -13
+28 -13
View File
@@ -29,8 +29,9 @@ async function getAuthType() {
/** /**
* Créer un client OAuth2 * Créer un client OAuth2
* @param {string} [customRedirectUri] - URI de redirection personnalisée (optionnel) * @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 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 clientSecret = await getOne("SELECT value FROM settings WHERE key = 'google_client_secret'");
const appUrl = await getOne("SELECT value FROM settings WHERE key = 'app_url'"); 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'); 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 || 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) { 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( return new google.auth.OAuth2(
@@ -95,8 +107,9 @@ async function getApiKeyClient() {
/** /**
* Créer un client Google Calendar selon la méthode d'authentification * Créer un client Google Calendar selon la méthode d'authentification
* @param {number} userId - ID de l'utilisateur (pour OAuth2) * @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(); const authType = await getAuthType();
if (authType === AUTH_TYPES.SERVICE_ACCOUNT) { if (authType === AUTH_TYPES.SERVICE_ACCOUNT) {
@@ -110,7 +123,7 @@ async function getCalendarClient(userId = null) {
if (!userId) { if (!userId) {
throw new Error('User ID requis pour OAuth2'); 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 }); return google.calendar({ version: 'v3', auth: oauth2Client });
} }
} }
@@ -139,7 +152,7 @@ router.get('/auth-url', authenticateToken, async (req, res) => {
try { try {
// Permettre de passer une URL de redirection personnalisée via query param // Permettre de passer une URL de redirection personnalisée via query param
const customRedirectUri = req.query.redirect_uri; const customRedirectUri = req.query.redirect_uri;
const oauth2Client = await getOAuth2Client(customRedirectUri); const oauth2Client = await getOAuth2Client(customRedirectUri, req);
const authUrl = oauth2Client.generateAuthUrl({ const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline', access_type: 'offline',
@@ -175,7 +188,7 @@ router.get('/oauth-callback', async (req, res) => {
return res.status(400).send('État utilisateur invalide'); return res.status(400).send('État utilisateur invalide');
} }
const oauth2Client = await getOAuth2Client(); const oauth2Client = await getOAuth2Client(null, req);
// Échanger le code contre des tokens // Échanger le code contre des tokens
const { tokens } = await oauth2Client.getToken(code); 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 * 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( const tokenData = await getOne(
'SELECT * FROM google_oauth_tokens WHERE user_id = ?', 'SELECT * FROM google_oauth_tokens WHERE user_id = ?',
[userId] [userId]
@@ -309,7 +324,7 @@ async function getValidTokens(userId) {
throw new Error('Tokens OAuth non trouvés. Veuillez vous authentifier.'); throw new Error('Tokens OAuth non trouvés. Veuillez vous authentifier.');
} }
const oauth2Client = await getOAuth2Client(); const oauth2Client = await getOAuth2Client(null, req);
oauth2Client.setCredentials({ oauth2Client.setCredentials({
access_token: tokenData.access_token, access_token: tokenData.access_token,
refresh_token: tokenData.refresh_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 // 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 // Récupérer les événements des 30 prochains jours
const now = new Date(); 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' }); 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 // Préparer l'événement
const event = { 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' }); 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 // Préparer l'événement mis à jour
const event = { const event = {