La console d'octroi de mandat
Comment on ACCORDE un acces d'equipe. Le pendant cote operateur de content/handbook/fr/premier-jour.mdx, qui dit comment on le RECOIT. Livre par security-identity/2760. Le modele lui-meme (portees, mandats, resolveur)…
Comment on ACCORDE un acces d'equipe. Le pendant cote operateur de
content/handbook/fr/premier-jour.mdx, qui dit comment on le RECOIT.Livre par
security-identity/2760. Le modele lui-meme (portees, mandats, resolveur) est decrit par ADR 0013 etacces-delegue-et-frontiere-studio.md.
Le defaut que cet ecran ferme
Le mecanisme d'acces d'equipe etait complet : AccessGrant, la portee
platform, platformPermissionsFor, requirePlatformPermission, et des
pages /ops qui nomment chacune leur permission. Il n'y avait aucun
ecran pour accorder.
Le seul chemin etait POST /api/admin/access/grants avec un JSON compose
a la main, portant un subjectUserId brut. Recruter la personne qui
relit les posts demandait donc de la faire s'inscrire, retrouver son id en
base, composer le bon scopeType, la bonne liste d'allow et une
echeance. Consequence mesuree : /ops/content tournait en production et
son unique porteur possible etait le proprietaire, qui n'en a pas besoin.
Ou ca se passe
/admin/people/access, quatrieme outil de la categorie Personnes.
Role ADMIN uniquement, et c'est delibere : accorder reste un geste du
proprietaire. L'en-tete de src/app/api/admin/access/grants/route.ts
explique pourquoi l'octroi n'est pas encore ouvert aux owners
d'organisation — /api/me livre aujourd'hui le roster avec les e-mails,
l'abonnement et le solde, donc une console d'octroi sans projection
scopee serait une fuite avec un bouton.
Le geste, en quatre temps
- Chercher la personne par son e-mail. La recherche porte sur la
table
Userentiere, cote serveur, et l'ecran ne montre jamais un id. Un cuid ne se relit pas : accorder au mauvais compte ne se verrait qu'apres coup. - Cocher un ou plusieurs POLES. Un pole est une constante du code
(
GRANT_PRESETS,scope: "platform"), pas une liste tapee dans l'ecran. Lesplatform.*qu'il porte sont affichees sous sa case : le mandat se relit avant d'etre emis. - Poser une echeance en jours. Plafonnee par
MAX_GRANT_DAYS(180). L'ecran l'annonce ; c'estcreateGrantqui refuse. Un ecran qui recalcule une regle de securite est une deuxieme regle a maintenir. - Accorder. La ligne apparait dans « Mandats vivants », avec son echeance et un bouton de revocation.
Les poles, et pourquoi il y en a trois
| Pole | Ce qu'il ouvre |
|---|---|
| Contenu et growth | platform.content.operate, platform.growth.manage, platform.bulletin.approve |
| Production creative | platform.creative.review |
| Veille marche | platform.hub.read |
Un pole est derive du REGISTRE des permissions, jamais d'un organigramme.
Il n'existe aujourd'hui aucune platform.* de support ni de revenus,
donc un pole « Support » serait une case a cocher qui n'accorde rien —
et createGrant refuse d'ailleurs un mandat vide.
src/test/grant-poles-cover-platform-permissions.test.ts refuse qu'une
platform.* du registre n'appartienne a aucun pole : une permission
qu'aucun ecran ne sait accorder est une surface injoignable, ce qui
etait exactement le defaut du jour.
Une adresse sans compte est un cas normal
AccessGrant.subjectUserId est nullable, et subjectEmail vit a cote.
Accorder a une adresse inconnue emet un mandat EN ATTENTE : la ligne se
lie au User a la premiere connexion de cette adresse
(linkPendingGrants, appelee depuis l'event signIn de NextAuth).
Un mandat en attente n'accorde RIEN, et cela tient par la FORME de la
requete, pas par un filtre a maintenir : toutes les lectures du resolveur
filtrent sur subjectUserId, et un NULL n'y correspond jamais. La
colonne Etat de la liste le dit en toutes lettres plutot que de laisser
lire un acces qui n'existe pas encore.
Ce que l'ecran n'ajoute pas
Aucune nouvelle porte d'ecriture. Les deux server actions
(grantPlatformAccess, revokePlatformGrant) appellent les fonctions
existantes de src/lib/security/access-grants.ts, qui portent deja :
- les trois refus (
NON_DELEGABLE, uneplatform.*hors portee plateforme, une portee plateforme emise par une organisation) ; - le plafond de duree et le refus d'une echeance passee ;
- le journal des quatre actes (
grant.granted,grant.revoked,grant.narrowed,grant.renewed), ecrit DANS le module.
Les deux actions portent requireAdmin chacune — un export
"use server" est une entree HTTP publique — et ajoutent une ligne
logAdminAction : qui a accorde quoi, a qui, jusqu'a quand.
La revocation passe par grantBelongsToScopes(id, ["platform"]) avant
revokeGrant : revoquer se fait par id, et un id ne dit pas d'ou il
vient. Sans ce controle, la console plateforme pourrait couper le mandat
de marque d'un prestataire.
Ce qu'une revocation ne fait pas
Elle ferme l'acces des la requete suivante (rien n'est mis en cache
au-dela de React.cache). Elle ne reprend pas les octets deja copies :
les assets Studio sont des URLs Blob publiques sans TTL. Couper un acces
borne la duree d'exposition d'une PAGE, pas celle d'un fichier que
quelqu'un a deja enregistre.
La porte que le porteur voit
Accorder ne suffit pas : encore faut-il que la personne TROUVE son acces.
Jusqu'a security-identity/2803, le seul lien vers /ops de toute
l'application authentifiee vivait dans le menu de compte
(src/components/shared/account/dropdown.tsx), sous isAdminLike(userRole)
— exactement la condition que le mandat existe pour eviter. Les deux
autres liens partaient de pages /admin, gardees sur le meme role. Une
recrue mandatee devait donc connaitre l'URL par coeur, et un acces qu'on
ne peut pas trouver est un acces qui n'existe pas.
Le menu porte desormais une entree « Siege » separee de l'entree
« Admin », les deux pouvant coexister : un admin plateforme tient souvent
les deux. Sa condition est un booleen resolu SERVEUR par
GET /api/me/ops-access, qui appelle platformPermissionsFor et ne rend
qu'un bit — jamais la liste, que le navigateur n'a pas a interpreter
(client-does-not-resolve-permissions.test.ts). Une route a part plutot
qu'un champ de /api/me : un mandat ne depend d'aucune organisation et
n'a pas a faire grossir le chemin de lecture le plus chaud du produit.
L'entree ne nomme aucune surface. Le rail du cockpit filtre deja ce que
le porteur peut ouvrir (OPS_ROUTES), et chaque page porte sa propre
porte. src/test/ops-door-is-not-gated-on-a-role.test.ts garde la chaine
entiere : condition sans role dans le menu, verdict recu en prop et non
recalcule, endpoint resolu par les mandats.
Ce qui reste a faire
L'octroi par le proprietaire d'une organisation (phase 5) attend la projection
scopee de /api/me. Le panneau prestataires de ~/members couvre deja
l'octroi a l'echelle d'une ORGANISATION, avec ses presets tenant ; la
portee platform reste emise ici, et par un admin.