- 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
6.1 KiB
6.1 KiB
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
// 🔒 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 :
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 :
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
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
- Utilisateurs clients : Ne peuvent créer des salariés QUE dans leur propre organisation
- 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
- Rate limiting : Toujours en place (50 créations/heure par utilisateur)
- 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
# 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
# En tant que staff, ne pas sélectionner d'organisation
✗ Devrait échouer avec "no_organization" (400)
Test 3 : Staff - Création avec organisation inexistante
# Manuellement envoyer un employer_id invalide
✗ Devrait échouer avec "invalid_organization" (404)
Test 4 : Client - Création normale
# 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
# 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_idquand staff ET organisation sélectionnée - Validation client-side :
const orgOk = !isStaff || (isStaff && selectedOrg !== null);
✨ Améliorations futures possibles
- Ajouter un cache pour les vérifications staff (éviter une requête DB à chaque création)
- Ajouter un audit log des créations de salariés par staff dans d'autres organisations
- Permettre aux staff de créer des salariés dans plusieurs organisations en batch