Complete family planning application with: - React frontend with TypeScript - Node.js/Express backend with TypeScript - Python ingestion service for document processing - Planning ingestion service with LLM integration - Shared UI components and type definitions - OAuth integration for calendar synchronization - Comprehensive documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
283 lines
8.1 KiB
Markdown
283 lines
8.1 KiB
Markdown
# Corrections apportées au système OAuth
|
|
|
|
## Problème initial
|
|
|
|
**Erreur rencontrée** :
|
|
```
|
|
Accès bloqué : erreur d'autorisation
|
|
Required parameter is missing: response_type
|
|
Erreur 400 : invalid_request
|
|
flowName=GeneralOAuthFlow
|
|
```
|
|
|
|
**Cause** : L'URL OAuth générée était incomplète. Elle ne contenait que l'URL de base et le paramètre `state`, mais manquait tous les paramètres requis par OAuth 2.0 :
|
|
- `client_id`
|
|
- `redirect_uri`
|
|
- `response_type` ⚠️ **CRITIQUE**
|
|
- `scope`
|
|
- `access_type` (pour Google)
|
|
- `prompt` (pour Google)
|
|
|
|
## Corrections effectuées
|
|
|
|
### 1. Fichier manquant
|
|
✅ **Copié** `calendar.ts` du mauvais emplacement vers le bon :
|
|
- Source : `C:\Users\philh\OneDrive\Documents\Codes\backend\src\routes\calendar.ts`
|
|
- Destination : `C:\Users\philh\OneDrive\Documents\Codes\family-planner\backend\src\routes\calendar.ts`
|
|
|
|
### 2. URL OAuth complète (ligne 48-82)
|
|
|
|
**AVANT** (incorrect) :
|
|
```typescript
|
|
const authUrl = `${baseUrls[provider]}?state=${encodeURIComponent(state)}`;
|
|
```
|
|
|
|
**APRÈS** (correct) :
|
|
```typescript
|
|
// Google
|
|
const params = new URLSearchParams({
|
|
client_id: config.clientId,
|
|
redirect_uri: config.redirectUri,
|
|
response_type: "code", // ⚠️ PARAMÈTRE MANQUANT
|
|
scope: config.scope,
|
|
state: state,
|
|
access_type: "offline",
|
|
prompt: "consent"
|
|
});
|
|
authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
|
|
// Outlook
|
|
const params = new URLSearchParams({
|
|
client_id: config.clientId,
|
|
redirect_uri: config.redirectUri,
|
|
response_type: "code", // ⚠️ PARAMÈTRE MANQUANT
|
|
scope: config.scope,
|
|
state: state,
|
|
response_mode: "query"
|
|
});
|
|
```
|
|
|
|
### 3. Configuration centralisée
|
|
|
|
Ajout d'un objet `OAUTH_CONFIG` avec :
|
|
- Client IDs
|
|
- Redirect URIs
|
|
- Scopes appropriés pour chaque provider
|
|
|
|
### 4. Variables d'environnement
|
|
|
|
Ajout dans `backend/.env` :
|
|
```env
|
|
# OAuth Configuration - Google Calendar
|
|
GOOGLE_CLIENT_ID=your_google_client_id_here
|
|
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
|
|
GOOGLE_REDIRECT_URI=http://localhost:5000/api/calendar/oauth/callback
|
|
|
|
# OAuth Configuration - Outlook/Microsoft 365
|
|
OUTLOOK_CLIENT_ID=your_outlook_client_id_here
|
|
OUTLOOK_CLIENT_SECRET=your_outlook_client_secret_here
|
|
OUTLOOK_REDIRECT_URI=http://localhost:5000/api/calendar/oauth/callback
|
|
```
|
|
|
|
### 5. Documentation complète
|
|
|
|
Créé `OAUTH_SETUP.md` avec :
|
|
- Instructions étape par étape pour Google Cloud Console
|
|
- Instructions étape par étape pour Azure Portal
|
|
- Configuration des scopes
|
|
- Résolution des problèmes courants
|
|
- Mode développement sans OAuth
|
|
|
|
## Prochaines étapes requises
|
|
|
|
### Étape 1 : Configurer les identifiants OAuth (VOUS)
|
|
|
|
Suivez le guide `OAUTH_SETUP.md` pour :
|
|
1. ✅ Créer un projet Google Cloud
|
|
2. ✅ Activer l'API Google Calendar
|
|
3. ✅ Créer les identifiants OAuth 2.0
|
|
4. ✅ Copier le Client ID et Secret dans `.env`
|
|
5. ✅ Répéter pour Azure AD (Outlook)
|
|
|
|
### Étape 2 : Implémenter l'échange de code (DÉVELOPPEMENT)
|
|
|
|
Actuellement, le endpoint `/oauth/complete` crée une connexion factice. Il faut :
|
|
|
|
```typescript
|
|
// Échanger le code d'autorisation contre un access token
|
|
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
code: code,
|
|
client_id: OAUTH_CONFIG.google.clientId,
|
|
client_secret: OAUTH_CONFIG.google.clientSecret,
|
|
redirect_uri: OAUTH_CONFIG.google.redirectUri,
|
|
grant_type: "authorization_code"
|
|
})
|
|
});
|
|
|
|
const tokens = await tokenResponse.json();
|
|
// tokens.access_token, tokens.refresh_token, tokens.expires_in
|
|
```
|
|
|
|
### Étape 3 : Stocker les tokens de manière sécurisée
|
|
|
|
**⚠️ NE JAMAIS stocker en clair** :
|
|
- Utiliser un chiffrement AES-256-GCM (comme dans l'architecture PRONOTE)
|
|
- Stocker la clé de chiffrement dans une variable d'environnement
|
|
- Stocker les tokens dans une base de données ou fichier chiffré
|
|
|
|
### Étape 4 : Implémenter le refresh token
|
|
|
|
Les access tokens expirent (généralement 1h). Implémenter :
|
|
```typescript
|
|
async function refreshAccessToken(refreshToken: string, provider: CalendarProvider) {
|
|
const config = OAUTH_CONFIG[provider];
|
|
const response = await fetch(tokenEndpoint, {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
refresh_token: refreshToken,
|
|
client_id: config.clientId,
|
|
client_secret: config.clientSecret,
|
|
grant_type: "refresh_token"
|
|
})
|
|
});
|
|
return response.json();
|
|
}
|
|
```
|
|
|
|
### Étape 5 : Récupérer les événements du calendrier
|
|
|
|
Une fois l'access token obtenu :
|
|
|
|
**Google Calendar API** :
|
|
```typescript
|
|
const events = await fetch(
|
|
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`
|
|
}
|
|
}
|
|
);
|
|
```
|
|
|
|
**Microsoft Graph API** :
|
|
```typescript
|
|
const events = await fetch(
|
|
"https://graph.microsoft.com/v1.0/me/calendar/events",
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`
|
|
}
|
|
}
|
|
);
|
|
```
|
|
|
|
### Étape 6 : Normaliser les événements
|
|
|
|
Créer un format commun pour les événements des deux providers :
|
|
```typescript
|
|
type NormalizedEvent = {
|
|
id: string;
|
|
title: string;
|
|
start: string; // ISO8601
|
|
end: string; // ISO8601
|
|
description?: string;
|
|
location?: string;
|
|
attendees?: string[];
|
|
source: "google" | "outlook";
|
|
originalId: string;
|
|
};
|
|
```
|
|
|
|
## État actuel
|
|
|
|
✅ **Corrigé** : Erreur OAuth "response_type missing"
|
|
✅ **Ajouté** : Configuration complète des paramètres OAuth
|
|
✅ **Documenté** : Guide de setup détaillé
|
|
⏳ **À faire** : Configuration des identifiants OAuth par l'utilisateur
|
|
⏳ **À faire** : Implémentation complète du flow OAuth (échange de code, stockage sécurisé, refresh)
|
|
⏳ **À faire** : Récupération et synchronisation des événements
|
|
|
|
## Sécurité - Points d'attention
|
|
|
|
### ⚠️ CRITIQUE - À corriger immédiatement
|
|
|
|
1. **Clé OpenAI exposée dans Git** :
|
|
- La clé `sk-proj-efaTQ8cicJYU7k8RG...` est visible dans `.env`
|
|
- **ACTION REQUISE** : Révoquer immédiatement sur https://platform.openai.com/api-keys
|
|
|
|
2. **Tokens en mémoire** :
|
|
- Actuellement, les connexions sont stockées dans une `Map` en mémoire
|
|
- Problème : perte des données au redémarrage du serveur
|
|
- **Solution** : Stocker dans une base de données chiffrée
|
|
|
|
3. **Secrets en clair** :
|
|
- Les client secrets doivent être dans `.env` (qui doit être dans `.gitignore`)
|
|
- **Vérifier** que `.env` est bien ignoré par Git
|
|
|
|
### Recommandations de sécurité
|
|
|
|
```typescript
|
|
// Implémenter le chiffrement des tokens
|
|
import crypto from "crypto";
|
|
|
|
const ENCRYPTION_KEY = process.env.TOKEN_ENCRYPTION_KEY; // 32 bytes
|
|
const algorithm = "aes-256-gcm";
|
|
|
|
function encryptToken(token: string): { encrypted: string; iv: string; authTag: string } {
|
|
const iv = crypto.randomBytes(16);
|
|
const cipher = crypto.createCipheriv(algorithm, Buffer.from(ENCRYPTION_KEY, "hex"), iv);
|
|
let encrypted = cipher.update(token, "utf8", "hex");
|
|
encrypted += cipher.final("hex");
|
|
const authTag = cipher.getAuthTag();
|
|
return {
|
|
encrypted,
|
|
iv: iv.toString("hex"),
|
|
authTag: authTag.toString("hex")
|
|
};
|
|
}
|
|
|
|
function decryptToken(encrypted: string, iv: string, authTag: string): string {
|
|
const decipher = crypto.createDecipheriv(
|
|
algorithm,
|
|
Buffer.from(ENCRYPTION_KEY, "hex"),
|
|
Buffer.from(iv, "hex")
|
|
);
|
|
decipher.setAuthTag(Buffer.from(authTag, "hex"));
|
|
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
decrypted += decipher.final("utf8");
|
|
return decrypted;
|
|
}
|
|
```
|
|
|
|
## Test de la correction
|
|
|
|
Pour tester que l'erreur OAuth est corrigée :
|
|
|
|
1. Redémarrez le backend :
|
|
```bash
|
|
cd backend
|
|
npm run dev
|
|
```
|
|
|
|
2. Dans le frontend, testez la connexion Google/Outlook
|
|
|
|
3. Vérifiez que l'URL de redirection contient maintenant tous les paramètres :
|
|
```
|
|
https://accounts.google.com/o/oauth2/v2/auth?
|
|
client_id=...&
|
|
redirect_uri=...&
|
|
response_type=code& ← AJOUTÉ
|
|
scope=...&
|
|
state=...&
|
|
access_type=offline& ← AJOUTÉ
|
|
prompt=consent ← AJOUTÉ
|
|
```
|
|
|
|
4. Vous devriez voir la page de consentement Google/Microsoft (au lieu de l'erreur 400)
|
|
|
|
**Note** : Vous aurez une erreur de "client_id invalide" tant que vous n'aurez pas configuré les vrais identifiants OAuth dans le `.env`.
|