# 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