Sécurité
TimePick embarque plusieurs protections activées par défaut, sans configuration à faire côté opérateur : limitation de débit sur les endpoints sensibles, réponses anti-énumération, garde-fou sur le dernier administrateur, codes de secours à usage unique. Cette fiche décrit ce qui est protégé et ce qu'il n'y a pas besoin de régler — elle ne couvre ni le déroulé pas-à-pas de la connexion de secours (voir Usage — Connexion de secours), ni le durcissement de l'infrastructure (HTTPS, pare-feu VPS — voir Déploiement Coolify / VPS).
Rien à activer
Ces protections font partie du code serveur : elles s'appliquent dès le premier démarrage, sur toute instance (locale, Docker, Coolify), sans variable d'environnement ni réglage dans l'écran Paramètres.
Limitation de débit (rate limiting)
Certains endpoints sensibles sont limités en nombre de requêtes, indépendamment de toute configuration :
| Endpoint | Fenêtre | Quota |
|---|---|---|
POST /api/auth/login (demande de lien de connexion) | 1 minute | 5 requêtes par email ciblé |
POST /api/auth/login (demande de lien de connexion) | 1 minute | 30 requêtes par adresse IP |
POST /api/auth/emergency-login (connexion de secours) | 15 minutes | 5 requêtes par adresse IP |
POST /api/auth/resend-invitation (renvoi d'invitation) | 60 secondes | 1 requête par compte, clé unifiée quelle que soit la voie de renvoi (lien direct, événement, post-configuration) |
POST /api/admin/recovery-codes/generate (régénération des codes de secours) | 24 heures | 1 régénération par administrateur |
POST /api/uploads/email-image (upload d'image dans l'éditeur d'emails) | 1 minute | 30 requêtes |
POST /api/admin/settings/organization/logo (upload du logo de l'organisation) | 1 minute | 30 requêtes par adresse IP |
GET/PUT/DELETE /api/setup/smtp, GET/PUT /api/setup/organization (+ logo) pendant l'assistant /setup | 1 minute | 10 requêtes par adresse IP, budget commun |
POST /api/setup/create-admin et POST /api/setup/smtp/test (les deux routes de /setup qui déclenchent un envoi réel) | 1 minute | 10 requêtes par adresse IP, budget commun aux deux |
| Actions admin destructrices génériques (suppression, etc.) | 1 minute | 10 requêtes par administrateur |
| Envoi d'un email de test (Paramètres → Serveur d'email) | 1 minute | 5 requêtes par administrateur |
Les limites par adresse IP protègent contre un attaquant externe ; celles par administrateur ou par identité protègent contre un abus depuis un compte ou un jeton compromis. Aucune de ces valeurs n'est modifiable depuis l'interface — un besoin de quota différent (déploiement multi-tenant, forte volumétrie) implique une modification du code serveur.
Au-delà du quota, le serveur répond 429 avec un code d'erreur RATE_LIMITED. Ces limites sont fixes dans le code serveur ; elles ne s'ajustent pas depuis l'interface.
Connexion de secours : double protection
La connexion de secours (/emergency-login) cumule la limite par IP ci-dessus avec un verrou par compte : au-delà de 10 tentatives de code invalide en 1 heure pour un même administrateur, les tentatives suivantes échouent silencieusement (même réponse que pour un code invalide, pour ne pas révéler que le compte est verrouillé). Le détail de ce flux est décrit dans la fiche d'usage dédiée.
Chaque tentative de connexion de secours (réussie, code invalide, compte verrouillé, email inconnu, code expiré) est journalisée en base dans la table recovery_audit_log (adresse IP, user-agent, résultat, horodatage). Ce journal n'est pas exposé dans l'interface d'administration ; il sert de trace forensique en cas d'incident et se consulte directement en base par un mainteneur.
Anti-énumération des emails
Pour empêcher qu'un tiers déduise, en observant les réponses de l'API, quelles adresses email sont enregistrées dans l'instance, POST /api/auth/login (demande de lien de connexion) renvoie systématiquement le même message, que l'email corresponde ou non à un compte :
{ "data": { "message": "Si cet email est enregistré, vous recevrez un lien de connexion." } }Le comportement est identique en réponse HTTP (200) et en contenu, qu'un email ait effectivement été envoyé ou non — un observateur externe ne peut donc pas distinguer un email inscrit d'un email absent en regardant uniquement la réponse de l'API. La connexion de secours applique le même principe : un email admin inconnu ou un code invalide renvoient tous deux la même erreur générique INVALID_CREDENTIALS, sans distinction, et le serveur exécute systématiquement le même nombre d'opérations de hachage (bcrypt) pour égaliser le temps de réponse.
Durées de vie des liens et des sessions
La sécurité des flux de connexion repose aussi sur des durées de vie limitées, configurables sans redémarrage depuis l'écran Paramètres (onglet Authentification) :
- Lien de connexion administrateur : 24 heures par défaut.
- Lien de connexion membre : 7 jours par défaut (délai plus long car ces invitations concernent des créneaux planifiés à l'avance).
- Session active (après connexion) : 2 heures par défaut.
Ces valeurs sont détaillées dans Paramètres in-app ; elles ne relèvent pas de cette fiche mais complètent le dispositif : un lien ou une session volés perdent leur validité passé ce délai.
Révocation de session
Un appel authentifié à POST /api/auth/logout révoque immédiatement toutes les sessions actives du compte appelant, pas seulement celle utilisée pour l'appel : tout jeton JWT émis avant cet appel — y compris non expiré — est ensuite rejeté par les routes protégées avec 401 et le code SESSION_INVALID, y compris sur un autre appareil ou onglet. Il faut se reconnecter (nouveau lien de connexion) pour obtenir un jeton valide.
Le bouton « Déconnexion » de l'interface (tableau de bord admin, espace membre, en-tête du calendrier public) appelle cet endpoint avant de nettoyer le stockage local du navigateur : une déconnexion déclenchée depuis l'application révoque donc toutes les sessions du compte, sur tous les appareils et onglets, pas seulement celui utilisé pour se déconnecter.
Seul ce geste explicite — cliquer sur « Déconnexion » — déclenche la révocation serveur. Fermer l'onglet ou la fenêtre, laisser la session expirer, ou être déconnecté automatiquement après une erreur d'authentification sont des chemins purement locaux : ils vident le stockage du navigateur mais n'appellent pas l'endpoint, et le jeton reste valable côté serveur jusqu'à son échéance (2 heures par défaut, voir ci-dessus). Après un soupçon de compromission, seul un appel explicite à POST /api/auth/logout (via le bouton, ou directement contre l'API) garantit l'invalidation immédiate ; attendre une déconnexion locale ne suffit pas.
Révocation best-effort
L'appel à l'endpoint depuis l'interface est best-effort : il peut ne jamais atteindre le serveur ou ne jamais confirmer son résultat, sans que la déconnexion locale en soit affectée — le jeton est de toute façon supprimé du navigateur. Cas connus où la révocation serveur peut manquer alors que la déconnexion locale a bien lieu : serveur injoignable ou coupure réseau ; erreur serveur (5xx) sur l'appel — la requête part mais échoue côté serveur, sans remontée visible ; fermeture brutale du navigateur avant l'envoi effectif de la requête ; navigateur ancien (Firefox antérieur à la version 133) qui ne fait pas survivre la requête à la fermeture de la page. Dans tous ces cas, le jeton reste valable jusqu'à son expiration naturelle. L'endpoint reste par ailleurs appelable directement via l'API (voir Référence API) — utile par exemple pour invalider toutes les sessions d'un compte après un soupçon de compromission.
Protection du dernier administrateur
TimePick empêche de se retrouver sans aucun administrateur :
- Suppression bloquée — supprimer le dernier compte administrateur échoue avec une erreur
409(« Impossible de supprimer le dernier administrateur »). - Rétrogradation bloquée — changer le rôle du dernier administrateur vers « membre » échoue de la même façon (« Impossible de rétrograder le dernier administrateur »).
- Auto-suppression interdite — un administrateur ne peut pas supprimer son propre compte, quel que soit le nombre d'administrateurs restants (« Vous ne pouvez pas supprimer votre propre compte »).
Avec deux administrateurs ou plus, un administrateur peut rétrograder son propre compte (auto-démotion) après confirmation ; l'opération entraîne alors une déconnexion immédiate (qui révoque désormais aussi toutes les sessions actives du compte, comme décrit ci-dessus). Ces garde-fous s'appliquent uniquement au rôle administrateur : ils n'empêchent pas de désactiver ou reconfigurer d'autres aspects du compte, et ne remplacent pas une vraie politique de continuité (garder au moins deux administrateurs actifs en pratique, pas seulement un seul protégé par le système).
Codes de secours
Les codes de secours permettent à un administrateur de se reconnecter sans accès à sa messagerie (SMTP en panne, compte email inaccessible…). Points à retenir côté exploitation :
- Générés par lot de 8 codes, affichés une seule fois à l'écran au moment de la génération — TimePick ne les stocke que sous forme hachée (bcrypt) en base et ne peut donc plus les réafficher ensuite.
- Jamais envoyés par email : les récupérer implique d'être connecté à l'interface d'administration au moment de la génération.
- À noter et conserver hors ligne (gestionnaire de mots de passe, coffre-fort) immédiatement après génération.
- Régénérer une nouvelle série invalide immédiatement l'ancienne (limité à une régénération par 24 heures, voir tableau ci-dessus).
À conserver avant d'en avoir besoin
Un administrateur sans codes de secours valides et sans accès à sa messagerie n'a aucun moyen de reconnexion via l'application. Générer les codes dès la première connexion, avant qu'un incident SMTP ne survienne.
Le déroulé complet (génération, utilisation, expiration) est décrit dans Usage — Connexion de secours.
Rappels transverses
- Assistant de configuration initiale (
/setup) — accessible uniquement tant qu'aucun administrateur n'existe en base ; se referme automatiquement dès le premier admin créé. Détails dans Configuration initiale. - Secrets d'environnement —
JWT_SECRET(signature des sessions) etENCRYPTION_KEY(chiffrement du mot de passe SMTP et de la clé API du fournisseur d'email en base) sont générés automatiquement au premier démarrage s'ils ne sont pas fournis, et ne doivent jamais être recopiés d'un exemple. Si unJWT_SECRETest malgré tout fourni, le serveur refuse de démarrer (fail-fast) s'il fait moins de 32 caractères une fois les espaces retirés, ou s'il correspond à un motif de typechangeme/secret_key/password— générer une vraie valeur avecopenssl rand -base64 48. Ils sont recommandés en variables d'environnement en production, et obligatoires sur une plateforme sans disque persistant. Sur une installation existante, ne jamais poser uneENCRYPTION_KEYdifférente de celle en place — le mot de passe SMTP stocké deviendrait indéchiffrable et les administrateurs seraient verrouillés : suivre la procédure de promotion disque → environnement. - HTTPS en production — TimePick ne termine pas lui-même le TLS ; le chiffrement du trafic est à la charge du reverse proxy / de la plateforme d'hébergement. Voir Déploiement Coolify / VPS.
- Jeton de connexion masqué dans les journaux d'accès — un lien de connexion (
/login?token=...) transite en clair dans l'URL, et en production c'est le serveur TimePick qui sert cette page : la requête entre donc dans le journal d'accès. La valeur du jeton y est systématiquement remplacée par[MASQUÉ]. Un jeton lu en clair dans un journal ne donne plus une session réutilisable : s'il a déjà servi il ne vaut plus rien, et s'il n'a pas encore servi il ne permet qu'une seule connexion avant son expiration (24 h pour un lien administrateur, 7 jours pour un lien membre) — le masquage reste utile pour cette raison.