espace-paie-odentas/FIX_STAFF_SALARIE_CREATION.md
odentas 2760ea2b39 fix: Corriger création salarié en mode staff avec employer_id
- Réorganiser la logique de sécurité pour distinguer staff/client
- Staff: accepter employer_id fourni et vérifier existence de l'org
- Client: utiliser l'org associée et bloquer toute tentative cross-org
- Améliorer les logs pour identifier le type d'utilisateur
- Corriger le retour no_organization inapproprié pour les staff
2025-11-13 20:48:14 +01:00

174 lines
6.1 KiB
Markdown

# Fix - Création de salarié en mode Staff
## 🐛 Problème identifié
En mode staff, lors de la création d'un nouveau salarié, l'erreur `no_organization` apparaissait même après avoir sélectionné une organisation dans le menu déroulant.
### Cause racine
La fonction `resolveActiveOrg()` retourne `null` pour les utilisateurs staff (comportement attendu), mais l'API `/api/salaries` POST rejetait toute demande avec un `null`, même quand l'utilisateur staff fournissait un `employer_id` valide dans le body de la requête.
La logique de sécurité ne distinguait pas :
- **Utilisateurs clients** : doivent avoir une organisation associée via `organization_members`
- **Utilisateurs staff** : peuvent créer des salariés pour n'importe quelle organisation en fournissant un `employer_id`
## ✅ Solution implémentée
### Modifications dans `/app/api/salaries/route.ts`
La logique de sécurité a été réorganisée pour gérer les deux cas différemment :
#### 1. Vérification du statut staff en premier
```typescript
// 🔒 SÉCURITÉ : Vérifier si l'utilisateur est staff
let isStaff = false;
try {
const { data: staffRow } = await sbAuth.from('staff_users').select('is_staff').eq('user_id', user.id).maybeSingle();
isStaff = !!staffRow?.is_staff;
} catch {
// Fallback sur metadata
const userMeta = user?.user_metadata || {};
const appMeta = user?.app_metadata || {};
isStaff = Boolean((userMeta.is_staff === true || userMeta.role === 'staff') || (Array.isArray(appMeta.roles) && appMeta.roles.includes('staff')));
}
```
#### 2. Logique différenciée selon le type d'utilisateur
**Pour les utilisateurs STAFF :**
```typescript
if (isStaff) {
// Accepter l'employer_id fourni dans la requête
orgId = body.employer_id || null;
if (!orgId) {
return NextResponse.json(
{ ok: false, error: 'no_organization', message: 'Vous devez sélectionner une organisation' },
{ status: 400 }
);
}
// Vérifier que l'organisation existe
const { data: orgExists } = await sbAuth
.from('organizations')
.select('id')
.eq('id', orgId)
.maybeSingle();
if (!orgExists) {
return NextResponse.json(
{ ok: false, error: 'invalid_organization', message: 'Organisation introuvable' },
{ status: 404 }
);
}
}
```
**Pour les utilisateurs CLIENTS :**
```typescript
else {
// Utiliser l'organisation associée à l'utilisateur
const userOrgId = await resolveActiveOrg(sbAuth);
if (!userOrgId) {
return NextResponse.json(
{ ok: false, error: 'no_organization', message: 'Aucune organisation associée à votre compte' },
{ status: 403 }
);
}
// 🔒 SÉCURITÉ CRITIQUE : Vérifier que l'employer_id fourni correspond à l'org de l'utilisateur
if (body.employer_id && body.employer_id !== userOrgId) {
return NextResponse.json(
{ ok: false, error: 'unauthorized_organization', message: 'Vous ne pouvez pas créer un salarié dans cette organisation' },
{ status: 403 }
);
}
orgId = userOrgId;
}
```
#### 3. Logs améliorés
```typescript
console.log('✅ [SÉCURITÉ] Vérifications réussies');
console.log(' - Utilisateur:', user.email);
console.log(' - Type:', isStaff ? 'STAFF' : 'CLIENT');
console.log(' - Organisation:', orgId);
console.log(' - Email salarié validé:', body.email_salarie);
console.log(' - Rate limit:', `${rateLimitMap.get(user.id)?.count || 0}/${RATE_LIMIT_MAX}`);
```
## 🔒 Sécurité
### Garanties maintenues
1. **Utilisateurs clients** : Ne peuvent créer des salariés **QUE** dans leur propre organisation
2. **Utilisateurs staff** : Peuvent créer des salariés pour n'importe quelle organisation, mais :
- Doivent obligatoirement sélectionner une organisation (pas de création sans org)
- L'organisation doit exister dans la base de données
3. **Rate limiting** : Toujours en place (50 créations/heure par utilisateur)
4. **Authentification** : Requise pour tous les utilisateurs
### Erreurs possibles
| Code d'erreur | Status | Cas |
|---------------|--------|-----|
| `unauthorized` | 401 | Utilisateur non authentifié |
| `rate_limit_exceeded` | 429 | Limite de 50 créations/heure dépassée |
| `no_organization` | 400 (staff) / 403 (client) | Aucune organisation sélectionnée ou associée |
| `invalid_organization` | 404 | Organisation sélectionnée introuvable (staff) |
| `unauthorized_organization` | 403 | Tentative de créer un salarié dans une autre org (client) |
## 🧪 Tests à effectuer
### Test 1 : Staff - Création avec organisation valide
```bash
# En tant que staff, sélectionner une organisation et créer un salarié
✓ Devrait réussir
✓ Le salarié devrait être associé à l'organisation sélectionnée
```
### Test 2 : Staff - Création sans sélectionner d'organisation
```bash
# En tant que staff, ne pas sélectionner d'organisation
✗ Devrait échouer avec "no_organization" (400)
```
### Test 3 : Staff - Création avec organisation inexistante
```bash
# Manuellement envoyer un employer_id invalide
✗ Devrait échouer avec "invalid_organization" (404)
```
### Test 4 : Client - Création normale
```bash
# En tant que client, créer un salarié
✓ Devrait réussir
✓ Le salarié devrait être dans l'organisation du client
```
### Test 5 : Client - Tentative de création dans une autre org
```bash
# Manuellement envoyer un employer_id d'une autre organisation
✗ Devrait échouer avec "unauthorized_organization" (403)
```
## 📝 Fichiers modifiés
- `/app/api/salaries/route.ts` - Logique de sécurité réorganisée
## 📝 Fichiers non modifiés (déjà corrects)
- `/app/(app)/salaries/nouveau/page.tsx` - Frontend déjà correct
- Charge la liste des organisations pour les staff
- Envoie `employer_id` quand staff ET organisation sélectionnée
- Validation client-side : `const orgOk = !isStaff || (isStaff && selectedOrg !== null);`
## ✨ Améliorations futures possibles
1. Ajouter un cache pour les vérifications staff (éviter une requête DB à chaque création)
2. Ajouter un audit log des créations de salariés par staff dans d'autres organisations
3. Permettre aux staff de créer des salariés dans plusieurs organisations en batch