Statut au 2026-11-05 : comptes, invitations, appareils, sessions, code rapide, droits par compte, CSRF et limitation des tentatives sont implémentés et testés (lot 1d ; détails et écarts dans l'ADR 0009, section « Implémentation »). Appairage d'un appareil par code : fait (
identity/pairing.go) ; le code seul n'ouvre rien, il faut être connecté pour le saisir, et le secret de suivi reste sur l'appareil. Restent planifiés : réinitialisation du mot de passe, MFA, jetons de lecture des médias. Les durées et seuils sont des valeurs par défaut, à confirmer par l'usage. Décisions associées : ADR 0008 (identifiants opaques), ADR 0009 (comptes individuels, sessions par appareil, code rapide), ADR 0017 (téléchargements), ADR 0018 (bibliothèque privée).
1. Comptes
1.1 Un compte par personne
Il n'y a pas de profils sous un compte. Chaque personne du foyer ou du cercle a son propre compte :
- e-mail (identifiant de connexion) et mot de passe (Argon2id) ;
- rôle :
admin,adultouchild; - nom affiché et couleur (identification visuelle sur les appareils partagés) ;
- droits réglés par l'admin (§6.2).
La progression, l'historique, « Ma liste », les préférences de lecture et la bibliothèque privée appartiennent au compte.
1.2 Rôles
| Rôle | Portée |
|---|---|
admin | Administration : bibliothèques, comptes et droits, invitations, jobs, diagnostics, chemins système, journaux, module Téléchargements. Ne voit pas les données privées des autres comptes (§7). |
adult | Navigation et lecture dans les bibliothèques accordées, selon ses droits ; gestion de son compte, de ses appareils, de ses sessions et de sa bibliothèque privée. |
child | Comme adult, avec limite d'âge obligatoire et sans accès aux réglages sensibles (changement d'e-mail, gestion des appareils d'autrui). |
Pas de rôle personnalisé en V1 : la combinaison « rôle + droits par compte » couvre l'usage d'un foyer.
1.3 Adhésion sur invitation uniquement
- Premier démarrage : création du compte administrateur via un parcours d'initialisation accessible uniquement tant qu'aucun admin n'existe, protégé par un jeton d'initialisation affiché dans les journaux de l'hôte (et, par défaut, depuis le réseau local seulement).
- Ensuite : pas d'inscription libre. L'admin crée une invitation ; la personne ouvre
/invitation/:codeet crée son compte (e-mail, mot de passe). - Invitation : code aléatoire ≥ 128 bits, stocké haché, usage unique, durée limitée (7 jours par défaut), révocable, avec rôle et droits prédéfinis par l'admin. Le code consommé ou expiré renvoie la même erreur générique qu'un code inconnu.
2. Mots de passe
- Argon2id (
golang.org/x/crypto/argon2). Paramètres de départ proposés :m = 64 MiB,t = 3,p = 1, à recalibrer sur la machine de référence (~250 ms). Paramètres stockés avec le hash pour permettre la montée en coût et le rehash à la connexion. - Longueur minimale de 8 caractères (plancher NIST SP 800-63B), choisie par le porteur du projet pour la facilité d'usage (2026-11-05). Il n'y a pas de règle de composition. La longueur se compte en caractères, pas en octets.
- Sont refusés, par une vérification locale sans appel externe :
- les mots de passe les plus courants (liste embarquée, en français et en anglais) ;
- un caractère répété ;
- une suite de chiffres ou de lettres (
12345678,abcdefgh) ; - une rangée de clavier (
azertyuiop,qwertyui) ; - la partie locale de l'e-mail, à peine modifiée.
- La force brute reste freinée par Argon2id et par la limitation des essais.
- Comparaison en temps constant ; même réponse et durée comparable pour « compte inconnu » et « mot de passe faux ».
- Réinitialisation : par l'admin (lien de réinitialisation à usage unique, même mécanique qu'une invitation). Pas d'envoi d'e-mail intégré en V1 (aucun serveur SMTP requis) ; à réévaluer.
3. Appareils partagés et sessions
3.1 Modèle serveur
| Enregistrement | Rôle |
|---|---|
devices | Un appareil client (navigateur, TV, mobile). Identifiant d'appareil aléatoire remis au client à la première connexion, stocké côté client (cookie dédié ou stockage natif). Nom, type, agent, dernière activité, dernière IP. |
account_sessions | Une session par couple (compte, appareil). Jeton opaque de 256 bits, seul son hash SHA-256 est stocké. État, horodatages, réseau (local/distant) à la création. |
device_unlock | Hash Argon2id du code rapide pour un couple (compte, appareil), compteur d'échecs, date de dernière authentification par mot de passe sur ce couple. |
Sessions opaques en base (pas de JWT) : révocation immédiate, liste des sessions visible, pas de clé de signature. Coût : une lecture en base par requête authentifiée, acceptable à l'échelle d'un foyer ; un cache mémoire à TTL court pourra être ajouté si une mesure le justifie.
3.2 Comptes mémorisés sur un appareil
- L'écran de connexion d'un appareil liste les comptes mémorisés sur cet appareil (nom affiché, couleur), c'est-à-dire ceux qui s'y sont déjà authentifiés par mot de passe et ont accepté d'y rester mémorisés.
- Cette liste est servie au seul porteur de l'identifiant d'appareil ; elle n'expose ni e-mail, ni identifiant de compte réutilisable ailleurs.
- Un compte peut se retirer d'un appareil ; l'admin et le propriétaire du compte peuvent révoquer un appareil (supprime sessions et
device_unlockassociés).
3.3 Code rapide (4 chiffres)
- Facultatif, par compte, et seulement si l'admin l'a autorisé pour ce compte.
- Valable uniquement sur un appareil déjà connu où ce compte s'est authentifié au moins une fois par mot de passe. Le code est lié au couple (compte, appareil) : un même code ne vaut rien sur un autre appareil.
- 5 essais échoués ⇒ le code est invalidé pour ce couple et le mot de passe est exigé ; un nouveau code peut être défini ensuite. Aucun déblocage automatique par simple attente.
- Pas d'énumération : la réponse d'échec ne distingue pas « code faux » de « code désactivé ».
- Désactivable pour l'accès distant (proposé : désactivé à distance par défaut) ; l'admin peut l'interdire globalement.
- Expiration proposée : mot de passe exigé à nouveau si aucune authentification par mot de passe sur ce couple depuis 90 jours.
Limite assumée : 4 chiffres = 10 000 combinaisons. Avec 5 essais, une personne qui tente au hasard a 0,05 % de chances de réussir. Le code rapide est une commodité locale pour passer d'un compte à l'autre sur un écran partagé, pas un facteur d'authentification fort. Il ne protège pas contre quelqu'un qui connaît ou observe le code. Les actions sensibles (§3.5) exigent toujours le mot de passe.
3.4 Transport
| Client | Transport | Protection |
|---|---|---|
| Web | Cookie __Host-protoflix_session, HttpOnly; Secure; SameSite=Lax; Path=/ | CSRF : vérification Origin (ou Sec-Fetch-Site) et jeton CSRF synchronisé (X-CSRF-Token) pour toute méthode non sûre |
| Natifs (mobile, TV) | Authorization: Bearer <jeton> | Jeton d'accès + jeton de renouvellement avec rotation |
En développement local en HTTP, __Host- et Secure sont relâchés uniquement sur loopback avec mode développement explicitement activé.
3.5 Durées de vie proposées
Implémenté au 2026-11-07 pour le code rapide seulement : une session ouverte ainsi n'administre pas tant que le mot de passe n'a pas été redonné, jusqu'au prochain verrouillage. La ressaisie à 15 min après une connexion par mot de passe n'est pas faite.
| Élément | Durée | Remarque |
|---|---|---|
| Session web (inactivité) | 30 jours glissants | Prolongée au plus une fois par heure |
| Session web (absolue) | 90 jours | |
| Actions sensibles | Mot de passe ressaisi depuis moins de 15 min | Changement d'e-mail ou de mot de passe, gestion des comptes et droits, sources de bibliothèque, suppression confirmée, activation des Téléchargements. Le code rapide ne suffit jamais. |
| Jeton d'accès natif | 1 heure | |
| Jeton de renouvellement natif | 90 jours glissants, rotation à chaque usage | Réutilisation d'un ancien jeton ⇒ révocation de toute la famille (détection de vol) |
| Jeton de lecture média | 4 h max, lié à la session de lecture | §5 |
| Invitation | 7 jours, usage unique |
3.6 Rotation et révocation
- Nouveau jeton à la connexion (mot de passe ou code rapide), au changement de privilège et au changement de mot de passe.
- Changement de mot de passe ⇒ révocation de toutes les autres sessions et de tous les codes rapides du compte.
- Changement des droits d'un compte par l'admin (ex. retrait de l'accès distant) : pris en compte à la requête suivante, sessions distantes révoquées si l'accès distant est retiré.
- Déconnexion = suppression serveur, pas seulement du cookie.
4. Limitation de débit et verrouillage
En mémoire du processus api en V1 (pas de Redis, ADR 0013) ; perdu au redémarrage, accepté. Le compteur d'échecs du code rapide, lui, est persisté dans device_unlock (il ne doit pas se réinitialiser par un redémarrage).
| Cible | Règle proposée |
|---|---|
| Connexion par mot de passe, par compte | 5 échecs ⇒ attente 1 min, puis doublement jusqu'à 1 h ; remise à zéro au succès |
| Connexion, par IP | 20 tentatives / 10 min ; au-delà 429 avec Retry-After |
| Code rapide, par (compte, appareil) | 5 échecs ⇒ mot de passe exigé (§3.3) |
| Code rapide, par appareil (tous comptes) | 10 échecs / heure ⇒ codes rapides suspendus sur l'appareil |
| Invitation, initialisation, réinitialisation | 10 / heure / IP |
| API authentifiée | Limite par compte et par famille de routes (valeurs à mesurer) |
Le verrouillage par compte est temporaire et progressif, jamais définitif, pour qu'un tiers ne puisse pas bloquer durablement un utilisateur légitime. Chaque échec est journalisé dans audit_events (sans la valeur soumise).
L'IP client n'est prise dans X-Forwarded-For que si la connexion vient de PROTOFLIX_TRUSTED_PROXIES. Le caractère local/distant d'une requête (utilisé pour l'accès distant et le code rapide) est déterminé côté serveur à partir de cette IP et de plages locales configurables — jamais d'une déclaration du client.
5. Jetons de lecture média
Les lecteurs (<video>, hls.js, lecteurs natifs) demandent fichiers, playlists, segments et sous-titres sans pouvoir toujours joindre Authorization.
- À la création d'une session de lecture (
POST /api/v1/playback/sessions), le serveur vérifie l'accès (bibliothèque, limite d'âge, accès distant, qualité maximale à distance), produit le plan de lecture et émet un jeton de lecture opaque. - Le jeton est lié à la session de lecture, au compte et à la version/fichier choisi. Il n'ouvre que les ressources de cette session.
- Il est porté dans le chemin (
/api/v1/media/s/{playbackToken}/...) pour que les URL relatives des playlists HLS fonctionnent sans réécriture. - Durée maximale 4 h, renouvelable via l'API authentifiée ; révoqué à l'arrêt, l'expiration ou l'échec de la session, et à la révocation du compte ou de l'appareil.
- À chaque requête de livraison, le serveur vérifie que la session est active et le compte actif (cache mémoire à TTL de quelques secondes acceptable).
- Les journaux masquent le jeton (
/api/v1/media/s/****/...).
Risque accepté : le jeton peut apparaître dans les journaux du reverse proxy ; portée et durée limitées.
6. Modèle de permissions
6.1 Règles
- Bibliothèques partagées : visibles seulement si accordées au compte (
library_access). Sinon réponse404, pas403, pour ne pas confirmer l'existence. - Limite d'âge par compte : Tous publics / -10 / -12 / -16 / aucune. Obligatoire pour
child. Contenu sans classification connue : masqué par défaut sous une limite d'âge (modifiable par l'admin). Correspondance entre systèmes de classification nationaux et ces paliers à définir avec le fournisseur de métadonnées (ADR 0016). - Accès distant : autorisé ou non par compte, avec qualité maximale à distance (plafond appliqué par le moteur de décision, pas par le client).
- Code rapide autorisé ou non par compte (§3.3).
- Bibliothèque privée : accessible au seul compte propriétaire (§7, ADR 0018). Elle n'est dans aucune liste de bibliothèques ; l'administrateur n'y a d'accès ni par les routes d'œuvres, ni par ses actions de correction, et voit « Contenu privé » à la place du titre dans les lectures. Tests :
privatespace/security_test.go. - Administration : rôle
adminexigé.
La limite d'âge et l'accès distant sont appliqués dans les requêtes du catalogue, de la recherche, de « Reprendre la lecture », des événements temps réel et de l'émission des jetons de lecture — pas seulement dans l'affichage.
6.2 Droits réglés par l'admin
| Droit | Valeurs |
|---|---|
| Bibliothèques visibles | liste de bibliothèques partagées |
| Limite d'âge | Tous publics, -10, -12, -16, aucune |
| Accès distant | oui / non ; qualité maximale à distance |
| Code rapide | autorisé / non |
| Quota de bibliothèque privée | taille (0 = désactivée) |
| Téléchargements | autorisé / non, destinations autorisées (si le module est activé) |
6.3 Où vérifier : couche domaine, pas seulement le handler
Règle non négociable : chaque service de domaine reçoit un acteur (Actor : compte, rôle, appareil, réseau local/distant) dans le contexte d'appel et vérifie lui-même l'accès à l'objet demandé.
- Le middleware HTTP authentifie et peut refuser tôt (ex. routes admin) ; ce refus est une défense supplémentaire, pas le contrôle.
- Les listes de catalogue intègrent le filtre d'accès dans le SQL (jointure
library_access, limite d'âge, propriétaire pour les bibliothèques privées) ; on ne charge pas puis filtre en mémoire. - Les jobs déclenchés par un compte portent l'identité de l'acteur ; les jobs système portent un acteur
systemexplicite. - Un service appelé sans acteur échoue (erreur de programmation) ; il ne suppose jamais un accès total.
7. Confidentialité entre comptes
Décision du 2026-11-07 : l'administrateur voit l'historique des lectures (qui, quoi, quel appareil, quand, et les événements de la lecture), sans limite de durée sauf réglage. C'est une exception de plus à ce qui suit ; chaque compte peut l'effacer pour lui-même en effaçant son historique. « Ma liste », les préférences et les bibliothèques privées restent hors de portée.
Sont privés au compte : « Ma liste », historique, progression, préférences de lecture, bibliothèque privée (fichiers et métadonnées), sessions et appareils (visibles du compte ; l'admin en voit l'existence pour les révoquer).
- L'admin gère les droits mais ne voit pas ces données. Aucun endpoint admin ne renvoie l'historique, la liste ou le contenu de la bibliothèque privée d'un autre compte. Le contrôle est serveur : les services de domaine refusent l'acteur admin sur ces objets, comme n'importe quel autre compte.
- Exceptions nécessaires, documentées et minimales :
- le tableau des lectures en cours montre à l'admin qui lit quoi (titre, mode, raisons de transcodage) pour les bibliothèques partagées. Pour un fichier de bibliothèque privée, il affiche « Contenu privé » sans titre ;
- les quotas : l'admin voit l'espace occupé par compte, pas les fichiers ;
- l'audit : connexions et actions sensibles, sans contenu visionné.
- Limite honnête : un administrateur système (accès root à l'hôte, au fichier de base SQLite, à ses sauvegardes ou au stockage) peut techniquement lire ces données — elles ne sont pas chiffrées de bout en bout. La confidentialité entre comptes protège contre les autres utilisateurs et contre l'admin via l'application, pas contre la personne qui contrôle la machine. Cette limite est affichée aux utilisateurs (texte d'information du compte).
8. Modules à droits spécifiques
- Téléchargements (ADR 0017) : module désactivé par défaut, activé par l'admin (avec mot de passe ressaisi). Un compte ne peut lancer un téléchargement que si l'admin l'y autorise, et seulement vers une destination permise : une bibliothèque partagée sur laquelle il a un droit d'écriture explicite, ou sa bibliothèque privée. La destination est désignée par identifiant, jamais par chemin.
- Bibliothèque privée (ADR 0018) : envoi de fichiers par le seul propriétaire, dans son quota ; objets invisibles de tout autre compte, admin compris (§7).
9. Tests exigés
- Pour chaque endpoint : propriétaire, autre compte
adult, comptechild, admin (y compris sur données privées : doit échouer), anonyme. - Accès distant simulé (IP hors plages locales via proxy de confiance) pour les comptes sans accès distant.
- Code rapide : appareil inconnu, autre appareil, 6e essai, accès distant, après changement de mot de passe.
- Test d'inventaire qui échoue si une route enregistrée n'a pas de politique d'accès déclarée.
10. MFA
- TOTP (RFC 6238) pour les comptes admin : proposé pour la phase 6, recommandé par défaut, avec codes de récupération à usage unique hachés. Le code rapide ne remplace jamais le second facteur.
- WebAuthn/passkeys : à évaluer après TOTP.
11. Audit
audit_events (append-only, rétention 1 an par défaut) : connexions réussies/échouées (mot de passe, code rapide), invitations créées/consommées/révoquées, création/désactivation de comptes, changements de rôle et de droits, appareils révoqués, modifications de sources de bibliothèque, suppressions confirmées, activation/désactivation des Téléchargements, téléchargements lancés (source, destination, compte), changements de configuration, relance/annulation de jobs par un admin. L'audit ne contient pas ce que les comptes regardent.
Source : docs/security/authn-authz.md, dans le dépôt de ProtoFlix.