Gestion des Agents (Backoffice)¶
Interface d'administration permettant aux administrateurs et gestionnaires de créer et gérer les comptes agents pour l'application mobile.
Vue d'ensemble¶
| Caractéristique | Détails |
|---|---|
| URL | /agents.php |
| Permissions | Admin, Gestionnaire |
| Base de données | Table agents |
| API Endpoints | /api/admin/agents/* |
Permissions¶
Matrice de permissions¶
| Action | Super Admin | Admin Collectivité | Admin Mairie | Gestionnaire | Lecteur |
|---|---|---|---|---|---|
| Voir agents | Tous | Sa collectivité | Sa mairie | Sa mairie | ❌ |
| Créer agent | ✅ | ✅ | ✅ | Uniquement rôle "agent" | ❌ |
| Créer responsable/admin | ✅ | ✅ | ✅ | ❌ | ❌ |
| Modifier agent | ✅ | ✅ | ✅ | Uniquement agents | ❌ |
| Réinitialiser mot de passe | ✅ | ✅ | ✅ | Uniquement agents | ❌ |
| Désactiver agent | ✅ | ✅ | ✅ | Uniquement agents | ❌ |
Règles de sécurité¶
Restrictions pour gestionnaires
Les gestionnaires ne peuvent :
- Créer que des agents (pas admin/responsable)
- Modifier/désactiver uniquement les agents (pas admin/responsable)
- Gérer uniquement les agents de leur mairie
Interface utilisateur¶
Liste des agents¶
Fonctionnalités :
- Affichage paginé avec compteur total
- Filtres :
- Rôle (agent, responsable, admin)
- Statut (actif, inactif)
- Recherche textuelle (nom, prénom, email)
- Informations affichées :
- Nom complet avec badge de rôle
- Mairie et collectivité
- Dernière connexion
- Créé par
Actions rapides :
- ✏️ Modifier
- 🔑 Réinitialiser mot de passe
- ✅/❌ Activer/Désactiver
Création d'agent¶
Formulaire :
Champs obligatoires:
- Email: validation format email, unicité vérifiée
- Nom: texte libre
- Rôle: agent | responsable | admin
Champs optionnels:
- Prénom: texte libre
- Mot de passe: min 6 caractères
- Forcer changement: checkbox (par défaut: coché)
Génération automatique du mot de passe :
Mot de passe automatique
Si le champ mot de passe est laissé vide :
- Un mot de passe aléatoire de 16 caractères est généré
- Il est affiché une seule fois dans une modal
- Le flag
force_password_changeest automatiquement activé - L'agent devra changer son mot de passe à la première connexion
Workflow de création :
Modification d'agent¶
Champs modifiables :
- Email (avec vérification d'unicité)
- Nom / Prénom
- Rôle (selon permissions)
- Statut actif/inactif
- Force password change
Mot de passe
Le mot de passe ne peut pas être modifié via l'édition. Utiliser "Réinitialiser mot de passe" à la place.
Réinitialisation de mot de passe¶
Processus :
- Clic sur "🔑 Réinitialiser mot de passe"
- Confirmation obligatoire
- Génération d'un nouveau mot de passe (16 caractères)
- Invalidation de tous les tokens JWT (déconnexion immédiate de l'agent)
- Affichage du mot de passe dans une modal
Sécurité
Lors de la réinitialisation :
- Tous les tokens JWT actifs sont invalidés
- L'agent est immédiatement déconnecté de l'app mobile
- Le mot de passe est affiché une seule fois
- Communication sécurisée obligatoire avec l'agent
Activation/Désactivation¶
Désactivation :
- Le compte agent passe à
actif = false - Tous les tokens JWT sont invalidés
- L'agent ne peut plus se connecter à l'application mobile
- Les données restent en base de données
Réactivation :
- Le compte repasse à
actif = true - L'agent peut se reconnecter avec ses identifiants
API Backoffice¶
Tous les endpoints sont sécurisés par authentification session PHP.
GET /api/admin/agents/list.php¶
Liste les agents selon les permissions de l'utilisateur connecté.
Query Parameters :
| Paramètre | Type | Description |
|---|---|---|
role |
string | Filtrer par rôle (agent, responsable, admin) |
actif |
int | Filtrer par statut (0, 1) |
search |
string | Recherche dans nom, prénom, email |
{
"success": true,
"data": [
{
"id": 1,
"mairie_id": 1,
"mairie_nom": "Mairie de Menton",
"mairie_ville": "Menton",
"collectivite_id": null,
"collectivite_nom": null,
"email": "agent@mairie.test",
"nom": "Dupont",
"prenom": "Jean",
"role": "agent",
"actif": true,
"force_password_change": true,
"last_login": "2026-01-18 10:00:00",
"created_at": "2026-01-15 09:00:00",
"updated_at": "2026-01-18 10:00:00",
"created_by": "Admin Menton"
}
],
"total": 1
}
POST /api/admin/agents/create.php¶
Crée un nouveau compte agent.
PUT /api/admin/agents/update.php¶
Modifie un agent existant.
POST /api/admin/agents/pairing_generate.php¶
Génère (ou régénère) un token de pairing QR pour un agent. Le token expire après 15 minutes et est à usage unique.
POST /api/admin/agents/pairing_revoke.php¶
Révoque l'appareil apparié d'un agent (perte/vol) sans désactiver son compte. L'agent devra scanner un nouveau QR code pour se reconnecter.
POST /api/admin/agents/disable.php¶
Active ou désactive un agent.
Effet de bord
La désactivation invalide tous les tokens JWT de l'agent.
Suivi de position en direct (agents_carte.php)¶
Carte backoffice affichant la position temps réel des agents (polling), distincte de /agents.php.
- API :
GET /api/admin/agents/positions_live.php,GET /api/admin/agents/position_history.php,POST /api/admin/agent_tracking_toggle.php - Rétention :
scripts/purge_agent_positions.php, pointssource='tracking'> 60 jours (voirbackend/CLAUDE.md) - Bannière session expirée : le polling détecte une session PHP expirée (401) et arrête le suivi en affichant
"Votre session a expiré — le suivi en direct est arrêté. Reconnectez-vous pour continuer."(avant fix : la carte se figeait silencieusement sans indication) - Distinction erreur / absence de données : les 4 points d'appel de la carte ne traitent plus
success:false(erreur API) comme équivalent à "aucune position disponible" — un message d'erreur explicite est affiché dans le premier cas - Périmètre de la collectivité connectée : au chargement, la carte trace en pointillés (
#1e3750) le contour de la commune ou de l'EPCI de l'utilisateur connecté et cadre la vue dessus, à la place de la vue France entière par défaut. Même découpage de scope quepositions_live.php:superadmin(pas de contour, vue globale),adminrattaché à une collectivité (contour EPCI), tout le reste —adminmono-mairie,gestionnaire(contour commune). Source géométrique : API publiquegeo.api.gouv.fr(/communes/{code_insee}?geometry=contour,/epcis/{siren}?geometry=contour), la même que le "liseré communal" de l'app citoyens — aucune donnée géométrique stockée côté UrbaFix. Le cadrage périodique sur les positions des agents (toutes les 30s) reste inchangé et prend le dessus dès qu'au moins un agent a une position active.
Modèle de données¶
Table agents¶
CREATE TABLE `agents` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mairie_id` int(11) NOT NULL,
`collectivite_id` int(11) DEFAULT NULL,
`email` varchar(255) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`nom` varchar(255) NOT NULL,
`prenom` varchar(255) NOT NULL,
`role` enum('agent','responsable','admin') DEFAULT 'agent',
`actif` tinyint(1) DEFAULT 1,
`force_password_change` tinyint(1) DEFAULT 0,
`last_login` timestamp NULL DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` int(11) DEFAULT NULL COMMENT 'ID du user qui a créé cet agent',
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
KEY `mairie_id` (`mairie_id`),
KEY `collectivite_id` (`collectivite_id`),
CONSTRAINT `fk_agents_mairie` FOREIGN KEY (`mairie_id`) REFERENCES `mairies` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_agents_collectivite` FOREIGN KEY (`collectivite_id`) REFERENCES `collectivites` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_agents_created_by` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Relations¶
Affectation multiple (incident_agents)
Un incident peut être assigné à plusieurs agents, avec un référent unique (is_referent). Gérée depuis incident_detail.php (backoffice). incidents.assigned_agent_id reste en base pour compatibilité mais n'est plus la source de vérité — utiliser incident_agents.
Sécurité¶
Authentification¶
- Session PHP pour l'accès backoffice
- Vérification du rôle (
adminougestionnaire) - Vérification des permissions spécifiques à chaque action
Mots de passe¶
- Hashage : bcrypt via
password_hash() - Longueur minimale : 6 caractères
- Génération automatique : 16 caractères aléatoires sécurisés (
random_bytes()) - Force changement : obligatoire pour mots de passe générés
Validation¶
// Email
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Email invalide']);
exit;
}
// Unicité email
$existing = $db->fetchOne('SELECT id FROM agents WHERE email = ?', [$email]);
if ($existing) {
http_response_code(409);
echo json_encode(['success' => false, 'message' => 'Cet email est déjà utilisé']);
exit;
}
// Hashage
$passwordHash = password_hash($password, PASSWORD_BCRYPT);
Journalisation¶
Toutes les actions sont loggées :
error_log("Agent créé: ID=$agentId, email=$email, par user_id={$user['id']}");
error_log("Agent modifié: ID=$agentId par user_id={$user['id']}");
error_log("Mot de passe réinitialisé pour agent ID=$agentId par user_id={$user['id']}");
error_log("Agent désactivé: ID=$agentId par user_id={$user['id']}");
Invalidation des tokens¶
Lors de réinitialisation ou désactivation :
// Invalider tous les refresh tokens
$db->execute(
'DELETE FROM agent_refresh_tokens WHERE agent_id = ?',
[$agentId]
);
Migration¶
Fichier SQL¶
Emplacement : /database/migration_agents.sql
Tables créées :
agents- Comptes agentsagent_refresh_tokens- Tokens JWTagent_activity_log- Journal d'activité
Modification :
- Ajout de
assigned_agent_iddans la tableincidents
Application de la migration¶
docker-compose exec -T db mysql \
-u urbafix_user \
-purbafix_pass_2024 \
urbafix < database/migration_agents.sql
Bonnes pratiques¶
Communication des mots de passe¶
Sécurité des mots de passe
Les mots de passe générés sont affichés une seule fois. Utilisez un canal sécurisé :
- ✅ Email chiffré
- ✅ Message privé sécurisé
- ✅ Remise en main propre
- ❌ Email non chiffré
- ❌ SMS
- ❌ Messagerie instantanée non chiffrée
Gestion des rôles¶
| Rôle | Utilisation recommandée |
|---|---|
| Agent | Agents terrain utilisant l'application mobile |
| Responsable | Chefs d'équipe, superviseurs |
| Admin | Directeurs de service, DST |
Désactivation vs Suppression¶
Pas de suppression
L'interface ne propose que la désactivation (pas de suppression).
Avantages :
- Historique préservé
- Incidents assignés conservés
- Possibilité de réactivation
- Traçabilité complète
Workflow complet¶
Tests¶
Scénario 1 : Création d'agent¶
- Se connecter au backoffice en tant qu'admin
- Naviguer vers
/agents.php - Cliquer sur "Ajouter un Agent"
- Remplir :
- Email :
test.agent@mairie.test - Nom :
Test - Prénom :
Agent - Rôle :
agent - Laisser le mot de passe vide
- Email :
- Enregistrer
- Vérifier : modal avec mot de passe généré
- Copier le mot de passe
- Vérifier : agent apparaît dans la liste avec badge "Actif"
Scénario 2 : Modification¶
- Cliquer sur "Modifier" pour un agent
- Changer le prénom
- Enregistrer
- Vérifier : modification appliquée dans la liste
Scénario 3 : Réinitialisation MDP¶
- Cliquer sur "Réinitialiser mot de passe"
- Confirmer
- Vérifier : modal avec nouveau mot de passe
- Copier le mot de passe
- Vérifier : tokens JWT invalidés (agent déconnecté de l'app mobile)
Scénario 4 : Désactivation¶
- Cliquer sur "Désactiver"
- Confirmer
- Vérifier : badge passe à "Inactif"
- Vérifier : agent ne peut plus se connecter à l'app mobile
- Cliquer sur "Activer"
- Vérifier : badge repasse à "Actif"