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

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

  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

# 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_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