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

  • resolveRole ne 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’est createAccessToken/la connexion réelle qui échoueront plus tard si la salle n’a jamais été créée (voir room.auto_create: false, LGB-011).
  • displayName est optionnel et distinct de identity. identity reste l’identifiant stable utilisé partout ailleurs (participants, webhooks, modération) ; displayName n’est qu’un nom affiché (Participant.name côté LiveKit), jamais utilisé pour identifier quelqu’un.
  • Un displayName vide/blanc est rejeté avant tout appel réseau, comme identity/roomName — jamais un jeton silencieusement sans nom alors qu’on en attendait un.
  • La règle global d’une politique a toujours priorité sur perRoom, 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.