tenant-role-policy
Intake { id, full_name, role } et politique de rôle par tenant
Ce que ça fait
Une application qui reçoit une identité applicative (un identifiant, un nom
complet, un rôle métier comme "directeur" ou "employé") doit la
transformer en un vrai jeton Legba : la bonne identité LiveKit, le bon nom
affiché, et le bon rôle Legba (host/participant/viewer) selon une
politique propre à son organisation (tenant). Cette recette montre comment
enchaîner ce qui existe déjà — resolveRole (LGB-023) et
createAccessToken — sans nouvelle API à apprendre.
Legba ne possède aucune notion de « tenant ». C’est un choix délibéré,
cohérent avec resolveRole (« Legba ne connaît aucun nom de rôle externe
particulier ») : l’application reste seule responsable de savoir quelle
politique appliquer à quel appelant. Cette recette montre le patron le plus
simple — une Map en mémoire — pas une fonctionnalité de la bibliothèque.
Exemple
import { resolveRole, createAccessToken, type RolePolicy } from "@legba-core/realtime";
// Une politique par tenant — stockage et récupération sont la
// responsabilité de l'application (base de données, configuration, etc.),
// pas de Legba. Exemple en mémoire pour rester concentré sur le flux.
const policiesByTenant = new Map<string, RolePolicy>([
[
"konbit-consulting",
{
// Toujours prioritaire : "directeur" est admin dans TOUTES les
// salles de ce tenant, quelle que soit la salle demandée.
global: { directeur: "host" },
// Complémentaire : seulement consultée pour un rôle externe absent
// de `global`, et seulement pour la salle "reunion-conseil".
perRoom: {
"reunion-conseil": { employe: "viewer" },
},
},
],
]);
interface IncomingParticipant {
id: string;
full_name: string;
role: string; // rôle métier de l'application, ex. "directeur", "employe"
}
async function mintToken(
tenantId: string,
roomName: string,
participant: IncomingParticipant,
apiKey: string,
apiSecret: string,
) {
const policy = policiesByTenant.get(tenantId);
if (!policy) throw new Error(`tenant inconnu : "${tenantId}"`);
// Rôle externe absent de la politique => repli "participant", jamais une
// erreur bloquante (comportement de resolveRole, réutilisé tel quel).
const legbaRole = resolveRole(policy, roomName, participant.role, "participant");
return createAccessToken({
apiKey,
apiSecret,
roomName,
identity: participant.id,
role: legbaRole,
displayName: participant.full_name,
});
}
Pièges
resolveRolene vérifie jamais l’existence de la salle (fonction pure, aucun appel réseau) — un nom de salle qui n’existe pas encore résout normalement ; c’estcreateAccessToken/la connexion réelle qui échoueront plus tard si la salle n’a jamais été créée (voirroom.auto_create: false, LGB-011).displayNameest optionnel et distinct deidentity.identityreste l’identifiant stable utilisé partout ailleurs (participants, webhooks, modération) ;displayNamen’est qu’un nom affiché (Participant.namecôté LiveKit), jamais utilisé pour identifier quelqu’un.- Un
displayNamevide/blanc est rejeté avant tout appel réseau, commeidentity/roomName— jamais un jeton silencieusement sans nom alors qu’on en attendait un. - La règle
globald’une politique a toujours priorité surperRoom, même si l’appelant s’attend à un comportement par salle — documenté dans LGB-023, pas un piège nouveau à cette recette, mais facile à oublier en composant les deux.