Blog ·
Panneaux en marque blanche : ce que voit le client
Mettre votre nom sur un panneau de revendeur est la partie facile. La difficile consiste à décider quels écrans existent pour votre client, et à l’imposer.
Par Provibr
Si vous revendez du VPS ou de l’hébergement, vos clients finissent par vouloir un panneau. Ils veulent redémarrer leur propre machine à deux heures du matin au lieu d’ouvrir un ticket, et vous le voulez aussi.
Cela se transforme en deux questions qui n’en semblent qu’une et ne le sont pas. Quel nom figure sur le panneau, et quelles parties du panneau existent pour cette personne. La première relève de l’image de marque, et c’est surtout de la présentation. La seconde est une frontière, et se tromper est la façon dont un de vos clients finit par lire la facture de votre fournisseur.
L’habillage s’arrête là où commencent les documents
Mettre un panneau à votre marque signifie en changer l’habillage : le logotype dans la barre latérale, le titre de la page, le favicon, le nom dans le bloc de compte. C’est déjà appréciable, et c’est là que s’arrêtent la plupart des fonctions de marque blanche.
La partie intéressante, c’est la liste des choses qui ne doivent jamais porter le nom du revendeur.
| Écran | Quel nom il porte | Pourquoi |
|---|---|---|
| Habillage du panneau, titres, favicon | Le revendeur | La relation dans laquelle se trouve le client |
| Déclaration de confidentialité | La plateforme | Un avis de traitement décrit qui traite réellement |
| Conditions générales | La plateforme | C’est l’accord de la plateforme, pas celui du revendeur |
| Accord de traitement des données | La plateforme | Idem, et il nomme les sous-traitants |
| Factures et documents proforma | La plateforme | C’est l’enregistrement financier d’une transaction réelle |
| La ligne sur un relevé bancaire | La plateforme | Un enregistrement permanent que personne ne peut annoter ensuite |
Le raisonnement tient en une phrase : un client de votre client ne doit jamais en venir à croire qu’il a un contrat avec quelqu’un d’autre que la partie avec laquelle il l’a réellement. Tout ce qui a un caractère juridique ou financier porte le nom de la contrepartie de ce document, et ce n’est pas une préférence de conception.
Ce qui compte davantage que la règle, c’est l’endroit où elle vit. Dans notre code, c’est une liste de chemins de fichiers, et un test parcourt l’arbre d’importation transitif depuis chacun d’eux, en échouant dès que l’un d’eux parvient à atteindre le module de marque. En largeur d’abord, pour que l’erreur nomme la chaîne la plus courte, celle qu’il faut couper.
Un contrôle des seules importations directes est trop facile à contourner : placez la recherche de marque dans une fonction utilitaire, importez cette fonction, et voilà. Et le garde-fou se teste dans les deux sens. Un chemin listé qui disparaît est une erreur plutôt qu’un saut silencieux, et le test affirme séparément que la mise en page du panneau atteint bien le module de marque, car un résolveur qui ne résout rien en silence marquerait chaque fichier en vert.
La connexion du revendeur n’est jamais à sa marque
Il existe une seconde règle, moins évidente, et que nous aurions manquée sans l’avoir énoncée : la marque ne s’applique que sur le domaine propre du client.
Quand le revendeur se connecte sur l’hôte de la plateforme, il voit la plateforme, pas sa propre marque. C’est là que vivent les factures, l’abonnement et la relation avec nous. La recherche de marque retombe sur l’habillage neutre dès qu’une requête n’est pas rattachée à un domaine de compte : un seul endroit décide, et aucun écran n’a besoin de s’en souvenir.
L’écran de gestion de la marque montre un aperçu de l’habillage destiné au client plutôt que d’être lui-même à la marque. Cela paraît pédant jusqu’à ce que l’on regarde ce qu’enseigne l’alternative : un revendeur qui voit son propre logo au-dessus de sa propre facture conclura raisonnablement que ses clients voient quelque chose de semblable.
Un logo blanc sur une barre latérale blanche
Le fond de notre panneau est blanc : un logo à fond transparent et à encre blanche y est donc invisible, et la personne qui le téléverse ne le découvrira pas, car elle le regarde dans un outil de conception, sur un plan de travail gris.
Nous avons mesuré plutôt que deviné, en composant chaque logo sur la couleur réelle de la surface dans un navigateur sans interface :
| Logo | Contraste sur la surface du panneau |
|---|---|
| Encre foncée, fond transparent | 16,46:1 |
| Encre blanche, fond transparent | 1,00:1 |
| Encre blanche sur sa propre plaque opaque | 17,74:1 |
| Encre gris clair | 1,24:1 |
La ligne à 1,00:1 ne veut pas dire « difficile à lire ». Chaque pixel est identique au fond. Le logo est dessiné et il n’y a rien à voir.
Deux décisions en sont sorties. Il n’y a délibérément aucune plaque sous le logo, car cela redessinerait la marque du client et donnerait un cadre à chaque logo normal pour sauver une seule exception. Et l’écran de téléversement avertit plutôt qu’il ne refuse, car une marque a le droit d’être gris clair.
La mesure compte l’encre, pas le recouvrement. Un logo doté d’une large marge transparente coïncide avec le fond sur la plus grande partie de sa surface tout en restant parfaitement visible ; ce qui compte, c’est la quantité de logo réellement dessinée et la distance entre cette encre et la couleur de la surface.
L’absence n’est pas une permission
Passons à la moitié la plus difficile. Quels écrans devraient exister pour le client d’un revendeur ?
L’instinct pousse à modéliser cela comme des permissions : donner un rôle au sous-client, cocher moins de cases. C’est la mauvaise forme, pour une raison qui n’apparaît qu’aux limites. Les permissions répondent à « ce membre a-t-il le droit de faire ceci ». La question ici est « cet écran existe-t-il sur ce domaine », et les deux se séparent dès qu’un propriétaire de compte se connecte pour regarder son propre panneau client. Il est le propriétaire. Chaque contrôle de permission le laisse passer.
Le modèle est donc une inversion plutôt qu’un droit. Une liste fixe indique quelles surfaces sont absentes sur un domaine de compte : la facturation, les réglages du compte, les tickets, les agents, les intégrations, le journal d’activité du compte, la vue de topologie, l’espace d’administration de la plateforme, ainsi que les pages d’inscription et de mot de passe oublié. Absentes pour tout le monde, propriétaire compris.
Trois d’entre elles demandent une justification, car la raison n’est pas celle qui saute aux yeux.
Les tickets sont absents parce que le client d’un revendeur entretient une relation de support avec le revendeur, pas avec nous. Si cet écran existait, il y lirait une réponse nommant une entreprise qui, d’après son propre panneau, n’existe pas.
Le journal d’activité est absent parce que c’est le journal du compte, pas celui du lecteur. L’issue honnête n’est pas un journal filtré mais « cet écran n’existe pas ici ». L’onglet d’activité par serveur reste, car celui-là est réellement cantonné à une machine qu’il possède.
La vue de topologie démontre le modèle. Elle repose sur le module des serveurs, et le module des serveurs est précisément la seule chose dont dispose un sous-client. Une barrière de permissions n’écarterait absolument rien.
Trois fermetures, parce qu’une page n’est pas une frontière
Un seul contrôle ne suffit pas, et chacun des trois attrape ce que les autres ne peuvent pas attraper.
Le premier se trouve dans la fonction de permission elle-même, où l’ordre de deux lignes fait tout :
- Ce module existe-t-il sur cette surface ? Sinon, refuser.
- Ce rôle dispose-t-il d’un accès complet ? Si oui, autoriser.
Placer le test de surface en premier est ce qui fait que « n’existe pas » l’emporte sur « a le droit de tout faire », et c’est ce qui fait tenir l’inversion partout plutôt que seulement dans les écrans dont quelqu’un s’est souvenu.
Le second est un garde-fou dans chaque page située sous ces chemins, qui produit un 404 ordinaire. Il ne prend délibérément aucun argument : un appel qui nommerait son propre écran laisserait une faute de frappe devenir une porte ouverte en silence.
Le troisième se trouve dans le proxy inverse, car une page ne peut pas attraper une requête qui n’atteint jamais une page. Un POST vers un chemin absent est refusé avant que le moindre code de page ne s’exécute. Les navigations passent, afin que le 404 de l’application s’affiche au lieu d’une erreur de proxy toute nue.
Le cookie est une règle du navigateur, pas du serveur
Voici la conclusion à emporter, même si vous ne construisez rien de ce qui précède.
Nos cookies de session sont limités à l’hôte. Aucun attribut Domain, donc une session ouverte sur un domaine client n’est pas envoyée à un autre, ni à l’hôte de la plateforme. C’est correct et c’est testé, y compris le cas négatif où l’activation des cookies inter-sous-domaines fait apparaître un Domain.
Ce n’est pas non plus une frontière. Le sous-client détient son propre jeton de session et peut l’envoyer où bon lui semble avec un client HTTP en ligne de commande. Seul un navigateur aurait refusé.
Nous avons mesuré ce que cela permettait, et c’était pire que prévu : un sous-client pouvait créer une organisation toute neuve dont il était le propriétaire, abonnement d’essai compris, par deux chemins de code indépendants, accessibles depuis quatre points d’entrée différents. Quatre organisations, comptées ensuite dans la base de données. Sur son propre domaine, la même requête était refusée par le proxy : la porte était donc fermée d’un côté et ouverte de l’autre.
La réparation n’a pas consisté à ajouter des contrôles à ces deux chemins. Elle a consisté à placer la frontière au point unique par lequel passe chaque requête authentifiée, et l’invariant « un sous-client ne devient jamais propriétaire d’une organisation » dans la seule fonction que partagent tous les chemins de création.
La même série de travaux a trouvé ailleurs la version générale du problème. Une clé d’API porte un rôle mais pas une surface. Ce n’était pas exploitable, car l’onglet où l’on crée une clé n’existe pas sur un domaine de compte. Mais c’est un bouton caché, pas une frontière, et la différence compte le jour où quelqu’un ajoute une seconde façon de créer des clés. Le correctif refuse le mandant plutôt que d’étiqueter la clé.
Aucune colonne ne dit « ceci est un sous-client »
Une dernière note de conception, car c’est le genre de décision qui rapporte discrètement.
Un sous-client est une ligne d’utilisateur ordinaire portant une adresse électronique réservée de la forme username@<organisation-id>.invalid, plus une ligne d’appartenance ordinaire au profil de permissions étroit. Il n’existe aucune colonne booléenne pour les marquer, et c’est volontaire : une seconde source de vérité peut se désynchroniser de l’adresse, et alors deux parties du système ne s’accordent plus sur l’identité de quelqu’un.
Comme l’identifiant d’organisation se trouve dans l’adresse, l’unicité est offerte par l’index qui existait déjà, et le même nom d’utilisateur peut exister chez deux revendeurs. Le formulaire de connexion demande un nom d’utilisateur et prend l’identifiant d’organisation dans l’hôte plutôt que dans le formulaire : personne ne se connecte donc à un autre compte en devinant un identifiant.
Et leur adresse n’est jamais marquée comme vérifiée, car personne ne peut la vérifier. Cette colonne signifie « cette adresse appartient de façon démontrable à cette personne », et la mettre à vrai pour une adresse qui n’atteint aucune boîte aux lettres déplacerait le mensonge vers un endroit moins visible.
Ce que cela ne fait pas
La personnalisation couvre l’habillage. Les textes de l’interface se lisent toujours comme notre produit, dans la langue que choisit le visiteur, et prétendre le contraire supposerait de traduire le vocabulaire d’un revendeur en neuf langues à sa place. La liste des surfaces absentes est une décision sur la forme du produit, pas un modèle de sécurité à elle seule : elle fonctionne parce que les trois fermetures qui la portent fonctionnent.
C’est ainsi que fonctionnent les domaines de compte chez Provibr, où l’agent tourne sur votre propre matériel et ne se connecte que vers l’extérieur.