mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
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:
2 files changed
+186
-36
No files matched your search
@@ -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
@@ -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 = {
|
||||
|
||||
Reference in new issue
Block a user