Merge pull request #67 from R0m1k3/claude/fix-stack-redeploy-error-011CV4ma2o8R5TFqh2v7L5hK

Fix: Restauration URL hardcodée pour OAuth Google Calendar
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-13 17:32:22 +01:00
commit 46bb1bf81f
2 files changed
+186 -36

No files matched your search

+174
View File
@@ -0,0 +1,174 @@
# 🚨 Résolution : Erreur 400 - Accès bloqué Google OAuth
## L'erreur que vous voyez
```
Accès bloqué : erreur d'autorisation
You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy
Error 400: invalid_request
```
## 🔍 Cause du problème
Cette erreur se produit quand votre **application OAuth est en mode "Testing"** dans Google Cloud Console et que l'utilisateur qui essaie de se connecter n'est pas dans la liste des testeurs autorisés.
## ✅ Solution complète (étape par étape)
### Option 1 : Ajouter l'utilisateur comme testeur (Solution rapide)
#### Étape 1 : Accéder à l'écran de consentement OAuth
1. Allez sur [Google Cloud Console](https://console.cloud.google.com)
2. Sélectionnez votre projet
3. Dans le menu de gauche : **APIs & Services** > **OAuth consent screen**
#### Étape 2 : Vérifier le statut de publication
Vous devriez voir :
```
Publishing status: Testing
```
#### Étape 3 : Ajouter des utilisateurs testeurs
1. Faites défiler jusqu'à la section **"Test users"**
2. Cliquez sur **"+ ADD USERS"**
3. Ajoutez l'adresse email : `michaelschal@gmail.com`
4. Cliquez sur **"SAVE"**
#### Étape 4 : Tester la connexion
1. Retournez sur NoteFlow
2. Dans **Administration** > **Google Calendar**
3. Cliquez sur **"Se connecter avec Google"**
4. ✅ Vous devriez maintenant pouvoir vous authentifier !
---
### Option 2 : Publier l'application (Solution permanente)
⚠️ **Attention** : Cette option est pour une utilisation publique. Si c'est pour un usage personnel, utilisez l'Option 1.
#### Étape 1 : Préparer l'application
1. Allez sur [Google Cloud Console](https://console.cloud.google.com)
2. **APIs & Services** > **OAuth consent screen**
#### Étape 2 : Compléter l'écran de consentement
Assurez-vous d'avoir rempli :
- ✅ App name (Nom de l'application) : `NoteFlow`
- ✅ User support email (Email de support)
- ✅ Developer contact information (Informations de contact)
- ✅ App logo (Optionnel mais recommandé)
- ✅ App domain (Domaine de l'application) : `note.ffnancy.fr`
- ✅ Authorized domains : `ffnancy.fr`
#### Étape 3 : Vérifier les Scopes (Permissions)
Dans l'onglet **"Scopes"**, assurez-vous d'avoir uniquement :
```
https://www.googleapis.com/auth/calendar.readonly
https://www.googleapis.com/auth/calendar.events
```
Ces scopes sont considérés comme "sensibles" mais **ne nécessitent PAS** de vérification Google pour un usage personnel.
#### Étape 4 : Publier l'application
1. Retournez sur l'onglet **"OAuth consent screen"**
2. Cliquez sur le bouton **"PUBLISH APP"**
3. Confirmez en cliquant sur **"CONFIRM"**
⚠️ **Note importante** :
- Votre app restera en status "In production" mais **"Not verified"**
- C'est **normal et acceptable** pour un usage personnel ou interne
- Les utilisateurs verront un avertissement mais pourront cliquer sur "Advanced" > "Go to NoteFlow (unsafe)" pour continuer
- Google ne nécessite une vérification que pour les apps publiques avec beaucoup d'utilisateurs
---
## 🔧 Vérifications supplémentaires
### 1. Vérifier l'URL de redirection
Dans **APIs & Services** > **Credentials** > Votre OAuth 2.0 Client ID :
L'URL de redirection doit être **EXACTEMENT** :
```
https://note.ffnancy.fr/api/calendar/oauth-callback
```
❌ **Erreurs courantes** :
- `https://note.ffnancy.fr/api/calendar/oauth-callback/` (slash à la fin)
- `http://note.ffnancy.fr/api/calendar/oauth-callback` (http au lieu de https)
- `https://www.note.ffnancy.fr/api/calendar/oauth-callback` (www en trop)
### 2. Vérifier l'API activée
Dans **APIs & Services** > **Library** :
1. Recherchez **"Google Calendar API"**
2. Vérifiez que le status est **"ENABLED"**
3. Si ce n'est pas le cas, cliquez sur **"ENABLE"**
### 3. Vérifier les credentials dans NoteFlow
Dans NoteFlow > **Administration** > **Google Calendar** :
1. Méthode d'authentification : **OAuth 2.0**
2. Client ID Google : Doit commencer par des chiffres et finir par `.apps.googleusercontent.com`
3. Client Secret : Doit commencer par `GOCSPX-`
4. URL du site : `https://note.ffnancy.fr` (automatiquement détectée)
---
## 📋 Résumé de la solution recommandée
### Pour un usage personnel (recommandé) :
```
1. Google Cloud Console > OAuth consent screen
2. Vérifier : Publishing status = Testing
3. Ajouter michaelschal@gmail.com dans "Test users"
4. SAVE
5. Retourner sur NoteFlow et se reconnecter
```
### Pour un usage public :
```
1. Compléter toutes les informations de l'écran de consentement
2. Publier l'application (PUBLISH APP)
3. Accepter que l'app soit "Not verified" (c'est normal)
4. Les utilisateurs devront cliquer sur "Advanced" > "Continue to NoteFlow"
```
---
## ❓ Questions fréquentes
### Pourquoi Google dit que mon app n'est pas conforme ?
En mode "Testing", seuls les utilisateurs testeurs listés peuvent accéder à l'application. C'est une mesure de sécurité de Google.
### Est-ce que je dois faire vérifier mon application par Google ?
**Non**, sauf si :
- Vous avez plus de 100 utilisateurs
- Vous publiez l'application publiquement
- Vous demandez des scopes sensibles en dehors du calendrier
Pour un usage personnel ou en petite équipe, le mode "Testing" avec des testeurs est suffisant.
### Combien de testeurs puis-je ajouter ?
Jusqu'à **100 testeurs** en mode "Testing". C'est largement suffisant pour un usage personnel ou une petite entreprise.
### L'authentification expire-t-elle ?
En mode "Testing", les tokens OAuth expirent après **7 jours**. En mode "Production", ils sont permanents (jusqu'à révocation).
---
## 🆘 Besoin d'aide ?
Si le problème persiste après avoir suivi ces étapes :
1. Vérifiez les logs de l'application : `docker-compose logs -f notes-todo-app`
2. Vérifiez que vous avez attendu 5-10 minutes après les changements dans Google Cloud Console
3. Essayez en navigation privée pour éliminer les problèmes de cache
4. Déconnectez-vous de Google Calendar dans NoteFlow et reconnectez-vous
---
## 📚 Documentation supplémentaire
- [Guide complet OAuth 2.0](./OAUTH_SETUP.md)
- [Configuration Google Calendar](./GUIDE_CONFIG_OAUTH.md)
- [Google OAuth 2.0 Documentation](https://developers.google.com/identity/protocols/oauth2)
+12 -36
View File
@@ -28,36 +28,17 @@ 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, req = null) {
async function getOAuth2Client() {
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'");
if (!clientId || !clientId.value || !clientSecret || !clientSecret.value) {
throw new Error('Client ID et Client Secret non configurés');
}
// 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) ||
(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 (Paramètres > Google Calendar > URL du site).');
}
// URL hardcodée pour note.ffnancy.fr
const redirectUri = 'https://note.ffnancy.fr/api/calendar/oauth-callback';
return new google.auth.OAuth2(
clientId.value,
@@ -107,9 +88,8 @@ 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, req = null) {
async function getCalendarClient(userId = null) {
const authType = await getAuthType();
if (authType === AUTH_TYPES.SERVICE_ACCOUNT) {
@@ -123,7 +103,7 @@ async function getCalendarClient(userId = null, req = null) {
if (!userId) {
throw new Error('User ID requis pour OAuth2');
}
const oauth2Client = await getValidTokens(userId, req);
const oauth2Client = await getValidTokens(userId);
return google.calendar({ version: 'v3', auth: oauth2Client });
}
}
@@ -150,9 +130,7 @@ router.get('/force-oauth2', async (req, res) => {
*/
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, req);
const oauth2Client = await getOAuth2Client();
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
@@ -188,7 +166,7 @@ router.get('/oauth-callback', async (req, res) => {
return res.status(400).send('État utilisateur invalide');
}
const oauth2Client = await getOAuth2Client(null, req);
const oauth2Client = await getOAuth2Client();
// Échanger le code contre des tokens
const { tokens } = await oauth2Client.getToken(code);
@@ -311,10 +289,8 @@ 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, req = null) {
async function getValidTokens(userId) {
const tokenData = await getOne(
'SELECT * FROM google_oauth_tokens WHERE user_id = ?',
[userId]
@@ -324,7 +300,7 @@ async function getValidTokens(userId, req = null) {
throw new Error('Tokens OAuth non trouvés. Veuillez vous authentifier.');
}
const oauth2Client = await getOAuth2Client(null, req);
const oauth2Client = await getOAuth2Client();
oauth2Client.setCredentials({
access_token: tokenData.access_token,
refresh_token: tokenData.refresh_token,
@@ -409,7 +385,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, req);
const calendar = await getCalendarClient(req.user.id);
// Récupérer les événements des 30 prochains jours
const now = new Date();
@@ -543,7 +519,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, req);
const calendar = await getCalendarClient(req.user.id);
// Préparer l'événement
const event = {
@@ -663,7 +639,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, req);
const calendar = await getCalendarClient(req.user.id);
// Préparer l'événement mis à jour
const event = {